Three.js 基础概念 #
Three.js 的核心是三个基本概念:场景(Scene)、相机(Camera)和渲染器(Renderer)。理解这三者的关系是学习 Three.js 的第一步。
核心三要素关系 #
text
┌─────────────────────────────────────────────────────────────┐
│ Three.js 核心架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 场景 │ │
│ │ Scene │◄──────────────────┐ │
│ └──────┬──────┘ │ │
│ │ │ │
│ │ 包含所有 3D 对象 │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │ 相机 │ │ │
│ │ Camera │───────────────────┤ │
│ └──────┬──────┘ │ │
│ │ │ │
│ │ 决定观察角度和范围 │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │ 渲染器 │ │ │
│ │ Renderer │───────────────────┘ │
│ └─────────────┘ │
│ │ │
│ │ 将场景渲染到屏幕 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Canvas │ │
│ │ 画布 │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
场景(Scene) #
场景是所有 3D 对象的容器,相当于一个虚拟的 3D 空间。所有的物体、光源、相机都需要添加到场景中才能被渲染。
创建场景 #
javascript
import * as THREE from 'three';
const scene = new THREE.Scene();
场景属性 #
背景色 #
javascript
scene.background = new THREE.Color(0x000000);
scene.background = new THREE.Color(0.5, 0.5, 0.5);
scene.background = new THREE.Color('skyblue');
背景纹理 #
javascript
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('sky.jpg');
scene.background = texture;
雾效果 #
javascript
scene.fog = new THREE.Fog(0x000000, 1, 100);
scene.fog = new THREE.FogExp2(0x000000, 0.05);
场景方法 #
添加对象 #
javascript
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const light = new THREE.DirectionalLight(0xffffff, 1);
scene.add(light);
移除对象 #
javascript
scene.remove(mesh);
查找对象 #
javascript
const object = scene.getObjectByName('myObject');
const objects = scene.getObjectsByProperty('material', material);
遍历场景 #
javascript
scene.traverse((object) => {
if (object.isMesh) {
object.material.wireframe = true;
}
});
场景图结构 #
text
Scene
├── Mesh (立方体)
│ └── Material
├── Mesh (球体)
│ └── Material
├── Group (组)
│ ├── Mesh (子物体1)
│ └── Mesh (子物体2)
├── DirectionalLight
├── AmbientLight
└── Camera (相机通常也添加到场景)
相机(Camera) #
相机决定了观察者从哪个角度、以什么方式观察场景。Three.js 提供了多种相机类型。
透视相机(PerspectiveCamera) #
透视相机模拟人眼视觉,近大远小,是最常用的相机类型。
javascript
const camera = new THREE.PerspectiveCamera(
75, // FOV:视角(度)
window.innerWidth / window.innerHeight, // Aspect:宽高比
0.1, // Near:近裁剪面
1000 // Far:远裁剪面
);
参数详解 #
text
FOV (视角)
▲
/|\
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ Near Plane \
/ (近裁剪面) \
/_____________________\
Far Plane
(远裁剪面)
| 参数 | 描述 | 推荐值 |
|---|---|---|
| FOV | 视角范围(度) | 45-90 |
| Aspect | 宽高比 | window.innerWidth / window.innerHeight |
| Near | 近裁剪面距离 | 0.1 |
| Far | 远裁剪面距离 | 1000-10000 |
相机位置 #
javascript
camera.position.set(0, 0, 5);
camera.position.x = 0;
camera.position.y = 2;
camera.position.z = 5;
camera.lookAt(0, 0, 0);
camera.lookAt(new THREE.Vector3(0, 0, 0));
正交相机(OrthographicCamera) #
正交相机没有透视效果,物体大小不随距离变化,常用于 2D 游戏、CAD 软件等。
javascript
const frustumSize = 10;
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.OrthographicCamera(
frustumSize * aspect / -2, // Left
frustumSize * aspect / 2, // Right
frustumSize / 2, // Top
frustumSize / -2, // Bottom
0.1, // Near
1000 // Far
);
参数详解 #
text
Top
▲
│
Left ├──┤ Right
│
▼
Bottom
正交投影视图:物体大小不随距离变化
相机对比 #
| 特性 | 透视相机 | 正交相机 |
|---|---|---|
| 透视效果 | 有 | 无 |
| 近大远小 | 是 | 否 |
| 适用场景 | 3D 游戏、产品展示 | 2D 游戏、CAD |
| 性能 | 相同 | 相同 |
| 真实感 | 高 | 低 |
相机控制 #
javascript
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 100;
controls.maxPolarAngle = Math.PI / 2;
相机动画 #
javascript
function animate() {
requestAnimationFrame(animate);
camera.position.x = Math.sin(Date.now() * 0.001) * 5;
camera.position.z = Math.cos(Date.now() * 0.001) * 5;
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
}
animate();
渲染器(Renderer) #
渲染器负责将场景和相机的内容绘制到屏幕上。
创建渲染器 #
javascript
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
渲染器配置选项 #
javascript
const renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('canvas'),
antialias: true,
alpha: false,
premultipliedAlpha: true,
preserveDrawingBuffer: false,
powerPreference: 'default',
failIfMajorPerformanceCaveat: false,
depth: true,
stencil: true
});
| 选项 | 描述 | 默认值 |
|---|---|---|
| canvas | 指定 Canvas 元素 | 自动创建 |
| antialias | 抗锯齿 | false |
| alpha | 透明背景 | false |
| powerPreference | GPU 偏好 | default |
| depth | 深度缓冲 | true |
| stencil | 模板缓冲 | true |
渲染器方法 #
设置尺寸 #
javascript
renderer.setSize(width, height);
renderer.setSize(width, height, false);
设置像素比 #
javascript
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
设置背景色 #
javascript
renderer.setClearColor(0x000000);
renderer.setClearColor(0x000000, 1);
渲染场景 #
javascript
renderer.render(scene, camera);
清除缓冲 #
javascript
renderer.clear();
renderer.clearColor();
renderer.clearDepth();
renderer.clearStencil();
高级渲染设置 #
阴影 #
javascript
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
色调映射 #
javascript
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
输出编码 #
javascript
renderer.outputColorSpace = THREE.SRGBColorSpace;
响应式处理 #
javascript
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
完整示例 #
基础场景 #
javascript
import * as THREE from 'three';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff88 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
带光照的场景 #
javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(2, 2, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff88 });
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true;
cube.receiveShadow = true;
scene.add(cube);
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.position.y = -0.5;
plane.receiveShadow = true;
scene.add(plane);
function animate() {
requestAnimationFrame(animate);
controls.update();
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
坐标系统 #
右手坐标系 #
Three.js 使用右手坐标系:
text
Y (上)
▲
│
│
│
└────────► X (右)
/
/
/
▼
Z (前/朝向观察者)
坐标单位 #
javascript
mesh.position.set(1, 2, 3);
mesh.position.x = 1;
mesh.position.y = 2;
mesh.position.z = 3;
旋转 #
javascript
mesh.rotation.set(Math.PI / 4, Math.PI / 2, 0);
mesh.rotation.x = Math.PI / 4;
mesh.rotation.y = Math.PI / 2;
mesh.rotation.z = 0;
缩放 #
javascript
mesh.scale.set(1, 2, 1);
mesh.scale.x = 1;
mesh.scale.y = 2;
mesh.scale.z = 1;
渲染循环 #
requestAnimationFrame #
javascript
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
带时间控制 #
javascript
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
cube.rotation.y = elapsedTime;
renderer.render(scene, camera);
}
animate();
固定帧率 #
javascript
const clock = new THREE.Clock();
const targetFPS = 60;
const interval = 1 / targetFPS;
let delta = 0;
function animate() {
requestAnimationFrame(animate);
delta += clock.getDelta();
if (delta >= interval) {
delta = delta % interval;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
}
animate();
常见问题 #
场景是黑屏 #
- 检查相机位置是否正确
- 检查物体是否在相机视野内
- 检查光照是否添加
- 检查材质是否正确
物体显示不完整 #
- 检查相机的 near 和 far 参数
- 检查物体是否超出裁剪范围
性能问题 #
- 降低像素比:
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) - 减少物体数量
- 使用简化几何体
- 开启视锥裁剪
下一步 #
现在你已经掌握了 Three.js 的核心三要素,接下来学习 几何体,创建各种 3D 形状!
最后更新:2026-03-28