Three.js × Blender:从建模到 Web 3D 的完整工作流深度解析
一名同时精通 Blender 建模与 Three.js 渲染的工程师,带你打通从艺术创作到 Web 实时渲染的全链路。
前言
很多开发者在学习 Three.js 时,习惯用代码"画"出几何体——一个 BoxGeometry,一个 SphereGeometry,就此打住。而建模师则沉浸在 Blender 的雕刻与材质世界里,对 Web 端渲染一无所知。
真正有价值的 3D Web 项目,往往需要这两者的深度结合:Blender 负责内容生产,Three.js 负责实时呈现。本文将从工作流、格式选择、材质映射、性能优化到动画同步,系统拆解这套协作体系的每一个关键节点。
一、为什么选择 Blender + Three.js?
Blender 的优势
Blender 是目前最强大的开源 3D 创作套件,集建模、雕刻、UV 展开、材质编辑、骨骼绑定、动画、渲染于一体。它的 PBR(基于物理的渲染)材质系统与 Three.js 的 MeshStandardMaterial 有高度的理论对应关系,这使得材质迁移成为可能,而非只能靠"近似"。
Three.js 的优势
Three.js 是 WebGL 的高级封装,让开发者无需手写 GLSL Shader 就能实现实时 3D 渲染。它支持 glTF 2.0 标准、骨骼动画、变形动画、PBR 材质、HDR 环境贴图,在浏览器端提供接近原生的视觉质量。
两者的天然契合点
| 特性 | Blender | Three.js |
|---|---|---|
| 材质模型 | Principled BSDF | MeshStandardMaterial |
| 动画系统 | Action / NLA | AnimationMixer |
| 导出格式 | glTF 2.0 原生支持 | GLTFLoader 原生支持 |
| 坐标系 | Y-up(可配置) | Y-up |
| PBR 贴图 | BaseColor / Roughness / Metallic / Normal | map / roughnessMap / metalnessMap / normalMap |
glTF 2.0 是连接两者的"通用语言",它由 Khronos Group 制定,Blender 对其有完整的原生导出支持,Three.js 也将其作为首推格式。
二、Blender 建模的 Web 友好实践
在 Blender 中建模时,如果目标是 Web 端实时渲染,需要从一开始就考虑"面向 Web 的建模规范",而不是按离线渲染的标准来做。
2.1 多边形控制:少即是多
离线渲染可以承受千万面模型,但 Web 端 GPU 对三角面数极为敏感。经验值如下:
- 单个主体模型:5,000 ~ 50,000 三角面(取决于镜头距离)
- 背景/远景物体:500 ~ 2,000 三角面
- 整个场景:尽量控制在 300,000 三角面以内(移动端减半)
实用技巧:
在 Blender 中使用 Decimate 修改器可以智能减面,
Ratio 参数从 1.0 逐渐降低,观察模型形变程度,
通常 0.5 ~ 0.7 可在不明显损失细节的前提下大幅减面。
高模细节应通过 法线贴图(Normal Map) 烘焙到低模上,而不是保留在几何体中。这是 Web 3D 最核心的优化手段之一。
2.2 UV 展开的关键性
Three.js 中所有贴图都依赖 UV 坐标。Blender 中的 UV 展开质量直接决定贴图利用率和最终画质。
UV 展开建议:
- 使用 Smart UV Project 快速处理机械类硬表面模型
- 有机体(角色、生物)使用 Mark Seam + Unwrap 手动控制接缝位置
- UV 岛之间保留 至少 4 像素的边距(Margin) ,防止贴图渗色
- 避免 UV 重叠(除非是对称模型且确定共用贴图)
2.3 坐标系与朝向
Blender 默认坐标系:Z 轴朝上,Y 轴朝前。 Three.js 坐标系:Y 轴朝上,Z 轴朝向观察者。
在 glTF 导出时,Blender 会自动进行坐标系转换,因此通常不需要手动旋转。但如果你在 Blender 中对模型进行了非标准旋转,导出后可能在 Three.js 中出现方向错误。
最佳实践: 在 Blender 中完成所有变换后,按 Ctrl+A → All Transforms 应用变换,确保对象的 Location/Rotation/Scale 归零。
三、PBR 材质:从 Blender 到 Three.js 的精确映射
这是整个工作流中最需要深入理解的环节。
3.1 Principled BSDF 与 MeshStandardMaterial 的对应关系
Blender 的 Principled BSDF 节点是工业级 PBR 着色器,其核心参数与 Three.js 的 MeshStandardMaterial 有如下对应:
| Principled BSDF 参数 | Three.js 对应属性 | 说明 |
|---|---|---|
| Base Color |
color / map
|
基础颜色/漫反射贴图 |
| Metallic |
metalness / metalnessMap
|
金属度(0=非金属,1=纯金属) |
| Roughness |
roughness / roughnessMap
|
粗糙度(0=镜面,1=完全漫反射) |
| Normal Map | normalMap |
法线贴图(切线空间) |
| Emission |
emissive / emissiveMap
|
自发光 |
| Alpha |
opacity / alphaMap
|
透明度 |
| Ambient Occlusion | aoMap |
环境遮蔽(需要第二套 UV) |
注意: glTF 2.0 格式将 Roughness 存储在贴图的 G 通道,Metallic 存储在 B 通道,合并为一张 metallicRoughnessMap。Three.js 的 GLTFLoader 会自动处理这个细节,开发者无需干预。
3.2 在 Blender 中烘焙 PBR 贴图
当模型使用了复杂的程序化材质(Noise、Voronoi、Wave 节点等)时,需要将其"烘焙"成位图才能在 Web 端使用。
烘焙流程:
- 选中模型,进入 Cycles 渲染引擎(烘焙必须使用 Cycles)
- 新建一张空白图像节点(Image Texture),不连接任何节点,只需选中它
- 在 Render Properties → Bake 中选择烘焙类型:
-
-
Diffuse(取消 Direct/Indirect,仅勾选 Color)→ Base Color 贴图 -
Roughness→ Roughness 贴图 -
Normal(Space: Tangent)→ 法线贴图 -
Combined→ 全局光照效果烘焙(含 AO、阴影,适合静态场景)
-
- 点击 Bake,等待计算完成
- 导出图像为 PNG(法线图)或 JPEG(颜色图,注意法线图不能用 JPEG 有损压缩!)
这里我推荐一个B站的烘焙教材,很不错的。
链接如下:
(www.bilibili.com/video/BV185…)
3.3 法线贴图的空间问题
Blender 默认导出**切线空间(Tangent Space)**法线贴图,Three.js 也默认使用切线空间法线,两者一致,无需额外处理。
但如果你在 Blender 中烘焙了**对象空间(Object Space)**法线贴图,则需要在 Three.js 中将 normalMapType 设置为 THREE.ObjectSpaceNormalMap:
material.normalMapType = THREE.ObjectSpaceNormalMap;
四、glTF 导出配置详解
glTF 是连接 Blender 和 Three.js 的核心桥梁,正确的导出配置至关重要。
4.1 .gltf vs .glb
| 格式 | 说明 | 适用场景 |
|---|---|---|
.gltf |
JSON 文本 + 外部 .bin + 外部贴图 |
调试、需要单独管理资产 |
.glb |
全部打包为单一二进制文件 | 生产环境,减少 HTTP 请求 |
推荐:生产环境使用 .glb,加载更快,管理更简单。
4.2 Blender 导出设置
File → Export → glTF 2.0,关键设置:
Format: glTF Binary (.glb)
✅ Include:
- Selected Objects(只导出选中对象,避免导出无关物体)
- Custom Properties(可传递自定义属性到 Three.js)
✅ Transform:
- Y Up(确保坐标系正确)
✅ Geometry:
- Apply Modifiers(应用所有修改器,但注意会破坏骨骼绑定)
- UVs / Normals / Tangents / Vertex Colors
- Loose Edges / Points(通常取消勾选)
✅ Animation:
- Animations(导出动画数据)
- Skinning(导出骨骼绑定)
- Shape Keys(导出变形目标/Morph Targets)
- NLA Strips(从非线性动画编辑器导出所有 Action)
✅ Draco Mesh Compression:
开启后可大幅压缩几何体数据,但 Three.js 需要额外加载 DRACOLoader
4.3 在 Three.js 中加载 glTF
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
// 配置 Draco 解码器(如果导出时开启了 Draco 压缩)
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/'); // 需要将 draco 解码器文件放在此路径
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.load(
'/models/scene.glb',
(gltf) => {
const model = gltf.scene;
// 遍历所有网格,开启阴影
model.traverse((node) => {
if (node.isMesh) {
node.castShadow = true;
node.receiveShadow = true;
// 如果使用 HDR 环境贴图,开启环境光反射
node.material.envMapIntensity = 1.0;
}
});
scene.add(model);
},
(progress) => {
console.log(`加载进度: ${(progress.loaded / progress.total * 100).toFixed(1)}%`);
},
(error) => {
console.error('加载失败:', error);
}
);
五、动画系统深度对接
Blender 的动画系统与 Three.js 的 AnimationMixer 是整个工作流中最复杂的对接点。
5.1 Blender 动画类型与 Three.js 对应
骨骼动画(Armature Animation)
最常见的角色动画类型。Blender 中为骨骼创建 Action,绑定到 Mesh。导出后 Three.js 通过 SkinnedMesh 和 AnimationClip 驱动。
// 加载后获取所有动画
const animations = gltf.animations; // AnimationClip[]
const mixer = new THREE.AnimationMixer(gltf.scene);
// 播放特定动画(如 "Walk" Action)
const walkClip = THREE.AnimationClip.findByName(animations, 'Walk');
const walkAction = mixer.clipAction(walkClip);
walkAction.play();
// 在渲染循环中更新
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta); // 关键:每帧更新动画混合器
renderer.render(scene, camera);
}
animate();
变形目标动画(Shape Keys / Morph Targets)
适用于面部表情、布料变形等。Blender 中的 Shape Key 导出为 glTF 的 Morph Target。
// 访问变形目标
const mesh = gltf.scene.getObjectByName('Face');
console.log(mesh.morphTargetDictionary); // { "Smile": 0, "Blink": 1, ... }
// 直接设置变形权重(0~1)
mesh.morphTargetInfluences[0] = 0.8; // 80% 的 Smile 表情
// 或通过 AnimationMixer 驱动
const smileClip = THREE.AnimationClip.findByName(animations, 'Smile');
mixer.clipAction(smileClip).play();
5.2 动画过渡:crossFadeTo
游戏和交互应用中,动画之间的平滑过渡至关重要:
let currentAction = idleAction;
function transitionTo(newAction, duration = 0.3) {
if (currentAction === newAction) return;
newAction.reset();
newAction.play();
currentAction.crossFadeTo(newAction, duration, true);
currentAction = newAction;
}
// 按键触发动画切换
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') transitionTo(runAction);
});
document.addEventListener('keyup', (e) => {
if (e.code === 'Space') transitionTo(idleAction);
});
5.3 NLA 编辑器与多 Action 管理
在 Blender 中,推荐使用 NLA(Non-Linear Animation)编辑器将不同 Action(Idle、Walk、Run、Attack)管理在同一骨骼上。导出时勾选 NLA Strips,Three.js 端会收到所有 Action 作为独立的 AnimationClip。
Blender 操作:
- 在 Dope Sheet 中切换到 NLA Editor
- 将 Action 下压为 NLA Strip
- 每个 Strip 对应一个独立动画
- 确保每个 Action 有清晰的命名(会直接成为 Three.js 中
clip.name)
六、灯光与环境:让 Web 端复现 Blender 的视觉效果
6.1 HDR 环境贴图
Blender 中使用 HDR 环境光照(World → Environment Texture),Three.js 中同样支持,且是让模型在 Web 端看起来"专业"的关键。
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
new RGBELoader().load('/hdr/studio_small.hdr', (texture) => {
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
scene.environment = envMap; // 影响所有 MeshStandardMaterial 的反射
scene.background = envMap; // 可选:将 HDR 作为背景
texture.dispose();
pmremGenerator.dispose();
});
推荐资源:Poly Haven(polyhaven.com/hdris) 提供大量免费高质量 HDR 文件。
6.2 灯光类型对应
| Blender 灯光 | Three.js 等价 |
|---|---|
| Sun | DirectionalLight |
| Point | PointLight |
| Spot | SpotLight |
| Area | RectAreaLight |
| World (HDR) | scene.environment |
注意: glTF 导出支持 Point、Spot、Directional 灯光(需开启 KHR_lights_punctual 扩展,Blender 默认勾选),Area Light 不支持直接导出,需在 Three.js 端手动添加。
6.3 阴影质量配置
// 渲染器阴影配置
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 柔和阴影
// 方向光阴影配置
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048; // 阴影贴图分辨率
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 50;
dirLight.shadow.camera.left = -10;
dirLight.shadow.camera.right = 10;
dirLight.shadow.camera.top = 10;
dirLight.shadow.camera.bottom = -10;
dirLight.shadow.bias = -0.001; // 防止阴影痤疮(Shadow Acne)
七、性能优化:从 Blender 到浏览器的极致压缩
7.1 纹理压缩:KTX2 + Basis Universal
传统 JPEG/PNG 贴图在 GPU 中需要解码为原始像素,占用大量显存。KTX2/Basis Universal 是可以直接在 GPU 上保持压缩状态的格式,显存占用可降低 4~8 倍。
工具链:
# 安装 KTX-Software
# 将 PNG 转换为 KTX2(UASTC 模式,高质量)
toktx --uastc --uastc_rdo_l 4 output.ktx2 input.png
# Three.js 中加载 KTX2
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
const ktx2Loader = new KTX2Loader()
.setTranscoderPath('/basis/')
.detectSupport(renderer);
loader.setKTX2Loader(ktx2Loader);
7.2 实例化渲染:InstancedMesh
场景中有大量相同模型(树木、石头、草地)时,使用 InstancedMesh 可以将数千次 Draw Call 合并为一次:
// 在 Blender 中建好单个树木模型,导出后:
const treeGeometry = treeModel.geometry;
const treeMaterial = treeModel.material;
const COUNT = 1000;
const instancedMesh = new THREE.InstancedMesh(treeGeometry, treeMaterial, COUNT);
const dummy = new THREE.Object3D();
for (let i = 0; i < COUNT; i++) {
dummy.position.set(
(Math.random() - 0.5) * 200,
0,
(Math.random() - 0.5) * 200
);
dummy.rotation.y = Math.random() * Math.PI * 2;
dummy.scale.setScalar(0.8 + Math.random() * 0.4);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;
scene.add(instancedMesh);
7.3 LOD(多细节层次)
对于远近距离视觉差异大的模型,在 Blender 中准备 3 个精度版本(高/中/低),Three.js 中根据距离自动切换:
const lod = new THREE.LOD();
// 高精度:0~10 米
lod.addLevel(highDetailMesh, 0);
// 中精度:10~50 米
lod.addLevel(medDetailMesh, 10);
// 低精度:50 米以上
lod.addLevel(lowDetailMesh, 50);
scene.add(lod);
// LOD 会在每帧自动根据相机距离切换
八、进阶:自定义 Shader 扩展 glTF 材质
Three.js 的 onBeforeCompile 钩子允许在不放弃 PBR 管线的前提下,向材质注入自定义 GLSL 代码。这是高阶扩展的核心技巧。
// 在 Blender 中建好基础 PBR 材质,导出后:
model.traverse((node) => {
if (node.isMesh && node.material.name === 'WindyGrass') {
node.material.onBeforeCompile = (shader) => {
// 注入 uniform
shader.uniforms.uTime = { value: 0 };
shader.uniforms.uWindStrength = { value: 0.3 };
// 在顶点着色器头部注入声明
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
uniform float uTime;
uniform float uWindStrength;
`
);
// 在顶点变换前注入风力位移
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
// 根据 Y 轴高度决定摆动幅度(根部固定)
float windFactor = position.y * uWindStrength;
transformed.x += sin(uTime * 2.0 + position.z * 0.5) * windFactor;
transformed.z += cos(uTime * 1.5 + position.x * 0.5) * windFactor * 0.5;
`
);
// 保存 shader 引用以便每帧更新
node.material.userData.shader = shader;
};
}
});
// 在渲染循环中更新 uniform
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
scene.traverse((node) => {
if (node.isMesh && node.material.userData.shader) {
node.material.userData.shader.uniforms.uTime.value = t;
}
});
renderer.render(scene, camera);
}
九、完整工作流总结
Blender 创作阶段
│
├── 建模(控制面数,UV 展开)
├── 材质(Principled BSDF,程序化贴图)
├── 烘焙(BaseColor / Roughness / Normal / AO)
├── 动画(骨骼 / Shape Key / 物理模拟烘焙)
└── 导出(glTF 2.0 / .glb / Draco 压缩)
│
▼
glb / gltf 文件
│
▼
Three.js 运行时阶段
│
├── 加载(GLTFLoader + DRACOLoader + KTX2Loader)
├── 材质增强(envMap / onBeforeCompile / 自定义 Shader)
├── 动画驱动(AnimationMixer / crossFadeTo)
├── 灯光配置(HDR 环境 + 实时灯光)
├── 性能优化(InstancedMesh / LOD / 纹理压缩)
└── 交互与后处理(Controls / EffectComposer)
十、推荐工具与资源
| 工具/资源 | 用途 |
|---|---|
| gltf.report(gltf.report/) | 分析 glTF 文件结构与优化建议 |
| glTF Viewer(gltf-viewer.donmccurdy.com/) | 快速预览 glTF/glb 文件 |
| KTX-Software(github.com/KhronosGrou…) | 纹理压缩工具 |
| Poly Haven(polyhaven.com/) | 免费 HDR / 贴图 / 3D 模型 |
| Three.js Editor(threejs.org/editor/) | 在线 Three.js 场景编辑器 |
| glTF Transform(gltf-transform.dev/) | 命令行 glTF 优化工具 |
| Blender glTF 文档(developer.blender.org/docs) | 官方导出插件文档 |
推荐大佬
- 郭隆邦:www.webgl3d.cn/
- Bruno Simon(布鲁诺·西蒙):github.com/brunosimon
最后
Three.js 与 Blender 的结合,不是简单的"导出然后加载",而是一套需要深度理解两个系统各自机制,并在接缝处做精细处理的工程实践。从 PBR 材质的精确映射,到动画系统的无缝对接,再到面向 Web 的性能优化,每个环节都有大量细节值得深挖。
掌握这套工作流,你将拥有从零到一打造高质量 Web 3D 体验的完整能力——既能胜任艺术侧的内容生产,也能驾驭工程侧的实时渲染。这正是当下 Web 3D 领域最稀缺的复合型能力。
现在AI还无法胜任3D可视化相关的工作,学起来为自己增加点筹码。需要相关blender、可视化学习资料的可以关注我私信获取
本文覆盖 Blender 4.x + Three.js r160+ 版本,部分 API 在旧版本中可能有差异