本文系统讲解如何从零搭建 Three.js 开发环境并成功运行第一个 3D 场景。涵盖核心概念解析、Vite 与 CDN 两种搭建路径对比、完整代码编写、动画循环与视口自适应配置及常见问题排查。
Three.js 入门:怎么搭建环境并运行第一个 3D 场景
时间:2026-11-04
浏览:170
文章目录
一、理解 Three.js 的工作原理与三要素
原生 WebGL 的开发成本较高,开发者需要手动编写顶点着色器与片元着色器,管理顶点缓冲区(VBO)以及矩阵乘法。Three.js 将复杂的底层图形管线封装为面向对象的层级架构。要成功渲染出任何 3D 画面,必须协同以下三个核心概念:
- 场景(THREE.Scene):相当于三维世界的容器。所有肉眼可见的网格模型(Mesh)、灯光(Light)、粒子和辅助线,都必须通过
scene.add()挂载到场景对象树中。 - 相机(THREE.PerspectiveCamera):相当于人眼的视角,负责将三维空间中的坐标按透视投影规则(近大远小)映射到二维视平面上。
- 渲染器(THREE.WebGLRenderer):负责底层的图形光栅化计算,它持有或绑定一个 HTML5
<canvas>元素,接收场景与相机作为输入,驱动 GPU 绘制出最终像素。
网格模型本身不能独立存在,它由几何体(Geometry)与材质(Material)组合而成:几何体定义物体的空间骨架(如顶点位置、法向量),材质则决定物体表面对光线的物理反应(如颜色、粗糙度与金属度)。当使用支持光照的物理材质时,场景中还必须配置光源,否则物体表面因缺乏漫反射光计算将呈现全黑色。
二、环境搭建:现代工程化(Vite)与原生引入(CDN)的对比与选择
在开始编写代码前,需要确定以何种方式引入 Three.js。目前主流的做法有两种:基于 Vite 的现代模块化工程构建,以及基于 CDN 的原生单文件引入。在相同的渲染目标下,两种做法的关键差异及其对开发流程的影响如下:
| 评估维度 | 方式 A:Vite 模块化工程构建(推荐) | 方式 B:CDN 原生引入单文件 |
|---|---|---|
| 依赖管理 | 通过 npm 与 package.json 锁定版本,可轻松集成轨道控制器、物理引擎与动画库。 |
通过 <script type="importmap"> 映射在线 URL,新增插件需手动查找兼容版本。 |
| 开发体验 | 具备极速热模块替换(HMR),代码修改即时生效;IDE 具备完整的 TypeScript 类型与 API 提示。 | 无自动刷新与类型补全,依赖远程网络连接质量,弱网或离线环境下无法加载模块。 |
| 资源与打包 | 构建时支持 Tree-shaking 自动剔除无用代码,本地 3D 模型(GLTF/GLB)与纹理贴图有统一哈希路径。 | 直接全量加载远程脚本;若本地双击以 file:// 打开,会触发浏览器的 CORS 跨域安全拦截。 |
| 适用定位 | 适合正式项目、生产环境交付、团队协作以及后续的功能扩展。 | 适合在线平台(如 CodePen)快速制作技术原型或单页面概念验证。 |
因为后续的三维开发通常需要加载外部纹理、三维模型或使用 OrbitControls 交互控制器,推荐采用 Vite 工程化方式 作为标准开发环境。
三、实战操作:5 步搭建并运行第一个 3D 场景
下面按照实际操作顺序,从初始化空目录开始,完整走完配置与编码过程。
第 1 步:初始化项目并安装 Three.js
打开系统终端,执行以下命令创建一个纯 JavaScript 的 Vite 项目,并安装 three 核心依赖包:
# 1. 创建基于原生 JS 模板的 Vite 工程
npm create vite@latest three-demo -- --template vanilla
# 2. 进入项目目录
cd three-demo
# 3. 安装 Three.js 核心库
npm install three
package.json 与 node_modules 依赖目录。
第 2 步:精简并配置 index.html
打开项目根目录下的 index.html,清除 Vite 默认的计数器样例代码,加入专用于承载 3D 画布的 <canvas> 标签,并设置全局样式去除默认边距:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>第一个 Three.js 3D 场景</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden; /* 隐藏滚动条,避免出现多余滚动 */
background-color: #1a1a1a;
}
#webgl-canvas {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<!-- 承载 WebGL 渲染内容的 Canvas 容器 -->
<canvas id="webgl-canvas"></canvas>
<!-- 引入入口脚本 -->
<script type="module" src="/main.js"></script>
</body>
</html>
第 3 步:编写场景核心代码(main.js)
清空 main.js,写入以下完整的 Three.js 初始化逻辑。每一部分均已附带详细的参数说明:
import * as THREE from 'three';
// ============================================================
// 1. 初始化场景 (Scene)
// ============================================================
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1e2022); // 设置沉浸式深灰背景色
// ============================================================
// 2. 初始化透视相机 (PerspectiveCamera)
// ============================================================
const camera = new THREE.PerspectiveCamera(
75, // 视野角度 (FOV,单位为度)
window.innerWidth / window.innerHeight, // 视口纵横比 (Aspect Ratio)
0.1, // 近截剪面 (Near)
1000 // 远截剪面 (Far)
);
// 将相机向后上方移动,以便俯瞰场景中心的几何体
camera.position.set(2, 2, 5);
camera.lookAt(0, 0, 0); // 视线精准对准三维坐标系原点
// ============================================================
// 3. 初始化 WebGL 渲染器 (WebGLRenderer)
// ============================================================
const canvas = document.querySelector('#webgl-canvas');
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true // 开启硬件抗锯齿,消除物体边缘锯齿
});
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置设备像素比,上限为 2,防止高刷 4K 屏过度消耗 GPU 性能
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// ============================================================
// 4. 构建三维网格模型 (Mesh = Geometry + Material)
// ============================================================
const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5); // 长宽高各为 1.5 单位的正方体
const material = new THREE.MeshStandardMaterial({
color: 0x879b88, // 优雅的鼠尾草绿表面
roughness: 0.35, // 表面微粗糙度
metalness: 0.2 // 微量金属质感
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube); // 将网格加入场景中
// ============================================================
// 5. 配置光照系统 (MeshStandardMaterial 必须有光源才能显色)
// ============================================================
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); // 基础环境光
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); // 斜射平行光
directionalLight.position.set(5, 8, 6);
scene.add(directionalLight);
// ============================================================
// 6. 监听窗口变化,保持画布与相机比例同步
// ============================================================
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
// 1. 刷新相机宽高比与内部投影矩阵
camera.aspect = width / height;
camera.updateProjectionMatrix();
// 2. 同步更新渲染输出视口与设备像素比
renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
// ============================================================
// 7. 动画驱动循环
// ============================================================
function animate() {
requestAnimationFrame(animate);
// 每帧令方块沿 X 轴和 Y 轴微量自转
cube.rotation.x += 0.01;
cube.rotation.y += 0.015;
// 驱动渲染器进行画面重绘
renderer.render(scene, camera);
}
// 启动执行
animate();
四、核心机制:逐帧动画与视口自适应解析
如果在 animate() 函数中仅仅调用 renderer.render(scene, camera) 一次,浏览器只会画出一张静止的单帧画面。通过 requestAnimationFrame(animate),浏览器在每次刷新屏幕前都会执行一次回调。在这个周期内逐步修改 cube.rotation 的数值,连续的静止帧就会在视觉上形成连贯流畅的 3D 旋转动画。
另一个至关重要的机制是窗口缩放监听(resize)。初学者经常只调用 renderer.setSize(),却忽略了调用 camera.updateProjectionMatrix()。
- 为什么必须更新投影矩阵?透视相机内部维护着一个 4x4 的投影矩阵,用于将视椎体内的顶点坐标映射为剪裁空间坐标。当浏览器宽度或高度发生变化时,如果仅仅改变了
camera.aspect数值而不调用updateProjectionMatrix(),投影矩阵仍然沿用初始化时的比例,渲染出来的立方体就会被挤压变扁或纵向拉长。 - 为什么限制 setPixelRatio 为 2?现代智能手机或 Retina 屏幕的设备像素比可能达到 3 或更高。如果直接传入
window.devicePixelRatio,GPU 需要处理 9 倍甚至更多的像素片元着色计算,容易导致发热与掉帧。将上限限制为 2 可以在肉眼无法察觉锯齿的前提下大幅节省能耗。
五、运行验证与常见报错排查
完成上述代码编写后,在终端运行项目启动命令:
npm run dev
终端会输出本地访问链接(通常为 http://localhost:5173/)。在浏览器中打开该网址,屏幕正中央将呈现一个在深灰背景下平滑旋转的浅绿成立体方块,方块上能清晰看到由平行光源照射产生的高光、侧面漫反射以及背光暗部。
- 相机位置在物体内部:相机默认位置在原点
(0, 0, 0),立方体也在原点。如果不显式执行camera.position.set(2, 2, 5)将相机向后移动,相机就会处于方块几何体内部,因背面剔除机制导致视野中什么也看不到。 - 使用了标准材质却忘记添加灯光:
MeshStandardMaterial是基于物理渲染(PBR)的材质,必须有光源才会被照亮。如果去掉了AmbientLight和DirectionalLight,方块将因全无光照而变成纯黑色。若需要无需灯光即可显色的测试材质,可临时替换为THREE.MeshBasicMaterial。 - 直接通过 file:// 协议双击打开:若脱离本地 HTTP 服务器直接在浏览器中打开
index.html,浏览器出于安全限制会阻止加载本地 ES 模块,控制台会报错Access to script at 'file://...' from origin 'null' has been blocked by CORS policy。必须通过 Vite、Live Server 或本地 Web 服务器协议访问。
六、总结与后续拓展
运行第一个 3D 场景是深入 Web3D 领域的起点。回顾整个过程,我们建立了坚实的三维渲染思维:以场景为对象树容器,以透视相机为观察窗口,以材质和几何体构筑物体形态,最后通过 WebGLRenderer 在动画循环中将数学计算转化为屏幕像素。
在掌握了上述基础后,后续可以顺畅地拓展更多能力:引入 three/addons/controls/OrbitControls.js 支持鼠标拖拽旋转缩放视角、引入 GLTFLoader 载入专业的 Blender 三维模型资产,或是添加阴影映射(ShadowMap)增强空间纵深感。通过 Vite 建立的规范工程体系,将为这些进阶功能的引入提供稳定可靠的开发支撑。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。