URP 渲染实战 / 第 3 章 第一个 URP Shader / 案例 3.3
案例 3.3顶点色入门
目标:读取网格自带的顶点色,与贴图和颜色相乘。顶点色是最便宜的“逐顶点数据通道”:美术可以在 DCC 软件里刷上 AO、渐变、草的摆动权重、溶解遮罩,第 17 章的植被风动就会用它。
Assets/Cookbook/Ch03/C03_03_VertexColor.shader(节选:io)
struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; // 顶点色:Mesh.colors,没有顶点色的网格读到 (1,1,1,1) }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; // 光栅化时在三角形内部线性插值 };
输入端用 COLOR 语义接收 Mesh.colors。如果网格根本没有顶点色,读到的是 (1,1,1,1),乘上去不改变结果,所以这个 Shader 对没有顶点色的模型也是安全的。
Assets/Cookbook/Ch03/C03_03_VertexColor.shader(节选:vertfrag)
Varyings vert (Attributes input) { Varyings output; output.positionCS = TransformObjectToHClip(input.positionOS.xyz); output.uv = TRANSFORM_TEX(input.uv, _BaseMap); output.color = input.color; // 原样传下去,插值交给硬件 return output; } half4 frag (Varyings input) : SV_Target { half4 tex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); // lerp(1, 顶点色, 强度):强度为 0 时乘以白色,等于不起作用 half4 vc = lerp(half4(1, 1, 1, 1), input.color, _VertexColorStrength); return tex * _BaseColor * vc; }
#ifdef VERTEX void main() { gl_Position = TransformObjectToHClip(positionOS.xyz); uv = TransformTex(texcoord0.xy, _BaseMap_ST); vColor = color; // 原样传下去,插值交给硬件 } #endif #ifdef FRAGMENT void main() { vec4 tex = texture(_BaseMap, uv); // lerp(1, 顶点色, 强度):强度为 0 时乘以白色,等于不起作用 vec4 vc = mix(vec4(1, 1, 1, 1), vColor, _VertexColorStrength); fragColor = tex * _BaseColor * vc; } #endif
顶点色从顶点着色器原样输出,光栅化器会把三个顶点的颜色在三角形内部做透视校正的线性插值(第 1 章讲过为什么要透视校正)。所以顶点色的精细程度取决于网格的密度:一个只有 8 个顶点的立方体,每个面只能表现一个双线性渐变。
lerp(a, b, t) 是线性插值 a + (b - a) * t,这里用 _VertexColorStrength 在“白色(不起作用)”与“顶点色”之间过渡。第 4 章会把 lerp、smoothstep 这一族函数画成曲线细讲。

顶点色与色彩空间线性空间工程里,Unity 会把材质面板上的 Color 属性从 sRGB 转成线性值,但不会对网格的顶点色做这种转换(以实际版本为准)。如果美术在 DCC 里按“看起来的颜色”刷顶点色,在 Shader 里当作颜色用时会偏亮,可以用 core 包
Color.hlsl 里的 SRGBToLinear 转一下;当作权重、遮罩这类“数据”用时则不需要转换。