批量材质生成流水线:从文本描述到 Unity 材质球的一键导入

在 3D 项目的原型开发与关卡白模打样阶段,美术和地景设计往往需要海量的 PBR 材质(如“风化的红砖墙”、“长满苔藓的湿滑花岗岩”、“太空飞船舱体生锈蒙皮”)。如果每个材质都需要手动在 Substance Designer 制作或在资产网站逐个下载,再手动拖拽 5 张通道贴图到 Unity Inspector 中配置材质球,一天下来能处理的材质屈指可数。
搭建一套端到端批量材质生成流水线(Automated Material Pipeline),可以实现:在后台输入一段文本描述,自动化多通道扩散模型生成贴图,完成物理通道打包(Channel Packing),并通过 Unity Editor 自定义脚本实现一键自动导入、材质球实例化与参数绑定。
流水线整体架构
[文本描述 Prompt] ("weathered red brick wall with moss")
│
▼
[Diffusion PBR 扩散生成器 (Python Backend)]
├── BaseColor (sRGB)
├── Normal (Tangent Space 16-bit)
├── Roughness & Metallic (Linear)
└── Height & AO (Linear)
│
▼
[通道自动打包 (Mask Map Channel Packer)]
├── R: Metallic
├── G: Ambient Occlusion
├── B: Detail Mask / Height
└── A: Smoothness (1.0 - Roughness)
│
▼ (导出 JSON 描述文件 + 纹理图集)
[Unity AssetPostprocessor & Editor Pipeline (C#)]
├── 自动识别目录与纹理导入设置 (NormalMap/Linear/ASTC)
├── 动态创建 URP/Lit Material 资源
└── 自动绑定贴图槽位并保存至 Assets/Materials/
Python 端:通道多合一打包与导出
在 PBR 标准渲染管线(如 Unity URP / HDRP)中,为了节约纹理采样器槽位与显存带宽,通常要求将 Metallic、AO、Smoothness 打包进一张 Mask Map(复合贴图) 中:
import os
import json
import numpy as np
from PIL import Image
def pack_pbr_textures(output_dir: str, mat_name: str,
base_color_img: Image.Image,
normal_img: Image.Image,
roughness_img: Image.Image,
metallic_img: Image.Image,
ao_img: Image.Image) -> dict:
"""
将离散 PBR 贴图打包并生成 Unity 导入清单
"""
os.makedirs(output_dir, exist_ok=True)
# 1. 统一缩放至 2048x2048
target_size = (2048, 2048)
bc = base_color_img.convert("RGB").resize(target_size, Image.Resampling.LANCZOS)
norm = normal_img.convert("RGB").resize(target_size, Image.Resampling.LANCZOS)
rough = roughness_img.convert("L").resize(target_size, Image.Resampling.LANCZOS)
metal = metallic_img.convert("L").resize(target_size, Image.Resampling.LANCZOS)
ao = ao_img.convert("L").resize(target_size, Image.Resampling.LANCZOS)
# 2. 计算 Smoothness = 255 - Roughness
rough_np = np.array(rough)
smooth_np = 255 - rough_np
# 3. 合成 Unity HDRP/URP 标准 Mask 贴图: RGBA -> [Metallic, AO, None/0, Smoothness]
mask_np = np.stack([
np.array(metal),
np.array(ao),
np.zeros_like(rough_np), # B 通道可留作细节掩码
smooth_np
], axis=-1)
mask_img = Image.fromarray(mask_np.astype(np.uint8), mode="RGBA")
# 4. 保存为 PNG
base_color_path = os.path.join(output_dir, f"{mat_name}_BaseColor.png")
normal_path = os.path.join(output_dir, f"{mat_name}_Normal.png")
mask_path = os.path.join(output_dir, f"{mat_name}_Mask.png")
bc.save(base_color_path, format="PNG")
norm.save(normal_path, format="PNG")
mask_img.save(mask_path, format="PNG")
# 5. 生成元数据元清单 (Metadata JSON)
manifest = {
"materialName": mat_name,
"shader": "Universal Render Pipeline/Lit",
"textures": {
"_BaseMap": f"{mat_name}_BaseColor.png",
"_BumpMap": f"{mat_name}_Normal.png",
"_MetallicGlossMap": f"{mat_name}_Mask.png"
},
"properties": {
"_Smoothness": 1.0,
"_Metallic": 1.0,
"_BumpScale": 1.0
}
}
json_path = os.path.join(output_dir, f"{mat_name}_metadata.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2)
return manifest
Unity Editor 端:C# 自动导入与材质生成器
在 Unity 引擎端,通过监听资产目录变动,使用 AssetDatabase 自动化完成纹理属性修正与材质球组装:
// C# Unity Editor 批处理自动化脚本
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.IO;
public class MaterialPipelineImporter : AssetPostprocessor
{
// 监听元数据 JSON 导入事件
private static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths)
{
foreach (string assetPath in importedAssets)
{
if (assetPath.EndsWith("_metadata.json"))
{
CreateMaterialFromMetadata(assetPath);
}
}
}
private static void CreateMaterialFromMetadata(string jsonPath)
{
string jsonText = File.ReadAllText(jsonPath);
MaterialMetadata meta = JsonUtility.FromJson<MaterialMetadata>(jsonText);
string directory = Path.GetDirectoryName(jsonPath);
string matPath = Path.Combine(directory, $"{meta.materialName}.mat");
// 1. 修正贴图导入设置 (NormalMap 必须标记为法线,Mask 必须保持 Linear 不勾选 sRGB)
SetupTextureImportSettings(Path.Combine(directory, meta.textures._BumpMap), TextureImporterType.NormalMap, false);
SetupTextureImportSettings(Path.Combine(directory, meta.textures._MetallicGlossMap), TextureImporterType.Default, false); // Linear
SetupTextureImportSettings(Path.Combine(directory, meta.textures._BaseMap), TextureImporterType.Default, true); // sRGB
AssetDatabase.Refresh();
// 2. 加载着色器与贴图
Shader urpLitShader = Shader.Find("Universal Render Pipeline/Lit");
Material mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
if (mat == null)
{
mat = new Material(urpLitShader);
AssetDatabase.CreateAsset(mat, matPath);
}
// 3. 关联贴图槽位
mat.SetTexture("_BaseMap", AssetDatabase.LoadAssetAtPath<Texture2D>(Path.Combine(directory, meta.textures._BaseMap)));
mat.SetTexture("_BumpMap", AssetDatabase.LoadAssetAtPath<Texture2D>(Path.Combine(directory, meta.textures._BumpMap)));
mat.SetTexture("_MetallicGlossMap", AssetDatabase.LoadAssetAtPath<Texture2D>(Path.Combine(directory, meta.textures._MetallicGlossMap)));
// 4. 激活材质关键字
mat.EnableKeyword("_NORMALMAP");
mat.EnableKeyword("_METALLICSPECGLOSSMAP");
EditorUtility.SetDirty(mat);
AssetDatabase.SaveAssets();
Debug.Log($"[MaterialPipeline] 成功自动生成材质球: {matPath}");
}
private static void SetupTextureImportSettings(string path, TextureImporterType type, bool sRGB)
{
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null)
{
importer.textureType = type;
importer.sRGBTexture = sRGB;
importer.SaveAndReimport();
}
}
[System.Serializable]
public class MaterialMetadata
{
public string materialName;
public string shader;
public TextureSlots textures;
}
[System.Serializable]
public class TextureSlots
{
public string _BaseMap;
public string _BumpMap;
public string _MetallicGlossMap;
}
}
#endif
生产环境落地收益
通过这一批处理流水线:
- 零人工失误:避免了人工导入时经常把 NormalMap 误当 Default 导致法线解析错误、或者忘记取消 Mask 贴图的 sRGB 勾选导致粗糙度严重非线性的常见低级失误。
- 打通批量 PCG 闭环:关卡策划在生成大世界地形或地牢房间时,程序可以直接调用 Python 服务生成专属的地表与砖石材质,瞬间完成整个关卡所有几何体的纹理贴合。
让技术美术从繁琐的机械拖放中彻底解放出来,将时间专注于光影艺术风格的整体把控。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/rain_sxr/article/details/164426197




