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 展开建议:

  1. 使用 Smart UV Project 快速处理机械类硬表面模型
  2. 有机体(角色、生物)使用 Mark Seam + Unwrap 手动控制接缝位置
  3. UV 岛之间保留 至少 4 像素的边距(Margin) ,防止贴图渗色
  4. 避免 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 端使用。

烘焙流程:

  1. 选中模型,进入 Cycles 渲染引擎(烘焙必须使用 Cycles)
  2. 新建一张空白图像节点(Image Texture),不连接任何节点,只需选中它
  3. 在 Render Properties → Bake 中选择烘焙类型:
    • Diffuse(取消 Direct/Indirect,仅勾选 Color)→ Base Color 贴图
    • Roughness → Roughness 贴图
    • Normal(Space: Tangent)→ 法线贴图
    • Combined → 全局光照效果烘焙(含 AO、阴影,适合静态场景)
  4. 点击 Bake,等待计算完成
  5. 导出图像为 PNG (法线图)或 JPEG(颜色图,注意法线图不能用 JPEG 有损压缩!)

这里我推荐一个B站的烘焙教材,很不错的。

链接如下:

(www.bilibili.com/video/BV185...)

3.3 法线贴图的空间问题

Blender 默认导出**切线空间(Tangent Space)**法线贴图,Three.js 也默认使用切线空间法线,两者一致,无需额外处理。

但如果你在 Blender 中烘焙了**对象空间(Object Space)**法线贴图,则需要在 Three.js 中将 normalMapType 设置为 THREE.ObjectSpaceNormalMap

ini 复制代码
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,关键设置:

yaml 复制代码
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

javascript 复制代码
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 通过 SkinnedMeshAnimationClip 驱动。

ini 复制代码
// 加载后获取所有动画
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。

ini 复制代码
// 访问变形目标
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

游戏和交互应用中,动画之间的平滑过渡至关重要:

ini 复制代码
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 操作:

  1. 在 Dope Sheet 中切换到 NLA Editor
  2. 将 Action 下压为 NLA Strip
  3. 每个 Strip 对应一个独立动画
  4. 确保每个 Action 有清晰的命名(会直接成为 Three.js 中 clip.name

六、灯光与环境:让 Web 端复现 Blender 的视觉效果

6.1 HDR 环境贴图

Blender 中使用 HDR 环境光照(World → Environment Texture),Three.js 中同样支持,且是让模型在 Web 端看起来"专业"的关键。

ini 复制代码
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 阴影质量配置

ini 复制代码
// 渲染器阴影配置
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 倍。

工具链:

csharp 复制代码
# 安装 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 合并为一次:

ini 复制代码
// 在 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 中根据距离自动切换:

scss 复制代码
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 代码。这是高阶扩展的核心技巧。

ini 复制代码
// 在 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);
}

九、完整工作流总结

css 复制代码
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) 官方导出插件文档

推荐大佬

最后

Three.js 与 Blender 的结合,不是简单的"导出然后加载",而是一套需要深度理解两个系统各自机制,并在接缝处做精细处理的工程实践。从 PBR 材质的精确映射,到动画系统的无缝对接,再到面向 Web 的性能优化,每个环节都有大量细节值得深挖。

掌握这套工作流,你将拥有从零到一打造高质量 Web 3D 体验的完整能力------既能胜任艺术侧的内容生产,也能驾驭工程侧的实时渲染。这正是当下 Web 3D 领域最稀缺的复合型能力。

现在AI还无法胜任3D可视化相关的工作,学起来为自己增加点筹码。需要相关blender、可视化学习资料的可以关注我私信获取


本文覆盖 Blender 4.x + Three.js r160+ 版本,部分 API 在旧版本中可能有差异

相关推荐
reembarkation3 小时前
vue3中使用howler播放音频列表
前端·vue.js·音视频
手握风云-3 小时前
基于 Java 的网页聊天室(三)
服务器·前端·数据库
weixin199701080163 小时前
《识货商品详情页前端性能优化实战》
前端·性能优化
Forever7_3 小时前
重磅!Vue3 手势工具正式发布!免费使用!
前端·前端框架·前端工程化
用户806138166594 小时前
发布为一个 npm 包
前端·javascript
树上有只程序猿4 小时前
低代码何时能出个“秦始皇”一统天下?我是真学不动啦!
前端·后端·低代码
TT_哲哲4 小时前
小程序双模式(文件 / 照片)上传组件封装与解析
前端·javascript
菜果果儿4 小时前
Vue 3 + TypeScript 常用代码示例总结
前端
前端付豪4 小时前
实现多角色模式切换
前端·架构