1. Three.js入门:从零构建3D场景的基础要素
第一次接触Three.js时,我完全被它简洁的API设计所震撼。这个基于WebGL的JavaScript库,让在浏览器中创建复杂3D场景变得像搭积木一样简单。记得最初尝试时,仅用30行代码就实现了一个旋转的彩色立方体,这种即时反馈的成就感是其他图形学工具难以比拟的。
Three.js的核心架构围绕三个基本要素展开:场景(Scene)、相机(Camera)和渲染器(Renderer)。场景就像是一个虚拟的摄影棚,所有3D对象都放置其中;相机决定了我们观察场景的视角,如同导演的取景框;而渲染器则是将这一切转化为屏幕上像素的魔法师。这种清晰的职责划分,使得代码组织变得非常直观。
新手常见误区:很多初学者会忽略渲染循环的重要性。Three.js不会自动更新画面,需要手动调用requestAnimationFrame来实现动画效果。
1.1 场景搭建基础步骤
创建一个基础3D场景通常遵循以下流程:
javascript复制// 1. 初始化场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
// 2. 设置相机(透视相机)
const camera = new THREE.PerspectiveCamera(
75, // 视野角度(FOV)
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();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 5. 动画循环
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
这段代码揭示了几点关键信息:
- 相机参数需要根据场景尺寸动态计算
- WebGLRenderer的antialias参数能显著提升边缘平滑度
- 物体变换应该在渲染前更新
1.2 性能优化第一课
在早期项目中,我犯过将所有对象都直接添加到场景中的错误。当模型数量超过1000个时,帧率就会急剧下降。后来通过以下技巧解决了问题:
- 对象池技术:复用几何体和材质
- InstancedMesh:批量渲染相同物体
- Frustum Culling:只渲染可见物体
- Level of Detail (LOD):根据距离切换模型精度
javascript复制// 实例化网格示例
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial();
const instances = 1000;
const mesh = new THREE.InstancedMesh(geometry, material, instances);
for (let i = 0; i < instances; i++) {
const matrix = new THREE.Matrix4();
matrix.setPosition(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 光照与材质:让3D世界活起来
当我的第一个立方体在场景中旋转时,我意识到没有光影的3D世界就像没有调料的菜肴——虽然能填饱肚子,但索然无味。Three.js提供了多种光源类型,每种都有其独特的应用场景。
2.1 光源类型对比实践
| 光源类型 | 特点 | 性能消耗 | 适用场景 |
|---|---|---|---|
| AmbientLight | 均匀照亮所有表面 | 极低 | 基础环境光 |
| DirectionalLight | 平行光线(如太阳光) | 中 | 室外场景主光源 |
| PointLight | 向所有方向发射 | 高 | 灯泡、蜡烛 |
| SpotLight | 锥形光束 | 较高 | 手电筒、舞台灯 |
| HemisphereLight | 模拟天空和地面反射 | 低 | 自然光照环境 |
在我的一个室内场景项目中,最终采用了这样的光源组合:
javascript复制// 环境光提供基础照明
const ambient = new THREE.AmbientLight(0x404040);
scene.add(ambient);
// 平行光模拟阳光透过窗户
const directional = new THREE.DirectionalLight(0xffffff, 0.8);
directional.position.set(10, 20, 10);
directional.castShadow = true;
scene.add(directional);
// 点光源作为台灯
const point = new THREE.PointLight(0xffaa00, 1, 10);
point.position.set(2, 3, 1);
scene.add(point);
阴影优化技巧:不是所有光源都需要投射阴影。通常只为主光源开启阴影,并合理设置shadow map的大小和精度。
2.2 材质系统深度解析
Three.js的材质系统让我又爱又恨——功能强大但参数复杂。经过多次调试,我总结出这些经验:
- MeshStandardMaterial 是最常用的PBR材质,支持金属度和粗糙度
- 物理正确的光照需要将renderer.physicallyCorrectLights设为true
- 法线贴图的强度通常设置在(0.5, 1.5)范围
- 透明度排序问题需要通过手动设置renderOrder解决
javascript复制const material = new THREE.MeshStandardMaterial({
color: 0x6699ff,
metalness: 0.8, // 金属质感程度
roughness: 0.2, // 表面粗糙度
envMap: cubeTexture, // 环境贴图
normalMap: normalTexture, // 法线
