{T}

Three.js 快速入门

本章节将带你创建第一个 Three.js 3D 场景,通过实践快速上手 Three.js 开发。

准备工作

在开始之前,确保你已经:

  1. 安装了 Node.js(推荐 v14+)
  2. 了解了基本的 HTML/CSS/JavaScript
  3. 安装了 Three.js(参见 简介与安装

第一个 Three.js 场景

完整示例

创建一个 HTML 文件,包含以下内容:

html
<!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 场景</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      background-color: #000;
    }
    canvas {
      display: block;
    }
  </style>
</head>
<body>
  <script type="module">
    import * as THREE from 'https://cdn.jsdelivr.net/npm/three@latest/build/three.module.js'
    import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@latest/examples/jsm/controls/OrbitControls.js'
 
    // 1. 创建场景
    const scene = new THREE.Scene()
    scene.background = new THREE.Color(0x333333)
 
    // 2. 创建相机
    const camera = new THREE.PerspectiveCamera(
      75, // 视野角度
      window.innerWidth / window.innerHeight, // 宽高比
      0.1, // 近裁剪面
      1000 // 远裁剪面
    )
    camera.position.set(0, 0, 5)
 
    // 3. 创建渲染器
    const renderer = new THREE.WebGLRenderer({ 
      antialias: true, // 开启抗锯齿
      alpha: true // 透明背景
    })
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.shadowMap.enabled = true // 开启阴影
    document.body.appendChild(renderer.domElement)
 
    // 4. 创建几何体和材质
    const geometry = new THREE.BoxGeometry(1, 1, 1)
    const material = new THREE.MeshStandardMaterial({
      color: 0x00ff00,
      metalness: 0.5,
      roughness: 0.5
    })
    const cube = new THREE.Mesh(geometry, material)
    cube.castShadow = true // 投射阴影
    scene.add(cube)
 
    // 5. 创建地面
    const groundGeometry = new THREE.PlaneGeometry(10, 10)
    const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 })
    const ground = new THREE.Mesh(groundGeometry, groundMaterial)
    ground.rotation.x = -Math.PI / 2
    ground.position.y = -1
    ground.receiveShadow = true // 接收阴影
    scene.add(ground)
 
    // 6. 创建光源
    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)
 
    // 7. 添加控制器
    const controls = new OrbitControls(camera, renderer.domElement)
    controls.enableDamping = true // 开启阻尼效果
    controls.dampingFactor = 0.05
 
    // 8. 渲染循环
    function animate() {
      requestAnimationFrame(animate)
      
      // 旋转立方体
      cube.rotation.x += 0.01
      cube.rotation.y += 0.01
      
      // 更新控制器
      controls.update()
      
      // 渲染场景
      renderer.render(scene, camera)
    }
 
    animate()
 
    // 9. 响应窗口大小变化
    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight
      camera.updateProjectionMatrix()
      renderer.setSize(window.innerWidth, window.innerHeight)
    })
  </script>
</body>
</html>

代码解析

让我们逐步分析这段代码的每个部分:

1. 导入 Three.js

javascript
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@latest/build/three.module.js'
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@latest/examples/jsm/controls/OrbitControls.js'
  • 第一行导入 Three.js 核心库
  • 第二行导入 OrbitControls 控制器(用于鼠标交互)

模块化导入方式对比

方式说明推荐场景
import * as THREE from 'three'全局导入,包含所有功能快速原型开发
import { Scene, Camera } from 'three'按需导入生产项目,减小体积
CDN 导入无需构建工具学习、演示

2. 创建场景(Scene)

javascript
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x333333)

场景是所有 3D 对象的容器,我们设置了深灰色的背景。

Scene 常用属性

属性类型说明示例
backgroundColor/Texture背景色或贴图scene.background = new THREE.Color(0x333333)
environmentTexture环境贴图scene.environment = envTexture
fogFog/FogExp2雾效果scene.fog = new THREE.Fog(0x000000, 1, 100)
javascript
// 添加雾效果示例
scene.fog = new THREE.Fog(0x333333, 1, 20) // 线性雾
// 或
scene.fog = new THREE.FogExp2(0x333333, 0.1) // 指数雾

3. 创建相机(Camera)

javascript
const camera = new THREE.PerspectiveCamera(
  75, // 视野角度(FOV)
  window.innerWidth / window.innerHeight, // 宽高比
  0.1, // 近裁剪面
  1000 // 远裁剪面
)
camera.position.set(0, 0, 5)

参数说明:

  • 视野角度(FOV):75 度,决定可视范围。常用值:45-90
  • 宽高比:窗口宽度/高度,必须与画布比例一致
  • 近裁剪面:距离相机 0.1 单位以内的对象不会被渲染
  • 远裁剪面:距离相机 1000 单位以外的对象不会被渲染

视锥体示意图

plaintext
          Far Plane (远裁剪面)
              ╱────────╲
             ╱          ╲
            ╱            ╲
           ╱   可视区域   ╲
          ╱                ╲
         ╱                  ╲
        ╱────────────────────╲
              Near Plane (近裁剪面)
              相机位置 (Camera)

相机位置设置

javascript
// 方式1:使用 set 方法
camera.position.set(x, y, z)
 
// 方式2:直接设置属性
camera.position.x = 0
camera.position.y = 0
camera.position.z = 5
 
// 设置相机朝向
camera.lookAt(0, 0, 0) // 看向原点
// 或
camera.lookAt(new THREE.Vector3(0, 0, 0))

4. 创建渲染器(Renderer)

javascript
const renderer = new THREE.WebGLRenderer({ 
  antialias: true, // 抗锯齿
  alpha: true // 透明背景
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.shadowMap.enabled = true
document.body.appendChild(renderer.domElement)
  • antialias:开启抗锯齿,让边缘更平滑(性能消耗约 5-10%)
  • alpha:支持透明背景,便于叠加到其他元素上
  • setSize:设置渲染器大小,应与 canvas 尺寸一致
  • setPixelRatio:设置像素比,适配高清屏(建议限制最大值)
  • shadowMap.enabled:开启阴影渲染

渲染器配置最佳实践

javascript
const renderer = new THREE.WebGLRenderer({ 
  antialias: true,
  alpha: true,
  powerPreference: 'high-performance' // 高性能模式
})
 
// 限制像素比,避免高清屏性能问题
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
 
// 设置阴影类型
renderer.shadowMap.type = THREE.PCFSoftShadowMap // 柔和阴影
 
// 启用物理正确的光照
renderer.physicallyCorrectLights = true
 
// 色彩管理
renderer.outputColorSpace = THREE.SRGBColorSpace
 
// 色调映射(HDR 效果)
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1.0

渲染信息查看

javascript
// 查看渲染统计信息
console.log(renderer.info)
// {
//   memory: { geometries: 1, textures: 1 },
//   render: { calls: 2, triangles: 36, points: 0, lines: 0 }
// }
 
// 在渲染循环中监控
function animate() {
  console.log('Draw Calls:', renderer.info.render.calls)
  console.log('Triangles:', renderer.info.render.triangles)
  // ...
}

5. 创建几何体和材质

javascript
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
  metalness: 0.5,
  roughness: 0.5
})
const cube = new THREE.Mesh(geometry, material)
  • 几何体(Geometry):定义形状(这里是 1x1x1 的立方体)
  • 材质(Material):定义外观(绿色,半金属半粗糙)
  • 网格(Mesh):几何体 + 材质的组合

常用几何体参数

几何体参数说明
BoxGeometrywidth, height, depth立方体尺寸
SphereGeometryradius, widthSegments, heightSegments半径和分段数
CylinderGeometryradiusTop, radiusBottom, height圆柱体
PlaneGeometrywidth, height平面尺寸

材质类型选择指南

材质特点适用场景
MeshBasicMaterial不受光照影响,性能最好简单物体、UI元素
MeshStandardMaterialPBR材质,效果真实大多数3D场景
MeshPhongMaterialPhong光照模型需要高光的物体
MeshLambertMaterialLambert光照模型漫反射物体
javascript
// MeshStandardMaterial PBR 材质详解
const material = new THREE.MeshStandardMaterial({
  color: 0x00ff00,        // 基础颜色
  metalness: 0.5,         // 金属度 (0-1),1=完全金属
  roughness: 0.5,         // 粗糙度 (0-1),0=光滑镜面
  envMapIntensity: 1.0,   // 环境贴图强度
  
  // 可选贴图
  map: colorMap,          // 颜色贴图
  normalMap: normalMap,   // 法线贴图
  roughnessMap: roughMap, // 粗糙度贴图
  metalnessMap: metalMap, // 金属度贴图
  aoMap: aoMap,           // 环境遮蔽贴图
  emissiveMap: emissiveMap, // 自发光贴图
  emissive: 0x000000,     // 自发光颜色
  emissiveIntensity: 1    // 自发光强度
})

6. 创建光源

javascript
const ambientLight = new THREE.AmbientLight(0x404040, 0.5)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
  • 环境光(AmbientLight):均匀照亮场景中所有对象,无方向
  • 方向光(DirectionalLight):模拟太阳光,产生阴影

光源类型详解

光源类型说明阴影性能典型用途
AmbientLight环境光基础照明
DirectionalLight平行光太阳光
PointLight点光源灯泡、火光
SpotLight聚光灯手电筒、舞台灯
HemisphereLight半球光天空+地面照明
javascript
// 创建多种光源示例
 
// 环境光 - 基础照明
const ambient = new THREE.AmbientLight(0x404040, 0.5)
scene.add(ambient)
 
// 方向光 - 主光源
const directional = new THREE.DirectionalLight(0xffffff, 1)
directional.position.set(5, 10, 5)
directional.castShadow = true
// 配置阴影
directional.shadow.mapSize.width = 2048
directional.shadow.mapSize.height = 2048
directional.shadow.camera.near = 0.5
directional.shadow.camera.far = 50
scene.add(directional)
 
// 点光源 - 辅助光源
const point = new THREE.PointLight(0xff0000, 1, 10)
point.position.set(0, 2, 0)
scene.add(point)
 
// 半球光 - 天空地面照明
const hemisphere = new THREE.HemisphereLight(0x87CEEB, 0x8B4513, 0.5)
scene.add(hemisphere)

7. 添加控制器

javascript
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true // 开启阻尼效果
controls.dampingFactor = 0.05

OrbitControls 配置详解

javascript
const controls = new OrbitControls(camera, renderer.domElement)
 
// 基础配置
controls.enableDamping = true       // 开启阻尼(惯性)
controls.dampingFactor = 0.05       // 阻尼系数
controls.autoRotate = false         // 自动旋转
controls.autoRotateSpeed = 2.0      // 自动旋转速度
 
// 限制范围
controls.minDistance = 1            // 最小缩放距离
controls.maxDistance = 100          // 最大缩放距离
controls.minPolarAngle = 0          // 垂直旋转最小角度
controls.maxPolarAngle = Math.PI    // 垂直旋转最大角度
 
// 禁用/启用交互
controls.enableZoom = true          // 启用缩放
controls.enableRotate = true        // 启用旋转
controls.enablePan = true           // 启用平移
 
// 平滑缩放
controls.zoomSpeed = 1.0
controls.rotateSpeed = 1.0
controls.panSpeed = 1.0
 
// 设置目标点
controls.target.set(0, 0, 0)
controls.update() // 更新控制器

其他控制器类型

控制器说明适用场景
OrbitControls轨道控制器通用3D预览
FlyControls飞行控制器飞行模拟
FirstPersonControls第一人称FPS游戏
PointerLockControls指针锁定沉浸式体验
TrackballControls轨迹球自由旋转

8. 渲染循环

javascript
function animate() {
  requestAnimationFrame(animate)
  
  // 旋转立方体
  cube.rotation.x += 0.01
  cube.rotation.y += 0.01
  
  // 更新控制器
  controls.update()
  
  // 渲染场景
  renderer.render(scene, camera)
}
 
animate()

渲染循环是 Three.js 的核心:

  1. requestAnimationFrame:浏览器优化的定时器,与屏幕刷新率同步(通常 60fps)
  2. 更新对象状态:修改位置、旋转、缩放等
  3. 更新控制器:如果有阻尼效果,需要每帧更新
  4. 渲染场景:将3D场景投影到2D画布

渲染循环优化

javascript
// 使用 Clock 获取准确的时间增量
const clock = new THREE.Clock()
 
function animate() {
  requestAnimationFrame(animate)
  
  const deltaTime = clock.getDelta()   // 两帧之间的时间差(秒)
  const elapsedTime = clock.getElapsedTime() // 总运行时间
  
  // 使用 deltaTime 实现帧率无关的动画
  cube.rotation.x += deltaTime
  cube.rotation.y += deltaTime * 0.5
  
  controls.update()
  renderer.render(scene, camera)
}

按需渲染(非连续动画场景)

javascript
let needsUpdate = true
 
function render() {
  if (!needsUpdate) return
  
  renderer.render(scene, camera)
  needsUpdate = false
}
 
// 只在需要时更新
controls.addEventListener('change', () => {
  needsUpdate = true
  render()
})
 
// 初始渲染
render()

构建工具项目

对于实际项目,推荐使用构建工具(如 Vite)。以下是完整的项目搭建步骤:

1. 创建项目

bash
# 创建项目目录
mkdir threejs-demo
cd threejs-demo
 
# 初始化项目
npm init -y
 
# 安装依赖
npm install three
npm install -D vite

2. 项目结构

plaintext
threejs-demo/
├── index.html
├── src/
│   ├── main.js
│   └── style.css
├── package.json
└── vite.config.js

3. 配置文件

vite.config.js

javascript
import { defineConfig } from 'vite'
 
export default defineConfig({
  server: {
    port: 3000,
    open: true
  }
})

package.json

json
{
  "name": "threejs-demo",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "three": "^0.150.0"
  },
  "devDependencies": {
    "vite": "^4.0.0"
  }
}

4. HTML 文件

index.html

html
<!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 Demo</title>
  <link rel="stylesheet" href="/src/style.css">
</head>
<body>
  <script type="module" src="/src/main.js"></script>
</body>
</html>

5. 样式文件

src/style.css

css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
 
body {
  overflow: hidden;
  background-color: #000;
}
 
canvas {
  display: block;
}

6. JavaScript 文件

src/main.js

javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
 
class ThreeScene {
  constructor() {
    this.scene = null
    this.camera = null
    this.renderer = null
    this.cube = null
    this.controls = null
    
    this.init()
  }
 
  init() {
    // 创建场景
    this.createScene()
    
    // 创建相机
    this.createCamera()
    
    // 创建渲染器
    this.createRenderer()
    
    // 创建对象
    this.createObjects()
    
    // 创建光源
    this.createLights()
    
    // 创建控制器
    this.createControls()
    
    // 添加事件监听
    this.addEventListeners()
    
    // 开始渲染循环
    this.animate()
  }
 
  createScene() {
    this.scene = new THREE.Scene()
    this.scene.background = new THREE.Color(0x333333)
  }
 
  createCamera() {
    const aspect = window.innerWidth / window.innerHeight
    this.camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000)
    this.camera.position.set(0, 0, 5)
  }
 
  createRenderer() {
    this.renderer = new THREE.WebGLRenderer({ antialias: true })
    this.renderer.setSize(window.innerWidth, window.innerHeight)
    this.renderer.setPixelRatio(window.devicePixelRatio)
    this.renderer.shadowMap.enabled = true
    document.body.appendChild(this.renderer.domElement)
  }
 
  createObjects() {
    // 创建立方体
    const geometry = new THREE.BoxGeometry(1, 1, 1)
    const material = new THREE.MeshStandardMaterial({
      color: 0x00ff00,
      metalness: 0.5,
      roughness: 0.5
    })
    this.cube = new THREE.Mesh(geometry, material)
    this.cube.castShadow = true
    this.scene.add(this.cube)
 
    // 创建地面
    const groundGeometry = new THREE.PlaneGeometry(10, 10)
    const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 })
    const ground = new THREE.Mesh(groundGeometry, groundMaterial)
    ground.rotation.x = -Math.PI / 2
    ground.position.y = -1
    ground.receiveShadow = true
    this.scene.add(ground)
  }
 
  createLights() {
    // 环境光
    const ambientLight = new THREE.AmbientLight(0x404040, 0.5)
    this.scene.add(ambientLight)
 
    // 方向光
    const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
    directionalLight.position.set(5, 5, 5)
    directionalLight.castShadow = true
    this.scene.add(directionalLight)
  }
 
  createControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.enableDamping = true
    this.controls.dampingFactor = 0.05
  }
 
  addEventListeners() {
    window.addEventListener('resize', () => this.onWindowResize())
  }
 
  onWindowResize() {
    this.camera.aspect = window.innerWidth / window.innerHeight
    this.camera.updateProjectionMatrix()
    this.renderer.setSize(window.innerWidth, window.innerHeight)
  }
 
  animate() {
    requestAnimationFrame(() => this.animate())
    
    // 旋转立方体
    this.cube.rotation.x += 0.01
    this.cube.rotation.y += 0.01
    
    // 更新控制器
    this.controls.update()
    
    // 渲染场景
    this.renderer.render(this.scene, this.camera)
  }
}
 
// 创建场景实例
new ThreeScene()

7. 运行项目

bash
npm run dev

浏览器会自动打开 http://localhost:3000,看到旋转的立方体。

添加更多对象

让我们扩展场景,添加更多 3D 对象:

javascript
createObjects() {
  // 立方体
  const boxGeometry = new THREE.BoxGeometry(1, 1, 1)
  const boxMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
  const box = new THREE.Mesh(boxGeometry, boxMaterial)
  box.position.set(-2, 0, 0)
  box.castShadow = true
  this.scene.add(box)
 
  // 球体
  const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32)
  const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
  sphere.position.set(0, 0, 0)
  sphere.castShadow = true
  this.scene.add(sphere)
 
  // 圆柱体
  const cylinderGeometry = new THREE.CylinderGeometry(0.3, 0.3, 1, 32)
  const cylinderMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff })
  const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial)
  cylinder.position.set(2, 0, 0)
  cylinder.castShadow = true
  this.scene.add(cylinder)
 
  // 圆环
  const torusGeometry = new THREE.TorusGeometry(0.5, 0.2, 16, 100)
  const torusMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00 })
  const torus = new THREE.Mesh(torusGeometry, torusMaterial)
  torus.position.set(0, 2, 0)
  torus.castShadow = true
  this.scene.add(torus)
 
  // 保存对象引用
  this.objects = { box, sphere, cylinder, torus }
}
 
animate() {
  requestAnimationFrame(() => this.animate())
  
  const time = Date.now() * 0.001
  
  // 旋转所有对象
  Object.values(this.objects).forEach((obj, index) => {
    obj.rotation.x += 0.01
    obj.rotation.y += 0.01
    obj.position.y = Math.sin(time + index) * 0.5
  })
  
  this.controls.update()
  this.renderer.render(this.scene, this.camera)
}

性能监控

添加性能监控工具:

javascript
import Stats from 'three/examples/jsm/libs/stats.module.js'
 
class ThreeScene {
  constructor() {
    this.stats = null
    // ...
  }
 
  init() {
    // ...
    this.createStats()
    // ...
  }
 
  createStats() {
    this.stats = new Stats()
    this.stats.showPanel(0) // 0: fps, 1: ms, 2: mb
    document.body.appendChild(this.stats.dom)
  }
 
  animate() {
    this.stats.begin()
    
    requestAnimationFrame(() => this.animate())
    
    // ...动画代码
    
    this.stats.end()
    this.renderer.render(this.scene, this.camera)
  }
}

常见问题

1. 场景是空白的

原因

  • 相机位置不正确
  • 没有调用 render()
  • 对象在视锥体之外

解决方案

javascript
// 检查相机位置
console.log(camera.position)
 
// 检查对象位置
scene.traverse((obj) => {
  console.log(obj.type, obj.position)
})
 
// 确保调用了 render()
renderer.render(scene, camera)
 
// 检查对象是否在视锥体内
const frustum = new THREE.Frustum()
const matrix = new THREE.Matrix4().multiplyMatrices(
  camera.projectionMatrix,
  camera.matrixWorldInverse
)
frustum.setFromProjectionMatrix(matrix)
 
if (frustum.intersectsObject(mesh)) {
  console.log('对象在视锥体内')
} else {
  console.log('对象在视锥体外,调整相机位置')
}

2. 对象是黑色的

原因

  • 没有光源
  • 使用了需要光照的材质(如 MeshStandardMaterial)
  • 法线方向错误

解决方案

javascript
// 添加光源
const light = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(light)
 
// 或使用不需要光照的材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
 
// 检查并修复法线
geometry.computeVertexNormals()
 
// 翻转法线(如果需要)
geometry.scale(-1, 1, 1)

3. 窗口大小变化时画面变形

原因:没有更新相机的宽高比

解决方案

javascript
window.addEventListener('resize', () => {
  // 获取新尺寸
  const width = window.innerWidth
  const height = window.innerHeight
  
  // 更新相机
  camera.aspect = width / height
  camera.updateProjectionMatrix()
  
  // 更新渲染器
  renderer.setSize(width, height)
})
 
// 如果渲染到容器内(而非全屏)
const container = document.getElementById('container')
const resizeObserver = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect
    camera.aspect = width / height
    camera.updateProjectionMatrix()
    renderer.setSize(width, height)
  }
})
resizeObserver.observe(container)

4. 性能问题

解决方案

javascript
// 1. 减少多边形数量
const geometry = new THREE.BoxGeometry(1, 1, 1, 4, 4, 4) // 较少分段
 
// 2. 使用低分辨率
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
 
// 3. 关闭抗锯齿(如果不需要)
const renderer = new THREE.WebGLRenderer({ antialias: false })
 
// 4. 合并几何体
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils'
const mergedGeometry = BufferGeometryUtils.mergeGeometries([geo1, geo2, geo3])
 
// 5. 使用 InstancedMesh(大量相同对象)
const count = 1000
const mesh = new THREE.InstancedMesh(geometry, material, count)
const matrix = new THREE.Matrix4()
for (let i = 0; i < count; i++) {
  matrix.setPosition(Math.random() * 10, Math.random() * 10, Math.random() * 10)
  mesh.setMatrixAt(i, matrix)
}
 
// 6. 使用 LOD(细节层次)
const lod = new THREE.LOD()
lod.addLevel(highPolyMesh, 0)   // 0米内显示高模
lod.addLevel(midPolyMesh, 10)   // 10米内显示中模
lod.addLevel(lowPolyMesh, 20)   // 20米内显示低模

5. 阴影显示异常

原因

  • 阴影贴图分辨率太低
  • 光源范围设置不当
  • 未开启对象阴影

解决方案

javascript
// 1. 提高阴影贴图分辨率
renderer.shadowMap.enabled = true
directionalLight.shadow.mapSize.width = 2048
directionalLight.shadow.mapSize.height = 2048
 
// 2. 调整光源阴影相机范围
directionalLight.shadow.camera.near = 0.1
directionalLight.shadow.camera.far = 100
directionalLight.shadow.camera.left = -20
directionalLight.shadow.camera.right = 20
directionalLight.shadow.camera.top = 20
directionalLight.shadow.camera.bottom = -20
 
// 3. 确保对象开启了阴影
mesh.castShadow = true      // 投射阴影
ground.receiveShadow = true // 接收阴影
 
// 4. 使用柔和阴影
renderer.shadowMap.type = THREE.PCFSoftShadowMap

6. 纹理显示模糊

解决方案

javascript
// 1. 检查纹理尺寸(建议2的幂次方)
const texture = textureLoader.load('texture.jpg')
// 1024x1024, 512x512, 256x256...
 
// 2. 设置正确的纹理过滤
texture.minFilter = THREE.LinearMipmapLinearFilter
texture.magFilter = THREE.LinearFilter
 
// 3. 生成 mipmaps
texture.generateMipmaps = true
 
// 4. 设置各向异性过滤
const maxAnisotropy = renderer.capabilities.getMaxAnisotropy()
texture.anisotropy = maxAnisotropy

7. 点击交互无响应

解决方案

javascript
// 确保正确设置射线检测
import { Raycaster, Vector2 } from 'three'
 
const raycaster = new Raycaster()
const mouse = new Vector2()
 
// 监听点击事件
canvas.addEventListener('click', (event) => {
  // 计算鼠标位置(归一化设备坐标)
  const rect = canvas.getBoundingClientRect()
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
  
  // 设置射线
  raycaster.setFromCamera(mouse, camera)
  
  // 检测相交对象
  const intersects = raycaster.intersectObjects(scene.children, true)
  
  if (intersects.length > 0) {
    console.log('点击了:', intersects[0].object)
  }
})

下一步

恭喜你完成了第一个 Three.js 场景!接下来你可以:

  1. 开发环境配置 - 配置完整的开发工具链
  2. 场景系统 - 深入理解场景概念
  3. 相机系统 - 学习不同的相机类型
  4. 内置几何体 - 探索更多几何体类型
  5. 材质基础 - 学习材质系统

相关资源