阅读视图

发现新文章,点击刷新页面。

three.js基础入门(一)

入门(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 实现相机交互
  • 学会使用动画系统
  • 尝试后处理(模糊、发光、景深等)
  • 欢迎留言补充
❌