1. Three.js初探:WebGL与Three.js的关系、Three.js核心概念、第一个3D场景

各位同学,欢迎来到《Three.js场景管理与优化实战》的第一课。

说实话,每次带新人入门Three.js,我都会先问一个问题:你为什么要学Three.js,而不是直接学WebGL?

这个问题背后,其实藏着整个课程最核心的认知。今天我们就从WebGL和Three.js的关系讲起,然后搭建你的第一个3D场景。

1.1 WebGL与Three.js:从底层到上层

WebGL是什么?说白了,它是浏览器里的一把“底层画笔”。它直接跟GPU打交道,能画出点、线、三角形。但问题是——它太底层了。

我刚开始接触WebGL时,想画一个旋转的立方体。你猜怎么着?我需要写几百行代码:创建缓冲区、编写着色器、处理矩阵运算、管理顶点数据……光是把一个三角形画出来,就够我折腾一整天。

Three.js是什么?它是基于WebGL封装的一个“高级画室”。你不需要关心GPU怎么工作,不需要写那些晦涩的着色器代码。你只需要告诉Three.js:“我要一个立方体,放在这里,转起来”,它就帮你搞定了。

核心关系一句话总结:

WebGL是底层API,Three.js是上层框架。Three.js帮你把WebGL的复杂性藏起来了。

举个例子。用原生WebGL画一个三角形,你需要:

  • 创建WebGL上下文
  • 编写顶点着色器和片元着色器(GLSL语言)
  • 创建缓冲区,上传顶点数据
  • 编写渲染循环
  • 处理视口和投影矩阵

而用Three.js呢?

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

// 创建一个立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
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();

看到了吗?十几行代码,一个旋转的绿色立方体就出来了。这就是Three.js的价值——它把WebGL的复杂度降低了至少一个数量级

我的建议:如果你只是想快速做出3D效果,直接学Three.js就够了。但如果你想深入底层,比如做自定义着色器、性能极致优化,那WebGL的知识还是得补上。我个人习惯是:先用Three.js快速出活,遇到性能瓶颈再回头研究WebGL

1.2 Three.js核心三要素:场景、相机、渲染器

Three.js里有三个东西,你几乎每个项目都会用到。它们就是场景(Scene)、相机(Camera)、渲染器(Renderer)

我经常跟学员打一个比方:

  • 场景就像是一个舞台,所有演员(物体)、灯光、背景都在上面。
  • 相机就像是一台摄像机,你通过它来“看”这个舞台。
  • 渲染器就像是一个摄影师,它负责把相机看到的画面“拍下来”,然后显示到屏幕上。

咱们一个一个来看。

1.2.1 场景(Scene)

场景是所有3D对象的容器。你创建的每一个物体、每一盏灯光,都要添加到场景里才能被渲染。

const scene = new THREE.Scene();
// 可以设置背景颜色
scene.background = new THREE.Color(0x87ceeb); // 天蓝色

场景本身还可以做一些全局设置,比如雾效、背景图等。这些我们后面章节会详细讲。

1.2.2 相机(Camera)

相机决定了你从哪个角度、以什么方式观察场景。Three.js提供了几种相机,最常用的是透视相机(PerspectiveCamera)正交相机(OrthographicCamera)

透视相机模拟人眼效果——近大远小。这是3D场景中最常用的。

const camera = new THREE.PerspectiveCamera(
  75,                // 视野角度(FOV),类似人眼的视角范围
  window.innerWidth / window.innerHeight, // 宽高比
  0.1,               // 近裁剪面
  1000               // 远裁剪面
);

这里有几个参数要注意:

  • FOV(视野角度):值越大,看到的范围越广,但物体显得越小。一般用45-75度。
  • 宽高比:通常设为窗口的宽高比,否则画面会拉伸。
  • 近/远裁剪面:只有在这个范围内的物体才会被渲染。太近或太远的物体会被“裁剪”掉。

避坑指南:我曾经遇到过一个项目,场景里明明有物体,但就是看不到。排查了半天,发现是相机的位置设错了——物体在相机后面。记住:相机默认看向Z轴负方向,所以物体要放在Z轴负方向,或者调整相机的位置和朝向。

1.2.3 渲染器(Renderer)

渲染器负责把场景和相机结合起来,生成最终的图像。最常用的是WebGL渲染器

const renderer = new THREE.WebGLRenderer({
  antialias: true  // 开启抗锯齿,让边缘更平滑
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

渲染器的domElement是一个canvas元素,它会被添加到页面中。所有3D画面都绘制在这个canvas上。

1.3 第一个3D场景:从零到一

好了,理论讲完了。咱们来动手搭建第一个3D场景。我会带着你一步步走,确保你能跑起来。

第一步:引入Three.js

你可以用CDN引入,也可以用npm安装。这里我用CDN,方便你快速测试。

<script type="importmap">
{
  "imports": {
    "three": "https://unpkg.com/three@0.160.0/build/three.module.js"
  }
}
</script>

<script type="module">
import * as THREE from 'three';
// ... 后续代码
</script>

第二步:创建核心三要素

// 1. 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e); // 深色背景

// 2. 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 2, 5); // 把相机往后拉,并抬高一点
camera.lookAt(0, 0, 0);       // 让相机看向原点

// 3. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

第三步:添加一个物体

我们来创建一个旋转的环面结(TorusKnot),它比立方体更有视觉冲击力。

// 创建几何体
const geometry = new THREE.TorusKnotGeometry(1, 0.3, 100, 16);
// 创建材质
const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  roughness: 0.3,
  metalness: 0.7
});
// 组合成网格对象
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// 添加灯光(MeshStandardMaterial需要灯光才能显示)
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);

第四步:渲染循环

function animate() {
  requestAnimationFrame(animate);
  
  // 让物体旋转
  mesh.rotation.x += 0.01;
  mesh.rotation.y += 0.02;
  
  renderer.render(scene, camera);
}
animate();

第五步:处理窗口变化

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

完整代码整合:把上面几段代码按顺序拼在一起,你就得到了第一个3D场景。一个旋转的、金属质感的环面结,在深色背景中缓缓转动。

1.4 本章小结

今天我们做了三件事:

  1. 理清了WebGL和Three.js的关系——Three.js是WebGL的上层封装,让你用更少的代码做更多的事。
  2. 掌握了Three.js的三大核心概念——场景是舞台,相机是眼睛,渲染器是摄影师。
  3. 亲手搭建了第一个3D场景——从引入库到渲染循环,完整走了一遍流程。

嗯,这里我要多说一句。很多初学者觉得Three.js入门简单,就跳过基础直接做项目。结果遇到问题完全不知道怎么排查。我的建议是:把今天这三个核心概念吃透,尤其是相机和渲染器的参数。后面所有复杂的场景,都是在这三个东西的基础上搭建的。

下一章,我们会深入讲解几何体与材质,教你如何创建各种形状,并给它们穿上漂亮的“外衣”。到时候见!