EdgeSplitModifier
是 Three.js 中用于分割网格的边缘,以便对具有较大角度的相邻面进行处理,使它们拥有独立的法线,解决法线平滑和光照不自然的问题。这个修饰符常用于在模型中创建硬边效果,特别是在需要表现出锐利边缘的几何体(例如机械零件、建筑等)时。
EdgeSplitModifier 有一个方法
js
EdgeSplitModifier()
创建一个新的EdgeSplitModifier对象。
// 创建一个 IcosahedronGeometry 几何体
const geometry = new THREE.IcosahedronGeometry(10, 3);
// 创建一个 MeshPhongMaterial 材质
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00, flatShading: true });
const mesh = new THREE.Mesh(geometry, material); // 使用修改后的几何体创建网格
mesh.castShadow = true;
mesh.receiveShadow = true;
// 将网格添加到场景中
scene.add(mesh);
// 从上方照射的白色平行光,强度为 0.5。
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set(0, 50, 0);
directionalLight.castShadow = true;
scene.add( directionalLight );
// 添加环境光来补充场景的整体光照 使得反光物体能显示
const ambientLight = new THREE.AmbientLight(0x404040); // 环境光
scene.add(ambientLight);
方法
- modify(geometry, cutOffAngle, tryKeepNormals)
- 这是 EdgeSplitModifier 的核心方法,用于对给定的几何体进行边缘分割。其参数如下:
- geometry (THREE.Geometry 或 THREE.BufferGeometry): 需要修改的几何体对象。此几何体会被分割,边缘将会被处理。
- cutOffAngle (float): 切割角度阈值,单位为弧度。只有当相邻面的角度大于这个值时,边缘才会被分割。典型值范围为 0 到 π(大约 3.14)。例如,cutOffAngle = 0.5 表示切割角度大于约 28.6 度的边缘会被分割。较小的角度会产生更多的分割。
- tryKeepNormals (boolean): 指定是否尝试保留原有的法线。设置为 true 时,尽可能保留原来的法线方向;设置为 false 时,会重新计算法线。一般情况下,若法线需要独立计算,tryKeepNormals 设置为 false 会更合适。
主要作用
- 分割边缘:
- 当相邻面之间的角度大于 cutOffAngle 时,EdgeSplitModifier 会将共享边的面分割开,确保每个面具有独立的法线。这样做的主要目的是避免光照计算时出现不自然的平滑过渡。
- 控制切割角度:
- 通过设置 cutOffAngle,用户可以控制分割的敏感度。较大的 cutOffAngle 会减少分割的数量,只有明显尖锐的角度才会被分割;较小的角度会导致更多的边缘分割,适用于需要更细致控制的情况。
- 法线处理:
- 设置 tryKeepNormals 为 false 时,EdgeSplitModifier 会重新计算分割后面片的法线,确保它们符合新的分割结果。若保留原始法线,可能会造成法线计算的不一致或光照效果不佳,因此通常建议在进行分割时重新计算法线。
- 适用场景:
- 硬边效果:当模型有明显的硬边或棱角时,可以使用此修饰符来分割边缘,使其更加清晰。常用于建筑、机械模型等。
- 光照优化:通过分割边缘,避免模型面之间的平滑过渡,使光照效果更加真实。
- 观察效果:
- 左侧网格(meshOriginal)没有应用 EdgeSplitModifier,你会看到面之间是平滑的,没有明显的硬边。
- 右侧网格(meshModified)应用了 EdgeSplitModifier,你会看到面之间的硬边更加明显,尤其是在光照变化下,效果更加显著。
在 3D 图形学中,硬边(Hard Edges)指的是一个几何体中相邻的两个面之间具有显著的角度差,从而形成一种明显的锐利边界效果。这种边界通常使几何体看起来更加棱角分明,而不是光滑连续的。 在示例中 当 cutOffAngle 接近 180 时边角会滑些,为0时边角较明显
js
// 声明一些变量,包括材质、修改器、网格和基础几何体
let map, modifier, mesh, baseGeometry;
// 参数配置对象,控制是否应用平滑着色、边缘分割、切割角度等
const params = {
smoothShading: true, // 是否启用平滑着色
edgeSplit: true, // 是否启用边缘分割
cutOffAngle: 20, // 切割角度,单位为度
showMap: false, // 是否显示纹理贴图
tryKeepNormals: true, // 是否保留法线
};
// 添加环境光,提供基础的光照
scene.add( new THREE.HemisphereLight( 0xffffff, 0x444444, 3 ) );
// 使用 OBJLoader 加载一个 Cerberus 模型
new OBJLoader().load(
'https://raw.githubusercontent.com/mrdoob/three.js/refs/heads/master/examples/models/obj/cerberus/Cerberus.obj',
function ( group ) {
// 获取加载的模型的几何体
const cerberus = group.children[ 0 ];
const modelGeometry = cerberus.geometry;
// 初始化边缘分割修改器
modifier = new EdgeSplitModifier();
// 合并几何体的重复顶点(优化几何体)
baseGeometry = BufferGeometryUtils.mergeVertices( modelGeometry );
// 创建网格并设置初始材质
mesh = new THREE.Mesh( getGeometry(), new THREE.MeshStandardMaterial() );
// 根据平滑着色选项设置材质的 flatShading 属性
mesh.material.flatShading = ! params.smoothShading;
// 旋转模型,使其正确朝向
mesh.rotateY( - Math.PI / 2 );
// 设置模型的缩放和位移
mesh.scale.set( 3.5, 3.5, 3.5 );
mesh.translateZ( 1.5 );
// 将网格添加到场景中
scene.add( mesh );
// 如果设置了显示纹理,并且纹理已经加载,应用纹理到网格
if ( map !== undefined && params.showMap ) {
mesh.material.map = map;
mesh.material.needsUpdate = true;
}
// 执行渲染
render();
}
);
// 加载纹理贴图
new THREE.TextureLoader().load( 'https://raw.githubusercontent.com/mrdoob/three.js/refs/heads/master/examples/models/obj/cerberus/Cerberus_A.jpg', function ( texture ) {
// 加载纹理并设置颜色空间
map = texture;
map.colorSpace = THREE.SRGBColorSpace;
// 如果网格已经创建,并且需要显示纹理,更新网格的材质
if ( mesh !== undefined && params.showMap ) {
mesh.material.map = map;
mesh.material.needsUpdate = true;
}
});
// 更新网格的几何体和材质
function updateMesh() {
// 切换切割角度的值(0 或 180)
params.cutOffAngle = params.cutOffAngle == 0 ? 180 : 0;
// 如果网格存在,更新其几何体和材质
if ( mesh !== undefined ) {
// 获取新的几何体
mesh.geometry = getGeometry();
// 检查材质是否需要更新
let needsUpdate = mesh.material.flatShading === params.smoothShading;
// 根据平滑着色参数调整材质的 flatShading 属性
mesh.material.flatShading = params.smoothShading === false;
// 如果纹理存在,并且显示纹理的选项被启用,更新纹理
if ( map !== undefined ) {
needsUpdate = needsUpdate || mesh.material.map !== ( params.showMap ? map : null );
mesh.material.map = params.showMap ? map : null;
}
// 更新材质
mesh.material.needsUpdate = needsUpdate;
}
}
// 绑定 DOM 元素点击事件,点击时更新网格
DOMEl.onclick = () => {
updateMesh();
}
// 根据参数选择是否应用边缘分割,返回相应的几何体
function getGeometry() {
let geometry;
// 如果启用了边缘分割,应用 EdgeSplitModifier
if ( params.edgeSplit ) {
geometry = modifier.modify(
baseGeometry,
params.cutOffAngle * Math.PI / 180, // 将角度转为弧度
params.tryKeepNormals // 保留法线选项
);
} else {
geometry = baseGeometry; // 否则使用原始几何体
}
return geometry;
}