设想你是一位魔法花园的园丁。别人造花园,是一株株种、一块块铺,累到腰酸背痛;你的方法却截然不同——你只做两件事:先写下一本《花园法则》,比如“溪流只沿着低洼走”、“树木不与湖泊为邻”;然后朝空中撒下一把“命运种子”。接下来,整座花园会在你眼前自己生长:山坡自动起伏,树林自然蔓延,鹅卵石小路严丝合缝地向远处延伸。这就是程序化内容生成(Procedural Content Generation,PCG)的魔力——用规则和随机性,让计算机自动创造场景。 《我的世界》的无尽地形、Roguelike 游戏里千变万化的随机地牢、《无人深空》中 1800 亿颗风格迥异的星球,背后都是 PCG 在默默“加班”。它绝不是“随便乱扔”,而是在一套明确法则的约束下,用随机性制造“看起来自然、玩起来新鲜”的内容。这篇教程,就是带你从零学会这本《花园法则》:从噪声函数铺出地形,到 L-system 长出植物,再到 WFC 拼出建筑关卡,最后教你验收与调优整座花园。

岗位关联

学会 PCG,你就能让 AI 自动产出地形、植被、关卡甚至整座城市,从手工美术的体力劳动中解放出来,成为一名真正“用法则塑造世界”的内容工程师。

目录
  1. 可视化演示
  2. PCG核心思想:规则代替手工
  3. 噪声函数生成地形:第一块积木
  4. L-system植物生长:让植物自己长出来
  5. WFC波函数坍缩:约束拼图大师
  6. 规则定义与种子控制:可控的随机
  7. 生成质量评估:花园验收与调优
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Perlin噪声地形生成、L-system递归分形植被、以及Wave Function Collapse约束传播坍缩过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+_captions.noise+'

PCG核心思想:规则代替手工

魔法花园园丁的法则书里从不画花,只写“花开七瓣、朝东朝阳”这种句子;别人花一个下午雕一百棵树,你只描述“树枝以 30 度分叉、春芽秋落”,然后让花园自己长。程序化内容生成(PCG)正是这个思路:把“逐个手绘内容”变成“定义规则 + 执行生成”。游戏中的地形、关卡、武器、音效,凡是能总结出规律的资产,都能交给 PCG 批量产出。

一个完整的 PCG 系统有三个部件:规则集描述“什么内容是合法的”,随机数生成器提供“每次都不一样”的多样性,生成器把规则和随机性搅拌成最终结果。正因如此,同一套规则换一个随机种子,就能得到一片全新但同样合格的花园。这套思想在 1980 年代 Roguelike 游戏里萌芽,如今已在开放世界、程序化美术中大放异彩。

World=Generate(Rules, Seed)\text{World} = \text{Generate}(\text{Rules},\ \text{Seed})

1. 输出内容 World:最终呈现给玩家的地形、建筑或关卡,是 PCG 产出的一个具体实例。

World\text{World}

2. 规则集 Rules:约束的集合,定义合法内容,比如“水不能高于山脚”“树间距不小于 2 米”。

Rules\text{Rules}

3. 随机种子 Seed:一个整数起点,决定生成中所有“随机”的选择,是多样性与可重现性的开关。

Seed\text{Seed}

4. 生成器 Generate:把规则和种子组合成具体输出的执行器,由噪声、L-system、WFC 等算法串联而成。

Generate(⋅)\text{Generate}(\cdot)

所以 PCG 不是“没有设计师”,而是把设计师从“画图的人”升级成“立法的人”。魔法花园里真正的辛勤不是挥锄头,而是反复打磨那本法则书——这正是 PCG 工程师的日常。

噪声函数生成地形:第一块积木

园丁铺地形时若让每格高度都完全随机,地面会像雷劈过的碎石滩;若全都一样,又成了死气沉沉的平原。魔法花园的秘密是“平滑的随机”:相邻点高度相近,隔得远才明显不同。这就是噪声(Noise),它不是一个一个孤立的随机数,而是一张“连续起伏的随机波浪”。

[Perlin 噪声](0) 是 Ken Perlin 为电影特效发明的算法,输入坐标输出 0~1 间平滑变化的数,自带自然的“起伏感”。但单层噪声太“肉”,只有大片的坡。图形学于是用多频叠加(fBm,分形布朗运动)把多层噪声叠起来:一层低频大振幅定骨架,再加一层高频小振幅补纹理。上方的可视化演示正是这个过程——从 1 层到 4 层,地形从“大肉块”变成有山脊、有碎石、有河床的丰富地表。

fbm(x,y)=∑i=0n−1ai noise(fix, fiy)\text{fbm}(x,y)=\sum_{i=0}^{n-1} a^{i}\,\text{noise}\left(f^{i}x,\ f^{i}y\right)

1. 总层数 n:参与叠加的噪声层数,n 越大细节越丰富,但计算量也随之上升。

nn

2. 第 i 层噪声:对坐标放大 \(f^i\) 倍后取样的噪声值,放大越多,变化越细碎。

noise(fix, fiy)\text{noise}\left(f^{i}x,\ f^{i}y\right)

3. 振幅衰减 a^i:每层振幅按 a^i 缩小(通常 a=0.5),保证高频层只是“点缀”而非“喧宾夺主”。

aia^{i}

4. 频率增长 f^i:坐标乘 f^i(通常 f≈2),让后续各层的“波纹”越来越密。

fif^{i}

5. 加权求和:把所有层加起来并归一化,得到既有大骨架又有小细节的高度值。

∑i=0n−1\sum_{i=0}^{n-1}

落地时,把 fbm 的结果映射成颜色或高度,就得到高度图:浅处是沙滩,中间是草原山坡,高处是雪山——魔法花园的“地形画布”就此铺好,后续植被、道路都在它上面作画。

L-system植物生长:让植物自己长出来

园丁从不雕刻树枝。他只在每棵小树苗耳边念一句咒语:“每长高一截,就分两个叉,一个向左 30 度,一个向右 30 度。”树苗听话地一遍遍执行,几个季节后就长成一棵枝繁叶茂的魔法橡树。[L-system](0)(林登迈耶系统)就是这句咒语的数学化——它由生物学家 Lindenmayer 提出,用形式语法模拟植物生长。

它只有两个核心部件:公理(初始字符串)和重写规则(一步生长怎么变)。字符串里的符号由“龟形解释器”翻译成画布动作:F 向前画枝、+ 左转、- 右转、方括号负责“记住/回到”分支起点。每迭代一次,主干变侧枝,侧枝再抽新枝——这就是分形:树枝的局部结构与整棵树惊人地相似。

ω=F,F→F[+F]F[−F]F\omega = F,\qquad F \to F[+F]F[-F]F

1. 公理 ω:初始字符串只有一个 F,代表刚出土的幼苗主干。

ω=F\omega = F

2. 重写规则:一根枝干长大后,生成左枝 [ +F ]、直枝 F、右枝 [ -F ] 三段新枝。

F→F[+F]F[−F]FF \to F[+F]F[-F]F

3. 方括号与方向:[ 把当前位置压栈保存,] 出栈返回起点;+ 与 - 控制左右转角。

[ ] , + , −[\,]\,,\ +\,,\ -

4. 迭代深度:每多一轮重写,分支层级加一,树冠复杂度指数增长,通常 4~6 轮即可生成一棵逼真的树。

FnF^{n}

在三维建模里,给符号加上“枝干半径随深度变细”“随机抖动角度”等参数,L-system 就能批量产出风格统一又各有微差的森林。园丁只需要背熟一句咒语,就能种出整片魔法森林。

WFC波函数坍缩:约束拼图大师

园丁铺鹅卵石小路时有个讲究:相邻石头必须咬合。他从不闭眼乱铺,而是专挑“选择余地最小的空位”先下手——比如周围已固定三块石头、只剩一种能接,就先钉那块;钉完,邻居们的候选跟着连锁收窄,像多米诺骨牌一样一路推进。这就是WFC(Wave Function Collapse,波函数坍缩)的直觉。

WFC 由 Maxim Gumin 在 2016 年开源后风靡游戏圈。它把输出地图视为格子阵列,每个格子初始同时“叠加”所有候选瓷砖;然后反复执行两个操作:最小熵坍缩(选候选最少的格子,锁定一种瓷砖)和约束传播(根据已定瓷砖,删除邻居中与之冲突的候选)。它非常适合生成拼接式关卡、像素艺术和风格统一的建筑群。若某格子候选被删空,说明约束冲突,此时需要回溯或换种子重来。

H(x)=−∑i∈C(x)pilog⁡pi,x∗=arg⁡min⁡xH(x)H(x) = -\sum_{i\in C(x)} p_i \log p_i,\qquad x^{*}=\arg\min_{x} H(x)

1. 候选集 C(x):格子 x 尚未被排除的瓷砖集合,初始时包含所有瓷砖类型。

C(x)C(x)

2. 概率 p_i:瓷砖 i 在候选集中出现的概率,均匀分布时即 1/|C|。

pip_i

3. 熵 H(x):对候选的不确定性求和并取负,候选越少,熵越低。

H(x)=−∑i∈C(x)pilog⁡piH(x) = -\sum_{i\in C(x)} p_i \log p_i

4. 最小熵选择:在所有格子中挑熵最小的先坍缩,把后期“卡死”的风险降到最低。

x∗=arg⁡min⁡xH(x)x^{*}=\arg\min_{x} H(x)

园丁靠“先钉最没悬念的石头”铺出严丝合缝的小路,WFC 则用同样的策略生成结构完整、风格统一的关卡。它本质上是把“满足局部约束”的老问题,变成了一个高效且可交互的拼图游戏。

规则定义与种子控制:可控的随机

同一个种子袋种出的玫瑰,株距和株数几乎一样,但每朵又不完全相同;换一袋种子,整片花园布局天翻地覆。PCG 里的“随机种子”正是这样:它是所有随机选择的起点,决定了整片“命运花园”的样貌。

计算机里的“随机”其实是伪随机——由数学公式从种子出发,推出一长串“看似随机”的数。常用的线性同余生成器(LCG)就是这样一个递推公式。正因为序列可复现,PCG 才能做到“同一种子 = 同一个世界”,方便测试、分享和运营活动(比如官方发布一个种子,所有玩家都能进入同一座奇观)。

xn+1=(axn+c) mod mx_{n+1} = (a x_n + c) \bmod m

1. 当前值 x_n:序列中的当前状态,最初的 x_0 就是随机种子。

xnx_n

2. 乘数 a 与增量 c:精心挑选的参数,决定序列是否均匀、周期是否够长。

a, ca,\ c

3. 模 m:对结果取模,把数值限制在 0 到 m-1 之间。

 mod m\bmod m

4. 种子初始化:x_0 即种子;同一种子产生完全相同的序列,从而生成完全相同的世界。

x0=Seedx_0 = \text{Seed}

在工程实践中,规则设计比随机数本身更关键:规则太松,内容四不像;规则太紧,内容千篇一律。好的做法是把关键参数(噪声振幅、分支角度、瓷砖权重)暴露成可调参数,让设计师像调音师一样微调“法则书”。

生成质量评估:花园验收与调优

花园长出来后,园丁要验收:小径通不通?湖泊有没有淹掉城堡?每片区域是否独一无二?如果哪处不对劲,他就回去修改法则书,再洒一次种子。PCG 工程同样遵循“生成—评估—调优”的闭环。

PCG 生成快但质量参差,通常从四个维度打分:视觉合理性(是否符合物理与美学常识)、玩法可玩性(玩家能否顺利通过)、资源多样性(是否大量重复)、性能开销(生成耗时与运行时负载)。多样性可以用统计学里的方差来量化:一批生成结果的方差越大,说明彼此差异越明显。

D=1N∑i=1N(xi−xˉ)2D = \frac{1}{N}\sum_{i=1}^{N}\left(x_i - \bar{x}\right)^{2}

1. 样本值 x_i:每次生成的结果特征,比如某关卡的房间数、某地形的平均高度。

xix_i

2. 平均值 x̄:所有样本的算术平均,作为参照中心。

xˉ=1N∑i=1Nxi\bar{x} = \frac{1}{N}\sum_{i=1}^{N} x_i

3. 偏差与平方:x_i - x̄ 是偏离量,平方后消除正负号并放大极端偏差。

(xi−xˉ)2\left(x_i - \bar{x}\right)^{2}

4. 求和平均:把各偏差平方加总再除以 N,得到“平均离散程度”。

1N∑i=1N\frac{1}{N}\sum_{i=1}^{N}

方差太大说明内容失控,太小说明千篇一律,工程师需要结合人工评审设定合理区间。园丁的验收也不是一次性的——每发现“花园长得不对”就回炉法则书,再播一次种子,直到满意为止。这条“立法—播种—验收”的循环,正是 PCG 工程师的看家本领。

AI 应用场景

开放世界星球的自动设计

阶段:内容生成与程序化美术

解决的问题:手绘 1800 亿颗星球在人力上完全不可能,传统手工建模只能覆盖极小区域且成本极高;用 PCG 让规则 + 种子自动生成每颗星球的地形、气候带与资源分布,使“无限探索”成为可能。

具体案例:《无人深空》用一组噪声函数叠加出星球高度图,用 L-system 生成异星植被,再以一套气候规则决定海洋与沙漠的边界。玩家每发现一颗星球,系统用该星球的坐标哈希作为种子现场生成,既保证独一无二,又保证下次到访完全一致。

Roguelike 地牢关卡的按局生成

阶段:关卡设计与玩法验证

解决的问题:手工设计地牢费时且容易让老玩家背板;WFC 以瓷砖约束一次生成整张地图,配合随机种子让每局游戏都“新鲜但可玩”。

具体案例:某独立 Roguelike 项目将房间、走廊、门、宝箱定义为瓷砖,用 WFC 拼出 20×20 地牢,再用“种子房间数方差”检验每局复杂度。玩家死亡后输入上一局种子,即可复盘同一张地图,社区因此形成“种子文化”。

游戏世界的植被批量布置

阶段:程序化资产生产

解决的问题:开放世界场景需要数万棵风格统一的树,手动种树既慢又易穿模;以 L-system 生成树的结构,再按噪声高度图决定“哪里是森林、哪里是草地”,自动摆放并规避道路与建筑。

具体案例:某开放世界项目用 L-system 生成 8 种树木模板,配合地形坡度、湿度噪声图做分布采样,在 4 平方公里地图上一键种下 2 万棵树;随后用“相邻树间距方差”检查是否过于密集,自动稀疏化处理。

Python 演示

python63 行
import randomfrom collections import Counter # 瓷砖邻接约束:对称设计,表示"x 可以邻接 y"TILE_RULES = {    'g': ['g', 't', 'w', 'b'],  # 草地:几乎什么都能接    't': ['g', 't'],            # 树:只靠草地/树    'w': ['g', 'w'],            # 水:只靠草地/水    'b': ['g', 'b'],            # 建筑:只靠草地/建筑}TILES = list(TILE_RULES.keys()) def compatible(a, b):    """瓷砖 a 与 b 能否相邻"""    return a in TILE_RULES[b] and b in TILE_RULES[a] def generate(size, seed):    rng = random.Random(seed)    grid = [[set(TILES) for _ in range(size)] for _ in range(size)]     def neighbors(x, y):        for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):            nx, ny = x + dx, y + dy            if 0 <= nx < size and 0 <= ny < size:                yield nx, ny     def propagate():        changed = True        while changed:            changed = False            for y in range(size):                for x in range(size):                    if len(grid[y][x]) == 1:                        continue                    keep = set()                    for nx, ny in neighbors(x, y):                        if len(grid[ny][nx]) == 1:                            fixed = next(iter(grid[ny][nx]))                            keep |= {t for t in grid[y][x] if compatible(t, fixed)}                    if keep and keep != grid[y][x]:                        grid[y][x] = keep                        changed = True     cells = [(x, y) for y in range(size) for x in range(size)]    rng.shuffle(cells)    for x, y in cells:        if len(grid[y][x]) > 1:            propagate()            if len(grid[y][x]) > 1:                grid[y][x] = {rng.choice(sorted(grid[y][x]))}    propagate()    return [''.join(next(iter(c)) for c in row) for row in grid] def render(rows):    icon = {'g': '.', 't': 'T', 'w': '~', 'b': '#'}    return '\n'.join(''.join(icon[ch] for ch in row) for row in rows) if __name__ == '__main__':    for seed in (7, 42, 2024):        world = generate(8, seed)        print('seed =', seed)        print(render(world))        print(Counter(''.join(world)))
目的:演示“随机种子 + 邻接约束”驱动的小规模 WFC 生成,输出 8×8 字符地图并统计瓷砖分布。
  1. grid = [[set(TILES) for _ in range(size)] for _ in range(size)]每个格子初始叠加全部候选瓷砖,模拟“波函数未坍缩”。
  2. def compatible(a, b):双向检查两块瓷砖能否相邻,是约束系统的核心。
  3. def propagate():约束传播:每当邻居确定,就删除当前格子与之冲突的候选。
  4. keep |= {t for t in grid[y][x] if compatible(t, fixed)}只保留能与已确定邻居和平共处的瓷砖。
  5. rng = random.Random(seed)用独立种子构造随机源,保证同种子可重现。
  6. rng.shuffle(cells)随机打乱坍缩顺序,避免固定顺序造成的方向偏置。
  7. grid[y][x] = {rng.choice(sorted(grid[y][x]))}从剩余候选中挑一个,完成该格子的坍缩。
  8. Counter(''.join(world))统计地图中各类瓷砖数量,做最基础的多样性检查。

常见误区

误区程序化内容生成不就是让程序随便随机生成吗?我丢一堆随机数进去,不就得到一堆新地图了?反正都可以无限生成。

正解PCG 的本质是“约束下的随机”——规则定义合法性,随机只负责在合法空间里挑选答案。就像魔法花园的园丁不是乱撒种子,而是按《花园法则》播种:树不能长在水里、路必须连通出口。真正的难点全在规则设计上。

为何常见:PCG 名字里带“程序化”和“生成”,初学者容易望文生义,忽略“内容”前面的“约束”二字。

误区只要随机种子一样,任何情况下生成的东西就完全一样,所以把种子报出来别人就能复现我的世界。

正解同一种子只在“同一套算法、同一版本、同一运行环境”下保证可重现。一旦你改了规则参数、升级了噪声函数、换了浮点运算顺序,哪怕种子没变,结果也会不同。种子锁定的只是“随机选择路径”,不是“整个世界快照”。

为何常见:种子最常见的用途就是在固定版本里做回归测试,大家很容易把这种约定俗成误当成绝对规律。

误区有了 PCG,美术师和关卡设计师都要失业了,AI 直接生成所有内容,人工只需要按一下按钮。

正解PCG 擅长快速产出“量大、结构合规”的初稿,但审美把控、叙事编排、异常修复仍离不开人。很多项目是“PCG 先生成,人工再打磨”——自动铺地形、人工修河流走向。设计师从“手绘者”转型为“立法者 + 评审者”。

为何常见:媒体常渲染“AI 取代人类”,加上 PCG 确实省掉大量重复劳动,让人高估了它对创意决策的替代能力。

误区WFC 不就是从一堆瓷砖里随机挑一块贴上去吗?我也能写,不需要什么波函数坍缩这么玄乎的名字。

正解WFC 的关键在于“约束传播”和“最小熵选择”:每贴一块,所有邻居的候选集都会连锁收窄,而且每次优先挑“最没得选”的格子下手,从而极大降低冲突概率。无脑随机贴瓷砖会频繁产生违和拼接,而 WFC 能在全局维持风格一致。

为何常见:从结果看都是“填满一张网格”,初学者不观察内部决策过程,自然以为只是随机。

误区Perlin 噪声不就是调用 random 函数返回随机数吗?跟随机数生成器没什么区别,用哪个无所谓。

正解普通随机数的每个值相互独立,画出来是满屏雪花;噪声函数则保证空间上平滑连续——相邻坐标的输出值接近,整体呈现自然的起伏。这正是地形、云朵、大理石纹理需要的“有结构的随机”。

为何常见:两者都叫“随机”,且核心都依赖随机种子,初学者容易把“随机值”和“平滑随机场”混为一谈。

面试题

Q1. PCG 的中文全称是?

单选题 · easy

  • A.程序化内容生成← 正确答案
  • B.程序化流程控制
  • C.预测式内容分析
  • D.感知计算图形学

答案:A。PCG 全称 Procedural Content Generation,译为“程序化内容生成”,强调用代码按规则生成内容资产。流程控制、预测分析、感知计算均与“内容资产自动化产出”无关。

Q2. PCG 中“随机种子”的主要作用是?

单选题 · easy

  • A.让结果完全不可预测
  • B.控制并重现生成结果← 正确答案
  • C.提升图形的渲染速度
  • D.自动提高画面分辨率

答案:B。种子是伪随机序列的起点,同一种子可复现同一次生成,是 PCG 可测试、可分享的基石。它不会让结果不可预测(序列可复现),也与渲染速度、分辨率无关。

Q3. Perlin 噪声的特点包括?

多选题 · easy

  • A.输出值平滑而连续← 正确答案
  • B.相邻坐标值较接近← 正确答案
  • C.输出完全随机无关
  • D.常用于地形高度图← 正确答案

答案:ABD。Perlin 噪声的关键是空间连续性:相邻坐标输出接近,整体像自然起伏的波浪,因此特别适合做高度图。C 描述的是普通随机数而非噪声。

Q4. fBm 中叠加多层噪声主要为了?

单选题 · mid

  • A.减少计算资源开销
  • B.增加生成地形的细节← 正确答案
  • C.让输出变得完全随机
  • D.消除所有随机性影响

答案:B。fBm 把低频大振幅和高频小振幅的多层噪声加权相加,低频定轮廓、高频补细节,让地形既有山峦骨架又有岩石纹理。叠加反而增加计算量,也不会消除随机性。

Q5. L-system 中“重写规则”的意义是?

单选题 · hard

  • A.每轮替换全部字符串← 正确答案
  • B.随机删除部分枝条
  • C.保持字符串完全不变
  • D.只负责美化显示效果

答案:A。L-system 的核心是迭代重写:每一轮把当前字符串中可匹配的符号替换成规则右侧的字符串,模拟植物逐层抽枝。它不会随机删除枝条,也不是装饰功能。

Q6. WFC 每次优先坍缩什么格子?

单选题 · mid

  • A.候选瓷砖数最少的← 正确答案
  • B.离屏幕最近的格子
  • C.颜色最鲜艳的格子
  • D.随机选取任意格子

答案:A。WFC 用“最小熵”策略:候选集合越小的格子越“没得选”,优先锁定它可以把后续冲突风险降到最低。这与屏幕位置、颜色、均匀随机都无关。

Q7. 下列哪些属于 PCG 典型应用?

多选题 · mid

  • A.开放世界地形生成← 正确答案
  • B.随机地牢关卡生成← 正确答案
  • C.树木植被批量布置← 正确答案
  • D.玩家社交关系网络

答案:ABC。地形(噪声)、地牢(WFC)、植被(L-system)都是 PCG 的经典战场,具有“大量、可规则化”的特点。玩家社交关系涉及推荐与图算法,不属于内容生成范畴。

Q8. WFC 中某个格子候选集变空说明?

单选题 · hard

  • A.该格子已经全部确定
  • B.约束之间发生冲突← 正确答案
  • C.该格子的熵值最高
  • D.生成过程完美结束

答案:B。候选集为空意味着没有任何瓷砖能与周围已确定的邻居共存,即约束冲突。此时需要回溯、换种子或调整规则,而不是当作“确定”或“结束”。

Q9. PCG 生成质量评估看哪些维度?

多选题 · mid

  • A.视觉是否自然合理← 正确答案
  • B.玩法是否有可玩性← 正确答案
  • C.内容是否有重复感← 正确答案
  • D.生成性能是否达标← 正确答案

答案:ABCD。PCG 质量评估从视觉合理性、玩法可玩性、资源多样性、性能开销四个维度综合打分,任何单一维度都容易顾此失彼,四者缺一不可。

Q10. L-system 中符号 "[" 的主要作用是?

单选题 · extreme

  • A.保存当前分支起始点← 正确答案
  • B.清空已经画出的树枝
  • C.把转向角度立刻清零
  • D.删除最近的抽枝结果

答案:A。在龟形解释器中,[ 把当前位置与方向压栈保存,] 再出栈恢复,从而实现“分支画完回到主干继续生长”。它不清空、不清零角度、不删除结果,是分支结构的关键语法。