React项目3D可视化利器:React-Three-Fiber

1,276 阅读6分钟

前言

随着数字技术的日新月异,3D可视化技术已在众多行业中占据举足轻重的地位,尤其在游戏开发、建筑设计、虚拟现实(VR)以及电子商务等领域,展现出其无尽的潜力与广阔的发展前景。

在众多3D可视化技术解决方案中,Three.js以其丰富的应用程序接口(API)和强大的功能,成为了中小项目3D可视化的首选。然而,尽管Three.js功能强大,但其API种类繁多,学习曲线较为陡峭,使用起来颇为繁琐。为了解决这一问题,React-Three-Fiber应运而生。它通过巧妙地封装Three.js的API,极大地简化了3D可视化开发的流程,使得开发者能够更加高效地进行创作


接下来将从以下方面阐述React-Three-Fiber的优点:

  1. 更优美的代码
  2. 更强大的事件交互
  3. 更显著的性能提升

优点一:更优美的代码

threejs原生代码:

const scene = new THREE.Scence();
const camera = new THREE.PerspectiveCamera(75, width / height);
const htmlEle = document.querySelector('container')

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.querySelector('container').appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandarMaterial();
const mesh = new THREE.Mesh(geometry, material);

scene.add(mesh);

function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene,, camera);
}

animate();

React-Three-Fiber代码:

<Canvas>
    <mesh>
        <boxGeometry />
        <meshStandardMaterial />
    <mesh>
</Canvas>

将原生Three.jsReact-Three-Fiber在创建三维场景时的代码量进行对比,不难发现React-Three-Fiber在代码简洁性和可维护性上展现出了显著优势。React-Three-Fiber凭借其清晰、条理分明的代码结构,不仅极大地提升了代码的可读性,还使得多人协作开发变得更加顺畅和高效。

优点二:更强大的事件交互

<mesh
    onClick={(e) => console.log('click')}
    onContextMenu={(e) => console.log('context menu')}
    onDoubleClick={(e) => console.log('double click')}
    onWheel={(e) => console.log('wheel spins')}
    onPointerUp={(e) => console.log('up')}
    onPointerDown={(e) => console.log('down')}
    onPointerOver={(e) => console.log('over')}
    onPointerOut={(e) => console.log('out')}
    onPointerEnter={(e) => console.log('enter')}
    onPointerLeave={(e) => console.log('leave')}
    onPointerMove={(e) => console.log('move')}
    onPointerMissed={() => console.log('missed')}
    onUpdate={(self) => console.log('props have been updated')}
/>

从上面的代码可以看出React-Three-Fiber提供了很多便捷的回调钩子,接下来以点击事件为例对比一下具体的内容

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

const onClick = (event) => {
    // 归一化设备坐标
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children);
    if (intersects.length > 0) {
        intersects[0].object.material.color.setHex(0xff0000); 
    }
}

window.addEventListener('click', onClick, false)

通过这两段代码可以看出,React-Three-Fiber 封装了许多底层逻辑,使得我们能够更方便地操作 Three.js。然而,它的局限性在于只能获取到对应的模型,而无法直接获取模型下的子模型数组(即射线穿透过的模型)。

对于大多数简单项目而言,React-Three-Fiber所提供的API已经足够满足需求。但当项目需求更为复杂时,开发者可以通过进一步封装逻辑来创建射线实例等方法,从而实现类似的功能。总的来说,React-Three-Fiber为开发者提供了极为便捷的API,使得他们无需深入关注底层实现的繁琐细节,而是可以将更多的精力投入到业务逻辑的创新与优化中。

优点三:更显著的性能提升

性能提升将会从渲染帧跟资源释放两个方面进行分析

渲染帧
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene,, camera);
}

3D模型在屏幕上展示最终形态还是2D展示,所以是在不断的对场景生成新的快照,进而实现了旋转、缩放的效果。requestAnimationFrame是浏览器下次重绘前执行的钩子,可以确保动画与屏幕刷新频率同步,从而提高视觉效果并减少资源消耗。

虽然我们不会一直操作 3D 场景,但快照却在持续更新,这无疑会造成性能消耗。React-Three-Fiber 对此进行了逻辑优化,使用起来也非常方便,只需在 canvas 标签上添加一个属性即可:

<Canvas frameloop="demand">

设置了该属性以后,当场景中的元素停止运动或者变化以后,将不会继续保持渲染状态。

资源释放

在3D模型的应用场景中,经常会将一些相似的基础模型统一添加到组(group)内。当需要销毁这些模型时,我们需要遍历组内的子模型,并对材质、纹理和几何体执行 dispose 函数,大概的流程如下:

  1. 移除模型
  2. 清理纹理资源
  3. 释放几何体资源
  4. 释放材质资源
function destroyModel(scene, modelName) {
    // 获取场景中的模型
    const model = scene.getObjectByName(modelName); 
    
    if (model) {
        // 从场景中移除模型 
        scene.remove(model);
        
        // 遍历模型的所有子对象 
        model.traverse(child => { 
            if (child instanceof THREE.Mesh) { 
                // 清理纹理资源 
                if (child.material.map) { 
                    child.material.map.dispose(); 
                    child.material.map = null; 
                } 
                if (child.material.lightMap) { 
                    child.material.lightMap.dispose(); 
                    child.material.lightMap = null; 
                } 
                if (child.material.emissiveMap) { 
                    child.material.emissiveMap.dispose(); 
                    child.material.emissiveMap = null; 
                }
                
                // 释放几何体资源
                if (child.geometry) { 
                    child.geometry.dispose(); 
                }
                
                // 释放材质资源 
                if (child.material) { 
                    child.material.dispose(); 
                } 
            } 
       }); 
   } 
}

// 使用示例
destroyModel(scene, 'modelName');

从上面的代码可以看出,销毁模型的流程确实比较繁琐,但这一操作是不可或缺的。当多个页面都存在模型时,如果不及时销毁模型,会很大程度地影响页面流畅度,从而影响用户体验。

React-Three-Fiber 在这方面进行了优化,只需要在组(group)标签上维护一个变量。当我们需要销毁模型时,只需将其置为 null 即可。

<group dispose={null}>
    <mesh geometry={globalGeometry} material={globalMaterial} />
</group>

除了以上提及到的两点,React-Three-Fiber还做了很多的性能相关的优化操作,大家可以继续深入学习

总结

React-Three-Fiber无疑给我留下了深刻的印象,但值得一提的是,它并非3D可视化的唯一或最优解决方案。在选择技术栈时,我们必须充分考虑项目的具体需求。例如,在需要对单个模型进行复杂操作的场景中,React-Three-Fiber可能并非最佳选择。

在使用React-Three-Fiber之前,建议先掌握一定的Three.js基础,这正如在深入学习VueReact之前,需要先了解前端基础技术(HTMLCSSJavaScript)一样。一旦熟悉了Three.js,学习React-Three-Fiber将会变得更加顺畅和自然。

在我看来,没有绝对最好的技术,只有最适合特定需求的技术。每种技术都拥有其独特的优势和局限性,关键在于我们如何根据项目的实际需求来做出明智的选择。因此,在决定采用何种技术之前,深入了解项目的需求是至关重要的。只有这样,我们才能确保所选的技术栈能够最大限度地满足项目的需求,从而实现最佳的开发效果。

本文旨在给各位推荐React-Three-Fiber这个插件,而不是教程文章,大家如果感兴趣可以去官网继续学习:r3f.docs.pmnd.rs/getting-sta…