首页 > web前端 > js教程 > 正文

JS 三维图形开发基础 - 使用 Three.js 创建交互式 3D 场景的步骤

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

js 三维图形开发基础 - 使用 three.js 创建交互式 3d 场景的步骤

在 JavaScript 中创建交互式 3D 场景,核心是利用 Three.js 库。它提供了一套直观的 API,让你能定义场景、摄像机、渲染器,然后往里添加各种三维物体,并最终通过动画循环和事件监听来实现用户交互。这就像搭建一个虚拟舞台,你得先有舞台、灯光、观众席,再把演员和道具放进去,最后编排他们的动作和观众的互动。

解决方案

要用 Three.js 构建一个交互式的 3D 场景,我们通常会经历这么几个阶段,这其实也是一个循序渐进的思考过程:

首先,得有个舞台。这就是

Scene
登录后复制
。所有你想在 3D 世界里看到的东西,无论是模型、灯光还是粒子,都得往这个
Scene
登录后复制
对象里塞。

接着,我们需要一双眼睛来看这个舞台。这就是

Camera
登录后复制
。Three.js 提供了好几种摄像机,最常用的是
PerspectiveCamera
登录后复制
,它模拟了人眼的透视效果,近大远小。设置它的视野(FOV)、长宽比、近裁剪面和远裁剪面,就决定了你能在屏幕上看到多大的范围和深度。

有了舞台和眼睛,还得有个画师把看到的画面画出来。这个画师就是

WebGLRenderer
登录后复制
。它负责把
Scene
登录后复制
Camera
登录后复制
看到的内容渲染到你的 HTML
canvas
登录后复制
元素上。初始化它,设置尺寸,然后把它添加到 DOM 里,你的 3D 画布就准备好了。

现在,舞台是空的,我们得放点东西进去。比如,一个简单的立方体。这需要两部分:

Geometry
登录后复制
(几何体,定义形状,比如
BoxGeometry
登录后复制
)和
Material
登录后复制
(材质,定义外观,比如
MeshBasicMaterial
登录后复制
)。把它们结合起来,就成了
Mesh
登录后复制
(网格)。
scene.add(mesh)
登录后复制
,你的第一个 3D 物体就出现在舞台上了。

光有静态的物体还不够,3D 世界的魅力在于动起来。这就需要一个动画循环。通常是一个

requestAnimationFrame
登录后复制
驱动的函数,在这个函数里,我们会更新物体的位置、旋转,然后调用
renderer.render(scene, camera)
登录后复制
来刷新画面。这是整个场景“活”起来的关键。

至于“交互式”,这是 Three.js 真正有趣的地方。最常见的交互是鼠标点击或触摸。你需要用到

Raycaster
登录后复制
。它就像从你的摄像机发射出一条射线,穿过你鼠标点击的屏幕坐标,然后检测这条射线是否与场景中的任何物体相交。如果相交了,你就能知道用户点击了哪个物体,然后做出相应的反应,比如改变颜色、移动或者触发动画。

别忘了灯光。一个没有灯光的 3D 场景是漆黑一片的。

AmbientLight
登录后复制
提供基础环境光,
DirectionalLight
登录后复制
模拟太阳光,
PointLight
登录后复制
就像灯泡。有了灯光,物体的材质才能被正确渲染,才能有明暗变化,甚至投射出阴影,让场景看起来更真实。

最后,为了让用户能方便地在场景中移动和观察,

OrbitControls
登录后复制
是个非常实用的工具。它能让你通过鼠标拖拽来旋转摄像机,滚轮来缩放,实现类似 CAD 软件的视角控制。

Three.js 开发中常见的性能瓶颈有哪些?如何优化?

在 Three.js 开发中,性能问题是常客,尤其当场景变得复杂时。我遇到过最头疼的,往往不是代码逻辑上的错误,而是那种“为什么我的帧率掉下来了?”的疑惑。这背后通常有几个罪魁祸首。

首先,Draw Calls(绘制调用)。每一次 GPU 绘制操作都需要 CPU 提交指令,这个过程是有开销的。如果你的场景里有成千上万个独立的网格(

Mesh
登录后复制
),每个网格都要单独绘制,那么 Draw Calls 就会飙升。优化的方法是合并几何体(Merge Geometries),把多个小的、使用相同材质的网格合并成一个大网格,或者使用实例化(Instancing)。Three.js 的
InstancedMesh
登录后复制
就是为此而生,它允许你用一个 Draw Call 渲染大量相同的几何体,每个实例可以有自己的位置、旋转和缩放。

其次是几何体复杂度。高面数的模型,尤其是那些从外部导入的 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 场景中的用户交互,实现点击、拖拽等功能?

在 Three.js 中实现用户交互,比如点击选中物体、拖拽移动物体,这块我觉得是真正让 3D 场景“活”起来的关键。它不像 2D DOM 元素那样可以直接监听

click
登录后复制
mousedown
登录后复制
事件,因为你点击的是屏幕上的一个点,而不是 3D 空间里的某个物体。这里就需要
Raycaster
登录后复制
出场了。

Raycaster
登录后复制
的原理其实挺直观的:它从摄像机的位置发出一条“射线”,穿过你鼠标点击的那个屏幕点,然后这条射线会去检测它是否与场景中的任何 3D 物体相交。

具体步骤是这样的:

  1. 监听 DOM 事件:首先,你得监听

    canvas
    登录后复制
    元素上的鼠标事件,比如
    mousemove
    登录后复制
    mousedown
    登录后复制
    mouseup
    登录后复制
    。当事件触发时,获取鼠标在
    canvas
    登录后复制
    上的坐标。

    Bing图像创建器
    Bing图像创建器

    必应出品基于DALL·E的AI绘图工具

    Bing图像创建器 45
    查看详情 Bing图像创建器
    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);
    登录后复制
  2. 创建 Raycaster:在你的渲染循环或者事件处理函数中,实例化一个

    Raycaster
    登录后复制

    const raycaster = new THREE.Raycaster();
    登录后复制
  3. 更新 Raycaster:使用摄像机和鼠标的归一化坐标来更新

    Raycaster
    登录后复制

    raycaster.setFromCamera(mouse, camera);
    登录后复制
  4. 检测相交物体:调用

    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
    登录后复制
    时,如果某个物体处于“正在拖拽”状态,你需要计算鼠标在 3D 空间中的新位置,并将物体移动到那里。这通常需要一些数学技巧,比如在物体所在的平面上投射射线,或者使用
    OrbitControls
    登录后复制
    enablePan
    登录后复制
    enableRotate
    登录后复制
    属性来辅助实现。一个常见的做法是,在物体被选中时,创建一个辅助平面(比如与摄像机垂直的平面,或者物体原始平面),然后计算鼠标在辅助平面上的新位置。
  • mouseup
    登录后复制
    时,取消“正在拖拽”状态。

这整个过程听起来有点绕,但一旦你理解了

Raycaster
登录后复制
的工作方式,很多复杂的交互都能基于它实现。关键在于将 2D 屏幕坐标正确地映射到 3D 空间中。

除了基础图形,Three.js 如何加载外部模型(如 GLTF/OBJ)并进行动画控制?

在实际项目里,我们很少只用

BoxGeometry
登录后复制
SphereGeometry
登录后复制
这样的基础图形。更多时候,我们会在 Blender、Maya 或其他 3D 软件里创建复杂的模型,然后导入到 Three.js 场景中。这里,加载外部模型和控制它们的动画是两个非常核心且常用的功能。

加载外部模型

Three.js 社区和生态非常成熟,它为多种流行的 3D 模型格式提供了加载器。其中最推荐和广泛使用的是 GLTF (GL Transmission Format)。GLTF 被称为“3D 领域的 JPEG”,因为它设计得非常紧凑,加载效率高,并且能包含模型的几何体、材质、纹理、动画、骨骼、场景结构等所有信息。

加载 GLTF 模型通常是这样的:

  1. 引入 GLTFLoader:它不是 Three.js 核心库的一部分,你需要从

    examples/jsm/loaders/GLTFLoader.js
    登录后复制
    引入。

    import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
    登录后复制
  2. 实例化 Loader

    const loader = new GLTFLoader();
    登录后复制
  3. 加载模型:调用

    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,你可能还会遇到 OBJFBX 格式。它们也有对应的加载器 (

OBJLoader
登录后复制
,
FBXLoader
登录后复制
),使用方式类似,但 GLTF 通常是首选,因为它更现代,对 Web 优化更好,并且能更好地封装动画数据。

动画控制

当模型加载进来并且带有动画数据时,Three.js 提供了一套强大的动画系统来控制它们,核心是

AnimationMixer
登录后复制
AnimationAction
登录后复制

  1. 创建 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();
    }
    登录后复制
  2. 更新 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
登录后复制
),甚至在不同的动画之间进行平滑过渡,这对于实现角色走路、跑步、跳跃等复杂行为至关重要。这套系统给了开发者极大的灵活性去编排和控制 3D 场景中的动态内容。

以上就是JS 三维图形开发基础 - 使用 Three.js 创建交互式 3D 场景的步骤的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号