【02】基于 Three.js 实现的交互式 3D 场景代码解析(webgl_instancing_ray ...

打印 上一主题 下一主题

主题 889|帖子 889|积分 2667

一、引言

在前端开发范畴,Three.js 是一款强大的用于创建和展示 3D 图形及场景的 JavaScript 库。今天我们要来详细解析一段利用 Three.js 构建的代码,这段代码实现了一个具有交互功能的 3D 场景,下面就让我们徐徐深入相识每一部分的功能及原理吧。官网示例https://github.com/mrdoob/three.js/blob/master/examples/webgl_instancing_raycast.html

二、模块导入部分

  1. <script type="module">
  2. import * as THREE from 'three';
  3. import Stats from 'three/addons/libs/stats.module.js';
  4. import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
  5. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  6. </script>
复制代码
在代码开头,我们利用 import 语句导入了几个重要的模块。


  • import * as THREE from 'three';:这行代码导入了整个 Three.js 库,后续我们将通过 THREE 这个定名空间来调用 Three.js 提供的各类 3D 相关的类和函数,比如创建相机、几何体、材质等。例如,我们之后会用 THREE.PerspectiveCamera 来创建透视相机。


  • import Stats from 'three/addons/libs/stats.module.js';:Stats 模块用于统计渲染性能相关的数据,比如帧率等信息,方便我们在开发过程中监测场景渲染的效率情况,相识是否存在性能瓶颈。


  • import { GUI } from 'three/addons/libs/lil-gui.module.min.js';:GUI 是图形用户界面相关的模块,通过它可以方便地在页面上创建交互式的控制面板,来调整场景中的一些参数,例如我们背面会用它来控制实例化网格(InstancedMesh)的数量等属性。


  • import { OrbitControls } from 'three/addons/controls/OrbitControls.js';:OrbitControls 模块能为我们提供围绕 3D 场景中的物体举行旋转、缩放等交互操作的控制功能,使得用户可以方便地从不同角度查看场景中的内容。
三、全局变量声明部分

  1. let camera, scene, renderer, controls, stats;
  2. let mesh;
  3. const amount = parseInt( window.location.search.slice( 1 ) ) || 10;
  4. const count = Math.pow( amount, 3 );
  5. const raycaster = new THREE.Raycaster();
  6. const mouse = new THREE.Vector2( 1, 1 );
  7. const color = new THREE.Color();
  8. const white = new THREE.Color().setHex( 0xffffff );
复制代码
这里声明白一系列在后续代码中会用到的全局变量:


  • camera、scene、renderer、controls、stats 这些变量分别对应着相机、场景、渲染器、交互控制以及性能统计对象,它们是构建和展示 3D 场景的焦点元素。比如 scene 是我们添加各种 3D 物体(如几何体、灯光等)的容器,renderer 则负责将场景中的内容渲染到页面上出现给用户。


  • mesh 变量用于存储背面创建的实例化网格对象,它是场景中的重要可视化元素之一。


  • amount 变量实验从页面的 URL 参数中获取一个数值,如果获取失败则默认赋值为 10。count 变量根据 amount 的值盘算得到,它代表了实例化网格中实例的总数量(这里是 amount 的立方)。


  • raycaster 是光线投射器对象,用于举行鼠标交互时检测与场景中物体的相交情况,比如判定鼠标指向了场景中的哪个具体的实例。mouse 变量用于存储鼠标在屏幕上的归一化坐标,方便 raycaster 举行位置盘算。


  • color 和 white 都是 THREE.Color 类型的对象,white 被设置为白色(十六进制颜色值 0xffffff),color 会在后续用于获取和设置实例的颜色等操作。
四、init 初始化函数部分

  1. function init() {
  2. camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
  3. camera.position.set( amount, amount, amount );
  4. camera.lookAt( 0, 0, 0 );
  5. scene = new THREE.Scene();
  6. const light = new THREE.HemisphereLight( 0xffffff, 0x888888, 3 );
  7. light.position.set( 0, 1, 0 );
  8. scene.add( light );
  9. const geometry = new THREE.IcosahedronGeometry( 0.5, 3 );
  10. const material = new THREE.MeshPhongMaterial( { color: 0xffffff } );
  11. mesh = new THREE.InstancedMesh( geometry, material, count );
  12. // 嵌套循环设置实例矩阵和颜色相关代码略(篇幅原因)
  13. scene.add( mesh );
  14. const gui = new GUI();
  15. gui.add( mesh, 'count', 0, count );
  16. renderer = new THREE.WebGLRenderer( { antialias: true } );
  17. renderer.setPixelRatio( window.devicePixelRatio );
  18. renderer.setSize( window.innerWidth, window.innerHeight );
  19. renderer.setAnimationLoop( animate );
  20. document.body.appendChild( renderer.domElement );
  21. controls = new OrbitControls( camera, renderer.domElement );
  22. controls.enableDamping = true;
  23. controls.enableZoom = false;
  24. controls.enablePan = false;
  25. stats = new Stats();
  26. document.body.appendChild( stats.dom );
  27. window.addEventListener( 'resize', onWindowResize );
  28. document.addEventListener( 'mousemove', onMouseMove );
  29. }
复制代码
init 函数负担了整个场景初始化的重要任务:


  • 相机创建与设置




    • camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 ); 创建了一个透视相机,视角为 60 度,近裁剪面间隔为 0.1,远裁剪面间隔为 100,而且根据当前窗口的宽高比来确定视野范围。





    • camera.position.set( amount, amount, amount ); 将相机放置在由 amount 值确定的位置上,使其从一个合适的角度看向场景中央(通过 camera.lookAt( 0, 0, 0 ); 来指定看向原点)。



  • 场景创建与灯光添加




    • scene = new THREE.Scene(); 创建了一个空的 3D 场景,后续所有的物体、灯光等都会添加到这个场景中。





    • 通过 const light = new THREE.HemisphereLight( 0xffffff, 0x888888, 3 ); 创建了一个半球光,颜色分别为白色(天空光)和灰色(地面光),强度为 3,并将其位置设置在 (0, 1, 0) 处,然后添加到场景中,为场景提供照明结果。



  • 创建实例化网格




    • 起首定义了一个二十面体的几何体 const geometry = new THREE.IcosahedronGeometry( 0.5, 3 );,这里半径为 0.5,细分级别为 3。





    • 接着创建了一个 MeshPhongMaterial 材质 const material = new THREE.MeshPhongMaterial( { color: 0xffffff } );,材质颜色为白色,用于给几何体赋予外观属性。





    • 然后创建了实例化网格 mesh = new THREE.InstancedMesh( geometry, material, count );,它会根据 count 的数量创建多个相同几何体和材质的实例,用于构建复杂的场景结果。之后通过嵌套的 for 循环设置每个实例的位置矩阵以及初始颜色等信息(代码中的嵌套循环部分),末了将 mesh 添加到场景中。



  • GUI 创建:const gui = new GUI(); 创建了图形用户界面,而且通过 gui.add( mesh, 'count', 0, count ); 为实例化网格的 count 属性添加了一个可调节的滑块,用户可以通过这个滑块来改变实例的数量,实时看到场景的变革结果。


  • 渲染器相关设置




    • renderer = new THREE.WebGLRenderer( { antialias: true } ); 创建了一个支持抗锯齿的 WebGL 渲染器,能让渲染出的图形边缘更加平滑。





    • renderer.setPixelRatio( window.devicePixelRatio ); 根据装备的像素比举行设置,确保在不同分辨率的装备上渲染结果的清晰度。





    • renderer.setSize( window.innerWidth, window.innerHeight ); 将渲染器的尺寸设置为和窗口大小同等,然后通过 renderer.setAnimationLoop( animate ); 指定了每一帧要实验的动画更新函数(这里是 animate 函数),末了将渲染器的 DOM 元素添加到页面的 body 标签中,如许才气将渲染的内容体现出来。



  • 交互控制设置




    • controls = new OrbitControls( camera, renderer.domElement ); 创建了轨道控制对象,使得用户可以通过鼠标拖动等操作来旋转查看场景中的物体,而且设置了 enableDamping = true 来启用阻尼结果,让旋转等操作更加平滑自然,同时禁用了缩放(enableZoom = false)和平移(enablePan = false)功能。



  • 性能统计与变乱监听




    • stats = new Stats(); 创建了性能统计对象,并将其 DOM 元素添加到页面 body 中,如许就能实时体现帧率等性能数据了。





    • 分别添加了窗口大小改变(window.addEventListener( 'resize', onWindowResize );)和鼠标移动(document.addEventListener( 'mousemove', onMouseMove );)的变乱监听器,对应的处置惩罚函数 onWindowResize 和 onMouseMove 会在相应变乱触发时实验相应的逻辑来更新场景状态等。

五、onWindowResize 窗口大小改变处置惩罚函数

  1. function onWindowResize() {
  2. camera.aspect = window.innerWidth / window.innerHeight;
  3. camera.updateProjectionMatrix();
  4. renderer.setSize( window.innerWidth, window.innerHeight );
  5. }
复制代码
当窗口大小发生改变时,这个函数会被调用。它重要做两件事:


  • 起首更新相机的宽高比 camera.aspect = window.innerWidth / window.innerHeight;,使其与新的窗口宽高比匹配,然后调用 camera.updateProjectionMatrix(); 来更新相机的投影矩阵,确保场景的透视结果能根据新的窗口尺寸精确体现。


  • 接着通过 renderer.setSize( window.innerWidth, window.innerHeight ); 让渲染器的尺寸也调整为新的窗口大小,保证渲染的内容能完备填充窗口。
六、onMouseMove 鼠标移动处置惩罚函数

  1. function onMouseMove( event ) {
  2. event.preventDefault();
  3. mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  4. mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  5. }
复制代码
当鼠标在页面上移动时,该函数会被触发。


  • event.preventDefault(); 阻止了默认的鼠标行为,克制出现一些不必要的欣赏器默认结果,比如滚动条的滚动等。


  • 然后将鼠标在页面上的坐标 event.clientX 和 event.clientY 转换为归一化的坐标,范围在 -1 到 1 之间,存储到 mouse 变量中。这个归一化坐标后续会被 raycaster 用于盘算与场景中物体的相交情况,是实现鼠标交互的关键步调之一。
七、animate 动画更新函数

  1. function animate() {
  2. controls.update();
  3. raycaster.setFromCamera( mouse, camera );
  4. const intersection = raycaster.intersectObject( mesh );
  5. if ( intersection.length > 0 ) {
  6. const instanceId = intersection[ 0 ].instanceId;
  7. mesh.getColorAt( instanceId, color );
  8. if ( color.equals( white ) ) {
  9. mesh.setColorAt( instanceId, color.setHex( Math.random() * 0xffffff ) );
  10. mesh.instanceColor.needsUpdate = true;
  11. }
  12. }
  13. renderer.render( scene, camera );
  14. stats.update();
  15. }
复制代码
这个函数是整个场景动画更新的焦点逻辑地点,每帧都会被调用:


  • controls.update(); 起首更新交互控制的状态,比如根据鼠标操作等更新相机的位置、角度等,保证交互的流畅性。


  • raycaster.setFromCamera( mouse, camera ); 根据当前鼠标的位置(通过 mouse 变量)和相机的信息,设置光线投射器的起点和方向,用于检测与场景中物体的相交情况。


  • const intersection = raycaster.intersectObject( mesh ); 实验光线投射检测,判定是否与实例化网格 mesh 有相交,如果有相交,会返回一个包含相交信息的数组。


  • 当 intersection.length > 0 即有相交情况时:




    • 获取相交的实例的 instanceId,然后通过 mesh.getColorAt( instanceId, color ); 获取该实例的当前颜色到 color 变量中。





    • 如果获取到的颜色与白色(之前定义的 white 颜色)相称,就通过 mesh.setColorAt( instanceId, color.setHex( Math.random() * 0xffffff ) ); 随机设置该实例的一个新颜色,而且设置 mesh.instanceColor.needsUpdate = true; 来告知渲染器实例的颜色有更新,必要重新渲染该实例的颜色。



  • 末了通过 renderer.render( scene, camera ); 举行场景的渲染,将当前状态下的场景内容根据相机视角渲染到页面上,同时调用 stats.update(); 来更新性能统计的数据,实时体现帧率等信息。
八、总结

通过对这段代码的详细解析,我们可以看到利用 Three.js 构建一个具有交互功能的 3D 场景必要涉及到多个方面的知识和操作,从基础的模块导入、对象创建,到复杂的鼠标交互、动画更新等逻辑。希望这篇文章能资助大家更好地理解 Three.js 的利用,在本身的项目中也能创建出炫酷的 3D 结果。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

杀鸡焉用牛刀

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表