1.3 技术美术的位置:桥接美术与程序(含 ART_SCOPE 分工表)

技术美术站在美术与程序之间,握着「规格拍板」与「接入会签」两件事。本节先讲清 TA 的三类工作与三个接口,再给出一张 ART_SCOPE 分工表,并用一段可跑的 TypeScript 路由表把它锁死:渲染原理必须判给 external,误判成本套的朴素路由会被负例测试当场拦下。

前两节把链路和角色都定了。但有一个位置始终最难摆:技术美术(TA)。他既不是纯美术——不靠画得好看取胜;也不是纯程序——不负责引擎实现。他站在两者中间,做的是把「好看」翻译成「能跑且不超预算」。这一节讲清他站的位置,并给出一张 ART_SCOPE 分工表:一张把「什么归本套讲、什么外链出去」写成可断言路由表的表。

技术美术到底做什么:三类工作

TA 的职责很容易被含糊成「美术和程序之间的翻译」。含糊的代价是:谁都能说自己在做 TA,谁也说不出 TA 交付了什么。把它切成三类可交付的工作,边界就清楚了:

类别解决什么典型产出物
规格与预算把风格目标落成可测参数预算表、材质参数表、LOD 规范
管线与工具让资产从 DCC 顺畅进引擎导入校验、图集/纹理管线、依赖检查
桥接与裁决在美术与程序之间做技术裁决可行性结论、接入规范、争议记录

三类里最容易被低估的是第三类。「可行性裁决」不是开会表态,而是给出可验证的结论:这套特效在低端机上能不能保住帧率、这个面数预算会不会让一批资产全部返工。裁决一旦含糊,美术与程序就会各自按对自己有利的口径理解,最后在集成期爆炸。

桥上的三个接口

TA 站在桥上,脚下是三个接口。每个接口都有明确的输入、输出与谁拍板:

接口上游下游TA 的角色
规格接口美术总监的风格目标全体美术把目标转成可测参数并拍板规格
管线接口画师的产出程序/引擎定义导入规范并会签接入
裁决接口美术诉求程序约束给可行性结论,不作最终风格拍板

第三个接口是分寸感的关键:TA 拍规格,不拍风格。风格最终拍板在美术总监(见上一节职责矩阵),TA 只对「这套风格在技术约束下能不能成立」负责。把这两件事混在一起,TA 就会从桥变成墙。

ART_SCOPE 分工表:什么归本套,什么外链出去

写一本讲美术规范的书,最大的风险是什么都想讲,结果什么都讲不深。所以本套在动笔前先立了一张 ART_SCOPE 分工表,把主题域分成两类:this-book(本套自己讲)与 external(外链既有内容,本套不重推)。

主题域归属外链目标
渲染原理 / 着色器编译 / 纹理压缩格式 / 骨骼动画数学 / 后处理 / 色彩管理 HDRexternal/posts/graphics/ 及 graphics 各专题
引擎实现 / RHI / 帧图 / 剔除 LOD / GPU 粒子 / 合批external引擎卷与 graphics
美术风格指南短文 / 性能预算短文 / 外包短文externalgame/industry/*
调色板 / 命名 / 预算 / 图集 / 管线 / 风格谱系 / 中台 / 工业化this-book本套

这张表的价值不在「分得对」,而在它可以被机器检查。下面这段是 P 层 TypeScript 内核 core/src/sp01/art_scope.ts,把表编码成一张路由表:给定主题,返回 this-book 或 external 加外链路径。

// file: core/src/sp01/art_scope.ts
// tier: P
// verify: node --test 'core/test/sp01_*.test.ts'
// ART_SCOPE 分工路由表:把「主题域」判给 this-book(本套自己讲)或 external(外链既有文章)。
// 编码成可断言的路由表 + 负例测试。external 路径全部取自 BRIEF §9.3 已逐个核验的清单。
// 只允许可擦除 TS 语法(禁 enum / namespace / 参数属性 / 装饰器)。

export type Owner = "this-book" | "external";

export type ScopeRule = {
  id: string;
  owner: Owner;
  match: string[];
  path: string | null;
};

// §9.3 已核验存在的绝对 URL(external 规则只能取这里的值)。
export const VERIFIED_URLS: string[] = [
  "/posts/graphics/",
  "/graphics-color-management-hdr-pipeline/",
  "/graphics-shader-compilation-pipeline/",
  "/graphics-texture-memory/",
  "/graphics-skeletal-animation/",
  "/graphics-post-processing/",
  "/graphics-gpu-particles-vfx/",
  "/graphics-mobile-rendering-optimization/",
  "/graphics-gpu-driven-rendering-lod/",
  "/graphics-render-graph/",
  "/helio-render-01-3-scope-vs-graphics/",
  "/game-art-direction-style-bible-2026/",
  "/game-performance-budget-mobile-pc-2026/",
  "/posts/game/industry/",
];

export const ART_SCOPE: ScopeRule[] = [
  // ---- external:渲染原理(graphics 专题 45 篇,是「原理的家」)----
  { id: "render-principle", owner: "external", path: "/posts/graphics/", match: ["渲染原理", "渲染管线原理", "光照原理", "着色模型"] },
  { id: "shader-compile", owner: "external", path: "/graphics-shader-compilation-pipeline/", match: ["着色器编译", "shader 编译"] },
  { id: "texture-format", owner: "external", path: "/graphics-texture-memory/", match: ["纹理压缩格式", "纹理显存"] },
  { id: "skinning-math", owner: "external", path: "/graphics-skeletal-animation/", match: ["骨骼动画数学", "蒙皮数学"] },
  { id: "post-process", owner: "external", path: "/graphics-post-processing/", match: ["后处理"] },
  { id: "color-hdr", owner: "external", path: "/graphics-color-management-hdr-pipeline/", match: ["色彩管理", "HDR"] },
  // ---- external:引擎实现(《渲染引擎》《引擎内核》卷)----
  { id: "engine-impl", owner: "external", path: "/helio-render-01-3-scope-vs-graphics/", match: ["引擎实现", "RHI"] },
  { id: "render-graph", owner: "external", path: "/graphics-render-graph/", match: ["帧图", "render graph"] },
  { id: "cull-lod", owner: "external", path: "/graphics-gpu-driven-rendering-lod/", match: ["剔除 LOD", "GPU 剔除"] },
  { id: "gpu-particle", owner: "external", path: "/graphics-gpu-particles-vfx/", match: ["GPU 粒子"] },
  { id: "batching", owner: "external", path: "/graphics-mobile-rendering-optimization/", match: ["合批", "批处理"] },
  // ---- external:撞车短文(§9.2)----
  { id: "style-bible-essay", owner: "external", path: "/game-art-direction-style-bible-2026/", match: ["美术风格指南短文", "风格指南短文"] },
  { id: "perf-budget-essay", owner: "external", path: "/game-performance-budget-mobile-pc-2026/", match: ["性能预算短文"] },
  { id: "outsourcing-essay", owner: "external", path: "/posts/game/industry/", match: ["外包短文"] },
  // ---- this-book:本套自己的地盘 ----
  { id: "palette", owner: "this-book", path: null, match: ["调色板", "配色"] },
  { id: "naming", owner: "this-book", path: null, match: ["命名", "资产命名"] },
  { id: "art-budget", owner: "this-book", path: null, match: ["美术预算", "面数预算", "骨骼预算", "材质槽预算", "预算"] },
  { id: "atlas", owner: "this-book", path: null, match: ["图集"] },
  { id: "pipeline", owner: "this-book", path: null, match: ["资产管线", "生产链路", "管线"] },
  { id: "style-gen", owner: "this-book", path: null, match: ["风格谱系", "风格向量", "风格距离"] },
  { id: "platform", owner: "this-book", path: null, match: ["中台"] },
  { id: "industrialization", owner: "this-book", path: null, match: ["工业化"] },
];

export type RouteResult = { owner: Owner; path: string | null; ruleId: string };

/** 首个命中的规则胜出(故 external 具体规则必须排在 this-book 泛化规则之前)。 */
export function route(topic: string): RouteResult | null {
  for (const rule of ART_SCOPE) {
    if (rule.match.some((k) => topic.includes(k))) {
      return { owner: rule.owner, path: rule.path, ruleId: rule.id };
    }
  }
  return null;
}

export function externalRules(): ScopeRule[] {
  return ART_SCOPE.filter((r) => r.owner === "external");
}

export function externalPaths(): string[] {
  return externalRules().map((r) => r.path as string);
}

规则顺序不是随便排的:external 的具体规则必须排在 this-book 的泛化规则之前。因为 route 取首个命中,「着色器编译管线」里既有 着色器编译 也有 管线——如果不把 shader-compile 排在 pipeline 前面,它就会被本套的泛化规则「抢走」,判成 this-book。这个坑下面有专门的负例锁死。

负例:渲染原理绝不能被本套收编

一张分工表最危险的失败,是越界:把本该外链的原理性内容收进本套,写成一篇四不像的浅文。测试的力气因此全花在负例上。

// file: core/test/sp01_art_scope.test.ts
// tier: P
// verify: node --test 'core/test/sp01_*.test.ts'
// ART_SCOPE 路由表断言:external 路径两两不同、全部在 §9.3 已核验清单内、this-book 规则不带外链。
// 负例是核心 —— 「渲染原理」必须判给 external;朴素路由器把它判成 this-book,必须被拦住。
import test from "node:test";
import assert from "node:assert/strict";
import {
  ART_SCOPE,
  VERIFIED_URLS,
  route,
  externalRules,
  externalPaths,
} from "../src/sp01/art_scope.ts";
import type { Owner } from "../src/sp01/art_scope.ts";

test("规则 id 唯一、match 非空", () => {
  const ids = ART_SCOPE.map((r) => r.id);
  assert.equal(new Set(ids).size, ids.length, "规则 id 重复");
  for (const r of ART_SCOPE) assert.ok(r.match.length > 0, `${r.id} 无匹配词`);
});

test("不变量:external 路径两两不同、条数等于去重条数、全部在已核验清单内", () => {
  const paths = externalPaths();
  assert.equal(paths.length, new Set(paths).size, "external 路径有重复");
  assert.equal(externalRules().length, paths.length, "external 规则数与路径数不符");
  const verified = new Set(VERIFIED_URLS);
  for (const p of paths) assert.ok(verified.has(p), `${p} 不在 §9.3 已核验清单`);
});

test("this-book 规则不带外链路径", () => {
  for (const r of ART_SCOPE) {
    if (r.owner === "this-book") assert.equal(r.path, null, `${r.id} 不应带 path`);
  }
});

test("external 规则必须带 path", () => {
  for (const r of externalRules()) assert.ok(r.path, `${r.id} 缺 path`);
});

test("正例:本套地盘判给 this-book", () => {
  for (const t of ["调色板", "资产命名", "美术预算", "图集", "资产管线", "风格谱系", "美术中台", "美术工业化"]) {
    const r = route(t);
    assert.ok(r, `${t} 未命中`);
    assert.equal(r.owner, "this-book", `${t} 应判 this-book,实得 ${r.owner}`);
  }
});

test("正例:原理与实现判给 external", () => {
  assert.equal(route("渲染原理")?.owner, "external");
  assert.equal(route("着色器编译")?.owner, "external");
  assert.equal(route("骨骼动画数学")?.owner, "external");
  assert.equal(route("引擎实现")?.owner, "external");
  assert.equal(route("合批")?.owner, "external");
  assert.equal(route("色彩管理")?.owner, "external");
});

test("负例:朴素路由器把渲染原理判成 this-book,真路由表必须不这么判", () => {
  // 朴素路由器:见到「原理」「管线」就收进本套 —— 这正是 §9.1 要防的越界
  const naive = (t: string): Owner => (t.includes("原理") || t.includes("管线") ? "this-book" : "external");
  assert.equal(naive("渲染原理"), "this-book"); // 朴素路由器确实会错判
  const r = route("渲染原理");
  assert.ok(r);
  assert.notEqual(r.owner, "this-book");
  assert.equal(r.owner, "external");
  assert.equal(r.path, "/posts/graphics/");
});

test("负例:着色器编译含「管线」二字,但不得被本套的管线规则抢走", () => {
  const r = route("着色器编译管线");
  assert.ok(r);
  assert.equal(r.owner, "external");
  assert.equal(r.ruleId, "shader-compile");
});

test("未覆盖主题返回 null(需要新增规则,而不是静默归到本套)", () => {
  assert.equal(route("完全没听过的主题域"), null);
});

在本机跑 node --test 'core/test/sp01_art_scope.test.ts',真实输出:

✔ 规则 id 唯一、match 非空 (0.917625ms)
✔ 不变量:external 路径两两不同、条数等于去重条数、全部在已核验清单内 (0.157375ms)
✔ this-book 规则不带外链路径 (0.081125ms)
✔ external 规则必须带 path (0.069125ms)
✔ 正例:本套地盘判给 this-book (0.685584ms)
✔ 正例:原理与实现判给 external (0.160208ms)
✔ 负例:朴素路由器把渲染原理判成 this-book,真路由表必须不这么判 (0.274042ms)
✔ 负例:着色器编译含「管线」二字,但不得被本套的管线规则抢走 (0.080208ms)
✔ 未覆盖主题返回 null(需要新增规则,而不是静默归到本套) (0.092125ms)
ℹ tests 9
ℹ pass 9
ℹ fail 0

两条负例守的是两个方向的越界:一条防「把原理收进本套」,一条防「泛化规则抢走具体规则」。只要这两条里的任何一条从「拦住」变成「放行」,分工表就已经失效了。

怎么用这张表

ART_SCOPE 不是写完就归档的文档,它在三个地方被反复消费:

  • 动笔前:新开一节先 route 一下主题,判成 external 的就只写一句外链,不展开。
  • 写作中:正文里出现越界论述,说明路由表该补规则了——补规则比补一段浅文便宜得多。
  • 验收时:把本节标题喂进 route,this-book 的才允许出现在本套目录里。

这也解释了本套为什么敢写「50 篇 × 3 卷」这么大的体量:边界不是靠自律守住的,是靠一张可断言的表守住的。

自检表

检查项合格标准
三类工作清楚规格 / 管线 / 桥接各有产出物
接口有拍板三个接口各自写明谁拍板
分工表可断言ART_SCOPE 是可跑的路由表,不是文档
越界有负例「渲染原理判成 this-book」有测试拦住
路径可核验external 路径全部来自已核验清单
边界清晰真实 DCC 与渲染实现外链,本套不重推

TA 站的是桥,不是墙:他把「好看」翻译成「能跑且不超预算」,但不替美术总监决定什么算好看。第 1 章到此收尾——链路、角色、分工三件事都立住了。下一章我们进入本套的第一块硬骨头:怎么把「清新治愈」这种形容词,翻译成一组可测参数。

阅读导航:上一节:1.2 角色分工与职责矩阵 · 下一节:2.1 从「治愈」「硬核」到可测参数 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「gameart」更多文章

  1. 附录 A:风格参数表、量化规则与数据集索引
  2. 9.3 等距资产的制作约束
  3. 9.2 等距 tile 与遮挡规则