GameTech/URP 渲染实战

案例 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)
VARYING vec2 uv;
VARYING vec4 vColor;      // 光栅化时在三角形内部线性插值

输入端用 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 这一族函数画成曲线细讲。

图 3-6球和环面纽结的顶点色由脚本按位置刷上(色相绕 Y 轴变化、亮度随高度变化)。左:强度 0,只剩白色;中:强度 1;右:顶点色 × 棋盘格贴图
顶点色与色彩空间线性空间工程里,Unity 会把材质面板上的 Color 属性从 sRGB 转成线性值,但不会对网格的顶点色做这种转换(以实际版本为准)。如果美术在 DCC 里按“看起来的颜色”刷顶点色,在 Shader 里当作颜色用时会偏亮,可以用 core 包 Color.hlsl 里的 SRGBToLinear 转一下;当作权重、遮罩这类“数据”用时则不需要转换。