给 AI 一台贴图工厂:TexGen 程序化贴图引擎与 MCP 架构实践
一次真实任务:让 AI 把一个只有纯色材质的 Unity 场景铺满纹理。
这件事暴露了两个问题——"AI 到底该怎么获得能力",和"贴图到底该怎么被生成"。
引子:一个「简单」的请求
我给一个 Unity 场景下了一条指令:完善当前场景,带上纹理。
场景里有 110 个对象,16 个材质,全部是标准着色器加一个纯色。看起来是体力活:找图、贴图、调参数。
但如果交给一个通用的 AI 编程助手,它会在第三步就卡住:
- ·它没有联网找图的能力(即使有,商用授权也是雷);
- ·它不能用 Midjourney / SD 生成——那些图不可平铺、不能派生法线、风格不统一、无法微调,要凑齐一整套同风格的 PBR 材质,成功率极低;
- ·它能在 Unity 里写 C# 脚本手搓贴图,但那是每个项目重造一遍轮子。
结果是:它要么给你一堆版权不明的网图,要么写一个几百行的 Texture2D 生成脚本,然后整个场景风格失控。
最后我选择了第三条路——在工程里装一个贴图生成引擎,让 AI 通过 MCP 去调用它。
这篇文章讲的就是这件事的两个层面:TexGen 这个引擎怎么把「贴图」变成可编程的东西,以及 MCP 这套架构怎么让 AI 在需要的那一刻才学会用它。
一、先看结果,再讲道理
那次任务的实际产出:
| 项 | 数量 |
|---|---|
| 程序化贴图 | 26 张 PNG(草地、岩石、铸铁、羽毛、旗布、云、太阳、靶环、六面天空 + 3 张法线) |
| 材质绑定 | 场景原有 16 个材质全部写入 _MainTex + 金属度 / 光滑度 |
| 平铺设置 | 地面 10×10、岩石 3×3、沙路 4×4、金属木纹 2×2 |
| 场景完善 | 六面天空盒、渐变背景板、靶板同心环、主光色温、炮口点光 |
整个过程的耗时,大部分不是"生成",而是"看截图、判断不对、调参数、再看"。
后来我又用同一套能力做了第二轮扩展:把模板库做到 33 个、加了 PBR 六通道派生、加了无缝平铺和域扭曲。砖墙那张贴图现在是这样生成的——
json
{
"width": 512, "height": 512,
"tiling": true,
"layers": [
{
"shape": "rect", "center": [256, 256], "size": [512, 512],
"fill": "pattern", "pattern": "brick",
"cell": [102, 39], "gap": 5, "gapColor": "#b6ad9c",
"color": "#9d452c", "stagger": 0.5, "variation": 0.2, "seed": 23
},
{
"shape": "rect", "center": [256, 256], "size": [512, 512],
"fill": "noise", "noise": "fbm", "scale": 14, "octaves": 4,
"color": "#00000000", "edgeColor": "#3a1d12",
"opacity": 0.3, "blend": "multiply", "seed": 55
}
]
}
两段 JSON,一张 512×512 可无缝平铺的砖墙,配套法线、粗糙度、AO 全部派生。没有一张位图资源,没有一个外部依赖。
二、MCP 真正的价值:不是「能调函数」,是「运行时自描述」
先把 MCP 这层讲清楚,因为它决定了后面所有设计的形状。
MCP(Model Context Protocol)常被理解成"给 AI 装工具"。但"能调函数"这件事,Function Calling 早就做到了。MCP 真正解决的是另一个问题:当工具有 339 个的时候,怎么让 AI 只学它此刻需要的那几个。
2.1 传统做法(把工具塞进提示词)为什么会崩
这个工程有 46 个工具模块、339 个动作。如果把每个工具的完整 JSON Schema 都放进系统提示词:
- ·保守估计 339 × 400 token ≈ 13.5 万 token;
- ·还没说一句话,上下文就烧掉大半;
- ·更要命的是准确率——选项太多时模型选错工具的概率显著上升,参数也会串(那么多叫
path的参数,含义各不相同)。
2.2 渐进式发现:四步走
这个工程的做法是:技能说明书里一个工具名都不写。
SKILL.md 只有 17 KB,全是"契约"——不包含任何能力清单。AI 要做事时,按固定次序走四步:
code
discover → 验证服务在线、确认是哪个 Unity 工程、锁定实例
groups / list / find → 定本次要用的成员(按 tag 或关键字检索)
schema / schemas → 只取这几个工具的精确 inputSchema
call → 按 schema 调用
关键的约束写在技能契约里,而且是硬约束:
工具名、参数名、参数取值,皆须出本次服务之返回,不得凭记忆猜测。
这条约束把"模型幻觉"从"可能出错"变成了"结构上不可能"——因为它根本没有地方去记住 339 个工具。
实际效果:常驻上下文从十几万 token 降到十几 KB,降低两个数量级;而每次任务的真实开销,只跟这次用到的工具数量成正比。
2.3 自描述:把「语法」也做成一次工具调用
这是我认为最值得抄的一个设计。
TexGen 有自己的 DSL(后面会讲)。AI 怎么学会它?不靠提示词教学,靠工具自己回答。
texgen 模块里有三个动作专门做这件事:
| 动作 | 回答什么问题 |
|---|---|
texgen.spec | 完整语法:有哪些图元、填充、混合、参数、取值范围 |
texgen.examples | 一组可直接投喂的示例指令表 |
texgen.templates | 33 个内置模板,按 tag 检索或按 name 取完整 spec |
也就是说:AI 不需要被训练过这个 DSL,它只需要知道"有个工具叫 texgen,它会告诉我怎么写"。
这就是"自描述"(self-describing capability)的价值——能力的使用方法跟着能力一起部署,随版本同步,永不过期。你不用维护一份"给 AI 看的文档",文档就是接口本身。
2.4 三个工程细节:线程、绑定、预算
MCP 服务跑在 Unity 编辑器进程里,而 Unity API 有两条铁律:只能在主线程调用,不能在主线程随便阻塞。
架构上做了三件事:
其一,HTTP 收发在后台线程,执行回主线程。 请求进来先在后台解析,然后 EnqueueTask 压进队列,由 EditorApplication.update 逐帧取出执行。这意味着 AI 的高速调用不会撕裂编辑器的帧循环,也意味着长任务会表现为"编辑器卡住"而不是"崩溃"——这是可以接受的那一种。
其二,工程绑定 + 实例锁。 客户端启动时读 INI 配置,比对 HTTP /discovery 返回的 instance_id,并支持 --expected-project 显式声明"我要操作的是哪个工程"。一旦实例漂移就拒绝执行、不自动重放。
这条规则救过我:我有一次从错误的目录执行了命令,服务直接返回 409,而不是把贴图写到隔壁工程里去。"宁可失败,不要错写"——对文件系统有写权限的 AI 工具,这应该是默认姿势。
其三,预算上限。 max_details 限制单次取定义的条数(默认 32),批量渲染有字节上限,schema 请求有白名单。工具不是无限的——上下文是稀缺资源,接口层要替模型守住这条线。
2.5 声明式注册:一个动作 = 一个工具
工具不是手写 JSON 注册的,而是靠 Attribute 反射:
csharp
[ToolModule("texgen", GroupEn = "Texture Generation", GroupZh = "程序化贴图")]
public class TexGenTool : ToolModuleBase
{
[ToolAction("render", "spec=require|output_path=require")]
private object ActionRender(JsonClass args) { /* ... */ }
}
ToolCatalog 反射扫描全部 [ToolModule] 类,为每个 [ToolAction] 生成精确的 inputSchema。加一个能力 = 加一个方法加一行 Attribute,不需要改注册表、不需要写兼容层、不需要重启服务。
参数声明也是强类型的,带范围、默认值、枚举、示例,且中英双语描述:
csharp
new MethodFloat("warp_amount", L.T(
"Peak displacement of the warp field in pixels. Default 12.",
"扭曲场的最大位移(像素),默认 12。"))
.SetRange(0f, 512f).SetDefault(12f),
这些元数据最后都变成 inputSchema 的一部分,直接喂给模型。给模型看的类型信息越精确,它乱猜的空间就越小。
三、TexGen:把「贴图」变成可序列化的中间表示
现在讲引擎本身。
3.1 为什么是 DSL,不是参数列表
最直觉的做法是给渲染函数开一堆参数:Render(size, color, roughness, ...)。但它很快就撞墙——一张贴图往往是多层叠加的:底色 + 噪波 + 砖缝 + 暗角 + 高光。
所以 TexGen 的核心抽象是图层栈 + JSON 指令表:
code
TgSpec
├── canvas: width / height / background / tiling / supersample
└── layers[]: TgLayer
├── 几何: shape(8 种) + center / size / radius / rotation / cornerRadius
├── 绘制: fill(7 种) + color / edgeColor / 渐变 + pattern + warp
└── 合成: blend(11 种) + opacity / blur
这个选择带来了四个"白送"的好处:
- 1.可序列化 —— 一张贴图就是一个 JSON 文件,可以进 Git、diff、review;
- 2.可被 AI 改写 —— 模型天然擅长改结构化数据,改一个
gap: 5 → 3比改 C# 代码安全得多; - 3.可做成模板 —— 33 个模板就是 33 个 JSON,不需要编译;
- 4.可复现 —— 同一个 spec + 同一个 seed,永远出同一张图。这是文生图做不到的。
3.2 能力盘点
| 维度 | 数量 | 内容 |
|---|---|---|
| 图元 | 8 | circle / ellipse / ring / rect / roundRect / line / polygon / star,全部走 SDF + 抗锯齿 |
| 填充 | 7 | solid / linear / radial / noise / ripple / grid / pattern |
| 图案 | 10 | brick / plank / tile / checker / stripe / hex / scale / weave / dot / cobble |
| 混合 | 11 | normal / add / multiply / screen / overlay / subtract / difference / darken / lighten / mask / erase |
| 噪声 | 5 | value / perlin / fbm / worley / ridged(全部有周期化变体) |
| 输出 | 6 | BaseColor / Height / Normal / Roughness / AO / ORM |
| 模板 | 33 | 地面 7 / 墙面 6 / 金属 3 / 织物 4 / 自然 5 / UI 4 / 图标特效 4 |
引擎本体 7 个源文件、4729 行、纯 C#、零外部依赖——不引任何第三方图像库,PNG 编码直接用 Unity 内置的 EncodeToPNG,光栅化、噪声、图案、法线全部手写。
3.3 一图流:从 spec 到落盘的链路
code
JSON spec
→ TgParser 解析 + snake_case 别名 + 参数夹取
→ TgRasterizer SDF 光栅化,supersample × 2~4 超采样
├─ TgNoise 噪声与周期化变体
├─ 图案生成器 10 种周期图案
└─ WarpPoint 域扭曲
→ PNG 编码 → AssetDatabase 导入 → 按通道设导入参数
TgTool 这层是 MCP 边界,只做三件事:参数校验、路径解析、结果落盘与导入。
这里有个刻意维持的分层:7 个文件里,Model / Parser / Rasterizer / Noise / MeshUv 五个完全不引用 UnityEditor,只有 TgTool 和 PreviewWindow 依赖编辑器 API。也就是说渲染核心是"纯计算 + Texture2D",理论上可以直接搬到 Player 侧,在运行时按需生成贴图——这个能力现在还没接进运行时包,但门是开着的。
四、四个真正拉开差距的能力
前面那些是"能画",下面这四个是"能用在项目里"。
4.1 无缝平铺:不是"看起来接近",是数学上闭合
贴图的第一个硬需求是平铺。地面铺 10×10 的时候,接缝会变成 10 道明显的线。
常见做法是把边缘做淡,视觉糊过去。这个引擎的做法是让噪声本身具有周期:
csharp
static int WrapIndex(int i, int period)
=> period <= 0 ? i : ((i % period) + period) % period;
哈希取格点时按周期取模,边界两侧访问的是同一批格点。所以左右边缘不是"接近",是严格相等。
配套改动还有两处,都不显眼但必须做:
- ·画布映射为整数格数:
u = x / width * nx,这样即使width不能被scale整除也无缝; - ·图案锚点从图层中心改为画布原点:否则画布尺寸不是格高整数倍时,会切出半行砖。
4.2 一个只有踩过才知道的坑:几何对齐 ≠ 颜色连续
砖墙做完,几何看着没问题,但拼成 2×2 之后,接缝处有一道色阶。
原因很反直觉:variation 参数按格子索引做亮度抖动防雷同。跨边界的那块砖,从左边被访问一次(col = 0),从右边又被访问一次(col = cols)。同一个格子,算出了两个颜色。
修法是让格子索引也参与取模:
csharp
private static Color JitterWrapped(Color c, TgLayer layer, int col, int row, int cols, int rows)
{
if (cols > 0) col = TexNoise.WrapIndex(col, cols);
if (rows > 0) row = TexNoise.WrapIndex(row, rows);
return Jitter(c, layer, col, row);
}
还有第二条规律:错缝图案需要偶数周期。brick 的交替错缝行数必须为偶数,hex 的隔列半行偏移要求列数为偶数——否则跨边界时 offset 翻转,接缝直接错位半格。
这里还留下一个方法论教训。我一开始用"首末列像素差 / 内部相邻列像素差"做量化,噪波类结果漂亮(草地 0.77、木纹 0.97),但砖缝、蜂窝这种本来就有硬边的图案被判成 18 倍异常——指标失效了。
最后改用的方法是:把成品拼成 2×2,人眼看中央十字缝。 简单、直接、不会骗人。
4.3 域扭曲:木纹和大理石的来源
有了图案还不够。木纹不是直线条纹,大理石不是斑马纹——它们的灵魂是规则被自然扰动。
域扭曲(domain warp)就是把采样坐标本身偏移掉,几何不动:
code
p' = p + (n1 - 0.5, n2 - 0.5) × 2 × amount
用两个独立 seed 的噪声场分别偏移 x 和 y。于是:
- ·
stripe + warp= 木纹(条纹被弯曲成流动的年轮感) - ·
noise + warp= 大理石(胞状噪波扭成蜿蜒脉络)
这一项是质变。没有它,引擎只能出"程序感的规则图案";有了它,才开始有"材质"的味道。
4.4 一次性派生整套 PBR
游戏里真正难的不是 albedo,是 albedo 之外那五张。手工做一整套 PBR 材质,成本远高于画一张图。
texgen.pbr 的做法是:渲染一次,用同一份像素派生六通道,并按通道分别设置导入参数。
| 通道 | 来源 | 导入设置 |
|---|---|---|
| BaseColor | 原渲染 | sRGB |
| Height | 亮度场 | Linear |
| Normal | 高度场 Sobel | NormalMap |
| Roughness | bias + (0.5 - height) × contrast | Linear |
| AO | 八方向、两档距离邻域最高点比较 | Linear |
| ORM | R=AO / G=Roughness / B=Metallic(glTF ORM 约定) | Linear |
三个设计取舍:
- ·AO 为什么是"八方向两档":13×13 全核采样在 1024² 上会爆炸到分钟级,八方向采样在视觉上几乎看不出差别,但快两个数量级;
- ·roughness 为什么跟高度反相:凸起处更容易被磨光、凹陷处更容易积灰,这是物理直觉,也让成品看起来"有使用痕迹";
- ·ORM 为什么要打包:R=AO / G=Roughness / B=Metallic 是 glTF 的通行约定,一张图三个通道,省两次纹理采样。这里有个坑值得提醒:Unity URP 的 Mask Map 是另一套排布(R=Metallic / G=Occlusion / B=Detail / A=Smoothness),不能直接混用;需要接 URP Lit 时得再补一个转排布的通道。我目前只出了 glTF 那套。
4.5 还有一项容易被忽略的能力:UV 感知
普通贴图引擎输出一张图就结束了。但游戏要的是"贴到模型上不出错"。
texgen.for_mesh 做的是:读 Mesh 的 UV 布局,按 submesh 或 UV 连通岛分区,不同区域用不同指令表,然后在接缝处做颜色外扩(padding),避免图集出现一圈圈岛轮廓。
这是从"贴图生成器"迈向"资产工厂"的一步——引擎知道它要贴到什么东西上。
五、为什么「程序化 + AI」比「文生图 + AI」更靠谱
这是最常被问的问题,值得单独说。
| 维度 | 文生图(SD / Midjourney) | 程序化(TexGen) |
|---|---|---|
| 可平铺 | 基本不行,要专门训练 | 数学保证 |
| 可派生 PBR | 需额外模型 / 手工 | 同源派生,天然对齐 |
| 可复现 | 不能(种子 + 模型 + 采样器全变) | 完全可复现 |
| 可微调 | 重写提示词,全图重来 | 改一个参数,只动那一层 |
| 风格一致性 | 弱,同提示词也不同风格 | 强,同一模板族 |
| 版权 | 有争议 | 无(纯算法生成) |
| 资源体积 | MB 级 PNG | KB 级 JSON |
| 进 Git | 二进制,无法 review | 文本,可 diff |
| 迭代速度 | 秒级但不可控 | 毫秒级且可控 |
| 表达能力上限 | 高(能画真实照片) | 中(结构化的表面) |
结论很清楚:它们不是替代关系,是分工关系。
文生图适合"一次性、非重复、需要真实感"的资产——海报、插画、概念图。程序化适合"重复、需要平铺、需要 PBR、需要量产变体"的资产——地面、墙面、材质库、UI 皮肤。
而游戏里 80% 的贴图工作量,恰好是后者。这就是这条路值得走的原因。
六、落地:三条路径
讲完技术,讲怎么用。
路径一:AI 直接生成(今天就能用)
最直接的方式,也是我实际在用的:
code
1. texgen.templates {tag: "wall"} → 挑模板
2. texgen.templates {name: "brick_wall"} → 取完整 spec
3. 改 spec(颜色/尺寸/gap) → AI 直接改 JSON
4. texgen.pbr {spec, output_dir, material_path}
→ 六通道落盘 + 挂到材质
5. camera.screenshot → 看效果
6. 不满意?改参数回到 3
注意第 5、6 步——这是我认为最有价值的一环:AI 不只是"生成",它有视觉反馈闭环。截图进来,模型判断"砖缝太宽",回去改 gap,再截图。这个循环就是人类技美的工作方式,只是快了一个数量级。
路径二:模板库作为团队的「风格基底」
33 个模板不只是给 AI 用的,它是团队的风格合同。
一个项目开始时,先定义 20 个模板(地面几种、墙面几种、金属几种),把颜色、尺度、粗糙度全部固化。之后所有新资产都从这套模板派生——风格不一致的问题从流程上消失了。
而新增模板只需要加一个 JSON 文件。不改代码、不重编译、不需要程序员。技美可以自己扩库。
路径三:进 CI,贴图变成可构建产物
这是我最看好的方向,也是目前还没做的。
既然 spec 是文本、渲染是可复现的,那么完全可以:
code
specs/*.json → CI 跑 texgen.batch → 产出 PNG → 提交到 assets/
好处是连锁的:
- ·贴图可 review:改的是 JSON,PR 里能看清"砖缝从 5 变 3";
- ·可以批量调:全项目 200 张砖墙的粗糙度整体 +0.1,改一个模板重跑;
- ·体积:仓库里存的是几 KB 的 spec,不是几百 MB 的 PNG;
- ·多分辨率:同一份 spec 出 512 / 1024 / 2048,按平台分发。
贴图从"美术资产"变成了"构建产物"。 这个转变的意义,和当年从手写 HTML 到模板引擎差不多。
七、还没做到的
诚实的部分,因为边界比能力更能说明一个东西的定位。
1. 做不出拓扑特征。 wood_grain 和 marble_stone 本质是"条纹 + 强扭曲",能出直纹木和脉络大理石,但做不出年轮、树节。那需要真实的拓扑结构,不是一个二维位移场能表达的。
2. 不能读位图。 渲染器不支持采样外部图像,所以无法"照片转材质"、无法描边化。这是下一步最值得做的:引入采样图元 + 描边(edge detect)+ 高度烘焙,能力会再上一个台阶。
3. 复杂图案的性能。 每个图层都是全画布遍历,hex 每像素 9 次邻域查询,512² 约 2.3 秒,2048² 会明显变慢。生产级需要分块采样或 GPU 计算着色器。
4. 带旋转的图案无法严格无缝。 旋转后的周期不再与画布轴对齐,这是几何事实,不是实现问题。
5. 批量渲染会被客户端超时打断。 texgen.batch 单次超过约 15 秒(6 张 512² 以上)会被默认超时截断——写操作其实已经完成,但响应丢了。这个坑我踩过,处理方式是分批 ≤5 张。顺带修了一个真 bug:批量写多张 PNG 时缺 DisallowAutoRefresh,资源监视器中途刷新会重载域、直接切断 JSON-RPC 响应(单张的 for_mesh 早有处理,batch 漏了)。
八、最后:这两个技术其实在讲同一件事
回头把 TexGen 和 MCP 放在一起看,会发现它们在解决同一个问题——如何让能力以"可发现、可组合、可验证"的形式存在。
- ·MCP 那一层:能力不能写死在提示词里,要在运行时被问到。工具自己描述自己,模型只负责决策。
- ·TexGen 那一层:贴图不能固化成一个 PNG,要有一个可序列化的结构。参数自己描述自己,人只负责意图。
两者都是把"结果"降级成"描述结果的数据"。一旦成为数据,它就能被 diff、被版本化、被批量重跑、被模型改写——而这三件事,恰好是 AI 最擅长的。
所以我认为,未来两年真正会被规模化使用的 AI 工程,不会是"让模型直接产出最终产物",而是:
让模型产出「能被确定性引擎执行的结构化描述」,然后由引擎保证质量。
模型负责意图与取舍,引擎负责一致性与可复现。这个分工下,AI 的幻觉不再是风险,而是"多试几个方案"的低成本探索。
贴图只是一个切口。同样的形状可以套到关卡布局、UI 系统、动画曲线、着色器、特效参数上——凡是能把"想要什么"和"怎么做出来"分开的领域,都适合这个模式。
这个贴图引擎现在只能画表面。但它验证的那条路,比它画的图要宽得多。
工具:Unity Skilled MCP(46 模块 / 339 动作)· TexGen 引擎(7 文件 / 4729 行 / 零依赖)*数据来源:Unity 2022.3 实机渲染验证,编译 error_count = 0,33 个模板全部通过校验*
更多推荐


所有评论(0)