关于 webgl-shader-constraints
面向 Unity 浏览器构建的开发者参考技能,涵盖 WebGL 着色器限制、变通方法与优化。内容区分 WebGL 1.0 和 2.0 的要求,并指导减少着色器变体及构建开销。
主要功能
- 01WebGL 1.0 与 2.0 限制
- 02循环与数组索引变通方法
- 03着色器变体剔除
- 04纹理与内存建议
- 05WebGL 性能建议
适用场景
- 01排查仅在浏览器构建中失败的着色器
- 02减少着色器构建体积与首次加载编译
- 03使 Unity 着色器适配 WebGL 1.0 或 2.0
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adevra/ unity-shader-agent-skills --skill webgl-shader-constraints安装后在 Claude Code 中输入/webgl-shader-constraints确认已加载。
详细介绍
Unity 的 WebGL 着色器限制与解决方法
触发条件
当用户正在为 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)
// 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];
// 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
}
// 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 图形 — https://docs.unity3d.com/530/Documentation/Manual/webgl-graphics.html
- Unity 6 WebGL2 文档 — https://docs.unity3d.com/6000.3/Documentation/Manual/WebGL2.html
- Unity Web 图形 API 简介 — https://docs.unity3d.com/6000.3/Documentation/Manual/web-graphics-apis-intro.html
- Unity Web 优化:图形 — https://docs.unity3d.com/2022.3/Documentation/Manual/web-optimization-graphics.html
- Unity:分析并优化 Web 构建 — https://unity.com/how-to/profile-optimize-web-build
- Unity Web:移除未使用的资源 — https://docs.unity3d.com/2022.3/Documentation/Manual/web-optimization-remove-resources.html
- WebGL2 基础知识:新增内容 — https://webgl2fundamentals.org/webgl/lessons/webgl2-whats-new.html
- Radiator 博客:2023 年 WebGL 技巧 — https://www.blog.radiator.debacle.us/2023/01/unity-webgl-tips-advice-in-2023.html
- Backtrace:WebGL 内存问题 — https://backtrace.io/blog/memory-and-performance-issues-in-unity-webgl-builds
- Playgama:2025 年 Unity WebGL — https://playgama.com/blog/general/unity-for-browser-games-boost-performance-and-engagement-effortlessly/
- JohannesDeml:WebGL 加载测试(700 多个构建)— https://github.com/JohannesDeml/UnityWebGL-LoadingTest
- CrazyGames:Unity 优化包 — https://github.com/CrazyGamesCom/unity-optimizations-package