WebGL+Three.js 入门与实战:系统学习 Web3D 技术
在数字化浪潮下,3D 技术不再局限于游戏与影视 —— 如今,网页端 3D 产品展示、元宇宙场景、数据可视化、AR/VR 交互等应用随处可见,而 WebGL 与 Three.js 正是实现这些 “浏览器端 3D 魔法” 的核心技术。对于开发者而言,掌握 Web3D 技术不仅能拓展业务边界,更能在元宇宙、数字孪生等新兴领域抢占先机。本文将从基础概念到实战项目,带你系统入门 WebGL 与 Three.js,逐步构建属于自己的 Web3D 应用。
一、Web3D 技术基石:理清 WebGL 与 Three.js 的关系
在学习前,必须先明确一个核心问题:WebGL 和 Three.js 到底是什么?两者并非竞争关系,而是 “底层引擎” 与 “上层工具库” 的协同组合,共同降低 Web3D 开发门槛。
(一)WebGL:浏览器渲染 3D 的 “底层引擎”
WebGL(Web Graphics Library)是一种基于 OpenGL ES 2.0 的浏览器原生 API,它允许开发者通过 JavaScript 直接操作 GPU,在网页中渲染 2D/3D 图形,无需依赖插件(如 Flash)。其核心作用是 “将 3D 数据(顶点、纹理、光照)转换为屏幕像素”,但直接使用 WebGL 存在显著痛点:
- 语法复杂:需手动编写着色器(Shader)代码(GLSL 语言),处理顶点坐标、纹理映射、光照计算等底层逻辑;
- 冗余代码多:创建一个简单的立方体,需编写数百行代码处理坐标系转换、缓冲区管理;
- 门槛高:需掌握计算机图形学基础(如矩阵变换、透视投影),新手难以快速上手。
例如,直接用 WebGL 绘制一个红色三角形,需编写顶点着色器(定义顶点位置)和片段着色器(定义颜色),再通过 JavaScript 初始化缓冲区、链接着色器程序 —— 整个过程涉及 10 + 步骤,对新手极不友好。
(二)Three.js:简化 Web3D 开发的 “上层工具库”
Three.js 是基于 WebGL 的开源 JavaScript 库,由 Ricardo Cabello(Mr.doob)开发,其核心价值是 “封装 WebGL 底层细节,提供直观的 3D 开发接口”。它就像给 WebGL 套了一层 “友好的外壳”,让开发者无需关注着色器、缓冲区等底层逻辑,只需通过简单的 API 即可构建 3D 场景。
Three.js 的核心优势:
- 封装底层复杂度:内置场景、相机、渲染器等核心组件,一行代码即可完成 WebGL 初始化;
- 丰富的 3D 资源:支持几何体(立方体、球体等)、材质(金属、玻璃等)、光源(平行光、点光等)、控制器(旋转、缩放等交互);
- 跨平台兼容性:自动适配不同浏览器与设备,支持响应式 3D 场景;
- 生态完善:配套加载器(加载 GLB/FBX 模型)、动画库(Tween.js)、物理引擎(Cannon.js),满足复杂需求。
简单来说:WebGL 是 “发动机”,Three.js 是 “方向盘与油门” —— 新手无需精通发动机原理,只需通过方向盘即可驾驶 3D 应用,这也是我们从 Three.js 入门 Web3D 的核心原因。
二、Web3D 基础:Three.js 核心组件与工作流程
Three.js 的 3D 场景由 “三大核心组件” 与 “辅助组件” 构成,所有 Web3D 应用的开发都围绕这些组件展开。理解它们的作用与关系,是入门的关键。
(一)三大核心组件:场景、相机、渲染器
这三个组件是构建任何 Three.js 应用的 “基石”,缺一不可,其关系可类比 “摄影”:
- 场景(Scene) :相当于 “摄影棚”,是所有 3D 物体(模型、光源、相机)的容器;
- 相机(Camera) :相当于 “摄像机”,决定从哪个角度观察场景;
- 渲染器(Renderer) :相当于 “显示器”,将相机捕捉到的场景渲染到网页的 DOM 元素(如)中。
1. 场景(Scene)
场景是 3D 物体的 “容器”,通过new THREE.Scene()创建,后续所有物体(如立方体、光源)都需通过scene.add(object)添加到场景中。例如:
import * as THREE from 'three';
// 创建场景
const scene = new THREE.Scene();
// 给场景设置背景色(可选)
scene.background = new THREE.Color(0xf0f0f0); // 浅灰色背景
2. 相机(Camera)
Three.js 提供两种常用相机,需根据场景需求选择:
- 透视相机(PerspectiveCamera) :模拟人眼视角,近大远小,适合 3D 游戏、产品展示等真实感场景;
- 正交相机(OrthographicCamera) :物体大小与距离无关,适合 2.5D 游戏、工程图纸等场景。
透视相机参数解析(最常用):
// 参数:fov(视野角度)、aspect(宽高比)、near(近裁剪面)、far(远裁剪面)
const camera = new THREE.PerspectiveCamera(
75, // 视野角度:越大看到的范围越广
window.innerWidth / window.innerHeight, // 宽高比:通常为窗口宽高比
0.1, // 近裁剪面:距离相机小于0.1的物体不渲染
1000 // 远裁剪面:距离相机大于1000的物体不渲染
);
// 设置相机位置(默认在(0,0,0),需调整位置才能看到场景)
camera.position.z = 5; // 相机向后移动5个单位(Three.js默认坐标系:Z轴向前)
3. 渲染器(Renderer)
渲染器负责将场景与相机结合,渲染到网页的元素中。Three.js 会自动创建,也可指定已有:
// 创建渲染器,开启抗锯齿(使边缘更平滑)
const renderer = new THREE.WebGLRenderer({ antialias: true });
// 设置渲染器尺寸(通常为窗口大小)
renderer.setSize(window.innerWidth, window.innerHeight);
// 将渲染器的DOM元素(<canvas>)添加到页面中
document.body.appendChild(renderer.domElement);
(二)辅助组件:几何体、材质、网格、光源
三大核心组件搭建了 “空舞台”,还需添加 “演员”(3D 物体)与 “灯光”(光源)才能构成完整场景:
1. 几何体(Geometry):3D 物体的 “形状”
Three.js 内置多种基础几何体,如立方体(BoxGeometry)、球体(SphereGeometry)、平面(PlaneGeometry)等,也支持加载外部模型(如 GLB/FBX)。例如,创建一个边长为 1 的立方体:
// 参数:宽、高、深
const geometry = new THREE.BoxGeometry(1, 1, 1);
2. 材质(Material):3D 物体的 “外观”
材质定义物体的颜色、纹理、光泽等外观属性,常用材质包括:
- MeshBasicMaterial:基础材质,不响应光源(无论有无灯光都显示颜色);
- MeshLambertMaterial:漫反射材质,响应光源,适合哑光物体(如塑料);
- MeshPhongMaterial:高光材质,支持镜面反射,适合金属、玻璃等物体。
例如,创建一个红色漫反射材质:
const material = new THREE.MeshLambertMaterial({ color: 0xff0000 }); // 红色
3. 网格(Mesh):几何体 + 材质的 “结合体”
几何体定义 “形状”,材质定义 “外观”,两者需通过Mesh结合才能成为可渲染的 3D 物体:
// 结合几何体与材质,创建立方体网格
const cube = new THREE.Mesh(geometry, material);
// 将立方体添加到场景中
scene.add(cube);
4. 光源(Light):3D 物体的 “照明”
没有光源的场景会一片漆黑(除了 MeshBasicMaterial),Three.js 常用光源包括:
- 平行光(DirectionalLight) :模拟太阳光,光线平行,适合全局照明;
- 点光(PointLight) :从一点向四周发射光线,适合灯泡、蜡烛等场景;
- 环境光(AmbientLight) :无方向的柔和光线,用于照亮场景所有物体,避免死角。
例如,添加平行光与环境光:
// 平行光:白色,强度0.5(0~1)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
// 设置平行光方向(默认从(0,0,0)出发,需调整位置)
directionalLight.position.set(5, 5, 5); // 从(5,5,5)指向原点
scene.add(directionalLight);
// 环境光:灰色,强度0.3,照亮整个场景
const ambientLight = new THREE.AmbientLight(0xcccccc, 0.3);
scene.add(ambientLight);
(三)核心工作流程:渲染循环
3D 场景的 “动态效果”(如旋转、动画)依赖渲染循环(RequestAnimationFrame),它会以浏览器刷新率(通常 60 帧 / 秒)持续渲染场景:
// 渲染循环函数
function animate() {
// 浏览器刷新时自动调用animate
requestAnimationFrame(animate);
// 让立方体绕X轴和Y轴旋转(添加动画效果)
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// 渲染场景:参数为场景和相机
renderer.render(scene, camera);
}
// 启动渲染循环
animate();
此时,运行代码会看到一个旋转的红色立方体 —— 这就是 Three.js 的 “Hello World”,整个过程仅需 50 行左右代码,远比直接用 WebGL 简单。
三、环境搭建:从零开始创建 Three.js 项目
掌握基础组件后,我们通过Vite+Three.js搭建开发环境(Vite 构建速度快,支持热更新,适合前端项目)。
(一)步骤 1:创建 Vite 项目
- 确保已安装 Node.js(建议 16.x 及以上),打开终端执行:
# 创建Vite项目,选择Vanilla(原生JavaScript)或Vue/React(根据需求)
npm create vite@latest threejs-demo -- --template vanilla
# 进入项目目录
cd threejs-demo
# 安装依赖
npm install
2. 安装 Three.js:
npm install three
(二)步骤 2:编写第一个 Three.js 程序
- 替换src/main.js内容为以下代码(完整的旋转立方体示例):
import * as THREE from 'three';
// 1. 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 2. 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
// 3. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 4. 创建立方体(几何体+材质+网格)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshLambertMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 5. 添加光源
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
const ambientLight = new THREE.AmbientLight(0xcccccc, 0.3);
scene.add(ambientLight);
// 6. 响应式调整(窗口 resize 时更新相机和渲染器)
window.addEventListener('resize', () => {
// 更新相机宽高比
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); // 必须更新投影矩阵
// 更新渲染器尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 7. 渲染循环
function animate() {
requestAnimationFrame(animate);
// 立方体旋转
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
2. 运行项目:
npm run dev
打开浏览器访问http://localhost:5173,即可看到一个旋转的红色立方体 —— 恭喜你,成功创建第一个 Three.js 应用!
四、实战项目:打造 3D 产品展示场景
入门后,通过实战项目巩固知识。我们将创建一个 “3D 手机展示场景”,实现以下功能:
- 加载外部 GLB 格式的手机模型;
- 实现鼠标控制(旋转、缩放、平移);
- 添加阴影效果与环境纹理;
- 实现模型点击交互。
(一)步骤 1:准备资源
- 3D 模型:从 Sketchfab(免费 3D 模型网站)下载手机 GLB 模型(推荐搜索 “iPhone 15 GLB”),将模型文件放入public/models/目录;
- 加载器:Three.js 加载 GLB 模型需GLTFLoader,需从three/addons/loaders/GLTFLoader.js导入。
(二)步骤 2:核心代码实现
1. 导入加载器与控制器
import * as THREE from 'three';
// 导入GLB模型加载器
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// 导入轨道控制器(实现鼠标交互)
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
2. 创建场景、相机、渲染器(基础配置)
// 场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff); // 白色背景
// 相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 3; // 相机位置调整(根据模型大小)
// 渲染器(开启阴影支持)
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 开启阴影渲染
document.body.appendChild(renderer.domElement);
3. 添加光源与地面(增强真实感)
// 平行光(开启阴影投射)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true; // 光源投射阴影
// 调整阴影分辨率(越高越清晰,性能消耗越大)
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// 环境光
const ambientLight = new THREE.AmbientLight(0xcccccc, 0.5);
scene.add(ambientLight);
// 地面(接收阴影)
const groundGeometry = new THREE.PlaneGeometry(10, 10); // 平面大小
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0xf5f5f5 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2; // 平面默认垂直,旋转90度使其水平
ground.position.y = -1; // 地面在模型下方1个单位
ground.receiveShadow = true; // 地面接收阴影
scene.add(ground);
4. 加载 GLB 模型并添加交互
// 创建模型加载器
const loader = new GLTFLoader();
let phoneModel = null; // 存储加载后的模型
// 加载手机模型
loader.load(
'/models/iphone15.glb', // 模型路径
(gltf) => {
// 加载成功回调
phoneModel = gltf.scene;
// 调整模型大小(根据实际模型调整缩放比例)
phoneModel.scale.set(0.8, 0.8, 0.8);
// 模型投射阴影
phoneModel.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
}
});
scene.add(phoneModel);
},
(xhr) => {
// 加载进度回调
console.log(`模型加载进度:${(xhr.loaded / xhr.total) * 100}%`);
},
(error) => {
// 加载失败回调
console.error('模型加载失败:', error);
}
);
// 添加轨道控制器(鼠标控制模型)
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 开启阻尼效果,使旋转更平滑
controls.dampingFactor = 0.05; // 阻尼系数(越小越平滑)
controls.screenSpacePanning = false; // 禁止屏幕空间平移(仅在轨道模式下平移)
controls.minDistance = 1; // 最小缩放距离
controls.maxDistance = 10; // 最大缩放距离
5. 实现模型点击交互
通过Raycaster(射线投射)实现 “点击模型弹出提示”:
// 创建射线投射器(检测鼠标与3D物体的交点)
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 鼠标点击事件
window.addEventListener('click', (event) => {
// 将鼠标坐标转换为Three.js标准化设备坐标(NDC)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 更新射线投射器的射线方向(从相机指向鼠标位置)
raycaster.setFromCamera(mouse, camera);
// 检测射线与场景中物体的交点(仅检测手机模型)
const intersects = raycaster.intersectObjects(phoneModel ? [phoneModel] : []);
if (intersects.length > 0) {
// 点击到模型,弹出提示
alert('你点击了iPhone 15模型!');
}
});
6. 渲染循环与响应式调整
// 渲染循环(需更新控制器)
function animate() {
requestAnimationFrame(animate);
controls.update(); // 轨道控制器需要在循环中更新
renderer.render(scene, camera);
}
animate();
// 窗口resize事件
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
(三)运行效果
启动项目后,你将看到:
- 一个 3D 手机模型放置在灰色地面上,带有阴影效果;
- 鼠标按住模型可旋转,滚轮可缩放,右键拖动可平移;
- 点击模型会弹出提示框。
五、进阶技巧:提升 Web3D 应用的真实感与性能
(一)增强真实感:环境纹理与 PBR 材质
- 环境纹理:添加天空盒或环境贴图,让模型反射周围环境,增强真实感。例如,加载 HDR 环境纹理:
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
// 加载HDR环境纹理
new RGBELoader()
.load('/textures/sky.hdr', (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture; // 场景环境纹理(影响所有PBR材质)
scene.background = texture; // 场景背景(可选)
});
2. PBR 材质:基于物理的渲染(PBR)材质(如MeshPhysicalMaterial)能模拟真实世界的光照效果,支持金属度、粗糙度等参数,适合产品展示:
const pbrMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0.8, // 金属度(0~1)
roughness: 0.2, // 粗糙度(0~1,越小越光滑)
clearcoat: 0.5, // 清漆层(模拟手机外壳的涂层)
});
(二)性能优化:应对复杂场景
当场景包含大量模型或高分辨率纹理时,需优化性能避免卡顿:
- 模型简化:使用 Blender 等工具简化模型面数(如将 10 万面的模型简化为 1 万面);
- 纹理压缩:将纹理压缩为 Basis Universal 格式,减少内存占用;
- 批量渲染:使用InstancedMesh批量渲染相同模型(如大量树木、粒子);
- LOD(细节层次) :根据模型与相机的距离加载不同精度的模型(远距用低精度模型)。
(三)动画与物理引擎
- 关键帧动画:Three.js 支持加载 GLB 模型中的动画,或用Tween.js实现简单动画:
import { Tween } from 'three/addons/libs/tween.module.js';
// 让模型沿Y轴上下移动
new Tween(phoneModel.position)
.to({ y: 0.5 }, 1000) // 1秒内移动到y=0.5
.repeat(Infinity) // 无限重复
.yoyo(true) // 往返运动(上去再下来)
.start();
2. 物理引擎:集成 Cannon.js 或 Ammo.js 实现碰撞检测、重力等物理效果,适合游戏场景:
import * as CANNON from 'cannon-es';
// 创建物理世界(重力沿Y轴向下)
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
// 创建物理地面(与Three.js地面对应)
const groundBody = new CANNON.Body({
mass: 0, // 质量为0,物体固定不动
shape: new CANNON.Plane()
});
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(groundBody);
六、学习资源与常见问题
(一)核心学习资源
- 官方文档:Three.js 官网(threejs.org/)提供完整 API 文档与示例库,是最佳学习资料;
- 示例库:Three.js Examples(threejs.org/examples/)包含数百个实战示例,涵盖模型加载、动画、物理引擎等;
- 模型与纹理:
-
- 模型:Sketchfab(免费 / 付费 3D 模型)、Blender(免费 3D 建模工具,可自制模型);
-
- 纹理:Texture Haven(免费 PBR 纹理)、Poly Haven(HDR 环境纹理);
- 教程:YouTube 频道 “Three.js Journey”(系统教程)、国内 “郭隆邦博客”(Three.js 中文教程)。
(二)常见问题解决
- 模型加载失败:检查模型路径是否正确(public 目录下的资源需用绝对路径)、是否跨域(本地开发用 Vite 可避免跨域);
- 阴影不显示:确保渲染器开启shadowMap.enabled = true、光源开启castShadow = true、物体开启castShadow/receiveShadow;
- 性能卡顿:简化模型、压缩纹理、关闭不必要的抗锯齿,或使用performance.now()分析渲染耗时。
七、总结:Web3D 技术的学习路径与未来
WebGL+Three.js 的学习路径可总结为 “三步进阶”:
- 入门:掌握 Three.js 三大核心组件、基础几何体与光源,实现简单 3D 场景;
- 实战:通过项目掌握模型加载、交互控制、纹理与阴影,提升场景真实感;
- 进阶:学习性能优化、动画、物理引擎,探索 AR/VR 与 Web3D 的结合。
随着元宇宙、数字孪生、AR/VR 的发展,Web3D 技术的应用场景将持续拓展 —— 从网页 3D 产品展示到虚拟会议、从在线 3D 游戏到数字展厅,掌握 Web3D 将为你的技术栈增添重要竞争力。
记住:Web3D 学习的核心是 “实践 + 拆解”—— 多看官方示例,多动手修改代码,将复杂场景拆解为基础组件,逐步积累经验。相信通过系统学习,你也能打造出令人惊艳的 Web3D 应用!