【Blender】Blender 通过 Python 实现模型大小压缩
引言
随着三维可视化项目的复杂化,模型体积快速膨胀常常导致加载慢、渲染卡顿、传输困难等问题。特别是在 WebGL、移动端、在线医学三维重建等场景中,对模型体积的控制成为核心优化点。本文将系统讲解如何借助 Blender 提供的 Python API,自动化实现模型的“瘦身”,从贴图压缩、点数简化到最终格式压缩,形成一套高效的模型优化流程。
一、模型压缩目标与思路
模型优化的核心目标是:在尽量不影响视觉质量的前提下,显著降低模型体积。通常包括以下几个关键方向:
优化方向 | 目标 | 工具/方式 |
---|---|---|
纹理压缩 | 减少贴图分辨率与大小 | Pillow、Blender 内部压缩 |
点数优化 | 简化网格面片数量 | Blender 中的 Decimate Modifier |
格式压缩 | 使用 glTF 的 Draco 编码 | gltf-pipeline、Blender glTF 导出插件 |
二、贴图压缩逻辑
纹理贴图往往是模型体积的“大头”,一个 4K 分辨率的纹理可以高达数十 MB。我们可以:
- 遍历所有材质的贴图;
- 读取贴图路径并压缩为新文件(比如缩放到 25% 分辨率);
- 将材质替换为压缩后贴图;
- 重载贴图进 Blender 场景中。
import bpy
import os
from PIL import Image
# 压缩参数
resize_ratio = 0.5 # 贴图缩放比例(0.5 = 缩小为 50%)
jpeg_quality = 10 # JPEG 质量(1~100,10 表示极强压缩)
# 替换材质中使用的贴图
def replace_image_in_materials(original_image_name: str, new_image: bpy.types.Image):
for mat in bpy.data.materials:
if not mat.use_nodes or not mat.node_tree:
continue
for node in mat.node_tree.nodes:
if node.type == 'TEX_IMAGE' and node.image and node.image.name == original_image_name:
node.image = new_image
print(f"✅ 替换材质 {mat.name} 中的贴图 {original_image_name} 为 {new_image.name}")
# 压缩贴图函数
def compress_image(image: bpy.types.Image):
if not image.filepath_raw:
print(f"⚠️ 跳过无效贴图(未保存或内嵌): {image.name}")
return None
src_path = bpy.path.abspath(image.filepath_raw)
if not os.path.exists(src_path):
print(f"⚠️ 贴图文件不存在: {src_path}")
return None
try:
img = Image.open(src_path)
new_size = (int(img.width * resize_ratio), int(img.height * resize_ratio))
img = img.resize(new_size, Image.LANCZOS)
# 生成压缩图路径
dir_name, base_name = os.path.split(src_path)
name_wo_ext = os.path.splitext(base_name)[0]
new_path = os.path.join(dir_name, f"{name_wo_ext}_compressed.jpg")
img.save(new_path, 'JPEG', quality=jpeg_quality)
# 重新导入新图作为 Image 对象
new_image = bpy.data.images.load(new_path)
replace_image_in_materials(image.name, new_image)
print(f"🎯 压缩并替换贴图: {image.name} ➝ {new_image.name}")
return new_image
except Exception as e:
print(f"❌ 压缩失败 {image.name}: {e}")
return None
# 遍历所有贴图并压缩替换
for image in bpy.data.images:
compress_image(image)
print("✅ 所有贴图压缩并替换完成!你现在可以手动导出 GLB。")
三、模型网格简化逻辑
模型面数直接影响文件大小和渲染效率。针对多边形数量过高的模型,可以通过 Blender 的 Decimate Modifier
实现简化:
- 遍历所有
Mesh
对象; - 为每个对象添加
Decimate Modifier
; - 设置
ratio
(如 0.3 表示保留 30% 面数); - 应用修改器将变更写入网格;
- 可选地,记录面数压缩前后的统计信息。
⚠️ 注意避免过度压缩导致“破面”或重要细节丢失,可通过可视化检查逐批处理。
参考代码结合第四章节↓
四、导出压缩格式
贴图和点数优化后,还可以进一步通过格式本身进行压缩:
- 使用 Blender 自带的 glTF 导出器;
- 启用
Draco
压缩选项(Mesh Compression
); - 可选择导出为
.glb
(二进制)格式,便于 Web 加载与传输。
也可使用命令行工具 gltf-pipeline 执行更强的压缩控制,例如:
gltf-pipeline -i input.glb -o output.glb --draco.compressMeshes
结合第三章节的优化点数,均衡配置压缩方案:
import bpy
import os
export_dir = "G:\\new\\models"
if not os.path.exists(export_dir):
os.makedirs(export_dir)
# 获取所有 MESH 对象(你也可以按 Collection 限定)
objects_to_export = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']
for obj in objects_to_export:
# 取消选择,激活目标对象
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
# 可选:添加 Decimate 修饰器进行简化
decimate = obj.modifiers.new(name="DecimateMod", type='DECIMATE')
decimate.ratio = 0.5 # 调整简化强度(0.5 表示保留一半面数)
bpy.ops.object.modifier_apply(modifier=decimate.name)
# 导出为压缩后的 GLB 文件
export_path = os.path.join(export_dir, f"{obj.name}.glb")
bpy.ops.export_scene.gltf(
filepath=export_path,
use_selection=True,
export_format='GLB',
export_apply=True,
export_draco_mesh_compression_enable=True,
export_draco_mesh_compression_level=6, # 0~10 越大压缩越强
)
print("批量压缩导出完成!")
五、流程建议与自动化集成
整个优化流程可以整理为以下步骤,可以根据需要酌情选取、组合方案,
通过 Python 脚本一次性处理整批模型文件:
- 加载模型;
- 遍历贴图并压缩;
- 网格简化处理;
- 更新材质;
- 导出为 glb(启用 Draco);
- 清理场景,准备下一个模型。
✅ 可结合文件夹批处理逻辑,实现模型文件夹一键压缩优化,非常适合用于模型发布前的自动化构建流程。
六、优化前后效果示例(参考)
优化阶段 | 文件大小 |
---|---|
原始 GLB | 472 MB |
压缩贴图后 | 40 MB |
简化网格后 | 12 MB |
使用 Draco 后 | 8 MB |
如需源码脚本或定制化压缩流程,欢迎留言交流!