案例 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(),柱子的影子里不会有阳光的反射。透明物体虽然不投射阴影,但完全可以接收阴影。

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