切线空间观察方向因子值重映射实现线性变化遮罩
切线空间类菲涅尔遮罩线性化方案
现有逻辑问题
你当前使用的计算式:
float mask = abs(-viewDirTangentSpace.x / viewDirTangentSpace.z)
本质是计算切线空间下视线与表面法线夹角θ的正切值tanθ:
- 视线垂直表面时θ=0,
tanθ=0 - 视线逐渐转向掠射角时θ趋近90°,
tanθ趋近无穷大
这个值天然呈非线性增长,且没有做值域约束,是导致效果不符合预期的核心原因。
线性重映射实现
要得到随夹角θ线性变化的遮罩,只需要通过反正切函数把正切值还原为线性的角度值,再归一化到[0,1]区间即可,同时补上除零保护避免z值过小时出现渲染异常:
const float PI_HALF = 1.5707963; const float EPSILON = 1e-4; // 计算带除零保护的tanθ float tanTheta = abs(-viewDirTangentSpace.x / max(viewDirTangentSpace.z, EPSILON)); // 还原为弧度制夹角θ,范围[0, PI/2) float theta = atan(tanTheta); // 线性映射到[0,1]区间,正对表面为0,掠射角为1 float linearMask = saturate(theta / PI_HALF);
如果需要匹配参考图里PS曲线调整后的过渡效果,不需要额外采样曲线纹理,直接加一个幂次参数控制边缘软硬即可,性能开销极低:
// edgePower参数控制边缘过渡:值越大边缘越锐利,值越小过渡范围越宽 // 匹配你给出的目标效果时,edgePower取1.2~2.0区间即可 float finalMask = pow(linearMask, edgePower);

如果需要1:1匹配PS里的自定义曲线,可以把曲线导出为256像素的1维查找表(LUT),在shader里用linearMask作为UV采样LUT即可,精度足够满足生产需求。
内容的提问来源于stack exchange,提问作者MaT
相关产品推荐
相关产品推荐

