…
实时光线追踪:法线修正和平滑
在玻璃光追中,法线处理的第一个问题是:Mesh 通常只记录朝向物体外部的法线,但光线既可能从空气进入玻璃,也可能从玻璃内部射向空气。
因此,每次命中表面时,都要先根据光线方向与几何法线的关系判断当前处于入射还是出射状态,再统一修正法线方向和折射率比例:
// 光线射入物体
dir_refl = reflect(r, n);
dir_refr = refract(r, -n, eta);
// 光线射出物体
dir_refl = reflect(r, -n);
dir_refr = refract(r, n, 1.0 / eta);
这里的关键不是单独翻转法线,而是让法线方向、入射介质和出射介质保持一致。否则可能得到错误的光线方向,甚至错误触发全反射。
法线平滑
法线处理的第二个问题来自低模曲面的平滑着色。玻璃侧面,尤其是在薄边和掠射角区域,三角形之间的法线变化较大;如果直接使用几何法线,容易出现明显的分面感。
图片占位符
实时渲染通常会根据交点的重心坐标,对三角形三个顶点的法线进行插值,得到平滑的着色法线。同时,我们还会保留由三角形边叉乘得到的几何法线:
geometricNormal = normalize(cross(p1 - p0, p2 - p0));
这里需要明确两类法线的分工:
- 几何法线(Geometric Normal):代表三角形的真实朝向,用于判断表面内外、处理自相交,并校验新生成光线的方向。
- 着色法线(Shading Normal):用于材质计算,让低模曲面呈现平滑的视觉效果。
在大多数情况下,使用着色法线计算光照没有问题。但在薄边、低模曲面或掠射角区域,着色法线可能与几何法线偏差较大。此时,根据着色法线生成的反射或折射方向,可能落到真实几何表面的错误一侧,进而产生黑点、漏光、自相交或边缘闪烁。
因此,生成候选方向后,还需要同时使用两类法线进行半球校验。在法线已经统一朝向入射侧的前提下:
bool validReflection =
dot(direction, shadingNormal) > 0.0 &&
dot(direction, geometricNormal) > 0.0;
bool validRefraction =
dot(direction, shadingNormal) < 0.0 &&
dot(direction, geometricNormal) < 0.0;
如果方向不合法,可以将该采样贡献置零或重新采样。Blender Cycles 的微表面采样也采用了类似的双法线校验,避免平滑几何产生错误的反射和折射方向。
这一部分可以总结为:几何法线负责保证光线方向在物理上合法,着色法线负责保证表面在视觉上平滑;两者需要分工使用,并在发射新光线前完成一致性校验。
参考: Blender Cycles hemisphere validation