1、Three.js入门:什么是Three.js、WebGL与Three.js的关系、Three.js能做什么、第一个3D场景(Hello Cube)
1.1 什么是Three.js?
Three.js,说白了就是一个让浏览器能跑3D的JavaScript库。
我刚开始接触前端3D时,第一反应是:这东西是不是很重?后来发现,它其实就是一个轻量级的封装层。你不需要懂图形学底层,也能在网页里搞出3D效果。
它的核心作用就三个:
- 创建3D物体——比如立方体、球体、模型
- 控制场景——灯光、相机、材质、动画
- 渲染输出——把3D画面画到浏览器里
嗯,你可以把它理解成「3D世界的jQuery」。虽然这个比喻不太严谨,但意思到了。
1.2 WebGL与Three.js的关系
很多人搞不清WebGL和Three.js的区别。我简单说一下。
WebGL是浏览器底层的一个API,它直接跟GPU打交道。你用WebGL画一个三角形,大概要写几十行代码。画一个立方体?上百行。而且全是矩阵运算、着色器代码,看着就头疼。
Three.js是在WebGL之上封装的一层。它把那些复杂的底层操作藏起来了。你只需要写:
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
三行代码,一个绿色立方体就出来了。
我在项目中遇到过不少新手,一上来就想直接学WebGL。我的建议是:先学Three.js,把3D思维建立起来。等你遇到性能瓶颈或者特殊需求时,再回头研究WebGL也不迟。
一句话总结:Three.js = WebGL的「傻瓜相机」模式。你按快门就行,对焦曝光的事交给它。
1.3 Three.js能做什么?
这个问题我经常被问到。其实Three.js能做的事,比你想象的多得多。
| 应用场景 | 具体例子 | 我见过的实际项目 |
|---|---|---|
| 产品展示 | 3D商品预览、汽车配置器 | 某家具品牌的在线展厅,用户能旋转沙发看细节 |
| 数据可视化 | 3D图表、城市热力图 | 智慧城市项目里,用3D柱状图展示人口密度 |
| 游戏 | 网页小游戏、互动体验 | 一个营销活动,用户控制3D角色收集金币 |
| 建筑可视化 | 室内漫游、BIM模型 | 地产公司的样板间VR预览 |
| 教育/医疗 | 分子结构、人体解剖 | 医学院的3D骨骼教学工具 |
你想想看,现在连电商详情页都开始用3D了。用户能拖拽旋转商品,比看图片直观多了。这个趋势只会越来越明显。
我的建议:别想着用Three.js做大型3A游戏,那是Unity和Unreal的事。Three.js的强项是轻量、快速、嵌入网页。做个小工具、展示页、互动体验,它比任何引擎都顺手。
1.4 第一个3D场景:Hello Cube
好了,理论说够了。咱们直接上手。
我习惯把Three.js的3D场景拆成四个要素:
- 场景(Scene)——放东西的容器
- 相机(Camera)——看东西的眼睛
- 渲染器(Renderer)——画东西的画笔
- 物体(Mesh)——被看的东西
缺一个都不行。就像拍电影,你得有舞台、摄像机、放映机、演员。
下面是最简版本的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello Cube</title>
<style>
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<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';
// 1. 创建场景
const scene = new THREE.Scene();
// 2. 创建相机(透视相机)
const camera = new THREE.PerspectiveCamera(
75, // 视角角度
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近裁剪面
1000 // 远裁剪面
);
camera.position.z = 5;
// 3. 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 4. 创建立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x44aa88 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 5. 加个灯光(不然立方体是黑的)
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
// 6. 动画循环
function animate() {
requestAnimationFrame(animate);
// 让立方体旋转
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
注意:如果你直接双击HTML文件打开,可能会遇到跨域问题。建议用VS Code的Live Server插件,或者用npx serve启动一个本地服务器。
这段代码跑起来后,你会看到一个青绿色的立方体在旋转。嗯,这就是你的第一个3D场景。
我曾经在给团队做培训时,有人问:为什么立方体是黑的?因为没加灯光。Three.js的默认材质需要光照才能显示颜色。如果你用MeshBasicMaterial,就不需要灯光了,但看起来没有立体感。
小技巧:调试时可以把camera.position.z改大改小,看看立方体怎么变化。数值越小,立方体看起来越大。这就是透视相机的效果。
1.5 代码拆解:每一行都在干什么
我习惯把代码拆成小块来理解。咱们过一遍关键部分:
- importmap:这是ES Module的加载方式。告诉浏览器从哪里下载Three.js库。版本号
0.160.0是我常用的稳定版。 - Scene:一个空容器。所有物体、灯光、辅助线都放进去。
- PerspectiveCamera:透视相机,模拟人眼效果。近大远小。四个参数分别是:视角、宽高比、近裁面、远裁面。
- WebGLRenderer:渲染器。它把3D场景画到
<canvas>上。setSize设置画布尺寸。 - BoxGeometry:几何体。1x1x1的立方体。你可以改成
2, 1, 1试试,会变成扁长方体。 - MeshStandardMaterial:标准材质。能响应光照,看起来更真实。
- Mesh:网格。几何体+材质=一个可以渲染的物体。
- DirectionalLight:方向光。模拟太阳光,从某个方向照射。
- requestAnimationFrame:浏览器自带的动画循环。每秒60帧,比
setInterval更省资源。
你想想看,这十几行代码背后,WebGL要处理多少东西?顶点着色器、片元着色器、缓冲区、矩阵变换……Three.js全帮你搞定了。
1.6 避坑指南
我刚开始用Three.js时踩过不少坑。分享几个最常见的:
- 场景是黑的——检查有没有加灯光。或者材质是不是用了
MeshBasicMaterial。 - 物体不显示——检查相机位置。如果相机在物体内部,或者距离太远,就看不到。
- 页面空白——检查渲染器有没有添加到DOM。很多人忘了
document.body.appendChild(renderer.domElement)。 - 动画不流畅——别用
setInterval做动画循环。用requestAnimationFrame。
我曾经有一次调试了半小时,发现是camera.position.z设成了负数。相机在物体后面,当然看不到。嗯,这种低级错误谁都犯过。
1.7 下一步做什么?
跑通Hello Cube之后,我建议你做三件事:
- 改颜色——把
0x44aa88换成你喜欢的颜色 - 改形状——把
BoxGeometry换成SphereGeometry或CylinderGeometry - 加多个物体——创建第二个立方体,放在不同位置
别怕改坏。Three.js的容错性很好,改错了最多页面空白,刷新一下就好。
下一章我们会深入场景、相机、渲染器这三个核心概念。你会发现,搞懂了它们,Three.js就入门了一半。
公众号:蓝海资料掘金营,微信deep3321