GameTech/URP 渲染实战

案例 8.11Matcap 与环境光照的对比入门

目标:实现带透视修正的 Matcap,并通过移动相机说明它与真正的环境光照的本质区别。

Assets/Cookbook/Ch08/Matcap.shader(节选:matcap)
half4 frag(Varyings i) : SV_Target
{
    float3 n = normalize(i.normalVS);
    float2 uv;
    if (_PerspectiveFix > 0.5)
    {
        // 透视修正:用“视线方向”构造一个局部坐标系,再把法线投影进去。
        // 否则物体在画面边缘时,Matcap 会被明显拉扯(只有正交相机下 n.xy 才是准确的)
        float3 v = normalize(i.positionVS);          // 观察空间里从相机指向表面
        float3 r = cross(v, n);
        uv = float2(-r.y, r.x);
    }
    else
    {
        uv = n.xy;                                   // 经典写法:直接取观察空间法线的 xy
    }
    uv = uv * 0.5 + 0.5;
    half3 c = SAMPLE_TEXTURE2D(_MatCap, sampler_MatCap, uv).rgb * _Tint.rgb;
    return half4(c, 1);
}
void main()
{
    vec3 n = normalize(normalVS);
    vec2 uv;
    if (_PerspectiveFix > 0.5)
    {
        // 透视修正:用“视线方向”构造一个局部坐标系,再把法线投影进去。
        // 否则物体在画面边缘时,Matcap 会被明显拉扯(只有正交相机下 n.xy 才是准确的)
        vec3 v = normalize(positionVS);          // 观察空间里从相机指向表面
        vec3 r = cross(v, n);
        uv = vec2(-r.y, r.x);
    }
    else
    {
        uv = n.xy;                               // 经典写法:直接取观察空间法线的 xy
    }
    uv = uv * 0.5 + 0.5;
    vec3 c = texture(_MatCap, uv).rgb * _Tint.rgb;
    fragColor = vec4(c, 1.0);
}

经典写法 uv = n.xy * 0.5 + 0.5 只在正交相机下准确;透视相机下,处在画面边缘的物体,视线方向与相机前方不一致,Matcap 会被明显拉扯。修正的思路是用“从相机指向该点的方向”构造局部坐标系:cross(v, n) 得到一个与视线和法线都垂直的向量,旋转 90° 后作为 UV。物体在画面中心时,它与经典写法完全一致。

图 8-17左:Matcap,从正面看;中:同一个 Matcap,相机绕到另一侧——高光和轮廓光的位置相对于画面完全没变;右:GILit 从同一侧看,光照随着世界中的主光和天空正确变化,阴影也投在地面上。
MatcapSH + 反射探针(GILit)
开销一次纹理采样SH 求值 + Cubemap 采样 + BRDF
光照与场景固定在相机上,换方向不变来自世界,随物体位置和朝向变化
阴影、遮挡没有实时阴影、AO、光照贴图
适合雕刻预览、UI 里的 3D 图标、风格化角色、低端设备的特效需要融入场景的一切物体

Matcap 并非“错误”的技术——它的可控性极强,美术画什么就是什么,第 15 章还会把它用于风格化渲染。只是要清楚它不是环境光照:它不知道物体在场景里的哪里、周围是什么,也不会和其他物体产生任何光照上的联系。