GameTech/URP 渲染实战

案例 19.6太阳高光入门

目标:太阳在水面上拉出一条闪烁的亮带(日光路径),被柱子挡住的地方没有高光。

天空盒里虽然也画了太阳,但它是烘焙在立方体贴图里的,位置不一定和场景里的方向光一致(本例为了拍日光路径,把方向光转到了相机对面,而天空贴图里的太阳仍在原处);环境反射还会按粗糙度取较模糊的 mip,得不到锐利的闪点。所以单独用主光算一次镜面高光。水面非常光滑,用指数很高的 Blinn-Phong,乘以第 5 章介绍过的归一化系数 (n+8)/(8π):指数增大时高光面积变小、峰值变高,总能量大致守恒,调指数时不用反复回头调强度。

Assets/Cookbook/Ch19/WaterCore.hlsl(节选:specular)
// 太阳高光:Blinn-Phong,指数很高(水面很光滑),乘上阴影,让高光不会出现在柱子的影子里
half3 SunSpecular(Light light, half3 normalWS, half3 viewWS)
{
    half3 h = SafeNormalize(light.direction + viewWS);
    half spec = pow(saturate(dot(normalWS, h)), _SpecPower);
    // 能量归一化的近似系数 (n+8)/(8π),让指数变大时高光变小但更亮(第 5 章)
    spec *= (_SpecPower + 8.0h) / (8.0h * PI);
    return light.color * spec * _SpecIntensity * light.shadowAttenuation;
}
// 太阳高光:Blinn-Phong,指数很高(水面很光滑),乘上阴影,让高光不会出现在柱子的影子里
vec3 SunSpecular(Light light, vec3 normalWS, vec3 viewWS)
{
    vec3 h = SafeNormalize(light.direction + viewWS);
    float spec = pow(saturate(dot(normalWS, h)), _SpecPower);
    // 能量归一化的近似系数 (n+8)/(8π),让指数变大时高光变小但更亮(第 5 章)
    spec *= (_SpecPower + 8.0) / (8.0 * PI);
    return light.color * spec * _SpecIntensity * light.shadowAttenuation;
}

高光乘了 light.shadowAttenuation:主光阴影在水面上同样有效(水面 Shader 里调用了 GetMainLight(TransformWorldToShadowCoord(positionWS))),柱子的影子里不会有阳光的反射。透明物体虽然不投射阴影,但完全可以接收阴影。

图 19-9太阳位于相机对面的低空。左:没有高光;中:指数 120,高光连成一大片,像一层油膜;右:指数 1200,碎成一个个闪点,才是“波光粼粼”。

高光的形状完全由法线决定。法线贴图的尺度、两层的强度、Gerstner 波的陡度都会改变日光路径的宽窄和碎裂程度。HDR 下高光的峰值远大于 1,开启 Bloom 后会有漂亮的光晕;如果画面里一片死白,先降低 _SpecIntensity 再看 Tonemapping 设置。