当前复现状态(2026-10-17)
已恢复有来源的场景规格、公共模块与配套源码,从空目录组装三卷累计工程。361 条明确命令已完成运行;初次报告顺序失败保留,在全部输入哈希不变的前提下刷新跨语言报告后通过。实际范围包含全分辨率渲染、定点/浮点判据、跨语言与浏览器验证;Python 3.14.6、Node 26 及所列图像/浏览器依赖为当前环境,长实验每条预算 1800 秒,未缩小采样或断言。
这些结果验证当前最终快照,不等同按章文件演进、所有正文数字或练习答案审校。CPU 图像与跨语言一致也不能替代真实 GPU、设备或全部画面的正确性验收。
这是《计算机图形学》三卷本的第二卷。它回答一个问题:颜色从哪来,能不能写成一组可逐像素断言、可被证伪的量?
卷一已经把骨架封版了:装配、深度、覆盖全部落成整数,Python 与 TypeScript 产出逐字节相同的像素缓冲(
bac659770178037a…)。本卷不碰骨架,只做一件事——把片元颜色的来源换掉。卷一里片元颜色是「逐顶点算完 Lambert、再插值到像素」;本卷改为「插值法线、逐像素求值」。这个替换是可度量的:同一份场景规格、同一套几何,两条路径的覆盖掩码差异是 0.0‰,而着色频率差异是 41261 像素(537.2526‰)、MAE 3.5764、PSNR 37.06 dB、最大通道差 48。这组数字就是本卷的入口实验:几何完全一致,差异纯粹来自「法线在哪里归一化、光照在哪里求值」。 把规格与实现分离的收益在这里兑现了——换着色不动装配,换装配不动规格。
做法仍然是那一句话:一份场景规格,三种宿主消费,两条一致性纪律。
config/scene_spec.json是唯一真源,同时驱动 S 层 Python + numpy 的 CPU 逐像素着色器(基准真源)、P 层 TypeScript 的定点着色内核(node --test)、W 层浏览器里的 WebGL2 片元着色器(Playwright 读像素)。三处读的是同一个文件。贯穿全书的仍是虚构场景 《烛影山谷》(工程名
lantern)与同名工具链。本卷新增的几何属性只有两样:逐顶点法线(地形用法线的解析解,球体用「顶点减球心」)与逐顶点 albedo。场景全部由程序生成,不引入任何第三方素材。
这套书与站内既有内容的分工
站内已经有大量相邻内容,写清分工比重复写一遍更有价值。下面这几处边界是本卷最容易混淆的,务必分清:
- 光照与阴影 讲的是实时 PBR 与级联阴影贴图的工程实现(C++/GLSL,跑在 GPU 上,关心 draw call 与帧时间)。本卷第 1、2、4、6 章从经验模型推到可断言的着色:Lambert 与 Phong 各要几次乘法、Blinn 替代 Phong 到底省不省、偏差该取多大、一个 BRDF 的能量积分能不能守住上界——全部给出实跑数字。
- 实时全局光照 讲实时 GI 的若干方案(探针、屏幕空间、体素)。本卷第 5 章做的是遮蔽的采样估计与误差:环境光遮蔽的解析解与数值估计差多少、采样数从 4 涨到 32 时误差怎么收敛、球谐重建留下多大残差。
- 色彩管理与 HDR 管线 是HDR 管线的工程配置(传输函数、色调映射算子、显示输出)。本卷第 7 章把传递函数整数化:解码方向用一张冻结的 256 项表,编码方向由该表用纯整数导出边界表,运行期只做整数二分——于是 Python 与 TypeScript 逐位一致。
- 纹理显存管理 讲纹理的显存占用、格式与压缩。本卷第 8 章讲过滤核与 mip 链的定点构造:wrap/clamp/mirror/border 四种寻址在同一 UV 序列下的对照、双线性权重的 Q16.16 实现、奇数尺寸 mip 的边界处理——关注数值,不是显存。
- 后处理 是后处理链的 API 与性能。本卷第 10 章把卷积核写成整数并给出误差与代价:核尺寸、归一化残差、Bloom 的多级降采样链。
- 抗锯齿方法
讲 MSAA / FXAA / TAA 的 API 实现。本卷第 11 章做抖动与蓝噪声的构造(站内
dithering命中为 0):用确定性序列把量化误差摊成噪声,再给出抗锯齿的度量。 - 大地形渲染 是大地形的 LOD 与裁剪工程。本卷第 9 章是噪声函数的整数构造:值噪声、Perlin、fBm 全部走整数哈希与整数插值,同一种子两种语言给出同一张纹理。
- glTF 资产管线 是资产导入导出。本卷第 3 章讲切线空间与法线重建的定点实现:法线为什么要用逆转置矩阵变换、切线怎么从位置与 UV 求出来、8 位法线贴图怎么解压回单位向量。
- 着色器编译与 SPIR-V 讲编译与反射。本卷第 13 章讲材质数据的规格化:材质怎么表示、变体怎么组合、数据驱动怎么做到不重编译。
- 集群前向渲染 是多光源的 GPU 实现。本卷第 14 章建的是光源求值的代价模型:不同类型光源各要多少运算、批处理的收益边界在哪。
- 《渲染引擎》
55 篇用 TypeScript 从零造一个引擎(材质变体、级联阴影、后处理、帧图)。那套是引擎架构;本卷是着色算法的跨语言逐字节证明——逐像素路径在 Python 与 TypeScript 下必须给出同一个
sha256。 - 《技术美术与管线工程》 50 篇做的是管线闸门与预算。那套管的是「画得起吗」;本卷第 15、16 章管的是「算得对吗」,并给出偏差的定位方法。
一句话:本卷做的是「颜色从哪来、这些量能不能被两种语言复现、误差有多大」,不是「怎么把画面调得更好看」。
明确不在本卷范围内
为了让边界清楚,下面这些这一卷不讲:
- 图形 API 的用法与工程实践(对象模型、命令缓冲、同步、资源状态)——见
graphics/专题与《渲染引擎》。 - 几何装配、边函数覆盖、深度缓冲、裁剪、直线光栅化——这些是卷一《几何、变换与光栅化》的内容,本卷一字不改地复用。
- 光线求交、加速结构、蒙特卡洛、路径追踪与全局光照——这些是卷三《光线追踪与全局光照》的内容。本卷第 5 章的球谐与遮蔽只是近似,不做真正的多次弹射。
- 材质的美术制作、贴图绘制、风格规范——见《游戏美术与技术美术》。
- 真实 GPU 上的性能剖析与真实驱动的浮点差异:本机 headless 的 WebGL2 后端是 ANGLE/SwiftShader 软件实现,不是真实 GPU。凡涉及真实硬件的结论,正文逐处显式标注「需真实 GPU 环境,命令行不可复现」。
- 任何第三方素材与贴图:贯穿案例为虚构场景,所有纹理、PNG / SVG 由程序化生成。
可运行性六档口径
代码块头部注释说明所属档位和验证命令。当前累计工程、独立归档及未验范围见页首「当前复现状态」;明确标记的命令、解释性片段和按章演进分别验收。正文历史输出保留原输入与环境,计时及机器相关数字不能当作本轮一致性保证。
| 档 | 数据源 | 算什么(真跑) | 产出 | 断言 |
|---|---|---|---|---|
| S | config/scene_spec.json | numpy 向量化的逐像素着色光栅化(插值法线 → 逐像素归一化 → 着色)+ 中间缓冲 | PNG + JSON + CSV + 纯 xml.etree SVG | 脚本 exit 0;SVG ET.fromstring 合法;定点不变量:重心权重和 == 面积(残差恒为 0)、法线模长在声明容差内 |
| P | 同一 spec | TypeScript 定点着色内核(整数插值 + BigInt 归一化) | core/test/cg_shade_*.test.ts | node --test '<glob>',fail == 0;与 S 层的像素缓冲 sha256 逐字节相同 |
| W | 同一 spec 内联进单文件 HTML | 浏览器 WebGL2 片元着色器真渲染 | web/dist/shade<NN>.html + out/shots/*.png | Playwright 无头:断言全绿 + canvas 已绘制 + 与 S 层的容差比对(占比 / MAE / PSNR) |
| D | 场景规格与材质表 | schema、字段表、不变量清单、检查清单 | JSON / Markdown 表 | lint;必须与 S/P/W 同节搭配 |
| X | 真实 GPU | 真实硬件上的性能剖析、真实驱动的浮点差异 | 无 | 正文逐处显式标注「需真实 GPU 环境,命令行不可复现」 |
| B | 可扰动参数 | 光源数 / 采样数 / 粗糙度 / 曝光 / mipmap 开关扰动 → 走样与相变阈值 | JSON | 软告警不硬失败;exit 0;数字按对应实验输入与环境解读 |
「着色正确」在本卷被操作化成几个可算的代理指标,只用于相对比较,禁绝对化:
- 图像误差:MAE、PSNR、最大通道差(用于两条着色路径、两个宿主之间的比对)
- 不一致像素占比:千分比(CPU 与 GPU 之间的覆盖归属与颜色差异)
- 能量守恒残差:半球积分的整数估计值减去 1,必须 ≤ 0
- 量化误差:传递函数往返的灰度级残差
- 走样阈值:参数扰动下首次出现可见走样的参数值
它们证明的是「这套实现自洽、可控、可分辨」,不是「画质更好」。
贯穿案例《烛影山谷》:卷二的视角
卷一里每个顶点只带颜色;卷二里每个顶点带法线与 albedo。这是本卷唯一的几何改动,其余全部复用卷一。
新增的顶点属性
| 属性 | 精度 | 来源 |
|---|---|---|
| 法线 | Q16.16 单位向量 | 地形:解析解 normalize(-dh/dx, 1, -dh/dz),中心差分步长 2.0 世界单位;球体:normalize(顶点 − 球心) |
| albedo | 8 位通道 ×3 | 地形按高度在两个 albedo 之间整数插值;球体为常数 |
渲染参数(与卷一同一份规格,未改)
| 字段 | 值 | 作用 |
|---|---|---|
width / height | 320 / 240 | 画布尺寸(像素) |
subpixelBits | 6 | 子像素精度,1 像素 = 64 单位 |
backgroundQ8 | [12,14,20] | 背景色(8 位通道) |
ambientQ16 | 13107 | 环境光项(Q16.16,约 0.2) |
directionalLight.dirQ16 | [-37837, 37837, 37837] | 方向光朝向 |
intensityQ16 | 58982 | 方向光强度(约 0.9) |
卷二的新基线:逐顶点与逐像素
同一份规格、同一套几何、同一个光照公式,只改着色频率。两条路径的实跑结果:
canvas : 320x240
逐顶点 (Gouraud) : 43731 覆盖 41.4 ms sha256 bac659770178037a…
复现卷一基线 : True
逐像素 (per-pixel) : 43731 覆盖 (569‰) 156.0 ms sha256 0d997343ca547ed5…
着色频率差异 : 41261 像素 (537.2526‰) MAE 3.5764 PSNR 37.06 dB 最大通道差 48
覆盖掩码差异 : 0.0‰
读法:两条路径的覆盖完全一致(43731 个像素、569‰、剔除 290 个三角形、0 个被相机丢弃),覆盖掩码差异是 0.0‰——这是「骨架封版」的独立证据。差异全部落在颜色上:41261 个像素不同,占画布的 537.2526‰。逐顶点路径的 sha256 同时命中卷一的基线,说明几何与投影路径一字未动。
耗时一行只作量级参考:逐顶点落在 30–58 ms、逐像素落在 155–172 ms(多次运行的抖动区间),只有比值(约 5.2 倍)是稳定的,单次绝对值不可信。
底座:卷二新增的两个文件
三种消费共享同一套底座。卷二在卷一的基础上只加两个文件:Python 侧的 sim/cgshade.py(地形法线解析解 + 逐像素着色光栅化器)与 TypeScript 侧的 core/src/lantern/shade.ts。底座只读,各章只在它之上写自己那一节的脚本与断言。
下面这段是 TypeScript 侧的完整内容,与工作区文件逐字节一致。它与卷一 raster.ts 的唯一结构差别是:插值的对象从颜色换成法线,着色调用从「每顶点一次」变成「每像素一次」——深度测试、背面剔除、覆盖掩码、屏幕包围盒扫描窗口的语义完全照抄。
// file: lantern/core/src/lantern/shade.ts
// tier: P
// verify: node --test 'core/test/cg_shade_*.test.ts'
// 卷二底座:地形法线解析解 + 插值法线的逐像素着色光栅化器。
// 与 Python 侧 sim/cgshade.py 的 terrain_normal / rasterize_shaded 逐字对齐。
//
// 与卷一 raster.ts 的唯一结构差别:插值的对象从**颜色**换成**法线**,
// 着色调用从「每顶点一次」变成「每像素一次」;深度测试、背面剔除、覆盖掩码、
// 屏幕包围盒扫描窗口的语义完全一致——卷一封版的骨架在这里原样复用。
//
// 量级上界(决定这里能用 Number):
// 屏幕 Q6 <= 320*64 = 2^14.3,边函数 <= 2 * 2^28.6 = 2^29.6
// 法线插值分子 <= 2^29.6 * 2^16 = 2^45.6,累加 3 项 <= 2^47.2 < 2^53 —— 精确
// 舍入:法线分量**可正可负**,故 Python 的 `//`(floor)与 Math.floor 同义;
// 归一化走 vNorm(BigInt + 整数 isqrt),与 Python 侧逐位一致。
import { SUB1 } from "./fixed.ts";
import { terrainHeight } from "./noise.ts";
import { type Vec3, vNorm } from "./vec.ts";
/** 地形法线中心差分步长:2.0 世界单位(Q16.16)。 */
export const NORMAL_STEP = 1n << 17n;
/** 地形法线的解析解 n = normalize(-dh/dx, 1, -dh/dz),与 cgshade.terrain_normal 对齐。 */
export function terrainNormal(x: bigint, z: bigint, seed: bigint, heightScale: bigint): Vec3 {
const d = NORMAL_STEP;
const hx = terrainHeight(x + d, z, seed, heightScale) - terrainHeight(x - d, z, seed, heightScale);
const hz = terrainHeight(x, z + d, seed, heightScale) - terrainHeight(x, z - d, seed, heightScale);
return vNorm([-hx, 2n * d, -hz]);
}
/** 着色函数:法线(Q16.16 单位向量)+ albedo(8 位通道)-> RGB(8 位通道)。 */
export type ShadeFn = (normal: Vec3, albedo: readonly number[]) => [number, number, number];
/** 一个待逐像素着色的三角形:屏幕 Q6 顶点、深度、顶点法线、顶点 albedo。 */
export type STri = {
xy: [number, number][];
d: number[];
n: [number, number, number][];
alb: [number, number, number][];
};
export type ShadeRasterResult = {
rgb: Uint8Array;
depth: Int32Array;
covered: Uint8Array;
coveredCount: number;
culled: number;
};
const DEPTH_MAX = 2147483647;
export function rasterizeShaded(
tris: STri[],
width: number,
height: number,
background: readonly number[],
shade: ShadeFn,
): ShadeRasterResult {
const rgb = new Uint8Array(width * height * 3);
for (let i = 0; i < rgb.length; i += 3) {
rgb[i] = background[0];
rgb[i + 1] = background[1];
rgb[i + 2] = background[2];
}
const depth = new Int32Array(width * height).fill(DEPTH_MAX);
const covered = new Uint8Array(width * height);
let coveredCount = 0;
let culled = 0;
const half = SUB1 >> 1;
for (const t of tris) {
const [x0, y0] = t.xy[0];
const [x1, y1] = t.xy[1];
const [x2, y2] = t.xy[2];
const area = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);
if (area <= 0) {
culled += 1;
continue; // 背面剔除(屏幕空间逆时针为正面)
}
const minx = Math.max(0, Math.floor(Math.min(x0, x1, x2) / SUB1));
const maxx = Math.min(width - 1, Math.floor(Math.max(x0, x1, x2) / SUB1));
const miny = Math.max(0, Math.floor(Math.min(y0, y1, y2) / SUB1));
const maxy = Math.min(height - 1, Math.floor(Math.max(y0, y1, y2) / SUB1));
if (minx > maxx || miny > maxy) continue;
const [d0, d1, d2] = t.d;
const [n0, n1, n2] = t.n;
const [a0, a1, a2] = t.alb;
for (let py = miny; py <= maxy; py += 1) {
const Y = py * SUB1 + half;
for (let px = minx; px <= maxx; px += 1) {
const X = px * SUB1 + half;
const w0 = (x2 - x1) * (Y - y1) - (y2 - y1) * (X - x1);
if (w0 < 0) continue;
const w1 = (x0 - x2) * (Y - y2) - (y0 - y2) * (X - x2);
if (w1 < 0) continue;
const w2 = (x1 - x0) * (Y - y0) - (y1 - y0) * (X - x0);
if (w2 < 0) continue;
const z = Math.floor((w0 * d0 + w1 * d1 + w2 * d2) / area);
const idx = py * width + px;
if (z < depth[idx]) {
depth[idx] = z;
// 插值法线(可正可负,floor 语义两侧一致),逐像素归一化后着色
const normal: Vec3 = [
BigInt(Math.floor((w0 * n0[0] + w1 * n1[0] + w2 * n2[0]) / area)),
BigInt(Math.floor((w0 * n0[1] + w1 * n1[1] + w2 * n2[1]) / area)),
BigInt(Math.floor((w0 * n0[2] + w1 * n1[2] + w2 * n2[2]) / area)),
];
const albedo = [
Math.floor((w0 * a0[0] + w1 * a1[0] + w2 * a2[0]) / area),
Math.floor((w0 * a0[1] + w1 * a1[1] + w2 * a2[1]) / area),
Math.floor((w0 * a0[2] + w1 * a1[2] + w2 * a2[2]) / area),
];
const c = shade(vNorm(normal), albedo);
const o = idx * 3;
rgb[o] = c[0];
rgb[o + 1] = c[1];
rgb[o + 2] = c[2];
if (covered[idx] === 0) {
covered[idx] = 1;
coveredCount += 1;
}
}
}
}
}
return { rgb, depth, covered, coveredCount, culled };
}
三条跨语言纪律(违反其一,逐字节一致必然崩):① 降精度一律用算术右移,定点除法用显式向零截断的实现,禁用裸 / 与 //——Python 的 // 是 floor,与 BigInt 的 /(向零截断)在负数上不一致;② 逐像素层的量级必须在 2^53 内,插值分子上界是 2^29.6 × 2^16,三项累加不超过 2^47.2,故可以用 Number + Math.floor,而矩阵链的中间量必须留在 BigInt 里;③ 整数类可以写「逐字节相同」,感知类派生量按声明容差断言,正文不写「GPU 与 CPU 逐位相等」。
全书目录(16 章 48 节)
第一部分 · 从经验模型到可断言的着色(第 1–4 章)
第 1 章 光照模型(S+P)
- 1.1 Lambert 与漫反射
:
max(0, N·L)为什么是整数安全的(S+P) - 1.2 Phong 与高光 :反射向量与幂次的整数构造(S+P)
- 1.3 着色频率与插值 :逐顶点与逐像素的差异度量(S+P)
第 2 章 材质与 BRDF(S+P)
- 2.1 BRDF 与能量守恒 :半球积分写成整数断言(S+P)
- 2.2 常见 BRDF 的定点化 :归一化系数的整数推导(S+P)
- 2.3 材质的组合与混合 :权重和的不变量与分层(S+P)
第 3 章 法线与法线贴图(S+P+W)
- 3.1 法线的变换 :为什么必须用逆转置矩阵(S+P)
- 3.2 切线空间 :从位置与 UV 求切线(S+P)
- 3.3 法线贴图的解压与重建 :8 位到单位向量的往返(S+P+W)
第 4 章 阴影(S+P+W)
- 4.1 阴影贴图的原理 :两趟渲染的完整链路(S+P+W)
- 4.2 分辨率、偏差与走样 :偏差的解析推导(S+P+W)
- 4.3 软阴影与 PCF :核尺寸与代价的权衡(S+P+W)
第二部分 · 间接光、材质与纹理(第 5–8 章)
第 5 章 环境光与近似全局光照(S+P)
- 5.1 环境光遮蔽 :半球可见性积分与数值估计(S+P)
- 5.2 SSAO 的采样与噪声 :采样数的收敛曲线(S+P)
- 5.3 环境贴图与球谐 :9 项系数的投影与重建(S+P)
第 6 章 基于物理的着色(S+P)
- 6.1 微表面理论 :GGX、Smith 与 Schlick 的整数实现(S+P)
- 6.2 金属度-粗糙度工作流 :参数扫描下的高光曲线(S+P)
- 6.3 能量守恒的验证 :各粗糙度的残差表(S+P)
第 7 章 色彩与色调(S+P)
- 7.1 线性空间与 sRGB :传递函数的冻结查表(S+P)
- 7.2 色调映射 :三条曲线的整数实现(S+P)
- 7.3 色彩管理的整数化 :量化误差与往返不变量(S+P)
第 8 章 纹理与采样(S+P)
- 8.1 纹理坐标与寻址模式 :四种寻址的整数实现(S+P)
- 8.2 过滤与 mipmap 的构造 :双线性与 mip 链(S+P)
- 8.3 各向异性过滤 :Ripmap 与质量代价曲线(S+P)
第三部分 · 程序化纹理与数值一致(第 9–12 章)
第 9 章 程序化纹理(S+P)
- 9.1 值噪声与 Perlin :整数哈希与插值(S+P)
- 9.2 分形噪声与 fBm :倍频叠加与幅度衰减(S+P)
- 9.3 程序化纹理的整数化 :跨语言同种子的同一张纹理(S+P)
第 10 章 后处理(S+P+W)
- 10.1 卷积与模糊 :核与归一化残差(S+P+W)
- 10.2 Bloom 与泛光 :多级降采样链(S+P+W)
- 10.3 色调与调色 :颜色查找表的整数化(S+P+W)
第 11 章 抗锯齿的进阶(S+P+W)
- 11.1 时间抗锯齿 :帧间累积与历史权重(S+P+W)
- 11.2 抖动与蓝噪声 :确定性序列的构造(S+P+W)
- 11.3 抗锯齿的度量 :边缘像素与走样指标(S+P+W)
第 12 章 着色的数值一致(S+P+W)
- 12.1 浮点的容差口径 :容差从哪来、怎么声明(S+P)
- 12.2 图像 digest 与误差度量 :摘要与度量的正确用法(S+P)
- 12.3 CPU 与 GPU 的比对 :不一致像素的归属分析(S+P+W)
第四部分 · 材质、光照与装配(第 13–16 章)
第 13 章 材质系统(S+P)
- 13.1 材质的表示与继承 :参数集与继承链(S+P)
- 13.2 着色变体 :变体爆炸与组合计数(S+P)
- 13.3 材质的数据驱动 :一份材质表驱动三种宿主(S+P)
第 14 章 光照的工程(S+P)
- 14.1 光源类型与表示 :方向光、点光与聚光的整数求值(S+P)
- 14.2 光照的批处理与代价 :光源数增长下的代价模型(S+P)
- 14.3 光照的调试视图 :把每一项贡献单独画出来(S+P)
第 15 章 着色管线的一致性(S+P)
- 15.1 跨语言着色对照 :同一着色器的两种实现(S+P)
- 15.2 中间缓冲的着色验证 :法线图、albedo 图与深度图(S+P)
- 15.3 偏差的定位方法 :从差异像素反查是哪一步错了(S+P)
第 16 章 装配:从着色到成像(S+P+W+B)
16.1 着色管线的装配 :从规格到成像的完整链路(S+P)
16.2 三种消费的联调 :三处产出的互相校验(S+P+W)
16.3 交付卷三 :把什么留给光追(S+P+B)
附录
- 附录 A 着色公式、材质参数表与数据集索引 :公式与参数速查(S+P)
怎么读
想先看主张,直接读第 1.3 节 ——它用一组数字证明「骨架封版、只换着色」是可度量的。想从最小的光照模型开始,走第 1.1 节 。只关心「怎么证明一个 BRDF 是守恒的」,看第 6.3 节 。只关心「为什么着色必须在线性空间算」,看第 7.1 节 。需要查公式与参数,直接翻附录 A 。
本卷的下一卷是《光线追踪与全局光照》:光栅化把「光从哪里来」简化成一次直接光照求值,卷三要把它还原成一个可以估计的积分——求交、加速结构、蒙特卡洛、路径追踪、辐射度、体积渲染。本卷第 16.3 节会把接口契约写清楚:哪些东西已经封版、卷三只替换哪一环。
完整项目配套
下载 lantern 三卷累计教学工程
。包内含当前正文、源码来源/哈希、明确命令清单及 verify.py;解压后准备依赖,运行 python3 verify.py --browser。长光追实验需要较长时间;超时、未运行和首次失败均保留,不计作通过。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。