第 15 章练习:顶点位移后的几何法线
对应教程:15 Noise 顶点位移后的几何法线
起始示例:位移与法线对照,修改 15-displaced-normals.js 和需要时的 15-displaced-normals.html。
练习 1:先手算一个斜坡
局部平面的高度定义为 h(x,y) = 0.2x + 0.1y。光方向是局部 +z,即 (0,0,1);暂时假设网格没有旋转或缩放。
**目标:**写出两个切向量、正确叉积、归一化法线和 Lambert 漫反射强度。再判断若仍使用旧法线 (0,0,1),漫反射强度是多少。
**完成标准:**能算出新旧光照强度的区别,并指出叉积顺序反了会发生什么。
练习 2:用可预测的坡面调试代码
**目标:**临时把示例中的 heightAt 换成练习 1 的线性坡面;对比“原始法线”“重算法线”和“法线颜色”模式。
**要求:**保留中心差分、normalMatrix 和光照代码;先关闭动画,将网格横向分段设为 2 与 96 分别观察。
**完成标准:**重算法线模式的法线颜色在整个坡面上基本一致,且与手算方向吻合;低分段与高分段都是相同的平面斜坡。恢复原 heightAt 后,低分段才会明显丢失 noise 起伏。
练习 3:非等比缩放下的法线
**目标:**给位移网格设置非等比缩放,例如 mesh.scale.set(1.3, 0.8, 0.5);在“重算法线”之外新增一个“错误矩阵”模式,用 mat3(modelViewMatrix) 直接变换局部法线,与 normalMatrix 的结果比较。
**要求:**只在新模式中使用错误矩阵;保留正确模式用于对照。关闭动画,固定光方向和高度参数。
**完成标准:**同一形状下两个模式的受光方向明显不同;能解释法线为什么需要逆转置,而位置使用普通 modelViewMatrix。
<details><summary>提示</summary>
非等比缩放会以不同倍率改变切向量,法线仍需与变换后的两条切线垂直。单纯给法线乘位置矩阵一般做不到这一点。
</details>
<a id=“reference-answers”></a>
第 15 章练习参考解答
对应练习:顶点位移后的几何法线。
练习 1:先手算一个斜坡
∂h/∂x = 0.2、∂h/∂y = 0.1,所以 Tx = (1,0,0.2)、Ty = (0,1,0.1)。Tx × Ty = (-0.2,-0.1,1)。长度为 √(0.2² + 0.1² + 1²) = √1.05,归一化法线为 (-0.2,-0.1,1)/√1.05。与 +z 光线的点积约为 1/√1.05 ≈ 0.976;旧法线给出的值是 1.0。若用 Ty × Tx,z 变负,正面照光时漫反射会被 max(dot,0) 截为 0。
练习 2:用可预测的坡面调试代码
暂时将顶点 shader 中 heightAt 函数替换为:
float heightAt( vec2 localXY ) {
return 0.2 * localXY.x + 0.1 * localXY.y;
}
中心差分对线性函数给出 hx = 0.2、hy = 0.1,与 uEpsilon 的具体正值无关(浮点计算会有微小误差)。局部法线因而在所有顶点相同。示例的网格本来有旋转,所以“法线颜色”显示的是经过 normalMatrix 的观察空间方向,并非直接显示 (-0.2,-0.1,1)/√1.05 的局部颜色;需先应用相同的网格旋转再比较。
线性坡面由任何分段数都能准确表示为平面,因此把横向分段从 96 降到 2,形状不应像原 noise 表面那样失真。完成观察后恢复原 heightAt。
练习 3:非等比缩放下的法线
在创建 mesh 后加入:
mesh.scale.set( 1.3, 0.8, 0.5 );
在 HTML 的模式下拉框新增 <option value="3">错误矩阵变换法线</option>。在顶点 shader 中声明并计算一个新的 varying:
varying vec3 vWrongNormalView;
// 在 main() 中,displacedNormalLocal 已算出之后:
vWrongNormalView = normalize( mat3( modelViewMatrix ) * displacedNormalLocal );
片元 shader 中也声明 varying vec3 vWrongNormalView;,把原有 normalView 的选择改成:
vec3 normalView = corrected;
if ( uMode == 0 ) normalView = normalize( vOriginalNormalView );
if ( uMode == 3 ) normalView = normalize( vWrongNormalView );
其余光照代码不变。位置变换依然由 modelViewMatrix 完成,错误只发生在模式 3 的法线变换中。法线要与两条变换后的切线保持垂直,因此应由位置矩阵的逆转置作用;对非等比缩放,直接乘位置矩阵通常使法线偏向错误方向。网格变宽后如有边缘被裁切,可把相机适当拉远,但两个模式必须使用同一相机位置比较。