Unity URP 实战:基于Kajiya-Kay与Marschner的头发着色器深度解析
1. 头发渲染为什么这么难?
第一次尝试做头发渲染的时候,我对着屏幕发呆了整整一天。为什么游戏里的头发看起来总是那么假?这个问题困扰了我很久。后来才发现,头发的光学特性比我们想象中复杂得多 - 每根头发实际上是个微型圆柱体,光线会在表面发生多次反射和折射。想象一下把10万根吸管捆在一起,每根都在反射光线,这就是我们要模拟的效果。
真实头发在光照下会呈现三个典型特征:首先是沿着发丝走向的明亮高光带(这就是Kajiya-Kay模型的贡献),其次是靠近发根处的彩色光晕(Marschner模型的发现),最后是头发内部的透光效果。在《最终幻想15》的开发日志中就提到,主角诺克提斯的头发渲染消耗了惊人的30%图形预算。
2. Kajiya-Kay模型实战解析
2.1 核心思想:用切线代替法线
传统光照模型依赖表面法线,但头发是各向异性材质。Kajiya-Kay的天才之处在于用切线方向(Tangent)替代法线(Normal)计算高光。具体实现时,我们需要在Shader中做这几个关键操作:
- 在顶点着色器正确传递切线空间数据
- 在片元着色器重建副切线(Bitangent)
- 使用半角向量计算高光强度
// 切线空间重建 half3 T = input.tangentWS.xyz; float sgn = input.tangentWS.w; half3 B = sgn * cross(input.normalWS.xyz, T); half3x3 TBN = half3x3(T, B, N);2.2 噪声扰动技巧
纯Kajiya-Kay会产生过于完美的高光带,这时候就需要噪声贴图来打破规律性。我推荐使用纵向拉伸的Noise贴图(UV的V方向与发丝走向一致),配合两个关键参数:
- _SpecNoise:控制噪点强度
- _SpecOffset:调整高光位置
half anisoNoise = SAMPLE_TEXTURE2D(_AnsioMap, sampler_AnsioMap, input.uv).r - 0.5; float3 t1 = ShiftTangent(B, N, _SpecOffset1 + anisoNoise * _SpecNoise1);3. Marschner模型的精髓实现
3.1 双高光系统
Marschner模型最显著的特征是双重高光:主高光(R)在发梢处呈现白色,次高光(TRT)在发根处带有彩色偏移。在URP中实现时,我们需要:
- 准备两套高光参数(颜色、偏移量、强度)
- 使用不同的切线偏移方向
- 为次高光添加色散效果
// 主高光 float3 specColor1 = _SpecColor1.rgb * sfd.albedo * _SpecColor1.a; float3 specular1 = specColor1 * D_KajiyaKay(t1, H, _SpecShininess1); // 次高光(带彩色偏移) float3 specColor2 = _SpecColor2.rgb * sfd.albedo * _SpecColor2.a; float3 specular2 = specColor2 * D_KajiyaKay(t2, H, _SpecShininess2);3.2 透光效果优化
头发在逆光时会产生漂亮的透光效果。我们可以通过两种方式增强:
- 在环境光计算中加强次表面散射
- 使用深度偏移模拟光线穿透
half3 env = SampleSH(N) * sfd.albedo * _SSSStrength;4. URP中的性能优化技巧
4.1 渲染顺序解决方案
半透明排序是头发渲染的老大难问题。经过多次测试,我总结出这个四步渲染方案:
- 深度预写入Pass:仅写入深度,解决边缘锯齿
- 不透明部分Pass:渲染实体部分
- 半透明背面Pass:剔除正面,防止内部穿透
- 半透明正面Pass:最终外观呈现
// 在Shader中添加多Pass配置 Pass { Name "DepthPrepass" ZWrite On ColorMask 0 Cull Off }4.2 计算精度取舍
移动端需要特别注意:
- 将half精度用于颜色计算
- 对高光计算保留float精度
- 禁用不必要的动态分支
5. 常见问题排查指南
5.1 高光断裂问题
当发现高光带出现断裂时,检查:
- 切线空间计算是否正确
- 噪声贴图是否出现UV拉伸
- 副切线方向是否与发丝走向一致
5.2 半透明排序异常
遇到渲染顺序错误时尝试:
- 调整模型顶点顺序(内层顶点在前)
- 检查Pass的ZTest设置
- 适当增加深度偏移值
6. 进阶效果提升方案
想要达到3A级效果,可以进一步:
- 添加发丝级动态模糊
- 实现基于物理的发色渐变
- 结合Compute Shader做动态发丝模拟
在最近的一个项目中,我们通过组合使用Kajiya-Kay高光和Marschner次表面散射,将头发渲染性能降低了40%的同时,视觉效果反而提升了。关键是把艺术效果参数化,让美术同学可以实时调整高光宽度、散射强度等参数。
