rain_sxr头像
批量材质生成流水线:从文本描述到 Unity 材质球的一键导入封面图

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

批量材质生成流水线:从文本描述到 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

生产环境落地收益

通过这一批处理流水线:

  1. 零人工失误:避免了人工导入时经常把 NormalMap 误当 Default 导致法线解析错误、或者忘记取消 Mask 贴图的 sRGB 勾选导致粗糙度严重非线性的常见低级失误。
  2. 打通批量 PCG 闭环:关卡策划在生成大世界地形或地牢房间时,程序可以直接调用 Python 服务生成专属的地表与砖石材质,瞬间完成整个关卡所有几何体的纹理贴合。

让技术美术从繁琐的机械拖放中彻底解放出来,将时间专注于光影艺术风格的整体把控。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/rain_sxr/article/details/164426197

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--