想象一下,奶奶有一本祖传菜谱,上面写着一行行“如果看到这个食材,就把它变成那样”的句子。神奇的是,如果你从一棵白菜开始,反复照着菜谱加工,就会发现桌上的菜越来越多、越来越丰盛——最后竟变成一整桌宴席。这就是 L-system(林登梅耶系统)的秘密:1968 年,生物学家林登梅耶观察藻类生长时,发现细胞分裂可以用“不断重写”的规则来描述,于是他把这个观察写成了一套优美的数学模型。今天,游戏里的森林、花园和藤蔓,很多都是靠这套模型“种”出来的——一个游戏美术师画一棵树要一整天,而 L-system 用一行规则,一秒钟就能长出一千棵各不相同的大树。本篇文章将带你从零认识这个“程序化种树”的魔法。

岗位关联

职业价值:掌握 L-system,等于掌握了“用规则代替手绘”的规模化生产能力。无论是开放世界的地表植被、SpeedTree 资产管线,还是运行时 LOD 动态加载,这套思维都是 AI 驱动游戏玩法模块的必备基本功,直接提升你在技术美术与程序化内容生成方向的核心竞争力。

目录
  1. 可视化演示
  2. L-system起源
  3. 文法规则基石
  4. 字符与命令
  5. 递归推演
  6. 随机变体
  7. LOD适配
  8. 植被批量应用
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示L-system产生式文法递归重写、海龟图形分支解释及随机变体控制生成多样化树木的交互式可视化。

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

L-system产生式文法递归重写 公理F经产生式规则递归展开为分支字符串 每轮迭代分支数量指数增长 F → F[+F]F[-F]F L-system文法以递归重写生成自相似分形结构

L-system起源

奶奶的菜谱开头,总有一页“总目录”,写清楚这桌宴席需要哪些食材、第一步从什么开始、后续每步怎么变。L-system 也有这样一本“总目录”——1968 年,生物学家林登梅耶在显微镜下观察一种叫蓝绿藻的植物,发现它的生长规律特别像菜谱:一个细胞按照规则“重写”成两个细胞,两个再变成四个,代代相传,最后长成独特的丝状形态。他把这个观察提炼成一套形式语言,也就是今天所说的 L-system(Lindenmayer System)。

这套“菜谱总目录”在数学上被定义得非常简洁:一个 L-system 由三个东西组成——字符表、公理和产生式规则。字符表是“食材清单”,公理是“初始食材”,产生式规则就是“每一道菜的做法”。三者合在一起,就给出了从一颗种子长成一棵树的完整指令。

就像翻开菜谱先看目录一样,理解 L-system 的第一步就是认清三要素:食材(字符表)、种子(公理)、做法(规则)。目录里的每一条都缺一不可,它们共同决定了一棵树最终会长成什么样。而你要做的,就是像厨师认食材一样,先把这套形式语言“拆开看懂”,再谈后面的生长与变化。

G=(V,ω,P)G = (V, \omega, P)
这个公式像菜谱的“总目录页”:V 是食材清单,ω 是第一步要拿出的初始食材,P 是后续所有做法的集合。
  1. V(字符表)它是整个系统的“食材清单”,列出所有允许出现的符号,例如 F(前进)、+(左转)、[(进栈)等。
    V={F,+,−,[,]}V = \{\text{F}, +, -, [, ]\}
  2. ω(公理)它是花园里的“第一颗种子”,整个生长过程都由这一个初始字符串出发。
    ω=F\omega = \text{F}
  3. P(产生式规则集)它是“菜谱集合”,每条规则告诉系统一个符号应当被替换成什么字符串。
    P={α→χ∣α∈V, χ∈V∗}P = \{\alpha \rightarrow \chi \mid \alpha \in V,\ \chi \in V^*\}

文法规则基石

菜谱上最常见的一行字是“一杯面粉 → 一个面团”。在 L-system 里,这种“如果看到什么,就把它变成什么”的句子,叫作产生式规则。它是整套系统的心脏。一个符号 F,一旦被规则命中,就立刻被替换成它后面的字符串——比如 F 变成 F[+F]F[-F]F,这就是植物长出第一个枝条分叉的时刻。

产生式规则在数学上写作 α → χ,读作“α 被重写为 χ”。α 是规则的前件,χ 是规则的后件。L-system 有一个非常优雅的特性:所有规则在每一代都是并行应用的。也就是说,当字符串里有 100 个 F 时,系统不会一个一个地慢慢替换,而是在同一时刻把这 100 个 F 同时换成各自的子字符串。这种“全场同步换装”正是模仿了自然界中细胞同步分裂的现象。

回过头来想,这行规则就像菜谱上的“白菜 → 醋溜白菜”。每次看到白菜,就把它变成一盘新菜,而且整桌的食材是同时处理的——不需要排队等待。正是这种“一行规则 + 全员并行”的机制,让 L-system 能在一夜之间,把一根光秃秃的枝干变成枝繁叶茂的树冠。

α→χ\alpha \rightarrow \chi
一行规则读作“所有 α 同时变成 χ”,就像菜谱上写着“所有白菜都切成丝”,一锅白菜在同一秒全部完成切配。
  1. α(前件)它指明“哪些食材需要被处理”,是规则的触发条件。
    α=F\alpha = \text{F}
  2. →(重写方向)它表示“被替换为”,是规则的核心动作,方向不可逆。
    F→F[+F]F[-F]F\text{F} \rightarrow \text{F[+F]F[-F]F}
  3. χ(后件)它是替换后的新字符串,决定了这个符号下一步长成什么样。
    χ=F[+F]F[-F]F\chi = \text{F[+F]F[-F]F}

字符与命令

菜谱光有食材当然不够,还得有“切、炒、翻面”这些动作指令。L-system 的字符表里,除了被重写的 F,还藏着一批“动作符号”——它们不参与重写,而是负责告诉绘制者(常常叫作“海龟”)该怎么动笔。F 表示向前画一条线段,+ 表示向右转一个固定角度,- 表示向左转同样角度,[ 表示把当前位置和朝向压入栈中保存,] 表示从栈中弹出并恢复之前保存的状态。

这套机制在计算机图形学里叫龟形解释(Turtle Graphics)。想象一只小海龟背着画笔在沙滩上爬:F 是它向前爬并留下痕迹,+ 和 - 是它在原地转个方向,[ 是它掏出一个纸条记下“我现在在这,面朝这边”,] 则是掏出纸条按原路返回继续画。这样,一棵树的分叉结构就被完美地拆解成一条连续的行走路线。

你可以把这套字符指令理解成“写给厨师的动作卡”:F 是“下锅翻炒”,+ 是“掂勺向右”,- 是“掂勺向左”,方括号是“暂时把锅放回架子上,待会儿再取”。每一张动作卡都对应一个具体的几何效果,而正是这一串连贯的动作,让抽象的字符串变成了眼前有血有肉的树形结构。

x′=x+d⋅cos⁡θ,y′=y+d⋅sin⁡θx' = x + d \cdot \cos\theta, \quad y' = y + d \cdot \sin\theta
这就是海龟每走一步的坐标更新:把“前进 d 步”拆成水平方向加垂直方向的两段位移,角度 θ 决定了它面朝哪个方向。
  1. x, y(当前位置)海龟脚下踩着的坐标点,是每次移动的起点。
    (x,y)(x, y)
  2. θ(当前朝向)海龟面朝的角度,+ 和 - 就是修改这个角度值。
    θ=θ0+rotation\theta = \theta_0 + \text{rotation}
  3. d·cosθ 与 d·sinθ(位移分解)把斜向的步长 d,分解成 x 方向的增量 d·cosθ 和 y 方向的增量 d·sinθ。
    d⋅cos⁡θ,d⋅sin⁡θd \cdot \cos\theta, \quad d \cdot \sin\theta

递归推演

有了菜谱和动作指令,接下来就是最关键的一步:反复使用。L-system 的长成过程不是一步到位的,而是从公理出发,一代一代地迭代。把当前字符串里的每一个可重写符号,用产生式规则替换成新字符串,得到下一代的字符串,然后再对下一代做同样的操作——如此往复,直到达到预定的迭代次数。

这个迭代过程可以写成非常简洁的递推式:第 0 代就是公理本身,第 n+1 代由第 n 代应用一次规则得到。每一代都比上一代多出更多分支、更长路径,就像菜谱被一遍又一遍地执行:一开始只是一个小面团,几轮之后桌面上摆满了饺子、面条和馒头。

递归推演是 L-system 生长的引擎,但也是性能的“火药桶”。字符串长度会随迭代轮数呈指数级暴涨——规则 F → FF 迭代 4 次就有 16 个 F,而带分支的规则膨胀得更快。就像面团发酵过头会撑破盆子一样,迭代次数一旦失控,内存和渲染开销会瞬间爆炸。聪明的做法,是像厨师控制火候那样,选一个恰到好处的迭代深度。

Ln+1=P(Ln)L_{n+1} = P(L_n)
这行式子说的是:把上一代的字符串整体丢进规则集 P 里“加工一遍”,出来的就是下一代。反复加工 n 次,就是从种子到参天大树的全过程。
  1. Lₙ(第 n 代字符串)它是迭代到第 n 轮时的中间产物,例如 L₀ = F。
    L0=ω=FL_0 = \omega = \text{F}
  2. P(Lₙ)(应用规则)把规则集 P 中的每一条产生式,同时作用到 Lₙ 中所有匹配的符号上。
    L1=P(F)=F[+F]F[-F]FL_1 = P(\text{F}) = \text{F[+F]F[-F]F}
  3. n+1(下一代索引)每应用一次规则,代数加一,字符串随之膨胀一轮。
    Ln+1=P(Ln),n=0,1,2,…L_{n+1} = P(L_n), \quad n = 0, 1, 2, \dots

随机变体

同一道菜,奶奶每次做出来味道都有一点点不同——今天多放一撮盐,明天少放半勺糖。L-system 也继承了这种“看心情”的智慧:给产生式规则加上概率,就成了随机 L-system。同一条规则可以准备多个候选替换串,每个候选带一个概率权重。应用规则时就像掷骰子,按概率挑一个来替换。

这样一来,一百棵树可以用同一套规则生成,却棵棵不同。有的树冠宽阔,有的树形高挑,有的分枝稀疏——但每一棵都保留着“这棵树”的基本轮廓。随机并不会让树变得面目全非,而是在规则的框架内制造自然的变异,就像同一道菜,万变不离其宗,始终是奶奶的味道。

随机变体就是 L-system 的“调味哲学”:规则是底料,概率是火候,随机种子是掌勺人的心情。游戏里的森林之所以看上去生机勃勃,正是因为这千万棵树共享着同一套规则,却各自拥有了属于自己的“随机命运”。每一棵都来自同一棵种子,却都长成了独一无二的模样。

α→χ1 (p1)∣χ2 (p2),p1+p2=1\alpha \rightarrow \chi_1\,(p_1) \mid \chi_2\,(p_2), \quad p_1 + p_2 = 1
这条规则的意思是:当遇到 α 时,有 p₁ 的概率变成 χ₁,有 p₂ 的概率变成 χ₂。所有备选概率加起来必须等于 1,保证“总有一种会发生”。
  1. χ₁ 与 χ₂(候选替换串)它们是同一个符号 α 的两种“变身方案”,对应不同的生长走向。
    χ1=F[+F]F[-F]F,χ2=F[++F][–F]\chi_1 = \text{F[+F]F[-F]F}, \quad \chi_2 = \text{F[++F][--F]}
  2. p₁ 与 p₂(概率权重)每个方案被选中的机会,p 越大越常出现。
    p1=0.7,p2=0.3p_1 = 0.7, \quad p_2 = 0.3
  3. |(互斥选择标记)竖线表示“二者选其一”,应用规则时只能挑一个方案执行。
    α→χ1 或 χ2\alpha \rightarrow \chi_1 \ \text{或} \ \chi_2
  4. p₁ + p₂ = 1(概率完备性)所有备选方案的概率之和必须等于 1,保证不会出现“无方案可用”的局面。
    p1+p2=1p_1 + p_2 = 1

LOD适配

站在山顶眺望整片森林,你看到的只是连绵的绿色;但当你走到一棵树下,才会看清枝干的纹理和叶片的脉络。游戏开发里的 LOD(Level of Detail,细节层级)正是模拟这种“距离感”:远处的树不需要每根枝条都画出来,近处的树才需要高精度模型。如果所有树木都用最高细节渲染,再强大的 GPU 也会被瞬间拖垮。

L-system 对 LOD 有天然的优势:它的复杂度完全由迭代次数控制。近处的树可以迭代 5-6 轮,枝条丰富,栩栩如生;远处的树迭代 2-3 轮,只保留基本轮廓;再远的树甚至可以直接退化成一张十字交叉的公告板(Billboard)贴图。从完整网格到简化网格再到公告板,这三级“菜品”对应了从满汉全席到工作简餐的层层递进。

LOD 适配就像一位精明的宴席大厨:远处宾客只看得到宴席的轮廓,那就端上摆盘漂亮的“简餐”;近处主宾需要细品风味,才端出工序繁复的“大菜”。既保全了整桌宴席的观感,又不至于让后厨(GPU)累得趴下。在引擎植被系统中,LOD 往往与 L-system 的迭代深度直接绑定,成为性能优化的第一道护城河。

Nk=N0×2−kN_k = N_0 \times 2^{-k}
意思是:第 k 级 LOD 的多边形数量,是最近处基准数量 N₀ 的 2⁻ᵏ 倍。每增加一级,面数减半,渲染负担也随之大幅下降。
  1. N₀(基准面数)摄像机最近处的树木使用最完整的几何体,拥有最多多边形。
    N0=100000N_0 = 100000
  2. k(LOD 等级)离摄像机越远,等级越高,k = 1, 2, 3…… 对应越来越简化的细节层级。
    k=1,2,3,…k = 1, 2, 3, \dots
  3. 2⁻ᵏ(衰减系数)每一级把面数除以 2,呈指数级下降,远处的树只需极低的面数即可表现轮廓。
    2−1=0.5,2−2=0.25,2−3=0.1252^{-1} = 0.5, \quad 2^{-2} = 0.25, \quad 2^{-3} = 0.125

植被批量应用

一旦你拥有了奶奶的菜谱,就可以把它开成连锁餐厅——批量复制,千店千面。L-system 在游戏产业里的落地,正是这个逻辑:用一份产生式规则作为“配方”,配合不同的随机种子作为“分店编号”,瞬间生成成千上万棵各不相同的树。SpeedTree 等专业植被工具,以及 Unreal、Unity 的植被系统,都内置了 L-system 生成器,技术美术只需调整规则和参数,就能批量产出整片森林。

在实际引擎流程中,L-system 的输出通常先被转成网格(Mesh),再经过 LOD 层级划分和碰撞体简化,最终被实例化渲染(Instanced Rendering)批量提交给 GPU。一棵树的模板只需要加载一次,却能通过坐标变换在场景中复现上千次。这种“一份配方、万份成品”的管线,让开放世界的百万棵树木成为现实。

从一条规则到一片森林,L-system 走完了一条完整的工业化之路:文法负责“定骨架”,随机负责“造个性”,LOD 负责“控成本”,引擎负责“上餐桌”。就像奶奶的菜谱从家庭厨房走向连锁餐厅,L-system 也让程序化内容生成从实验室走进了一个又一个开放世界,成为 AI 驱动游戏玩法的核心技术支柱。

Tk=G(seedk)T_k = G(\text{seed}_k)
这句公式是批量生成的总开关:第 k 棵树 T_k,就是 L-system 生成器 G 吃下第 k 个随机种子后产出的结果。一份配方 G,配上不同的种子,就有千千万万棵树。
  1. G(生成器)它代表整套 L-system 规则与解释器的组合,是把字符串变成几何体的“中央厨房”。
    G=(V,ω,P)G = (V, \omega, P)
  2. seed_k(第 k 个随机种子)它是第 k 棵树的“身份证号”,决定了这棵树的随机变体走向。
    seedk=1000+k\text{seed}_k = 1000 + k
  3. T_k(第 k 棵树)它是由种子 seed_k 唯一确定的一棵树,可用于场景摆放、碰撞和渲染。
    Tk=G(seedk)T_k = G(\text{seed}_k)

AI 应用场景

开放世界森林批量生成

阶段:程序化内容生成(PCG)

解决的问题:开放世界往往需要数万棵形态各异的树木,人工建模一棵精品树需要 3-5 个工作日,手工铺满整个地图在成本和工期上完全不可行。L-system 用一份规则 + 一万个随机种子,在场景加载时批量生成整片森林,既保证多样性又控制人力成本。

具体案例:某开放世界项目使用一条 10 行的 L-system 规则,配合 1 万个随机种子,在关卡流送时生成了整个山谷的植被群。内存中仅需保存 3 个高精度模板 + 每棵树的种子号,渲染时实例化绘制,帧率稳定在 60 FPS。

SpeedTree 程序化资产流水线

阶段:DCC 工具程序化辅助

解决的问题:SpeedTree 这类专业植被工具内置了 L-system 模块,允许技术美术先用文法生成树的基础骨架,再在工具中手调细节。传统方式完全手雕每根树枝,效率极低;现在 80% 的枝干结构由 L-system 自动生成,美术只负责最后的“精修”。

具体案例:某 3A 工作室的植被团队为 30 个树种各设计了一套产生式规则,配合 LOD 预设,一次导出 LOD0-LOD3 四级网格和 Billboard 公告板,整个资产管线从 6 个月压缩到 6 周。

运行时动态 LOD 森林

阶段:引擎运行时优化

解决的问题:一次性展开整片森林的全部 L-system 细节会导致卡顿。引擎运行时根据玩家与树木的距离,动态调整 L-system 的迭代次数——近处迭代 5 轮、远处迭代 2 轮、极远处直接切换公告板。

具体案例:某开放世界项目在 Unreal Engine 中把 L-system 迭代深度绑定到距离阈值:玩家靠近时树木从 LOD2 平滑切换到 LOD0,枝干逐级细化,远处视景的三角面数减少了 78%,高空俯瞰帧率提升近一倍。

Python 演示

python68 行
import random  class LSystemForest:    """一个极简的 L-system 森林生成器(教学演示版)"""     def __init__(self, axiom, rules, iterations):        self.axiom = axiom        self.rules = rules        self.iterations = iterations     def generate(self, seed=None):        """反复迭代产生式,生成结构化字符串"""        if seed is not None:            random.seed(seed)        current = self.axiom        for _ in range(self.iterations):            nxt = []            for ch in current:                if ch in self.rules:                    candidates = self.rules[ch]                    r = random.random()                    acc = 0.0                    for rule, prob in candidates:                        acc += prob                        if r <= acc:                            nxt.append(rule)                            break                    else:                        nxt.append(candidates[0][0])                else:                    nxt.append(ch)            current = ''.join(nxt)        return current     @staticmethod    def count_branches(s):        """统计方括号对数,粗略估算分支数量"""        return s.count('[')     @staticmethod    def estimate_polygons(s):        """按每 4 个 F 折合 1 个圆柱段,粗估几何复杂度"""        return s.count('F') // 4  def demo_forest():    # 一条"通用树"配方:F 长枝,[ ] 分叉,+/- 转向    rule_common = [        ("F[+F]F[-F]F", 0.7),        ("F[++F][--F]", 0.3),    ]    system = LSystemForest(axiom="F", rules={"F": rule_common}, iterations=4)     for tree_id in range(3):        text = system.generate(seed=tree_id)        print(f"第 {tree_id} 棵树: 字符数={len(text)}, "              f"分支数={system.count_branches(text)}, "              f"估算面数={system.estimate_polygons(text)}")     print("--- 同一颗种子可复现:")    a = system.generate(seed=42)    b = system.generate(seed=42)    print("种子 42 两次生成结果一致:", a == b)  if __name__ == "__main__":    demo_forest()
目的:这是一个完整可运行的 L-system 森林生成器,演示了“一份规则 + 不同随机种子 = 多样化树木”的核心思想。运行后你会看到三棵不同形态的树的字符串规模统计,以及随机种子保证可复现的特性。
  1. def generate(self, seed=None):核心生成方法,接收随机种子,返回最终 L-system 字符串
  2. random.seed(seed)用种子初始化随机数,保证同一种子生成完全相同的树
  3. if ch in self.rules:检查当前字符是否在规则表中,决定是否进行重写
  4. candidates = self.rules[ch]取出该字符的所有候选规则(带概率权重)
  5. r = random.random()掷一枚 0~1 之间的骰子,决定采用哪条候选规则
  6. nxt.append(rule)将选中的替换串加入新一代字符串
  7. nxt.append(ch)对不在规则表中的字符(如 +、-、[、])原样保留
  8. current = ''.join(nxt)把逐字符拼接的列表合并为新一代完整字符串
  9. s.count('[')统计方括号数量,用于估算这棵树的分支数
  10. s.count('F') // 4用 F 的个数粗估几何复杂度,帮助做 LOD 决策

常见误区

误区我一直以为 L-system 就是“种树插件”,除了生成花花草草,其他场景根本用不上它。

正解L-system 本质是一套通用的“重写系统”,只要目标能被拆解成“一个符号反复替换成更复杂结构”的规律,它就能派上用场。城市道路网、建筑立面、溶洞管道、甚至音乐节奏都能用 L-system 生成。它就像奶奶的菜谱,把简单食材不断加工成宴席——食材不限于白菜,宴席也不限于素菜。

为何常见:因为 L-system 最经典、最直观的演示就是植物生长,教程和案例都围绕着树和花,大家自然形成了“种树专用”的刻板印象。

误区想让树更逼真,那就把迭代次数调到 10 轮 20 轮,越复杂越真实,反正电脑跑得动。

正解迭代次数每增加 1,字符串长度按指数膨胀。迭代 4 轮的字符串可能只有几百字符,迭代 8 轮就变成几十万字符,迭代 12 轮直接撑爆内存。真实项目的做法是控制迭代在 3-6 轮,再结合 LOD 分级,让近处的高迭代和远处的低迭代各司其职。就像发面,发过了头,面团就酸了。

为何常见:初学时会直观地把“更多迭代 = 更多细节 = 更好效果”,忽略了字符串爆炸带来的内存与渲染成本,直到程序卡死才意识到问题的严重性。

误区既然加上了随机,那树就随便长了,想怎么长怎么长,跟抛硬币一样没有规律。

正解随机 L-system 的“随机”发生在一组明确的候选规则之间,每条规则仍然严格定义“符号变成什么”。随机只是决定了用哪条规则,不会打破文法本身的约束。好比你掷骰子决定今天吃面还是吃饭——无论骰子怎么滚,出的都是菜,不会滚出一块石头。

为何常见:因为“随机”这个词在直觉上等于“不可控”,初学者容易忽略概率权重和规则边界的存在,把随机变体误解成完全无序的噪声。

误区L-system 生成的树这么有自相似性,它不就是分形吗?反正都是递归生成,换了个名字而已。

正解两者关系密切但不等同。分形强调几何体在任何尺度下都具有自相似结构,是一种数学对象;L-system 则是一种“重写系统”,描述了如何通过规则迭代生成字符串,再由解释器转成几何。L-system 可以生成非分形的结构,比如有限深度的植物、带随机扰动的形态;分形也可以用 L-system 之外的方法生成,比如迭代函数系统(IFS)。

为何常见:因为经典的 L-system 树形在视觉上恰好呈现出强烈的自相似性,教程中两者又经常一起出现,容易让人把“相似”当成“相同”。

面试题

Q1. L-system 中作为整个生长过程出发点的初始字符串称为?

单选题 · easy

  • A.公理← 正确答案
  • B.定理
  • C.规则集
  • D.迭代器

答案:A。公理(Axiom)是 L-system 三要素之一,它是迭代生长的起点字符串。规则集是产生式规则的集合,定理和迭代器与 L-system 无关。就像菜谱的“初始食材”,公理决定了整盘宴席的第一把面粉。

Q2. 在龟形解释中,字符 + 通常表示什么动作?

单选题 · easy

  • A.向左旋转角度
  • B.向右旋转角度← 正确答案
  • C.向前移动一步
  • D.压入栈保存状态

答案:B。在标准 L-system 约定中,+ 表示海龟向右旋转固定角度,- 向左旋转,F 前进,[ 和 ] 负责栈的压入与弹出。就像掂勺时向右翻是 +,向左翻是 -,动作方向必须分清。

Q3. 公理为 F,产生式为 F → F[+F]F[-F]F,迭代 2 次后,字符串中 [ 的数量是多少?

单选题 · mid

  • A.4
  • B.6
  • C.8← 正确答案
  • D.16

答案:C。第一次迭代把 F 替换为 F[+F]F[-F]F,产生 2 个 [,此时共有 4 个 F。第二次迭代每个 F 再次替换,每个替换串又含 2 个 [,所以总数为 4×2=8。这是 L-system 字符串指数膨胀的一个典型缩影。

Q4. L-system 字符串经过多轮迭代后,长度通常如何变化?

单选题 · easy

  • A.线性增长
  • B.指数增长← 正确答案
  • C.保持不变
  • D.先增后减

答案:B。如果规则导致一个符号替换为多个符号,字符串长度随迭代轮数呈指数级增长。比如 F→FF 每轮翻倍,4 轮后达到 16 个字符。这是 L-system 必须结合 LOD 控制开销的根本原因,就像面团越发酵越大。

Q5. 场景中一棵树离摄像机很远时,最合理的 LOD 处理方式是?

单选题 · mid

  • A.简化结构渲染← 正确答案
  • B.增加迭代次数
  • C.替换为最高模
  • D.不做任何处理

答案:A。远处的树在屏幕上只占很少像素,应该降低迭代次数、用简化网格甚至公告板渲染。增加迭代或使用高模会白白浪费 GPU 性能,不做处理则完全失去了 LOD 优化的意义,正如远处看不清的细节不必端上桌。

Q6. 随机 L-system 中,某符号的所有候选产生式概率之和必须满足什么?

单选题 · hard

  • A.必须小于 1
  • B.必须等于 1← 正确答案
  • C.必须大于 1
  • D.没有约束

答案:B。概率完备性要求 p₁+p₂+……+pₙ=1,保证每次遇到该符号时,必然能选中一个候选规则,不会出现“无规则可用”的异常。这就像掷骰子,六个面概率加起来必须正好是 100%,否则总有掷空的时候。

Q7. L-system 与纯分形几何学的核心区别在于?

单选题 · hard

  • A.两者本质上完全相同
  • B.L-system 是重写系统,分形强调自相似几何← 正确答案
  • C.分形无法用程序生成
  • D.L-system 只能生成植物

答案:B。L-system 是一种形式语言重写机制,关注“符号如何被替换”;分形是一种自相似几何对象的数学特性。两者可以互相借用,但概念层级不同:L-system 是“生成过程”,分形是“几何属性”,正如菜谱是过程,而蛋糕的千层结构是结果。

Q8. 下列哪些属于 L-system 的三个核心要素?

多选题 · easy

  • A.字符表← 正确答案
  • B.公理← 正确答案
  • C.产生式规则← 正确答案
  • D.全局光照

答案:ABC。L-system 三要素是字符表 V、公理 ω、产生式规则集 P,它们分别对应“食材清单”“第一颗种子”“菜谱做法”。全局光照是渲染技术,与 L-system 的形式化定义无关。

Q9. L-system 在游戏植被批量生成中的优势包括?

多选题 · mid

  • A.少量规则描述复杂结构← 正确答案
  • B.可随机生成大量变体← 正确答案
  • C.每棵树需要手工雕刻
  • D.天然适配 LOD 动态调整← 正确答案

答案:ABD。L-system 用极简规则生成复杂树冠,结合随机种子能批量产出多样性树木,迭代次数控制又天然契合 LOD。手工雕刻每棵树正是传统流程的痛点,也是 L-system 要取代的对象。

Q10. 一棵 L-system 树需要跨越多个 LOD 级别渲染时,正确的策略有?

多选题 · extreme

  • A.近处保留完整文法展开← 正确答案
  • B.远处只做等比缩小不简化
  • C.远处降低迭代次数← 正确答案
  • D.远处改用公告板替代← 正确答案

答案:ACD。LOD 正确策略是“近处高细节、远处低细节”:近处完整展开文法以确保视觉质量,远处降低迭代次数保留轮廓,极远处用公告板贴图替代网格。等比缩小不改变面数,无法带来性能收益,所以 B 是错误做法。这就像宴席上近宾吃大菜、远宾看摆盘。