材质属性与 CBUFFER:Properties 如何变成 GPU 常量
材质面板上拖一个滑条,GPU 里某个常量就变了——中间这条链路(Properties → uniform → CBUFFER)平时看不见,但它决定材质能不能吃到 SRP Batcher 的加速,也决定「参数不生效」这类 bug 的定位方向。
Properties 到 shader 变量的映射
Properties 块只是声明,shader 代码里还必须声明同名变量才会真正接上:
| Properties 声明 | shader 变量声明 | 备注 |
|---|---|---|
Color | float4 _Color | RGBA,线性空间值 |
2D | sampler2D _MainTex + float4 _MainTex_ST | _ST = Tiling(xy) Offset(zw),TRANSFORM_TEX 就是乘它 |
Range(0,1) | float _Smoothness | 滑条 |
Float / Vector | float / 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 不兼容会编译报错吗?去哪确认兼容性?