普通视图

发现新文章,点击刷新页面。
昨天 — 2025年6月30日首页

Three.js性能优化实战:让3D丝滑如德芙

作者 有才叔
2025年6月30日 10:44

在Three.js的世界里,炫酷特效固然吸引眼球,但性能优化才是保证用户体验的王道。想象一下:你的3D场景卡成PPT,用户手机发烫能煎蛋,这体验绝对劝退。今天咱们就聊聊那些让Three.js飞起来的实战技巧!

优化核心思路:少干活,干聪明活

技巧1:几何体合并 - 减少Draw Call

每次绘制调用(Draw Call)都是性能杀手。当场景有5000个立方体时,合并成一个几何体,Draw Call就从5000降到1!

// 创建合并几何体
const mergeGeometry = new THREE.BufferGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

// 生成5000个立方体位置
const count = 5000;
const positions = new Float32Array(count * 3); // 每个立方体3个坐标值
for (let i = 0; i < count * 3; i += 3) {
  positions[i] = (Math.random() - 0.5) * 200; // x
  positions[i + 1] = (Math.random() - 0.5) * 200; // y
  positions[i + 2] = (Math.random() - 0.5) * 200; // z
}

// 设置几何体属性
mergeGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const points = new THREE.Points(mergeGeometry, material);
scene.add(points);

技巧2:实例化渲染 - 动态物体的救星

需要移动的物体怎么办?InstancedMesh来帮忙!它只上传一次几何体数据,通过变换矩阵批量渲染。

// 创建实例化网格
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const instances = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, instances);

// 设置每个实例位置
const matrix = new THREE.Matrix4();
for (let i = 0; i < instances; i++) {
  matrix.setPosition(
    (Math.random() - 0.5) * 100,
    (Math.random() - 0.5) * 100,
    (Math.random() - 0.5) * 100
  );
  instancedMesh.setMatrixAt(i, matrix);
}
scene.add(instancedMesh);

// 旋转动画(高效!)
function animate() {
  requestAnimationFrame(animate);
  instancedMesh.rotation.x += 0.01;
  renderer.render(scene, camera);
}
animate();

技巧3:纹理优化 - 别让高清图拖垮GPU

4K纹理虽好,但小屏幕上纯属浪费!试试这些技巧:

  • 使用CompressedTextureLoader加载压缩纹理
  • 动态调整分辨率:texture.generateMipmaps = true
  • 离屏渲染用Math.floor(size / 2)逐级缩小
// 智能加载纹理
const textureLoader = new THREE.TextureLoader();
textureLoader.load('texture.jpg', texture => {
  // 根据设备调整尺寸
  const maxSize = Math.min(1024, renderer.capabilities.maxTextureSize);
  texture.image.width = Math.min(texture.image.width, maxSize);
  texture.image.height = Math.min(texture.image.height, maxSize);
  texture.needsUpdate = true;
});

技巧4:LOD(细节分级) - 远观勿近玩

离摄像机远的物体,用低模就够了!Three.js的LOD系统自动切换模型精度。

// 创建LOD对象
const lod = new THREE.LOD();

// 添加不同距离的模型
const highRes = loadModel('high.glb'); // 精细模型
const lowRes = loadModel('low.glb');  // 简化模型

lod.addLevel(highRes, 50);  // 50像素内显示精细模型
lod.addLevel(lowRes, 200);  // 200像素外显示简化模型

scene.add(lod);

// 在渲染循环中更新
function updateLOD() {
  lod.update(camera);
}

技巧5:渲染策略 - 看不见就别画

视锥剔除:只渲染摄像机可见范围内的物体(Three.js默认开启)
遮挡剔除:用OcclusionCuller跳过被遮挡的物体
按需渲染:静态场景用renderer.setAnimationLoop(null)停止循环渲染

// 智能渲染控制
let needsUpdate = false;

// 只有场景变化时才渲染
function checkRender() {
  if (needsUpdate) {
    renderer.render(scene, camera);
    needsUpdate = false;
  }
  requestAnimationFrame(checkRender);
}

// 当物体移动时标记更新
object.position.x += 0.1;
needsUpdate = true;

性能检测神器

动手前先诊断性能瓶颈:

// 显示性能面板
import Stats from 'stats.js';
const stats = new Stats();
stats.showPanel(0); // 0: fps, 1: ms, 2: mb
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();
  // 渲染逻辑...
  stats.end();
}

// 查看Draw Call数量
console.log(renderer.info.render.calls);
昨天以前首页

three.js基础入门(一)

作者 彬师傅
2025年6月28日 23:19

入门(three.js使用、相机设置、材质定义、光照设置、自定义geometry等,大概分成三章)

进阶(shader学习、自定义材质、部分源码阅读,大概两三章)

如果还有想要了解three.js其他相关的内容,欢迎留言补充


image.png

一、Three.js 是什么?

Three.js 是一个基于 WebGL 的 JavaScript 3D 库。

WebGL 本身功能强大但复杂,即使一个简单的绘制三角形都需要很多代码;Three.js 把这个常用的三维渲染需要用到的能力进行了封装,简单调用组合就能快速实现 3D 场景。

为什么选择 Three.js?

  • 门槛低:不懂 WebGL 也能上手
  • 功能全:支持光照、阴影、动画等
  • 社区活跃:教程和案例非常多
  • 跨平台:只要是浏览器,就能运行

它被广泛用于产品展示、游戏、可视化等场景


二、Hello World 示例

下面我们写一个最简单的 three.js 程序,在网页上可以看到一个旋转的立方体。

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Three.js Hello World</title>
  <style>body { margin: 0; }</style>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
  <script>
    // 创建一个场景(Scene)
    const scene = new THREE.Scene();
    // 创建一个相机(Camera)
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
    //创建一个渲染器(Renderer)
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 创建一个立方体(Box)
    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    camera.position.z = 5;

    // 动画,每一帧都会旋转
    function animate() {
      requestAnimationFrame(animate);
      cube.rotation.x += 0.01;
      cube.rotation.y += 0.01;
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>
</html>

三、材质类型

在现实中,砖和玻璃就算形状一样,视觉效果也完全不同。在 Three.js 中,这种视觉差异由“材质(Material)”控制。

常用材质介绍:

材质名 是否受光照影响 简介
MeshBasicMaterial ❌ 否 最简单,不受光影响,常用于调试
MeshStandardMaterial ✅ 是 现代物理材质,效果真实,推荐使用
MeshPhongMaterial ✅ 是 支持高光、光照,但不如 Standard 拟真
MeshLambertMaterial ✅ 是 柔和漫反射,适合基础物体
MeshNormalMaterial ❌ 否 表面法线可视化,常用于开发调试

如果场景中物体需要考虑光照影响推荐MeshStandardMaterialMeshBasicMaterial 材质更简单,但是因为不受光看起来很平

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>body { margin: 0; }</style>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
  <script>
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.z = 5;

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

    // 创建两种材质的球体,左边为 MeshBasicMaterial(不受光照),右边为 MeshStandardMaterial(受光照)
    const geometry = new THREE.SphereGeometry(1, 32, 32);

    const basicMat = new THREE.MeshBasicMaterial({ color: 0x44aaff});
    const standardMat = new THREE.MeshStandardMaterial({ color: 0x44aaff, metalness: 0.7, roughness: 0.3 });

    const basicSphere = new THREE.Mesh(geometry, basicMat);
    const standardSphere = new THREE.Mesh(geometry, standardMat);
    
    basicSphere.position.x = -1.5;
    standardSphere.position.x = 1.5;
    scene.add(basicSphere);
    scene.add(standardSphere);

    // 平行光
    const light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(5, 5, 5);
    scene.add(light);

    // 环境光
    const ambient = new THREE.AmbientLight(0xffffff, 0.3);
    scene.add(ambient);

    function animate() {
      requestAnimationFrame(animate);
      basicSphere.rotation.y += 0.01;
      standardSphere.rotation.y += 0.01;
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>
</html>

image.png 效果是不是差异很大,需要注意的是使用MeshStandardMaterial一定要添加光照(重要的事情说三遍),上面的代码添加了平行光DirectionalLight和环境光AmbientLight,如果不添加光照的话就是漆黑一片

不加光照效果 image.png

示例代码:

const material = new THREE.MeshStandardMaterial({
  color: 0xff0000,
  roughness: 0.5,
  metalness: 0.8
});

四、Geometry与 Mesh

几何体相当于一个 3D 模型的“骨架”,只负责定义形状,不负责外观材质。Three.js 提供了多个内置几何体类,常见包括:

常用几何体:

new THREE.BoxGeometry()        // 立方体
new THREE.SphereGeometry()     // 球体
new THREE.PlaneGeometry()      // 平面
new THREE.ConeGeometry()       // 圆锥
new THREE.TorusGeometry()      // 圆环

几何体定义形状,但还不能被渲染。只有当几何体与材质(Material)结合,才能生成真正可见的 3D 物体,这个组合称为 Mesh

组合方式:几何体 + 材质 = 网格(Mesh)

const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.MeshStandardMaterial({ color: 0x00ffff });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

scene.add(mesh) 把它们添加到场景中

Geometry 就像“模具”,Material 就像“颜料”,Mesh 则是已经上色后的成品

BufferGeometry

Three.js 中的大多数几何体都继承自 BufferGeometry

// 自定义 BufferGeometry
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
  -1.0, -1.0, 0.0,
   1.0, -1.0, 0.0,
   0.0,  1.0, 0.0
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

关于自定义geometry后续可以详细介绍,我们会自己定义一些顶点来绘制three.js默认不支持的图形


五、相机与光源

即使你在网页上放了 100 个模型,没有相机和光,啥也看不到,

相机类型:

  • PerspectiveCamera(透视相机)——真实世界的视角,近大远小,是最常用的 3D 相机
  • OrthographicCamera(正交相机)——常用于 2D,平行投影,没有透视效果
// 创建透视相机
const perspectiveCamera = new THREE.PerspectiveCamera(
  75, window.innerWidth / window.innerHeight, 0.1, 1000
);
perspectiveCamera.position.set(0, 0, 5);

// 创建正交相机
const aspect = window.innerWidth / window.innerHeight;
const orthoCamera = new THREE.OrthographicCamera(
  -aspect * 5, aspect * 5, 5, -5, 0.1, 1000
);
orthoCamera.position.set(0, 0, 5);
camera.position.z = 5;

光源种类:

光源类型 说明
AmbientLight 环境光,提供均匀照明
DirectionalLight 平行光,如太阳光
PointLight 点光源,如灯泡
SpotLight 聚光灯,可以打出圆锥区域
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

示例代码: 这个示例中用了AmbientLight、DirectionalLight和PointLight,我还加了一个gui面板可以控制光线,可以粘贴代码跑一下,体验光照对场景的影响

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>body { margin: 0; }</style>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.min.js"></script>
  <script>
    const scene = new THREE.Scene();

    const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.set(4, 3, 6);
    camera.lookAt(0, 1, 0);

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    document.body.appendChild(renderer.domElement);

    const sphereGeo = new THREE.SphereGeometry(1, 64, 64);
    const sphereMat = new THREE.MeshStandardMaterial({ color: 0x66ccff, metalness: 0.5, roughness: 0.4 });
    const sphere = new THREE.Mesh(sphereGeo, sphereMat);
    sphere.castShadow = true;
    sphere.position.y = 1;
    scene.add(sphere);

    const planeGeo = new THREE.PlaneGeometry(10, 10);
    const planeMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee });
    const plane = new THREE.Mesh(planeGeo, planeMat);
    plane.rotation.x = -Math.PI / 2;
    plane.receiveShadow = true;
    scene.add(plane);

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
    scene.add(ambientLight);

    const dirLight = new THREE.DirectionalLight(0xffffff, 1);
    dirLight.position.set(5, 5, 5);
    dirLight.castShadow = true;
    scene.add(dirLight);

    const pointLight = new THREE.PointLight(0xffaa00, 1, 15);
    pointLight.position.set(-3, 2, 2);
    pointLight.castShadow = true;
    scene.add(pointLight);

    // gui控制面板
    const gui = new dat.GUI();

    const ambientFolder = gui.addFolder('Ambient Light');
    ambientFolder.add(ambientLight, 'visible').name('开启');
    ambientFolder.add(ambientLight, 'intensity', 0, 1, 0.01).name('强度');

    const dirFolder = gui.addFolder('Directional Light');
    dirFolder.add(dirLight, 'visible').name('开启');
    dirFolder.add(dirLight, 'intensity', 0, 2, 0.01).name('强度');
    dirFolder.add(dirLight.position, 'x', -10, 10).name('位置X');
    dirFolder.add(dirLight.position, 'y', -10, 10).name('位置Y');
    dirFolder.add(dirLight.position, 'z', -10, 10).name('位置Z');

    const pointFolder = gui.addFolder('Point Light');
    pointFolder.add(pointLight, 'visible').name('开启');
    pointFolder.add(pointLight, 'intensity', 0, 2, 0.01).name('强度');
    pointFolder.add(pointLight.position, 'x', -10, 10).name('位置X');
    pointFolder.add(pointLight.position, 'y', -10, 10).name('位置Y');
    pointFolder.add(pointLight.position, 'z', -10, 10).name('位置Z');

    ambientFolder.open();
    dirFolder.open();
    pointFolder.open();

    function animate() {
      requestAnimationFrame(animate);
      sphere.rotation.y += 0.01;
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>
</html>

image.png


下一章

  • 加载 3D 模型(GLTF)
  • 添加 OrbitControls 实现相机交互
  • 学会使用动画系统
  • 尝试后处理(模糊、发光、景深等)
  • 欢迎留言补充

Three.js 补间动画与相机移动:让数字世界动起来的魔法

作者 LeonGao
2025年6月28日 10:14

在 Three.js 的数字宇宙里,静态场景就像是沉默的雕塑,虽然精致却少了些生机。而补间动画和相机移动,就是赋予这个世界灵魂的神奇魔法,让场景中的元素 “活” 过来,带着观众穿梭于奇妙的虚拟空间。接下来,我们就化身 Three.js 的魔法师,学习如何施展这两大魔法。

一、补间动画:让元素翩翩起舞

补间动画,简单来说,就是告诉 Three.js 从 A 状态到 B 状态,如何优雅地 “过渡”。想象一下,你的模型是一位害羞的舞者,补间动画就是指挥它从舞台一侧缓缓走到另一侧,同时完成各种优美动作的 “舞蹈编排”。

1. 引入补间动画库

在正式开始编舞前,我们需要一个得力助手 ——gsap(GreenSock Animation Platform)。它就像是动画界的瑞士军刀,功能强大又灵活。通过npm install gsap将它引入项目,或者直接在 HTML 文件中添加。

2. 创建基础场景

首先,搭建一个 Three.js 的基础舞台。创建场景、相机和渲染器,就像搭建一个空的剧场,等待演员(模型)和观众(相机)入场:

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

3. 添加需要动画的元素

在剧场里放置一个 “演员”,比如一个正方体:

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

4. 编写补间动画

现在,让正方体这位 “舞者” 动起来!我们使用gsap来编写它的舞蹈动作:

gsap.to(cube.position, {
    x: 3, // 移动到x=3的位置
    duration: 2, // 动画持续2秒
    ease: "power2.out" // 动画的缓动效果,这里让结束时更平滑
});

上面的代码就像是给正方体下达了一个指令:在 2 秒内,以一种优雅的方式(power2.out缓动效果)移动到 x 轴为 3 的位置。你还可以组合更多属性,让动画更复杂,比如同时改变位置、旋转和缩放:

gsap.to(cube, {
    position: { x: 3, y: 2, z: 1 },
    rotation: { x: Math.PI / 2, y: Math.PI / 4 },
    scale: 2,
    duration: 3,
    ease: "elastic.out(1, 0.5)"
});

这里的正方体就像一个活泼的精灵,在 3 秒内完成移动、旋转和放大,最后还带着俏皮的弹性效果停下。

二、相机移动:带领观众探索世界

相机就是观众的眼睛,控制相机移动,就像是带着观众在虚拟世界中漫步、飞翔、穿梭。

1. 基础移动

最基础的相机移动,就是改变相机的位置属性position。比如,让相机沿着 z 轴向后退:

camera.position.z -= 1;

这就像观众往后撤了一步,看到的场景范围更大了。如果想让相机平滑移动,可以结合补间动画:

gsap.to(camera.position, {
    z: 10,
    duration: 2,
    ease: "linear"
});

这样,观众就像是坐着缓缓后退的观光车,慢慢欣赏场景的变化。

2. 轨道控制

为了让观众能更自由地探索场景,我们可以添加轨道控制器OrbitControls。它就像是一个智能导游,观众可以通过鼠标操作,围绕场景中的某个点旋转、缩放、平移相机。

首先,引入轨道控制器:

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

然后,初始化控制器:

const controls = new OrbitControls(camera, renderer.domElement);

现在,观众就能通过鼠标操作,360 度无死角地欣赏场景啦!

3. 路径动画

更炫酷的是,我们可以为相机设计一条专属的 “旅行路线”,让观众沿着精心设计的路径游览场景。这里可以使用Path和Spline来定义路径。

先创建一条路径:

const path = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-5, 0, 0),
    new THREE.Vector3(0, 5, 0),
    new THREE.Vector3(5, 0, 0)
]);

这条路径就像是为相机规划的一条山间小路,从左边出发,爬到山顶,再到右边。然后,使用补间动画让相机沿着路径移动:

const points = path.getSpacedPoints(100);
let i = 0;
const animateCamera = () => {
    requestAnimationFrame(animateCamera);
    if (i < points.length) {
        camera.position.copy(points[i]);
        i++;
    }
};
animateCamera();

这样,观众就像是坐上了一辆自动驾驶的小车,沿着这条美丽的路径,欣赏 Three.js 世界里的独特风景。

三、魔法升级:让动画与移动配合无间

补间动画和相机移动并不是孤立的,将它们巧妙结合,能创造出令人惊叹的视觉效果。比如,在场景中的物体开始表演精彩动画时,相机适时地拉近镜头,聚焦关键动作;当物体完成表演,相机缓缓拉远,展示整个场景的全貌。就像电影导演一样,通过镜头和画面的配合,讲述一个引人入胜的数字故事。

在 Three.js 的魔法世界里,补间动画和相机移动的可能性是无穷无尽的。希望通过这篇教程,你已经掌握了这两大魔法的基本咒语。快去发挥你的创意,打造属于自己的动态数字奇观吧!

上面的内容展示了 Three.js 补间动画与相机移动的核心技巧。你对文章的深度、案例还有其他想法,或者想补充其他功能,都能随时告诉我。

❌
❌