SuperAPI AI 工具集

webgl-shader-constraints

编程与开发

查看项目

更新于 10-15 11:32

技能介绍

用途

该技能用于在用户为 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 为目标。