three.js中如何翻转顶点几何体的部分法向量以修复VTK模型渲染孔洞
问题背景
我测试了PCL三角化方案,实验后得到了一个.vtk格式文件。尝试在Windows环境下通过浏览器使用three.js渲染该文件,效果如下:
可以看到渲染结果存在大量孔洞,这些孔洞对应的三角面法向量与其他面方向相反。
我需要翻转方向相反的法向量,以此得到如下所示的干净网格效果:
核心疑问
- 我该如何实现该需求?
- 是直接在three.js中处理更合适,还是在PCL库的三角化阶段处理更好?
测试代码
以下是我测试用的HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <!--title>four.js webgl - loaders - vtk loader</title--> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <link type="text/css" rel="stylesheet" href="main.css"> </head> <body> <script type="module"> var console = (window.console = window.console || {}); import * as THREE from '../build/three.module.js'; import { TrackballControls } from './jsm/controls/TrackballControls.js'; import { VTKLoader } from './jsm/loaders/VTKLoader.js'; import { VertexNormalsHelper } from './jsm/helpers/VertexNormalsHelper.js'; let container, camera, controls, scene, renderer, vnh; init(); animate(); function init() { camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 1e10); camera.position.set( -1, 0, 0 ); scene = new THREE.Scene(); scene.add( camera ); // light const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x000000, 1 ); scene.add( hemiLight ); const dirLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); dirLight.position.set( 2, 2, 2 ); scene.add( dirLight ); const material = new THREE.MeshLambertMaterial( { color: 0xffffff } ); const loader = new VTKLoader(); loader.load( "models/vtk/point_cloud_mesh.vtk", function ( geometry ) //loader.load( "models/vtk/point_cloud_mesh_not_smoothed.vtk", function ( geometry ) { //geometry.applyMatrix(new THREE.Matrix4().makeScale(-1, -1,-1)); //geometry.computeFaceNormals(); //var tmp; //console.log(geometry.index.count); //console.log(geometry.userData); geometry.computeVertexNormals(); const mesh = new THREE.Mesh( geometry, material ); mesh.position.set( - 0.075, 0.005, 0 ); mesh.scale.multiplyScalar( 0.2 ); scene.add( mesh ); //vnh = new VertexNormalsHelper(mesh, 0.001, 0x00ff00, 1); //scene.add( vnh ); } ); // renderer renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); container = document.createElement( 'div' ); document.body.appendChild( container ); container.appendChild( renderer.domElement ); // controls controls = new TrackballControls( camera, renderer.domElement ); controls.minDistance = .1; controls.maxDistance = 0.5; controls.rotateSpeed = 5.0; window.addEventListener( 'resize', onWindowResize ); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); controls.handleResize(); } function animate() { requestAnimationFrame( animate ); controls.update(); //if (vnh) vnh.update(); renderer.render( scene, camera ); } </script> </body> </html>
当前进展
目前我通过WestLangley的建议已经得到了初步的预期效果,如下所示:
但问题还未完全解决:WestLangley的方案虽然有效,但并非唯一解,目前仍有部分法向量朝向手掌内部,我希望找到能让所有法向量都朝向模型外侧的方案。
解决方案
优先推荐在PCL三角化阶段从源头解决法向量对齐问题,效果和性能更优,three.js端可作为临时修复方案使用。
方案1:PCL端预处理(首推)
在三角化完成后添加法向量全局对齐步骤:
- 三角化前先使用
pcl::NormalEstimation计算点云初始法向量 - 调用
pcl::OrientNormalsTowardsViewpoint方法将所有法向量统一朝向预设视点,或使用pcl::MeshNormalOrientation类实现网格法向量全局一致化,确保所有三角面法向量均朝向网格外侧 - 导出处理后的网格为vtk格式,后续渲染无需额外处理
方案2:three.js端修复
如果无法调整PCL端处理逻辑,可通过以下两种方式修复:
快速消洞方案
直接修改材质的side属性,强制渲染三角面的正反两面,无需处理法向量即可消除孔洞:
// 将原材质代码替换为下面的内容 const material = new THREE.MeshLambertMaterial( { color: 0xffffff, side: THREE.DoubleSide } );
法向量统一朝向方案
如果需要严格统一所有法向量朝向外侧,可按以下步骤处理:
- 加载完几何数据后,先计算网格的包围盒中心作为参考点
- 遍历所有顶点的法向量,计算对应顶点到参考点的向量与法向量的点积,若点积为负说明法向量朝向内侧,将该法向量取反
- 处理完成后标记
geometry.attributes.normal.needsUpdate = true使修改生效
内容的提问来源于stack exchange,提问作者Jumogehn
相关产品推荐
相关产品推荐

