You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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端预处理(首推)

在三角化完成后添加法向量全局对齐步骤:

  1. 三角化前先使用pcl::NormalEstimation计算点云初始法向量
  2. 调用pcl::OrientNormalsTowardsViewpoint方法将所有法向量统一朝向预设视点,或使用pcl::MeshNormalOrientation类实现网格法向量全局一致化,确保所有三角面法向量均朝向网格外侧
  3. 导出处理后的网格为vtk格式,后续渲染无需额外处理

方案2:three.js端修复

如果无法调整PCL端处理逻辑,可通过以下两种方式修复:

快速消洞方案

直接修改材质的side属性,强制渲染三角面的正反两面,无需处理法向量即可消除孔洞:

// 将原材质代码替换为下面的内容
const material = new THREE.MeshLambertMaterial( { color: 0xffffff, side: THREE.DoubleSide } );

法向量统一朝向方案

如果需要严格统一所有法向量朝向外侧,可按以下步骤处理:

  1. 加载完几何数据后,先计算网格的包围盒中心作为参考点
  2. 遍历所有顶点的法向量,计算对应顶点到参考点的向量与法向量的点积,若点积为负说明法向量朝向内侧,将该法向量取反
  3. 处理完成后标记geometry.attributes.normal.needsUpdate = true使修改生效

内容的提问来源于stack exchange,提问作者Jumogehn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 04:15:02