Three.js 全套深入精讲教程(零基础到项目级)
第一章:Three.js 简介 + 环境安装
1.1 什么是 Three.js?底层原理与定位
Three.js 是基于 WebGL/WebGPU 的浏览器 3D 渲染引擎,本质是对浏览器底层图形 API 的高级封装。
原生 WebGL 极其繁琐:需要手动写着色器、处理矩阵、顶点缓冲、纹理绑定、帧缓存、透视裁剪。Three.js 屏蔽所有底层复杂度,让前端用面向对象的方式写 3D。
核心本质:
-
WebGL = 原生绘图指令(难、底层、代码量巨大)
-
Three.js = WebGL 业务框架(开箱即用、组件化、场景化)
Three.js 能做什么?
-
3D 可视化、数字孪生、大屏沙盘
-
3D 模型展示、家装/汽车/商品 3D 预览
-
网页 3D 游戏、小游戏场景
-
WebXR 虚拟现实、AR 叠加
-
Shader 特效、粒子、流体、光影艺术
核心设计思想
Three.js 所有画面只靠 三大核心组件:
Scene 场景:所有物体、灯光、相机的容器(舞台)
Camera 相机:观察视角(决定你看到什么)
Renderer 渲染器:把画面画到画布上
公式:画面 = 场景 + 相机 + 渲染器逐帧绘制
1.2 安装方式全解(CDN / NPM / 框架适配)
方式一:CDN 零配置
使用 ES Module 导入,无需打包工具:
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three'
console.log(THREE)
</script>
方式二:NPM 工程化
npm install three
# 可选 TS 类型
npm install @types/three -D
导入核心 + 插件:
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
版本选择避坑
-
稳定版:0.158~0.160
-
0.165+ 偏向 WebGPU 新特性,兼容性略差
-
绝对不要用 0.130 以下老版本,大量 API 废弃
1.3 最小完整可运行骨架
包含:场景、相机、渲染器、自适应、动画循环、抗锯齿、像素比优化
import * as THREE from 'three'
// 1. 场景
const scene = new THREE.Scene()
scene.background = new THREE.Color(0xf1f1f1)
// 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)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) // 超分优化
document.body.appendChild(renderer.domElement)
// 4. 基础立方体测试
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshNormalMaterial()
)
scene.add(cube)
// 5. 动画循环
function animate() {
requestAnimationFrame(animate)
cube.rotation.y += 0.01
renderer.render(scene, camera)
}
animate()
// 6. 窗口自适应
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
第二章:相机、控件、光照与阴影、几何体
2.1 相机系统
相机决定「用户看到的 3D 投影结果」,是 3D 画面的核心透视来源。
2.1.1 透视相机 PerspectiveCamera
模拟人眼:近大远小、透视畸变
new THREE.PerspectiveCamera(FOV, 宽高比, 近裁剪面, 远裁剪面)
-
FOV 视场角:纵向可视角度,越大越广角、变形越严重(常规 45~75)
-
near 近裁剪面:距离相机过近的物体不渲染,不能设 0
-
far 远裁剪面:过远物体裁剪,过大产生深度抖动
2.1.2 正交相机 OrthographicCamera(无透视)
物体大小不受距离影响,用于:CAD、2.5D、UI、沙盘
const aspect = window.innerWidth / window.innerHeight
const camera = new THREE.OrthographicCamera(–5 * aspect, 5 * aspect, 5, –5, 0.1, 100)
2.1.3 相机核心方法
-
camera.lookAt(x,y,z):看向目标点
-
camera.updateProjectionMatrix():修改参数后必须更新矩阵
-
camera.position.set():位置三轴设置
2.2 控制器 OrbitControls(全配置精讲 + 避坑)
轨道控制器:拖拽旋转、滚轮缩放、右键平移,是必备控件。
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
const controls = new OrbitControls(camera, renderer.domElement)
// 惯性阻尼(顺滑关键)
controls.enableDamping = true
controls.dampingFactor = 0.04
// 缩放限制
controls.minDistance = 2
controls.maxDistance = 20
// 旋转角度限制(防穿帮)
controls.minPolarAngle = 0
controls.maxPolarAngle = Math.PI / 1.8
// 开关功能
controls.enableZoom = true
controls.enablePan = true
controls.enableRotate = true
// 自动旋转
controls.autoRotate = false
controls.autoRotateSpeed = 1.5
// 动画循环必须更新
function animate() {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene, camera)
}
常用其他控制器
-
FirstPersonControls:第一人称漫游
-
PointerLockControls:FPS 锁定鼠标
-
TransformControls:物体拖拽缩放 Gizmo
2.3 光照系统(所有光源原理 + 实战)
Three.js 材质分为:受光材质 / 不受光材质。
MeshStandardMaterial、MeshLambert、MeshPhong 必须有光源才能显示明暗。
2.3.1 环境光 AmbientLight
均匀照亮全场,无方向、无阴影,用于补光。
const ambient = new THREE.AmbientLight(0xffffff, 0.4)
scene.add(ambient)
2.3.2 平行光 DirectionalLight(太阳光、主光源)
模拟太阳,方向固定,可产生高质量阴影。
const dirLight = new THREE.DirectionalLight(0xffffff, 1)
dirLight.position.set(10, 10, 10)
scene.add(dirLight)
2.3.3 点光源 PointLight(灯泡)
从一点向四周发散,有衰减、有阴影。
2.3.4 聚光灯 SpotLight(手电筒/舞台灯)
锥形照射,可设置角度、边缘羽化。
2.3.5 半球光 HemisphereLight(室外环境光)
上天下地渐变,模拟自然天光,氛围感极强。
2.4 阴影系统(深度优化 + 解决锯齿/条纹)
阴影是 3D 真实感核心,需要四步固定配置
// 1. 渲染器开启阴影
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap // 软阴影最真实
// 2. 光源开启阴影
dirLight.castShadow = true
// 3. 阴影分辨率(清晰度)
dirLight.shadow.mapSize.set(2048, 2048)
// 4. 物体投射/接收阴影
cube.castShadow = true
floor.receiveShadow = true
阴影常见问题解决
-
阴影锯齿:提升 mapSize
-
阴影条纹闪烁:配置 light.shadow.bias = -0.001
-
阴影范围太小:调整 shadow.camera 视口
2.5 几何体系统(内置几何体 + 自定义顶点)
几何体 = 顶点坐标集合,决定物体形状。
所有几何体底层都是 BufferGeometry(GPU 缓冲顶点)。
2.5.1 常用内置几何体大全
// 立方体、球体、圆柱、圆锥、平面、圆环、胶囊
new THREE.BoxGeometry()
new THREE.SphereGeometry()
new THREE.CylinderGeometry()
new THREE.ConeGeometry()
new THREE.PlaneGeometry()
new THREE.TorusGeometry()
new THREE.CapsuleGeometry()
2.5.2 自定义几何体(手写顶点)
自由创建任意形状,面试高频考点。
const geometry = new THREE.BufferGeometry()
const points = new Float32Array([
–1, –1, 0,
1, –1, 0,
0, 1, 0
])
geometry.setAttribute('position', new THREE.BufferAttribute(points, 3))
geometry.computeVertexNormals()
第三章:材质、纹理、线条绘制
3.1 材质系统(所有材质区别 + PBR 精讲)
材质决定物体:颜色、反光、粗糙、透明、金属、发光。
3.1.1 基础材质(不受光)
-
MeshBasicMaterial:纯色、无光影、性能最高
-
MeshNormalMaterial:法线调试材质
3.1.2 光照材质(传统)
-
MeshLambertMaterial:漫反射、无高光
-
MeshPhongMaterial:漫反射+高光、塑料质感
3.1.3 PBR 物理材质(项目首选)
MeshStandardMaterial 真实物理渲染,两大核心参数:
-
metalness 金属度:0=绝缘体 1=金属
-
roughness 粗糙度:0=镜面光滑 1=完全粗糙
const mat = new THREE.MeshStandardMaterial({
color: 0xcccccc,
metalness: 0.7,
roughness: 0.2
})
3.1.4 高级材质
-
MeshPhysicalMaterial:玻璃、折射、透光、车漆
-
MeshToonMaterial:卡通二次元风格
3.2 纹理系统(贴图全解 + 纹理优化)
纹理就是贴在模型表面的图片,是 3D 细节的核心。
3.2.1 纹理加载器
const loader = new THREE.TextureLoader()
const texture = loader.load('/texture.jpg')
3.2.2 九大常用贴图类型
-
map 颜色贴图
-
normalMap 法线贴图(凹凸细节)
-
roughnessMap 粗糙度贴图
-
metalnessMap 金属度贴图
-
aoMap 环境光遮蔽(缝隙变暗)
-
displacementMap 位移贴图(改变模型形状)
-
alphaMap 透明贴图
-
emissiveMap 自发光贴图
-
envMap 环境反射贴图
3.2.3 纹理重复、偏移、旋转
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(3,3)
3.3 线条绘制(直线、虚线、曲线、管道)
3.3.1 基础线条 Line
const points = [
new THREE.Vector3(–2,0,0),
new THREE.Vector3(2,0,0)
]
const geo = new THREE.BufferGeometry().setFromPoints(points)
const mat = new THREE.LineBasicMaterial({color:0xff0000})
const line = new THREE.Line(geo,mat)
scene.add(line)
3.3.2 虚线
const mat = new THREE.LineDashedMaterial({
color:0x00ff00,
dashSize:0.2,
gapSize:0.1
})
line.computeLineDistances()
3.3.3 平滑曲线 + 管道
CatmullRomCurve3 平滑样条曲线 + TubeGeometry 管道几何体
第四章:动画、3D文本、模型加载、插件生态
4.1 动画系统(原生动画 + GSAP + 骨骼动画)
4.1.1 原生帧率无关动画
必须用 Clock 计算时间,避免帧率波动导致速度不一致
const clock = new THREE.Clock()
function animate(){
requestAnimationFrame(animate)
const t = clock.getElapsedTime()
cube.rotation.y = t * 2
cube.position.y = Math.sin(t) * 0.5
renderer.render(scene,camera)
}
4.1.2 GSAP 专业动画
import gsap from 'gsap'
gsap.to(cube.position,{
x:2,
duration:1,
repeat:–1,
yoyo:true
})
4.1.3 模型骨骼动画
AnimationMixer + AnimationAction 实现人物行走、动作切换
4.2 3D 文本创建(TextGeometry + 高性能文字)
4.2.1 原生 3D 文字
需要加载 json 字体,支持挤出厚度、倒角
4.2.2 Troika-Text 高性能文字
无需字体文件、支持描边、渐变、超大文字量渲染
4.3 3D 模型加载(glb/gltf 主流格式精讲)
glb/gltf 是目前行业标准 3D 格式,体积小、支持PBR、动画、贴图
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
const loader = new GLTFLoader()
loader.load('/model.glb',(gltf)=>{
const model = gltf.scene
model.scale.set(1,1,1)
scene.add(model)
})
模型遍历与批量设置阴影
model.traverse(child=>{
if(child.isMesh){
child.castShadow = true
child.receiveShadow = true
}
})
4.4 常用库与插件生态
-
后处理:辉光、景深、AO、描边、调色
-
物理引擎:cannon-es 刚体碰撞、重力、弹跳
-
调试工具:lil-gui 参数调试、stats 帧率监控
-
UI 库:three-mesh-ui 3D 空间UI
-
粒子系统:自定义粒子、雪花、雨水、火焰
-
WebGPU 新版渲染器、高性能大批量渲染
完整代码:vue/ main.js
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { ExtrudeGeometry } from 'three'
// ———————- 1.基础初始化 ———————-
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x0f172a)
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 2000)
camera.position.set(0, –120, 150)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(innerWidth, innerHeight)
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.body.appendChild(renderer.domElement)
// 控制器
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05
controls.minDistance = 60
controls.maxDistance = 300
controls.maxPolarAngle = Math.PI / 2.1
// 光照
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8)
dirLight.position.set(80, 120, 100)
dirLight.castShadow = true
dirLight.shadow.mapSize.set(2048, 2048)
scene.add(dirLight)
// 地面底板
const floorGeo = new THREE.PlaneGeometry(260, 220)
const floorMat = new THREE.MeshStandardMaterial({ color: 0x1e293b })
const floor = new THREE.Mesh(floorGeo, floorMat)
floor.rotation.x = –Math.PI / 2
floor.receiveShadow = true
scene.add(floor)
// ———————- 2.经纬度转平面坐标(简易墨卡托投影) ———————-
// 中国经纬度范围:lng:73~135 lat:18~54
const LNG_MIN = 73, LNG_MAX = 135
const LAT_MIN = 18, LAT_MAX = 54
const MAP_SIZE = 200 //地图整体尺寸
/**
* 经纬度转three平面xy
* @param {number} lng 经度
* @param {number} lat 纬度
* @returns {THREE.Vector2}
*/
function lngLatToPoint(lng, lat) {
const x = ((lng – LNG_MIN) / (LNG_MAX – LNG_MIN) – 0.5) * MAP_SIZE
const y = ((1 – (lat – LAT_MIN) / (LAT_MAX – LAT_MIN)) – 0.5) * MAP_SIZE
return new THREE.Vector2(x, y)
}
// ———————- 3.geojson解析生成立体省份 ———————-
/**
* 创建省份立体Mesh
* @param {Array} coordinates 多边形坐标数组
* @param {string} name 省份名称
* @returns {THREE.Mesh|null}
*/
function createProvinceMesh(coordinates, name) {
const shapeArr = []
for (const ring of coordinates) {
const pts = ring.map(([lng, lat]) => lngLatToPoint(lng, lat))
const shape = new THREE.Shape(pts)
shapeArr.push(shape)
}
if (shapeArr.length === 0) return null
const extrudeOpt = {
depth: 3, // 凸起高度
bevelEnabled: false
}
const geo = new ExtrudeGeometry(shapeArr, extrudeOpt)
geo.computeVertexNormals()
const mat = new THREE.MeshStandardMaterial({
color: 0x2563eb,
side: THREE.DoubleSide
})
const mesh = new THREE.Mesh(geo, mat)
mesh.userData = { provinceName: name, baseColor: 0x2563eb }
mesh.castShadow = true
mesh.receiveShadow = true
return mesh
}
// 加载本地public/china.json
async function loadMapData() {
const res = await fetch('/china.json')
const geojson = await res.json()
const mapGroup = new THREE.Group()
geojson.features.forEach(feature => {
const { geometry, properties } = feature
const provinceName = properties.name
const { type, coordinates } = geometry
if (type === 'Polygon') {
const mesh = createProvinceMesh(coordinates, provinceName)
mesh && mapGroup.add(mesh)
} else if (type === 'MultiPolygon') {
// 多块省份(海岛等)
coordinates.forEach(poly => {
const mesh = createProvinceMesh(poly, provinceName)
mesh && mapGroup.add(mesh)
})
}
})
scene.add(mapGroup)
return mapGroup
}
// ———————- 4.鼠标拾取 hover / 点击省份 ———————-
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
let hoverMesh = null
let mapGroup = null
window.addEventListener('mousemove', e => {
mouse.x = (e.clientX / innerWidth) * 2 – 1
mouse.y = –(e.clientY / innerHeight) * 2 + 1
})
window.addEventListener('click', () => {
if (hoverMesh) {
console.log('点击省份:', hoverMesh.userData.provinceName)
// 可在这里做弹窗、跳转、下钻逻辑
}
})
function checkHover() {
if (!mapGroup) return
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObjects(mapGroup.children)
// 还原上一个hover
if (hoverMesh) {
hoverMesh.material.color.set(hoverMesh.userData.baseColor)
hoverMesh = null
}
if (intersects.length > 0) {
hoverMesh = intersects[0].object
hoverMesh.material.color.set(0xf59e0b) // hover高亮黄色
}
}
// ———————- 5.添加城市点位标记 ———————-
function addCityPoint(lng, lat, cityName) {
const pos = lngLatToPoint(lng, lat)
const sphereGeo = new THREE.SphereGeometry(1.2, 12, 12)
const sphereMat = new THREE.MeshStandardMaterial({ color: 0xef4444 })
const point = new THREE.Mesh(sphereGeo, sphereMat)
point.position.set(pos.x, 3.2, pos.y)
point.userData = { cityName }
scene.add(point)
}
// 示例:北京
addCityPoint(116.40, 39.90, '北京')
// ———————- 6.渲染循环、窗口 resize ———————-
function animate() {
requestAnimationFrame(animate)
controls.update()
checkHover()
renderer.render(scene, camera)
}
window.addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight
camera.updateProjectionMatrix()
renderer.setSize(innerWidth, innerHeight)
})
// 启动
loadMapData().then(group => {
mapGroup = group
console.log('地图渲染完成')
})
animate()


