材质属性与 CBUFFER:Properties 如何变成 GPU 常量

Unity 知识 · Shader 区域 · 语言与结构 · 第 4 篇 更新于 2026-10-15

材质面板上拖一个滑条,GPU 里某个常量就变了——中间这条链路(Properties → uniform → CBUFFER)平时看不见,但它决定材质能不能吃到 SRP Batcher 的加速,也决定「参数不生效」这类 bug 的定位方向。

Properties 到 shader 变量的映射

Properties 块只是声明,shader 代码里还必须声明同名变量才会真正接上:

Properties 声明shader 变量声明备注
Colorfloat4 _ColorRGBA,线性空间值
2Dsampler2D _MainTex + float4 _MainTex_ST_ST = Tiling(xy) Offset(zw),TRANSFORM_TEX 就是乘它
Range(0,1)float _Smoothness滑条
Float / Vectorfloat / float4

常见 bug「参数拖了没反应」:Properties 写了但代码里没声明同名变量;或声明了但片元里没用它;或名字差一个字母(_MainTex vs _MainTexture)。三者都是静默失败,Frame Debugger 里看该 Draw Call 实际传了什么参数即可定位(见 /notes/unity/performance-2/)。

uniform 与常量缓冲(CBUFFER)

GPU 侧,这些参数以 uniform(统一常量) 身份进入 shader:整个 Draw Call 内所有线程读到同一个值,编译期编译器可以把它折进指令、放进寄存器——这也是「材质级开关走 uniform 分支几乎免费」的原因(见 /notes/unity/principles-4/)。

现代图形 API 不鼓励零散传常量,而是打包成**常量缓冲(constant buffer)**一次绑定。SRP(URP/HDRP)用 SRP Batcher 做批处理:把每个材质的参数预先打包进 GPU 端常驻的缓冲区,切材质时只改一个指针,不再重传参数——CPU 端 Draw Call 开销大幅下降(GPU 端 Draw Call 还在,别和 GPU Instancing 混为一谈,见 /notes/unity/performance-1/)。

SRP Batcher 兼容的唯一硬条件

该 shader 所有 Properties 变量,必须全部放进每个 Pass 里一个名为 UnityPerMaterial 的 CBUFFER:

Shader "MyGame/SRPBatcherFriendly"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color   ("Tint", Color) = (1,1,1,1)
        _Smoothness ("Smoothness", Range(0,1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);   // 纹理不进 CBUFFER

            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST;
                float4 _Color;
                float  _Smoothness;
            CBUFFER_END

            struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; };
            struct V2F { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; };

            V2F vert(Attributes v) {
                V2F o;
                o.positionCS = TransformObjectToHClip(v.positionOS);
                o.uv = v.uv * _MainTex_ST.xy + _MainTex_ST.zw;
                return o;
            }
            float4 frag(V2F i) : SV_Target {
                return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv) * _Color;
            }
            ENDHLSL
        }
    }
}

规则清单:

  • 纹理与采样器不进 CBUFFER(TEXTURE2D/SAMPLER 放外面是对的)。
  • 所有 Properties 对应的标量/向量都要进,漏一个整个 shader 不兼容。
  • 每个 Pass 都要声明(内容一致,编译器会合并)。
  • 名字必须是 UnityPerMaterial(URP 约定)。

什么会破坏兼容

  • 参数声明在 CBUFFER 外(最常见:抄的老 shader 直接 float4 _Color;)。
  • 用了 UNITY_INSTANCING_BUFFER 之外的每物体全局常量、或自定义全局 CBUFFER 装材质参数。
  • 多个 CBUFFER 分散装 Properties 变量。

不兼容的后果不是报错,而是该 shader 的 Draw Call 退回传统逐参数重传路径——场景里材质种类多时 CPU 时间明显上去。检查方法:Frame Debugger 选中 Draw Call 看 SRP Batcher 兼容性标记,或 Rendering Debugger(Ctrl+\)的 SRP Batcher 面板,见 /notes/unity/performance-2/。

与光照参数的关系

引擎自带的光照参数(_MainLightPosition、阴影距离等)在 URP 的另一套 CBUFFER(如 UnityPerDraw、URP_LightLoop 内部)里,不需要也不应该自己声明——它们由引擎按 tile/帧批量更新,这正是 URP 前向单 Pass 能一次算多灯的基础,见 /notes/unity/engineering-1/。

自检三问

  • 材质参数拖了没反应,三个最常见的静默原因是什么?
  • _MainTex_ST 要不要进 UnityPerMaterial?_MainTex 本体呢?
  • SRP Batcher 不兼容会编译报错吗?去哪确认兼容性?