一、引言
在前端开发范畴,Three.js 是一款强大的用于创建和展示 3D 图形及场景的 JavaScript 库。今天我们要来详细解析一段利用 Three.js 构建的代码,这段代码实现了一个具有交互功能的 3D 场景,下面就让我们徐徐深入相识每一部分的功能及原理吧。官网示例https://github.com/mrdoob/three.js/blob/master/examples/webgl_instancing_raycast.html
二、模块导入部分
- <script type="module">
- import * as THREE from 'three';
- import Stats from 'three/addons/libs/stats.module.js';
- import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
- </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 场景中的物体举行旋转、缩放等交互操作的控制功能,使得用户可以方便地从不同角度查看场景中的内容。
三、全局变量声明部分
- let camera, scene, renderer, controls, stats;
- let mesh;
- const amount = parseInt( window.location.search.slice( 1 ) ) || 10;
- const count = Math.pow( amount, 3 );
- const raycaster = new THREE.Raycaster();
- const mouse = new THREE.Vector2( 1, 1 );
- const color = new THREE.Color();
- 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 初始化函数部分
- function init() {
- camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
- camera.position.set( amount, amount, amount );
- camera.lookAt( 0, 0, 0 );
- scene = new THREE.Scene();
- const light = new THREE.HemisphereLight( 0xffffff, 0x888888, 3 );
- light.position.set( 0, 1, 0 );
- scene.add( light );
- const geometry = new THREE.IcosahedronGeometry( 0.5, 3 );
- const material = new THREE.MeshPhongMaterial( { color: 0xffffff } );
- mesh = new THREE.InstancedMesh( geometry, material, count );
- // 嵌套循环设置实例矩阵和颜色相关代码略(篇幅原因)
- scene.add( mesh );
- const gui = new GUI();
- gui.add( mesh, 'count', 0, count );
- renderer = new THREE.WebGLRenderer( { antialias: true } );
- renderer.setPixelRatio( window.devicePixelRatio );
- renderer.setSize( window.innerWidth, window.innerHeight );
- renderer.setAnimationLoop( animate );
- document.body.appendChild( renderer.domElement );
- controls = new OrbitControls( camera, renderer.domElement );
- controls.enableDamping = true;
- controls.enableZoom = false;
- controls.enablePan = false;
- stats = new Stats();
- document.body.appendChild( stats.dom );
- window.addEventListener( 'resize', onWindowResize );
- document.addEventListener( 'mousemove', onMouseMove );
- }
复制代码 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 窗口大小改变处置惩罚函数
- function onWindowResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize( window.innerWidth, window.innerHeight );
- }
复制代码 当窗口大小发生改变时,这个函数会被调用。它重要做两件事:
- 起首更新相机的宽高比 camera.aspect = window.innerWidth / window.innerHeight;,使其与新的窗口宽高比匹配,然后调用 camera.updateProjectionMatrix(); 来更新相机的投影矩阵,确保场景的透视结果能根据新的窗口尺寸精确体现。
- 接着通过 renderer.setSize( window.innerWidth, window.innerHeight ); 让渲染器的尺寸也调整为新的窗口大小,保证渲染的内容能完备填充窗口。
六、onMouseMove 鼠标移动处置惩罚函数
- function onMouseMove( event ) {
- event.preventDefault();
- mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
- mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
- }
复制代码 当鼠标在页面上移动时,该函数会被触发。
- event.preventDefault(); 阻止了默认的鼠标行为,克制出现一些不必要的欣赏器默认结果,比如滚动条的滚动等。
- 然后将鼠标在页面上的坐标 event.clientX 和 event.clientY 转换为归一化的坐标,范围在 -1 到 1 之间,存储到 mouse 变量中。这个归一化坐标后续会被 raycaster 用于盘算与场景中物体的相交情况,是实现鼠标交互的关键步调之一。
七、animate 动画更新函数
- function animate() {
- controls.update();
- raycaster.setFromCamera( mouse, camera );
- const intersection = raycaster.intersectObject( mesh );
- if ( intersection.length > 0 ) {
- const instanceId = intersection[ 0 ].instanceId;
- mesh.getColorAt( instanceId, color );
- if ( color.equals( white ) ) {
- mesh.setColorAt( instanceId, color.setHex( Math.random() * 0xffffff ) );
- mesh.instanceColor.needsUpdate = true;
- }
- }
- renderer.render( scene, camera );
- stats.update();
- }
复制代码 这个函数是整个场景动画更新的焦点逻辑地点,每帧都会被调用:
- 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企服之家,中国第一个企服评测及商务社交产业平台。 |