用途
该技能用于在用户为 WebGL 浏览器部署开发 Unity 着色器时提供约束规则与变通方案。触发场景包括:WebGL、浏览器游戏、Web 构建、WebGL 1.0、WebGL 2.0、WebGPU、GLSL、OpenGL ES 2.0/3.0、着色器 Web 编译、Web 构建体积、Web 着色器变体、Always Included Shaders,以及任何将 Unity 内容部署到浏览器的提及。当用户报告仅在 Web 构建上出现的着色器错误,或处理与着色器相关的构建体积问题时同样适用。
核心规则
- WebGL 1.0(OpenGL ES 2.0)禁止动态数组索引,只能使用常量或循环计数器变量索引数组,这是 WebGL 1.0 上着色器失败的首要原因。
- WebGL 1.0 仅允许计数型 for 循环,不支持 while 与 do-while;循环计数器必须在 for 语句中初始化、比较和递增,循环边界必须是常量表达式。
- WebGL 2.0(OpenGL ES 3.0)解除循环与索引限制,但新增强制精度限定符要求:所有变量必须显式声明精度(highp、mediump、lowp)或使用默认精度语句。
- WebGL 仅支持烘焙 GI 与非方向光照贴图,不支持实时 GI、方向光照贴图以及带 L2 球谐的光探针。
- 切勿在 WebGL 构建中使用 Standard Shader,它会编译数百个变体,可能使构建体积增加 50MB 以上;应使用 Simple Lit、Unlit 或关键字最少化的自定义着色器。
- 应积极剥离 Web 着色器变体,每个变体都会增加下载体积和初始着色器编译时间(在浏览器中会阻塞主线程)。
WebGL 1.0 与 2.0 功能对比
| 功能 | WebGL 1.0(ES 2.0) | WebGL 2.0(ES 3.0) |
|---|---|---|
| 数组索引 | 仅常量/循环索引 | 允许动态索引 |
| 循环 | 仅计数型 for | 所有循环类型 |
| GPU 实例化 | 否 | 是 |
| 3D 纹理 | 否 | 是 |
| 纹理数组 | 否 | 是 |
| 整数纹理 | 否 | 是 |
| texelFetch | 否 | 是 |
| Uniform Buffer Objects | 否 | 是 |
| 顶点纹理单元 | 保证 0 个 | 保证 ≥16 个 |
| MRT(多渲染目标) | 否 | 是 |
| 延迟渲染 | 不可能 | 可能(但开销大) |
| 方向光照贴图 | 否 | 否(Unity 限制) |
| 精度限定符 | 可选 | 强制 |
循环与索引变通方案(WebGL 1.0)
动态数组索引在 WebGL 1.0 上失败:
// FAILS on WebGL 1.0 — dynamic array indexing
half4 colors[4];
int index = someCalculation();
half4 result = colors[index]; // ERROR: non-constant index
// WORKAROUND — unroll with if/else
half4 result;
if (index == 0) result = colors[0];
else if (index == 1) result = colors[1];
else if (index == 2) result = colors[2];
else result = colors[3];
while 循环在 WebGL 1.0 上失败:
// FAILS on WebGL 1.0 — while loop
int i = 0;
while (i < count) { /* ... */ i++; } // ERROR
// WORKAROUND — counting for loop with constant bound
for (int i = 0; i < 16; i++) // 16 is a constant
{
if (i >= count) break; // dynamic exit
// ... loop body
}
非常量循环边界在 WebGL 1.0 上失败:
// FAILS on WebGL 1.0 — non-constant loop bound
for (int i = 0; i < _DynamicCount; i++) { } // ERROR
// WORKAROUND — constant max with early break
#define MAX_ITERATIONS 32
for (int i = 0; i < MAX_ITERATIONS; i++)
{
if (i >= _DynamicCount) break;
// ... loop body
}
Web 着色器变体剥离
着色器变体是 WebGL 构建体积的最大来源,每个变体都必须由浏览器下载并编译。
步骤 1:移除 Always Included Shaders。在 Project Settings > Graphics > Always Included Shaders 中移除所有不需要项,仅 Standard Shader 就会增加数百个变体。
步骤 2:使用 shader_feature 替代 multi_compile。
// BAD for web — compiles ALL keyword combinations
#pragma multi_compile _ _FEATURE_A
#pragma multi_compile _ _FEATURE_B
// = 4 variants (2 × 2)
// GOOD for web — only compiles variants used by materials in build
#pragma shader_feature _ _FEATURE_A
#pragma shader_feature _ _FEATURE_B
// = only variants referenced by materials in scenes
步骤 3:使用局部关键字(每个着色器最多 64 个)。
// Global keywords (shared pool of 256, wasteful)
#pragma multi_compile _ _MY_KEYWORD
// Local keywords (per-shader pool of 64, efficient)
#pragma shader_feature_local _ _MY_KEYWORD
步骤 4:针对 Web 的条件编译。
// Strip expensive features on WebGL
#if !defined(SHADER_API_GLES) && !defined(SHADER_API_GLES3)
// Desktop-only features here (SSR, complex lighting, etc.)
#endif
// Or target mobile/web specifically
#if defined(SHADER_API_MOBILE) || defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
// Simplified mobile/web path
#else
// Full desktop path
#endif
步骤 5:使用 IPreprocessShaders 进行程序化剥离。
using UnityEditor.Build;
using UnityEditor.Rendering;
using System.Collections.Generic;
class WebGLShaderStripper : IPreprocessShaders
{
public int callbackOrder => 0;
public void OnProcessShader(Shader shader, ShaderSnippetData snippet,
IList<ShaderCompilerData> data)
{
for (int i = data.Count - 1; i >= 0; i--)
{
// Strip deferred variants (useless on WebGL)
if (snippet.passType == PassType.Deferred)
{
data.RemoveAt(i);
continue;
}
// Strip specific keywords
var keywords = data[i].shaderKeywordSet;
if (keywords.IsEnabled(new ShaderKeyword("_SHADOWS_SOFT")) ||
keywords.IsEnabled(new ShaderKeyword("_ADDITIONAL_LIGHTS")))
{
data.RemoveAt(i);
}
}
}
}
WebGL 性能提示
- 着色器编译会阻塞主线程。与原生构建不同,WebGL 同步编译着色器,着色器越多首次加载冻结越久,应尽量减少变体数量。
- Web 上使用 URP 而非 Built-in。URP 在正确配置后能产生更小的构建和更少的着色器变体。
- 纹理压缩很重要。WebGL 2.0 使用 ASTC(首选)或 ETC2,DXT/BC 格式仅在桌面浏览器可用。
- 内存严重受限。WebGL 运行在浏览器内存沙箱内,庞大的着色器变体集合可能触发 OOM 崩溃。
- 在移动浏览器上测试。iOS Safari 的 WebGL 性能显著差于桌面 Chrome,许多在桌面 Chrome 上可用的效果在移动 Safari 上会失败或卡顿。
WebGPU(未来)
Unity 6 具有实验性 WebGPU 支持。可用时,WebGPU 将移除许多 WebGL 限制:计算着色器、存储缓冲区、更好的线程。但截至 2025 年,WebGL 2.0 仍是浏览器游戏的生产目标。应编写面向 WebGL 2.0 的着色器,并将 WebGPU 规划为渐进增强。
限制
该技能聚焦于 Unity 在 WebGL 浏览器部署下的着色器约束与变体剥离,不涉及非 Unity 引擎的 WebGL 开发,也不提供具体项目的完整着色器实现。WebGPU 部分为实验性说明,生产环境仍以 WebGL 2.0 为目标。