---
title: "Unity3D WebGL 框架全景"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/unity3d-webgl-framework-panorama/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/WebGL/Unity3D WebGL 框架全景.md"
content_hash: c581db96a2d7f796879b07cd0f3114b0f530c194fbfd18d389d4ed9c37da0ea9
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
```text
我现在需要深入理解Unity3D工程构建为WebGL的技术框架。特别是：涉及到哪几种语言，语言编译的工具链是什么，最终产物是什么，涉及到哪些模块或者层级，层级之间的调用关系是什么。这样我可以理解Unity3D - WebGL 应用的完整的体系结构与技术底层，乃至于我基本上能在大脑中模拟、认识应用的完整结构
```

# Unity3D WebGL 框架全景
![Web 平台与 WebGL 运行栈](/media/d2801687418797f17545.png)

### 0|WebGL应用介绍

#### 容器
> 无论是“标准浏览器 WebGL 应用”还是“微信/抖音小程序版 WebGL”，它们都被装在 **“JS 引擎（逻辑） + 渲染容器（如WebView） + JSBridge”** 这一三层盒子里，而这三层盒子的组合就是广义的 _运行时_。

| 概念                            | 在浏览器里的体现                            | 在小程序里的体现                            | 说明                 |
| ----------------------------- | ----------------------------------- | ----------------------------------- | ------------------ |
| **脚本运行时 (JavaScript Engine)** | V8、SpiderMonkey、JavaScriptCore      | 同样是 V8 或 QuickJS 的裁剪版；有时用独立进程       | 真正解释 / JIT JS 的地方  |
| **渲染容器 (WebView / Canvas)**   | `<canvas>` + GPU 进程 / WebGL context | 宿主 App 内部的轻量级 WebView 或自己实现的 GPU 绑定 | 把像素画到屏幕、接触 GPU     |
| **系统调用网关 (JSBridge)**         | 原生 Web API 由浏览器统一维护                 | `wx.*` / `tt.*` / `my.*` … 各家实现     | 让 JS 能用摄像头、支付等特权能力 |
| **安全沙箱**                      | 同源策略、CSP、Iframe 隔离                  | App 级沙箱 + 白名单域名                     | 防数据越界、防恶意脚本        |

#### 共性
- **前端逻辑全部写成 JavaScript/TypeScript**
    - 无论是在 PC/Mobile 浏览器里跑的标准 WebGL 游戏，还是嵌入微信、抖音、支付宝的小程序——核心代码都是 JS（或编译到 JS 的 WASM/asm.js），靠同一套事件循环、同一套垃圾回收与语法特性。

- **图形接口都是 WebGL（或 WebGL 封装）**
    - 浏览器里直接拿 `canvas.getContext('webgl')`。
    - 小程序侧通常把 WebGL API 重新暴露一遍（如 `wx.createWebGLRenderingContext`、`tt.createWebGLRenderingContext`），底下仍是 OpenGL ES/Metal/Vulkan 到 GPU 的固定流水线。

- **运行在沙箱里的单线程事件循环**
    - 代码只能通过异步回调或 `postMessage()` 与宿主通信，线程安全由宿主保证。
    - 内存和文件系统均受限，只能用宿主暴露的 Storage、IndexedDB、临时文件接口。

- **通过“宿主-JSBridge”访问原生能力**
    - 浏览器的 `Web API`（Fetch、WebSocket、Audio …）。
    - 小程序的 `wx.* / tt.* / my.*` 能力集合（网络、支付、定位）。
    - 逻辑上都是把 JS 调用序列化 → 原生线程执行 → 回调结果再序列化回 JS。

### 1 | Emscripten是什么？它和 Clang / LLVM 是什么关系？

| 层级                        | 职责                                                                                                                                                                                                                                                                | 关键产物                        |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| **Clang**                 | C/C++ 前端：把源码 ➜ 统一的 LLVM IR                                                                                                                                                                                                                                        | `.bc` (LLVM bit‑code)       |
| **LLVM**                  | 通用优化 & 代码生成框架；包含 wasm32/wasm64 目标后端                                                                                                                                                                                                                               | 优化后的 IR                     |
| **Emscripten（`emcc` 驱动）** | 把 *“生成 Web 可运行工件”* 所有脏活包揽下来：① 调用 Clang 产出 LLVM IR② 调 `wasm-ld` / Binaryen 优化、链接成 `.wasm` ③ 生成 **Glue JS (`xxx.framework.js`)**，提供 POSIX 运行时、文件系统、`gl*`/`emscripten_*` 系统调用④ 包含 `emcc`, `emar`, `emconfigure` 等前端脚本，像一套 “Unix for WebAssembly” SDK | `.wasm` + `*.js` + `*.html` |

> 简言之：**Clang/LLVM 负责“把 C++ 变 IR→机器码”，Emscripten 负责“把 IR 变 *Web 可执行* + 给它配齐生态”**。Emscripten 在内部仍使用标准 LLVM 后端，但再包上一层 JS Runtime 和工具链特有库。([Stack Overflow][1], [emscripten.org][2])

---

### 2 | WebAssembly 与传统汇编（二进制机器码）的关系

| 维度   | **WebAssembly (.wasm)**          | **传统汇编/机器码 (x86/ARM)**         |
| ---- | -------------------------------- | ------------------------------ |
| 目标对象 | 抽象、虚拟的栈机 ISA；与硬件无关               | 针对单一 CPU 架构；寄存器直接暴露            |
| 格式   | 压缩二进制段 + 结构化控制流                  | 二进制(ELF/PE) 或文本 `.s`           |
| 安全   | 强沙盒；无指针运算、无自扩写代码；加载时字节级验证        | 原生权限；可能执行任意指令                  |
| 运行流程 | **下载 → 解析/验证 → JIT/AOT 编译 → 执行** | 已是最终机器码，CPU 直接执行               |
| 调试符号 | `.wasm.map`, DWARF ► DevTools    | `.pdb`, `.dSYM`, ELF `.symtab` |

**执行链条**

1. 浏览器或 Wasm 运行时读取 `.wasm`，做 **字节码验证**。
2. 使用 **Baseline JIT**（Chrome Liftoff, Firefox Baseline）快速翻译到本机指令并缓存。
3. 热路径再交给 **优化 JIT**（Chrome TurboFan, Firefox Ion）重编译，得到接近本地 C++ 性能的机器码。([MDN Web Docs][3], [Stack Overflow][4])

所以 **`.wasm` 是“可移植汇编” → 浏览器即时变成本机汇编**；而传统静态编译直接在构建时就生成特定 CPU 的汇编 / 机器码。

---

### 3 | Glue JS 是什么？它与普通 JavaScript 有何不同？

| 特征        | **Glue JS (`xxx.framework.js`)**                                                                                                                                                                                                 | **手写 JavaScript** |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| 来源        | **Emscripten 自动生成**                                                                                                                                                                                                              | 开发者撰写             |
| 职责        | - 加载 & 实例化 `.wasm` (`WebAssembly.instantiateStreaming`)- 构造全局 `Module` 对象，导出 `ccall/cwrap`、FS、GL、Pthreads shim- 把 C/POSIX 调用（`open`, `glDrawArrays`, …）**映射**到浏览器 API 或 TypedArray 操作- 处理字符串/数组在 JS ↔ Wasm 之间的编码、堆管理 | 任意业务逻辑            |
| 语言        | 仍是标准 ES5/ES6 JS；但大量宏化、函数名压缩、TypedArray 操作                                                                                                                                                                                        | 标准 JS             |
| 与 Wasm 交互 | 双向 FFI：• JS 调用 `wasm.exports.myFunc` 或 `cwrap`• Wasm 侧通过 `EM_ASM` 或 `dynCall_js` 回到 JS                                                                                                                                   | 仅在 JS 世界          |

> 把它叫“Glue”是因为它 **“黏合”** 两个运行时：一头连 Wasm 的线性内存/函数导出，另一头连浏览器 Web APIs，让 C/C++ 代码能“像在桌面 POSIX 环境”那样调用文件、线程、OpenGL，却最终落到 JS 方法和 WebGL。([emscripten.org][5], [liveBook][6])

[1]: https://stackoverflow.com/questions/64690937/what-is-the-difference-between-emscripten-and-clang-in-terms-of-webassembly-comp?utm_source=chatgpt.com "llvm - What is the difference between Emscripten and Clang in terms of ..."
[2]: https://emscripten.org/docs/introducing_emscripten/about_emscripten.html?highlight=clang&utm_source=chatgpt.com "About Emscripten — Emscripten 4.0.5-git (dev) documentation"
[3]: https://developer.mozilla.org/en-US/docs/WebAssembly?utm_source=chatgpt.com "WebAssembly | MDN - MDN Web Docs"
[4]: https://stackoverflow.com/questions/78337617/how-does-wasm-code-run-in-browsers-and-how-does-it-interact-with-javascript?utm_source=chatgpt.com "webassembly - How does Wasm code run in browsers and how does it ..."
[5]: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html?utm_source=chatgpt.com "Interacting with code — Emscripten 4.0.9-git (dev) documentation"
[6]: https://livebook.manning.com/book/webassembly-in-action/c-ccall-cwrap-and-direct-method-calls/v-1/?utm_source=chatgpt.com "C ccall, cwrap, and direct method calls"


## Unity → WebGL 构建体系结构全景

> **一句话先行**
> Unity 把 **C# 脚本 + C++ 引擎** 经过 **IL2CPP → Emscripten** 编译成 **WebAssembly (.wasm)**，再配上一组 **Glue JS** 和资源包，最终在浏览器里由一段 **Loader 脚本** 拉起运行时，借 **WebGL/WebAudio** 等 API 驱动 GPU 和外设。

---

### 1 涉及的语言与职责

| 语言                       | 典型文件                                       | 在构建链里的角色                   |
| ------------------------ | ------------------------------------------ | -------------------------- |
| **C#**                   | `*.cs`（游戏脚本）                               | 被 **IL2CPP** 转成 C++        |
| **C / C++**              | Unity Runtime（物理、渲染核心）                     | 与脚本一起进 wasm                |
| **JavaScript**           | `*.loader.js`, `*.framework.js`, `*.jslib` | Glue／Boot loader；桥接浏览器 API |
| **WebAssembly (binary)** | `*.wasm`                                   | 执行沙盒，承载全部 native 逻辑        |
| **HTML**                 | `index.html`                               | 页面容器，注入 Loader             |
| **GLSL ES**              | (打包入 Asset)                                | GPU 着色器，由 WebGL 编译         |

---

### 2 编译工具链

```text
C# ─► IL2CPP (C# → C++)
             │
C/C++ (引擎) ─┤
             ▼
        Clang/LLVM
             │ (via Emscripten)
             ▼
        wasm-ld  ──►   unity.wasm
             │
      Binaryen/opt   (wasm‑opt, DWARF→map)
```

* **IL2CPP**：将托管 IL 解码成等价 C++ 源，再交给 C++ 编译器。
* **Emscripten**：封装 Clang + wasm‑ld；生成 `.wasm` 与 **Glue JS (framework.js)**。([Unity Docs][1])
* **wasm‑opt / wasm‑strip**：二进制优化、符号剥离。
* **Brotli/GZIP**：构建后可选压缩，浏览器端解码。([Unity Discussions][2])

---

### 3 最终产物目录（Unity 2022+ 默认）

```text
Build/
 ├─ WebGLBuild.loader.js   ← Boot & progress bar
 ├─ WebGLBuild.framework.js← Emscripten glue, Module 对象
 ├─ WebGLBuild.data        ← 资源包 (场景、纹理…)
 ├─ WebGLBuild.wasm        ← C++/C# 编译结果
 ├─ WebGLBuild.js.symbols  ← 堆栈符号 (可选)
 └─ WebGLBuild.wasm.br     ← Brotli 压缩版 (可选)
index.html                 ← 模板，可插入脚本/UI
```

*旧版本只有 `UnityLoader.js` + `UnityWebGL.js`，2020 LTS 起拆分为 `*.loader.js` + `*.framework.js`. ([Stack Overflow][3])*

---

### 4 运行时层级与调用关系

```mermaid
graph TD
A[index.html] --> B(Loader JS)
B --> C{准备阶段}
C -->|下载 *.data *.wasm| D(Module.configure)
D --> E[WebAssembly.instantiateStreaming]
E --> F{Wasm VM}
F --> G[Unity Runtime & GameLogic]
G -->|JSlib/SendMessage| H(Glue JS calls)
H --> I(Web APIs: WebGL, WebAudio, Input)
I -->|GPU cmds| J(GPU Driver)
```

1. **Loader JS** 把文件名映射到 URL（可带解压），显示进度条。
2. **framework.js** 创建 **`Module` 对象**：

   * 定义 `preloadPlugins`, `onRuntimeInitialized`, `cwrap`, `ccall`。
3. **`WebAssembly.instantiateStreaming`** 把 `.wasm`+内存映射到 VM。
4. Wasm 内部通过 **Emscripten syscalls**(`gl*`,`emscripten_*`) 回到 JS 层，再调 WebGL/WebAudio。
5. **JSlib 插件**：用户写的 `*.jslib` 可通过 `EMSCRIPTEN_BINDINGS` 让 C# 调浏览器 API。

---

### 5 模块职责一览

| 层级               | 主要文件               | 职责                                     | 调用方向                    |
| ---------------- | ------------------ | -------------------------------------- | ----------------------- |
| **Boot 入口**      | `.loader.js`       | 找 CDN、本地路径、解压、创建 `<script>`            | ↑ HTML                  |
| **Glue / Shell** | `.framework.js`    | 配置 Module、映射 `gl*` Syscalls、管理 wasm 内存 | ↔ Boot / Wasm / Browser |
| **Wasm VM**      | `.wasm`            | 执行 Unity Runtime、游戏脚本 JIT 后逻辑          | ↑ Glue                  |
| **浏览器 API 层**    | WebGL/WebAudio/DOM | GPU、音频、输入、文件                           | n/a                     |

---

### 6 JS ↔ Wasm 互操作示例

**C# 调 JS**

```csharp
[DllImport("__Internal")]
private static extern void JsAlert(string msg);

JsAlert("Hello WebGL!");
```

对应 **plugin.jslib**

```js
mergeInto(LibraryManager.library, {
  JsAlert: function (ptr){
    alert(UTF8ToString(ptr));
  }
});
```

**JS 调 C#**

```js
Module.ccall('OnExternalEvent', null, ['number'], [42]);
```

---

### 7 多线程与文件系统补充

* **Pthreads**：`Player Settings ▸ Enable Threads` → Emscripten 生成 `.worker.js`；需 `Cross-Origin-Opener-Policy` 与 `SharedArrayBuffer` 安全头。
* **Virtual FS**：Emscripten `MEMFS` 把 `*.data` 解包到 wasm 内存；可映射 IndexedDB 做持久化。

---

### 8 Unity WebGL vs 微信原生小游戏（复习）

| 方面  | Unity WebGL      | 原生小游戏                           |
| --- | ---------------- | ------------------------------- |
| 语言  | C#/Wasm          | JS/TS                           |
| 引擎  | Unity Runtime    | WeChat Game Runtime (CocosLite) |
| 加载链 | Loader JS + Wasm | JS 直跑                           |
| 包体  | 首包 5‑20 MB       | < 4 MB                          |
| 渲染  | WebGL            | WebGL/Canvas2D                  |
| 线程  | Pthreads（灰度）     | 主线程 + Worker                    |

---

### 9 脑内模拟口诀

> **“Loader 找文件 → Glue 创 Module → Wasm 起 VM → Runtime 写 WebGL”**

* 想象 `.wasm` 像 **DLL**；`framework.js` 像 **C 动态链接器**；`loader.js` 像 **启动脚本**。
* 每次 `glDrawArrays` 源自 Wasm 调用，穿过 Glue 映射成浏览器 WebGL 命令。
* 所有 C# 调 JS 的桥都在 `.jslib` 汇合；反向则用 `ccall/cwrap`。

掌握这些层级和调用流，你就能在脑海中精准定位：**崩溃发生在哪段、性能瓶颈在何层、要拦截数据应挂在哪边**。后续无论补符号映射、注入监控，亦或改 Loader 自定义 CDN 逻辑，都可胸有成竹。

[1]: https://docs.unity3d.com/6000.1/Documentation/Manual/webgl-native-plugins-with-emscripten.html?utm_source=chatgpt.com "Manual: Web native plug-ins for Emscripten - Unity"
[2]: https://discussions.unity.com/t/compressed-webgl-build-fails-loading/824658?utm_source=chatgpt.com "Compressed WEBGL Build fails loading - Unity Discussions"
[3]: https://stackoverflow.com/questions/75665855/unity-webgl-build-and-run-fails-404-loader-js-missing?utm_source=chatgpt.com "Unity WebGL Build and Run Fails - 404 .loader.js missing?"
