答案是使用Three.js创建交互式3D场景需构建场景、相机、渲染器,添加物体与灯光,通过动画循环和Raycaster实现交互;性能优化包括减少Draw Calls、LOD、纹理压缩、控制后处理及Web Worker计算;用户交互通过Raycaster将鼠标坐标映射为3D空间射线检测相交物体,实现点击与拖拽;外部模型常用GLTF格式加载,配合AnimationMixer和AnimationAction控制动画播放。

在 JavaScript 中创建交互式 3D 场景,核心是利用 Three.js 库。它提供了一套直观的 API,让你能定义场景、摄像机、渲染器,然后往里添加各种三维物体,并最终通过动画循环和事件监听来实现用户交互。这就像搭建一个虚拟舞台,你得先有舞台、灯光、观众席,再把演员和道具放进去,最后编排他们的动作和观众的互动。
要用 Three.js 构建一个交互式的 3D 场景,我们通常会经历这么几个阶段,这其实也是一个循序渐进的思考过程:
首先,得有个舞台。这就是
Scene
Scene
接着,我们需要一双眼睛来看这个舞台。这就是
Camera
PerspectiveCamera
有了舞台和眼睛,还得有个画师把看到的画面画出来。这个画师就是
WebGLRenderer
Scene
Camera
canvas
现在,舞台是空的,我们得放点东西进去。比如,一个简单的立方体。这需要两部分:
Geometry
BoxGeometry
Material
MeshBasicMaterial
Mesh
scene.add(mesh)
光有静态的物体还不够,3D 世界的魅力在于动起来。这就需要一个动画循环。通常是一个
requestAnimationFrame
renderer.render(scene, camera)
至于“交互式”,这是 Three.js 真正有趣的地方。最常见的交互是鼠标点击或触摸。你需要用到
Raycaster
别忘了灯光。一个没有灯光的 3D 场景是漆黑一片的。
AmbientLight
DirectionalLight
PointLight
最后,为了让用户能方便地在场景中移动和观察,
OrbitControls
在 Three.js 开发中,性能问题是常客,尤其当场景变得复杂时。我遇到过最头疼的,往往不是代码逻辑上的错误,而是那种“为什么我的帧率掉下来了?”的疑惑。这背后通常有几个罪魁祸首。
首先,Draw Calls(绘制调用)。每一次 GPU 绘制操作都需要 CPU 提交指令,这个过程是有开销的。如果你的场景里有成千上万个独立的网格(
Mesh
InstancedMesh
其次是几何体复杂度。高面数的模型,尤其是那些从外部导入的 CAD 或 DCC 软件导出的模型,未经优化就直接丢进 WebGL,那帧率肯定会很难看。LOD(Level of Detail) 是个好策略,根据物体与摄像机的距离,动态切换不同细节程度的模型。距离远的用低模,距离近的用高模。另外,几何体简化(Geometry Simplification)工具也很有用,可以减少多边形数量。
再来是纹理。高分辨率的纹理,尤其是没有经过压缩的,会占用大量的显存,并且加载时间也会很长。纹理压缩是必须的,像 ETC2、ASTC、Basis Universal 这样的格式,能大大减小纹理文件大小和显存占用。同时,纹理图集(Texture Atlas)也能减少 Draw Calls,把多个小纹理打包成一张大图。
还有后处理效果(Post-processing effects)。像泛光(Bloom)、景深(Depth of Field)、环境光遮蔽(SSAO)这些效果虽然能让场景看起来更酷炫,但它们通常需要额外的渲染通道,会显著增加 GPU 的负担。用的时候要克制,只在必要的地方使用,并且尝试调整参数,找到性能和视觉效果的平衡点。
最后,JavaScript 本身的运算。如果你在动画循环里做了大量的复杂计算,比如物理模拟、粒子系统更新,而这些计算又没有放到 Web Worker 里,那么它会阻塞主线程,导致渲染卡顿。将耗时计算 offload 到 Web Worker 是个不错的选择。
在 Three.js 中实现用户交互,比如点击选中物体、拖拽移动物体,这块我觉得是真正让 3D 场景“活”起来的关键。它不像 2D DOM 元素那样可以直接监听
click
mousedown
Raycaster
Raycaster
具体步骤是这样的:
监听 DOM 事件:首先,你得监听
canvas
mousemove
mousedown
mouseup
canvas
const mouse = new THREE.Vector2();
window.addEventListener('mousemove', (event) => {
// 将鼠标坐标从屏幕像素转换为 Three.js 的归一化设备坐标 (NDC)
// NDC 范围是 -1 到 1
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}, false);创建 Raycaster:在你的渲染循环或者事件处理函数中,实例化一个
Raycaster
const raycaster = new THREE.Raycaster();
更新 Raycaster:使用摄像机和鼠标的归一化坐标来更新
Raycaster
raycaster.setFromCamera(mouse, camera);
检测相交物体:调用
raycaster.intersectObjects()
scene.children
const intersects = raycaster.intersectObjects(scene.children, true); // true 表示递归检测子对象
if (intersects.length > 0) {
const firstIntersectedObject = intersects[0].object;
// 找到了!现在可以对这个物体做点什么了
// 比如改变它的颜色
firstIntersectedObject.material.color.set(0xff0000);
} else {
// 没有物体被点击
}对于拖拽功能,这会稍微复杂一些,因为它涉及状态管理。你需要:
mousedown
Raycaster
mousemove
OrbitControls
enablePan
enableRotate
mouseup
这整个过程听起来有点绕,但一旦你理解了
Raycaster
在实际项目里,我们很少只用
BoxGeometry
SphereGeometry
加载外部模型
Three.js 社区和生态非常成熟,它为多种流行的 3D 模型格式提供了加载器。其中最推荐和广泛使用的是 GLTF (GL Transmission Format)。GLTF 被称为“3D 领域的 JPEG”,因为它设计得非常紧凑,加载效率高,并且能包含模型的几何体、材质、纹理、动画、骨骼、场景结构等所有信息。
加载 GLTF 模型通常是这样的:
引入 GLTFLoader:它不是 Three.js 核心库的一部分,你需要从
examples/jsm/loaders/GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';实例化 Loader:
const loader = new GLTFLoader();
加载模型:调用
loader.load()
loader.load(
'path/to/your/model.gltf',
function (gltf) {
// 模型加载成功后,gltf 对象包含了场景、动画等所有信息
scene.add(gltf.scene); // 将模型添加到场景中
// 如果模型有动画,动画数据在 gltf.animations 数组中
if (gltf.animations && gltf.animations.length) {
// 后续会用到这些动画数据
// console.log(gltf.animations);
}
},
// 进度回调函数
function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
// 错误回调函数
function (error) {
console.error('An error happened', error);
}
);除了 GLTF,你可能还会遇到 OBJ 和 FBX 格式。它们也有对应的加载器 (
OBJLoader
FBXLoader
动画控制
当模型加载进来并且带有动画数据时,Three.js 提供了一套强大的动画系统来控制它们,核心是
AnimationMixer
AnimationAction
创建 AnimationMixer:
AnimationMixer
gltf.scene
Mixer
let mixer; // 定义一个全局或可访问的变量来存储 mixer
// 在 gltf.load 的成功回调中
if (gltf.animations && gltf.animations.length) {
mixer = new THREE.AnimationMixer(gltf.scene);
// 获取第一个动画剪辑
const clip = gltf.animations[0];
// 创建一个动画动作
const action = mixer.clipAction(clip);
// 可以设置动画模式,比如循环播放
action.setLoop(THREE.LoopRepeat, Infinity);
// 播放动画
action.play();
}更新 AnimationMixer:
AnimationMixer
deltaTime
const clock = new THREE.Clock(); // 创建一个时钟对象来计算 deltaTime
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta(); // 获取时间增量
if (mixer) {
mixer.update(delta); // 更新动画混合器
}
renderer.render(scene, camera);
}
animate();通过
AnimationAction
setEffectiveTimeScale
setEffectiveWeight
fadeIn
fadeOut
以上就是JS 三维图形开发基础 - 使用 Three.js 创建交互式 3D 场景的步骤的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号