从零开始学习three.js(16):一文详解three.js中的法向量Normal Vector

发布于:2025-05-15 ⋅ 阅读:(10) ⋅ 点赞:(0)

Three.js 法向量详解:原理、应用与实践

在三维图形学中,法向量(Normal Vector) 是决定物体表面光照效果的核心要素之一。Three.js 作为流行的 WebGL 框架,通过法向量实现逼真的光影渲染。本文将深入探讨法向量的原理、Three.js 中的实现方式及常见问题解决方案。


一、法向量的定义与作用

1.1 什么是法向量?

法向量是垂直于三维模型表面某一点的单位向量,用于描述该点的朝向。在光照模型中,法向量决定了光线与表面的夹角,直接影响漫反射和高光强度。

1.2 法向量的作用

  • 光照计算:在光照模型(如 Phong 照明模型)中,法向量与光源方向向量共同决定物体表面的漫反射光强度。通过计算法向量与光源方向向量的点积,可以得到光照的漫反射分量。例如,当法向量与光源方向向量同向时,说明物体表面正对着光源,漫反射光最强;当两者垂直时,漫反射光较弱,甚至几乎没有,使物体表面呈现出不同的明暗程度,从而营造出逼真的光照效果,让物体在场景中有立体感。同时,法向量也参与镜面反射光的计算,影响高光的生成位置和强度,使得物体表面具有光泽感,更贴近真实材质的光照表现。。
  • 阴影生成:包括面的朝向判断,根据法向量的方向,可以判断面的朝向。在渲染过程中,Three.js 会根据面法向量的方向来决定是否显示该面。如果一个面的法向量背向摄像机,通常情况下这个面会被认为是背面,Three.js 默认不渲染背面(可以通过设置 material.side 来改变这一行为),这样可以提高渲染效率,避免渲染不可见的面,减少不必要的计算和图形绘制操作。
  • 视差效果:法线贴图是一种纹理贴图,它存储了相对于模型表面切线空间的法向量信息。与普通颜色纹理不同,法线贴图中的每个像素代表一个法向量。这些法向量会 “扰动” 模型表面原本的法向量,让原本平滑的表面看起来像是有细微的凹凸不平,从而在光照计算时产生更复杂的阴影和高光效果,模拟出精细的表面细节。。
  • 基于法向量的碰撞检测(简单情况):在一些简单的三维场景中,可以利用法向量来进行碰撞检测的初步判断。例如,当一个球体接近一个平面时,可以通过比较球心到平面的距离与球体半径的关系,以及球心与平面的相对位置与平面法向量的方向来判断是否发生碰撞。如果球心到平面的距离小于球体半径,并且球心在平面的法向量指向的一侧,那么可以认为发生了碰撞,进而触发相应的碰撞响应操作,如反弹、停止移动等。

二、Three.js 中的法向量设置

2.1 几何体自动生成法向量

对于内置几何体(如 BoxGeometrySphereGeometry),Three.js 会自动计算法向量:

const geometry = new THREE.SphereGeometry(5, 32, 32);
geometry.computeVertexNormals(); // 自动计算顶点法线

2.2 自定义法向量

对于自定义几何体(如 BufferGeometry),需手动指定法线数组:

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([...]); // 顶点坐标
const normals = new Float32Array([...]);  // 每个顶点对应的法向量

geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));

2.3 面法线 vs 顶点法线

  • 面法线(Face Normal) :每个三角形面片的统一法线,适用于平面物体。
  • 顶点法线(Vertex Normal) :每个顶点的独立法线,通过插值实现平滑过渡。

三、法向量在着色器中的应用

3.1 顶点着色器传递法向量

在自定义着色器中访问法向量属性:

// 顶点着色器
varying vec3 vNormal;
void main() {
    vNormal = normalMatrix * normal; // 转换到视图空间
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

3.2 片元着色器中的光照计算

使用法向量实现 Lambert 漫反射:

// 片元着色器
varying vec3 vNormal;
uniform vec3 lightDirection;

void main() {
    float intensity = max(dot(normalize(vNormal), normalize(-lightDirection)), 0.0);
    gl_FragColor = vec4(vec3(intensity), 1.0);
}

四、常见问题与解决方案

4.1 模型光照异常

  • 问题:表面出现不规则的明暗条纹。

  • 解决:检查法向量是否归一化,或在材质中设置 flatShading: true

    new THREE.MeshPhongMaterial({ flatShading: true });
    

4.2 法线贴图方向错误

  • 问题:应用法线贴图后光照方向混乱。

  • 解决:调整切线空间:

    geometry.computeTangents(); // 计算切线
    

4.3 动态修改法向量

当顶点位置变化时(如变形动画),需重新计算法线:

geometry.attributes.position.needsUpdate = true;
geometry.computeVertexNormals();

五、高级应用场景

1. 地形生成中的法向量计算

在地形系统中,通过相邻顶点高度差计算法向量:

const v1 = new THREE.Vector3(deltaX, 0, heightRight - heightLeft);
const v2 = new THREE.Vector3(0, deltaY, heightBottom - heightTop);
const normal = v1.cross(v2).normalize(); //叉乘得到垂直向量[6](@ref)

2. 边缘检测与几何处理

使用EdgesGeometry结合法向量实现轮廓高亮:

const edges = new THREE.EdgesGeometry(model.geometry, {
  thresholdAngle: 30 //基于法向量夹角阈值检测边缘
});

六、调试与优化技巧及注意事项

6.1 可视化法向量

使用 VertexNormalsHelper 辅助线观察法线方向:

import { VertexNormalsHelper } from 'three/addons/helpers/VertexNormalsHelper.js';
const helper = new VertexNormalsHelper(mesh, 2, 0xff0000);
scene.add(helper);

6.2 性能优化

  • 合并几何体:减少法线数据重复。
  • 压缩法线数据:使用 MeshQuantizedMaterial 节省显存。

6.3 手动VS自动法向量:

  • 自动计算消耗更多CPU,复杂几何体建议预计算法向量;

6.4 实例化渲染优化:

  • 对重复物体使用InstancedBufferAttribute共享法向量数据;

6.5 法线贴图压缩:

  • 使用RGBFormat存储法线贴图减少显存占用。

七、结语

正确理解和使用法向量是 Three.js 开发中的关键技能。通过手动调整法线、结合法线贴图技术,开发者能够实现从风格化渲染到高精度仿真的多样化效果。实践中建议善用调试工具验证法线方向,确保光照计算符合预期。