欢迎光临
我们一直在努力

Three.js 全套深入精讲教程(零基础到项目级)

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()

    赞(0)
    未经允许不得转载:171主机测评 » Three.js 全套深入精讲教程(零基础到项目级)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址