想象你准备搬家,屋里塞满了书架、桌椅和几十个收纳箱,搬家师傅看着卡车直摇头:“装不下!”怎么办?聪明的做法有两手:一手是“合并”——把两把造型相似的椅子捆成一件,把零碎的小物件归进同一个收纳盒;另一手是“精简清单”——在箱子上用编号和简写,代替“三层橡木书柜×1”这种冗长描述。3D模型的世界里,那些由成百上千万个三角形拼成的“数字雕塑”,也卡在同样的困境:游戏场景加载慢、网页模型转半天。网格压缩与简化,就是那套“搬家哲学”:把相似的几何合并成更大的面,用数学风险值判断哪些合并不伤外观,再把数据记录得更加紧凑,最后按观看距离决定展示的精细程度。读完这篇文章,你会掌握从几何简化到数据压缩的完整链路,亲手让模型“瘦身”。

岗位关联

网格压缩是3D渲染性能优化的核心技能,掌握边坍缩、误差度量与编码压缩,能让模型加载提速数倍——游戏、Web3D与AR从业者的必备内功。

目录
  1. 可视化演示
  2. 网格压缩概述
  3. 边坍缩算法
  4. 二次误差度量
  5. 顶点数据编码
  6. LOD多级细节
  7. 质量评估与落地
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示边坍缩操作流程、二次误差度量QEM边选择策略、以及LOD多级细节自动生成的完整网格简化过程。

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

边坍缩简化操作 选择误差最小的边进行坍缩 合并两端顶点移除相邻三角形 Collapse(v1, v2) -> v_new, remove adjacent triangles 边坍缩是网格简化的核心原子操作

网格压缩概述

搬家这天,师傅一脸为难:卡车就那么大,可屋里光是椅子就有八把、书有三箱。你想了想,决定做两件事:一是把相似的物件合并装箱,减少“件数”;二是在纸箱上只写编号,省得每次搬抬都读一长串物品名。3D模型的世界里,这个“卡车”就是显存和带宽。

一个3D模型由顶点和三角形面组成。一个手游场景动辄几百万个三角形,每个顶点又要记录坐标、法线、颜色等多份属性,数据量瞬间爆炸。所谓网格压缩,就是两条路:一是“几何简化”,通过算法减少三角形数量——相当于把零碎物件合并;二是“数据压缩”,用更紧凑的格式存储顶点信息——相当于写更精简的装箱单。两者常常配合使用,先在几何上瘦身,再在存储上压一压。

要估算一个模型占多大空间,可以用一个简单的公式:

从公式可以看出,压缩的三个发力点刚好对应着三处乘数:减少顶点数(边坍缩)、削减属性(简化数据)、缩小字节数(量化编码)。这也是后面各节的路线图。回到搬家现场:你要做的不只是把物件“塞进去”,而是让每件东西都物尽其用——3D模型压缩的智慧,正在于用最小的数据承载最多的视觉信息。

D=V×A×B\displaystyle D = V \times A \times B
公式含义:模型数据量 \(D\) 等于顶点数 \(V\) 乘以每个顶点的属性数 \(A\),再乘以每个属性占用的字节数 \(B\)。
  1. 顶点数 \(V\)网格包含多少个顶点,顶点越多模型越精细,数据量也越大。
    VV
  2. 属性数 \(A\)每个顶点除了坐标,还带法线、纹理坐标、颜色等,属性越多开销越大。
    AA
  3. 字节数 \(B\)每个属性用多少字节存储,32位浮点占4字节,16位半浮点占2字节。
    BB

边坍缩算法

搬家公司给了你一个建议:两把藤编椅,远看造型几乎一样,为什么不捆成一件?你果断照办。网格简化里的“边坍缩”,就是这个动作——把两个靠得很近、几乎不增加视觉信息的顶点,合并成一个新顶点,于是它们之间的那条边消失了,周围的三角形也随之减少。

为什么会减少三角形?请看:一条边连接着两个顶点,这条边通常被两个三角形共享。把两个顶点合并成一个之后,共享这条边的两个三角形各损失一个顶点,塌成一条线,只好被移除;与此同时,这两个三角形的四条外边界两两接合,重新闭合出两个“瘦身成功”的三角形。一次坍缩,三角形数量净减2。如果反复执行,网格就一圈圈地瘦下来。

那么,该坍缩哪条边?最简单的策略是“贪心”:每次都挑最短的那条边——它在视觉上最不明显,合并后最不易察觉。这个过程可以写成:

现实中的网格简化不会只靠“最短边”这个单一指标,而是要综合考虑“合并后会带来多大视觉变形”。这正是下一节二次误差度量要解决的问题。回到搬家现场,你现在学会了第一步:把相似的物件归拢到一起——但怎么判断“哪些物件合并了不心疼”?得先学会量一量合并的代价。

Collapse(vi,vj)→v′\displaystyle \text{Collapse}(v_i, v_j) \rightarrow v'
公式含义:选取一条以 \(v_i\) 和 \(v_j\) 为端点的边,把这对顶点合并成一个新顶点 \(v'\),原边随之消失。
  1. 选定边在网格上挑选一条边 \(e\),起点为 \(v_i\),终点为 \(v_j\)。
    e=(vi, vj)e = (v_i,\ v_j)
  2. 合并顶点生成一个新顶点 \(v'\),取代 \(v_i\) 和 \(v_j\) 的位置。
    v′=vi+vj2v' = \frac{v_i + v_j}{2}
  3. 更新连通关系把原来连到 \(v_i\)、\(v_j\) 的所有三角形的指针指向 \(v'\)。
    {vi, vj}→v′\{v_i,\ v_j\} \rightarrow v'
  4. 移除退化面共享边 \(e\) 的两个三角形因顶点重合而退化,从网格中删除。
    faces←faces∖{degenerate pairs}\text{faces} \leftarrow \text{faces} \setminus \{\text{degenerate pairs}\}

二次误差度量

合并两件家具前,你心里得盘算:这两件东西拼一起,会不会互相挤压、把表面磕坏?搬家公司有一种评估法——每件物件都标记一个“损伤系数”,合并后如果系数很小,说明几乎无伤大雅;如果系数很大,就得换个对象合并。QEM(二次误差度量)就是网格简化界的“损伤系数”。

为了定义这个“损伤”,我们先看单个顶点。一个顶点被多个三角形共用,每个三角形都处在一个平面上。顶点一旦移动,它到每个关联平面的距离就会变化——距离的平方和就是“移动的代价”。用线性代数的语言,每个平面可以用一个4维向量 \(p = [a, b, c, d]^{\top}\) 表示(平面方程 \(ax+by+cz+d=0\)),点到平面的带符号距离等于 \(p^{\top} [x, y, z, 1]^{\top}\)。距离平方累加后,优雅地化简成了一个二次型:

得到 \(Q_v\) 之后,误差计算从“求和”变成了一个矩阵乘法:\(\Delta(v) = \tilde{v}^{\top} Q_v \tilde{v}\)。坍缩一条边时,新顶点的Q就是两个端点Q之和。更有意思的是,如果我们希望新顶点的误差最小,可以解一个线性方程组:

工程实现时,通常把每条边的坍缩代价预先算好放进优先队列(最小堆),每次弹出代价最小的边进行坍缩,再更新受影响的邻居顶点代价。回到搬家比喻:QEM就像你手里的“风险评估表”,你不再凭感觉“看哪两件离得近”,而是精确地知道合并哪一对“损伤最小”——于是每搬一件,心里都有底。

Δ(v)=∑p∈Π(v)(p⊤v~)2=v~⊤(∑p∈Π(v)pp⊤)v~\displaystyle \Delta(v) = \sum_{p \in \Pi(v)} \left( p^{\top} \tilde{v} \right)^2 = \tilde{v}^{\top} \left( \sum_{p \in \Pi(v)} p p^{\top} \right) \tilde{v}
公式含义:把误差函数对 \(x, y, z\) 求导并令导数为零,得到的 \(v^{*}\) 就是能让变形最小的最优新位置。
  1. 求导置零对误差函数分别关于 \(x, y, z\) 求偏导,令其等于0。
    ∂Δ∂x=∂Δ∂y=∂Δ∂z=0\frac{\partial \Delta}{\partial x} = \frac{\partial \Delta}{\partial y} = \frac{\partial \Delta}{\partial z} = 0
  2. 线性方程组求导结果化简为一个3×3的线性方程组 \(A x = -b\)。
    Qv[xyz1]=0Q_v \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = 0
  3. 矩阵求逆若 \(Q_v\) 可逆,最优位置就是 \(Q_v^{-1}\) 最后一列的前三个分量。
    v∗=Qv−1[0, 0, 0, 1]⊤v^{*} = Q_v^{-1} [0,\ 0,\ 0,\ 1]^{\top}

顶点数据编码

行李终于打好了包,接下来是写装箱单。你不必在箱子上写“直径三十厘米的圆形玻璃茶几”,只需写“T3”——一个编号省下十几个字。顶点数据压缩的思路同出一辙:浮点坐标太“啰嗦”,我们要用更紧凑的方式记录它们。

第一个手段是“量化”。一个32位浮点数用4个字节,可以表达极大的动态范围,但对大多数模型而言,几何坐标的范围并不大。我们可以把整个模型的包围盒内坐标映射到一个整数网格上:先找到所有坐标的最小值和最大值,然后把每个坐标值映射到 \(2^n\) 个整数格子之一。比如用16位,坐标从“任意小数”变成“0到65535的整数”,存起来直接省一半空间:

第二个手段是“增量编码”。仔细观察顶点序列会发现:相邻顶点的坐标数值往往差得很小。如果只存储“与前一个顶点的差值”,差值通常远小于原始数值,需要的位数就少得多。这种利用空间局部性消除数值冗余的手法,正是增量编码:

再往后还有熵编码(对出现频率高的差值用短码,低频用长码,如Huffman编码)和索引优化(把三角形顶点索引按邻接关系重排提高缓存命中率)。这些手段叠加后,顶点数据常常能再压掉一半以上。回到装箱单:编号、差值、短码——每一条都在用更少的笔墨记录同样多的信息,让模型在磁盘和网络里“跑得更快”。

v^=⌊v−vmin⁡vmax⁡−vmin⁡×2n⌋\displaystyle \hat{v} = \left\lfloor \frac{v - v_{\min}}{v_{\max} - v_{\min}} \times 2^{n} \right\rfloor
公式含义:第 \(i\) 个顶点的编码值 \(d_i\),等于它与前一个顶点 \(\hat{v}_{i-1}\) 的差值。
  1. 取差值计算相邻两个顶点量化值的差。
    di=v^i−v^i−1d_i = \hat{v}_i - \hat{v}_{i-1}
  2. 解码还原解码时从第一个顶点开始逐个累加差值即可恢复全部坐标。
    v^i=v^i−1+di\hat{v}_i = \hat{v}_{i-1} + d_i

LOD多级细节

行李打包好了,但你突然意识到:搬家卡车不是一夜之间开到的,它从远处驶来时,你其实只会看见一个轮廓。网格世界也是同理:远处的山、大街上的人群,真的需要动辄几十万个三角形吗?LOD(Level of Detail,多级细节)技术的回答是:不需要。

LOD的思路是:为同一个模型准备多个“精细度”版本——高模(细节最丰富)、中模、低模。摄像机离得远,就显示低模;离得近,才换高模。渲染引擎每帧只需考虑“当前应该用哪一级”,相当于搬家时先送必需行李,远处运输只载轮廓。关键在于切换时机:切换点设得太近,用户会看到“跳变”;设得太远,低模的粗糙又会被察觉。经典做法是用误差阈值来推算切换距离:

更进阶的“连续LOD”甚至不准备离散的几个版本,而是利用前文的边坍缩序列,按需生成任意精度的网格——用多少三角形,现场“解包”即可。这就像搬家时按需打开箱子:远看先打开“轮廓箱”,走近再打开“细节箱”。LOD与边坍缩一脉相承:没有高效的简化算法,就没有高、中、低模的快速生产。

dswitch=k⋅εmax⁡\displaystyle d_{\text{switch}} = k \cdot \sqrt{\varepsilon_{\max}}
公式含义:切换距离 \(d_{\text{switch}}\) 与当前级别的最大几何误差 \(\varepsilon_{\max}\) 的平方根成正比,\(k\) 是一个经验常数。
  1. 误差提取\(\varepsilon_{\max}\) 是这一级模型与上一级(更精细级)之间用QEM等指标算出的最大偏差。
    εmax⁡=max⁡vΔ(v)\varepsilon_{\max} = \max_v \Delta(v)
  2. 开方放大误差取平方根后,视觉上可感知的变形随距离的平方衰减,开方让切换点落在合适距离。
    εmax⁡\sqrt{\varepsilon_{\max}}
  3. 经验系数乘以常数 \(k\),把度量单位从“模型空间”换算到“世界空间”。
    d=k⋅εmax⁡d = k \cdot \sqrt{\varepsilon_{\max}}

质量评估与落地

搬完家,师傅让你验收:会不会有柜角磕坏了、桌面刮花了?网格压缩也一样,简化算法跑完,必须回答一个问题:简化后的模型,跟原来比“差了多少”?这时需要一把“尺子”。

最常用的尺子是Hausdorff距离。它衡量两个点集之间的最大偏差:对原始模型上每个点,找到它在简化模型上最近的点,量出距离;反过来再做一次,取双向的最大值。这个最大值就是两个网格之间的“最坏形变”——就像量出那个磕得最深的柜角:

除了距离误差,还有两件事要盯住:一是“拓扑保持”——模型上的孔洞、把手、连通关系不能被简化抹平,否则一个杯子会变成一块饼;二是“视觉保真”——最简单粗暴的验收方法是把两个模型并排渲染,肉眼对比或用屏幕截图算PSNR。工程实践中,业界常用MeshLab、OpenMesh或自研工具链,把边坍缩+QEM跑成离线流水线,一键产出多级LOD。回到搬家现场:验收合格,师傅才敢把家具送到新家;同样,误差达标,模型才敢进游戏。这,就是网格压缩从算法到落地的最后一公里。

dH(A,B)=max⁡(sup⁡a∈Ainf⁡b∈B∥a−b∥, sup⁡b∈Binf⁡a∈A∥b−a∥)\displaystyle d_H(A, B) = \max\left( \sup_{a \in A} \inf_{b \in B} \|a - b\|,\ \sup_{b \in B} \inf_{a \in A} \|b - a\| \right)
公式含义:\(A\) 是原网格,\(B\) 是简化网格。先看A中每个点 \(a\) 到B的最近距离,取最大;再看反向,最终取两者的较大者。
  1. 单点最近距离对 \(A\) 中的一点 \(a\),在 \(B\) 中找最近的 \(b\),算欧氏距离。
    inf⁡b∈B∥a−b∥\inf_{b \in B} \|a - b\|
  2. 单向最大偏差遍历 \(A\) 所有点,取这些最近距离的最大值。
    sup⁡a∈Ainf⁡b∈B∥a−b∥\sup_{a \in A} \inf_{b \in B} \|a - b\|
  3. 双向最坏情况方向颠倒再算一次,两者取大,保证“从A看B、从B看A”都不会漏掉大偏差。
    max⁡(forward, backward)\max(\text{forward},\ \text{backward})

AI 应用场景

手游场景的LOD流送优化

阶段:几何简化与LOD生成

解决的问题:开放世界手游的地形与建筑动辄上千万三角形,直接加载会爆显存、掉帧。传统做法是手工制作低模,工作量巨大且容易出错。利用边坍缩+QEM自动从高模生成多个LOD级别,并在摄像机移动时动态切换,可以让帧率提升30%以上。

具体案例:某开放世界手游中,城市区域建筑逐个执行离线简化:用QEM算出每栋楼的2级LOD,再按本节的切换距离公式设定临界点。玩家跑图时,远处的楼房以低模呈现,走近后平滑切回高模,肉眼几乎察觉不到跳变,包体反而小了40%。

网页3D商品模型的秒开加载

阶段:顶点数据编码与压缩

解决的问题:电商网站的3D看鞋、看家具功能,模型几十MB,用户在弱网下等得抓狂。传统方案是简单降低纹理,治标不治本。对顶点坐标做16位量化和增量编码后,数据量能压缩到原来的1/4,再配合成熟的网格编码器,LOD+量化双管齐下,加载时间从10秒降到2秒。

具体案例:某家具电商将3D模型流水线接入顶点量化+熵编码,同时按比例预生成0.5x/0.3x两个简化级别。首屏只加载0.3x低模,用户旋转查看超过0.5秒后,后台流式加载高模,购物车转化率明显提升。

AR家装应用的实时简化

阶段:边坍缩与QEM实时计算

解决的问题:AR试摆家具时,摄像头画面与3D模型叠加需要极低的延迟,模型文件过大还会拖慢启动。传统做法是预烘焙固定LOD,但用户可能把沙发放到各种距离,固定级别不够灵活。利用连续LOD思想,结合边坍缩序列按需生成任意精度网格,实时平衡画质与帧率。

具体案例:某AR家装App在用户靠近虚拟家具时逐渐增加三角形数量:距离2米用8000面,0.5米用30000面。每次切换由QEM的误差阈值驱动,全程帧率稳定在60fps,用户几乎感受不到模型在“变化”。

Python 演示

python95 行
import numpy as np  def make_sphere(rings=4, segs=8):    verts, faces = [], []    for r in range(rings + 1):        phi = np.pi * r / rings        for s in range(segs + 1):            th = 2 * np.pi * s / segs            verts.append((np.sin(phi) * np.cos(th),                          np.cos(phi),                          np.sin(phi) * np.sin(th)))    for r in range(rings):        for s in range(segs):            a = r * (segs + 1) + s            faces += [(a, a + 1, a + segs + 1),                      (a + 1, a + segs + 2, a + segs + 1)]    return verts, faces  def build_quadrics(verts, faces):    Q = [np.zeros((4, 4)) for _ in verts]    for f in faces:        p0, p1, p2 = (np.array(verts[i]) for i in f)        n = np.cross(p1 - p0, p2 - p0)        n /= np.linalg.norm(n) + 1e-12        p = np.array([n[0], n[1], n[2], -n @ p0])        for i in f:            Q[i] += np.outer(p, p)    return Q  def collapse_info(verts, Q, a, b):    Qn = Q[a] + Q[b]    A, c = Qn[:3, :3], Qn[:3, 3]    try:        pos = np.linalg.solve(A + 1e-9 * np.eye(3), -c)    except np.linalg.LinAlgError:        pos = (np.array(verts[a]) + np.array(verts[b])) / 2    cost = pos @ A @ pos + 2 * c @ pos + Qn[3, 3]    return cost, pos  def simplify(verts, faces, ratio=0.5):    verts = [np.array(v, float) for v in verts]    faces = [list(f) for f in faces]    Q = build_quadrics(verts, faces)    target = max(int(len(faces) * ratio), 1)    while len(faces) > target:        best = None        for f in faces:            for k in range(3):                a, b = f[k], f[(k + 1) % 3]                cost, pos = collapse_info(verts, Q, a, b)                if best is None or cost < best[0]:                    best = (cost, a, b, pos)        _, a, b, pos = best        verts[a] = pos        Q[a] += Q[b]        nf = []        for f in faces:            t = [a if x == b else x for x in f]            t = [x - 1 if x > b else x for x in t]            if len(set(t)) == 3:                nf.append(t)        faces = nf        del verts[b], Q[b]    return verts, faces  def avg_error(ov, sv, sf):    sv = [np.array(v) for v in sv]    total = 0.0    for v in ov:        p = np.array(v)        best = 1e9        for f in sf:            p0, p1, p2 = (sv[i] for i in f)            n = np.cross(p1 - p0, p2 - p0)            n /= np.linalg.norm(n) + 1e-12            best = min(best, abs((p - p0) @ n))        total += best    return total / len(ov)  def main():    v, f = make_sphere()    print("原始网格: %d 顶点, %d 三角形" % (len(v), len(f)))    sv, sf = simplify(v, f, 0.5)    print("简化后: %d 顶点, %d 三角形" % (len(sv), len(sf)))    print("平均几何误差: %.4f" % avg_error(v, sv, sf))  if __name__ == "__main__":    main()
目的:用Python实现基于QEM的边坍缩简化,把一个经纬球从64个三角形化简到32个,并输出平均几何误差,演示了文中二次误差度量的完整计算链路。
  1. def build_quadrics(verts, faces):为每个顶点累计关联平面的外积,得到Q矩阵,对应文中的 \(Q_v = \sum p p^{\top}\)。
  2. pos = np.linalg.solve(A + 1e-9 * np.eye(3), -c)解线性方程组求最优新顶点位置,对应 \(v^{*} = Q^{-1}[0,0,0,1]^{\top}\)。
  3. cost = pos @ A @ pos + 2 * c @ pos + Qn[3, 3]用二次型计算这条边坍缩的代价,作为贪心选择的依据。
  4. while len(faces) > target:迭代式地反复坍缩代价最小的边,直到达到目标三角形数。
  5. del verts[b], Q[b]删除被合并的顶点,并用索引平移修正所有面的引用,完成一次真正的坍缩。
  6. avg_error采样原顶点到简化网格的平面距离,用简单的误差指标量化“简化伤了多少”,呼应质量评估小节。

常见误区

误区“网格简化嘛,就是每隔几个三角形删掉一个,面数自然就少了。”

正解直接删三角形会留下破洞和尖刺,像搬家时随手扔了几件家具导致桌子缺腿。真正的简化是通过边坍缩把两个顶点合并,让周围三角形“自然重塑”,而不是生硬抽除。QEM边坍缩会在每步评估代价,保证删除的是视觉上最不重要的结构。

为何常见:初学者容易把“简化”理解为“删除”,忽略了网格是拓扑连通的结构,删除单个三角形会破坏连通性。

误区“每次选误差最小的边,迭代很多轮之后模型质量一定最高。”

正解贪心选择只能保证每一步局部最优,长期执行可能积累出狭长三角面、法线翻转等“隐形损伤”,就像搬家时每次都挑最近的箱子搬,最后发现通道被堵死了。工程上通常会给代价函数加边界保持、面积惩罚等正则项。

为何常见:教程里常强调“贪心+最小代价”的框架,让初学者误以为全局最优就是局部最优的简单叠加。

误区“反正远处看不清,离得很远就切换到最低模,肯定没错。”

正解切换点设得太远,虽然省了性能,但玩家走近时会看到明显的“跳变”——原本光滑的轮廓突然出现棱角。切换距离应与误差匹配:低模误差大,必须放到足够远才不露馅,对应公式 \(d = k\sqrt{\varepsilon_{\max}}\)。

为何常见:人们直觉上认为“性能优先”,忽略了视觉连续性,没有意识到切换跳变对体验的伤害比多画几万个三角形更严重。

误区“16位能省一半,那8位不是更省?直接上8位。”

正解量化位数越低,坐标落在整数格子上的“颗粒”越粗,表面会出现可见的阶梯状瑕疵,就像用粗麻绳捆精装书会勒出印子。8位只有256个格,一个拳头大小的模型就能看出棱角;16位(65536格)通常是视觉无损的甜点,再低就得分块量化或做误差补偿。

为何常见:只看到“位数少字节数少”的线性关系,忽略了量化误差对感知质量的非线性伤害。

误区“边坍缩会把孔洞和把手抹平,精度要求高的工业模型绝不能用。”

正解合理的品质控制完全能保住拓扑。QEM代价函数可以加入“拓扑惩罚”:当坍缩会导致非流形边、孔洞消失或边界收缩时,把该边的代价设为无穷大,跳过它。就像搬家时分清“结构件”和“装饰件”,承重墙绝不能拆,装饰摆件随便挪。工业级MeshLab、Simplygon等工具都有成套约束。

为何常见:把“算法默认行为”误当成“算法能力上限”,忽略了约束条件设计同样是算法的重要组成。

面试题

Q1. 网格压缩中“边坍缩”指的是下列哪种操作?

单选题 · easy

  • A.删除网格中的悬浮孤立顶点
  • B.把一条边的两个端点合并成一个新顶点← 正确答案
  • C.降低顶点坐标的浮点精度
  • D.移除相互重叠的三角形面片

答案:B。边坍缩的核心是把边 \( (v_i, v_j) \) 的两个端点合并为 \( v' \),删除这条边以及共享它的退化三角形。删除悬浮顶点叫“顶点清理”,降精度属于量化,都不是边坍缩。就像搬家时把两把椅子捆成一件,而不是把椅子扔了或改名字。

Q2. 用QEM做网格简化时,顶点 \(v\) 的误差矩阵 \(Q_v\) 是如何构建的?

单选题 · mid

  • A.由网格包围盒的长宽比决定
  • B.累加该顶点所有关联平面的外积 \(p p^{\top}\)← 正确答案
  • C.等于顶点坐标的协方差矩阵
  • D.由相邻顶点的法线叉乘得到

答案:B。每个关联三角形所在平面记作 \(p = [a,b,c,d]^{\top}\),外积 \(p p^{\top}\) 是一个4×4矩阵,所有 \(p p^{\top}\) 累加就得到 \(Q_v\)。它量化了顶点移动带来的“平面偏差平方和”,而不是包围盒或协方差。这就像给每件家具的“损伤系数”累计起来,而不是看房间大小。

Q3. 边坍缩时,最优新顶点的位置 \(v^{*}\) 通常如何求解?

单选题 · mid

  • A.取两个端点的几何中点
  • B.求解 \(Q_v^{-1}[0,0,0,1]^{\top}\) 对应的线性方程组← 正确答案
  • C.对两个端点坐标做加权平均,权重随机
  • D.用顶点颜色的均值替代坐标

答案:B。把误差函数 \(\Delta(v) = v^{\top}Q_v v\) 对坐标求导并置零,会得到线性方程组,其解正是最优位置。中点只是 \(Q_v\) 不可逆时的退化回退方案。简单平均或随机权重都没有最小化几何误差,不是最优解。

Q4. 顶点量化压缩的基本思想是?

单选题 · easy

  • A.用64位双精度提升精度
  • B.把浮点坐标映射到有限整数格并记录范围← 正确答案
  • C.把顶点坐标转成字符串存储
  • D.只保留三角形索引不存坐标

答案:B。量化先用包围盒把坐标归一化到 \([0,1]\),再乘 \(2^n\) 取整成整数,解码时依据记录的 \(v_{\min}\)、\(v_{\max}\) 还原。它把32位浮点压成16位甚至更短,类似于在装箱单上用编号代替冗长的物品全名。

Q5. 增量编码主要利用了相邻顶点数据中的什么特性?

单选题 · mid

  • A.数值上相近的空间局部冗余← 正确答案
  • B.颜色通道的统计独立性
  • C.法线方向的对称性
  • D.纹理坐标的周期性

答案:A。网格中相邻顶点的坐标往往离得很近,坐标值差得很小,因此只存差值 \(d_i = \hat{v}_i - \hat{v}_{i-1}\) 可以用更少位数表示。这是空间局部性带来的数值冗余,与颜色、法线、纹理的统计特性无关。

Q6. 以下哪些属于网格压缩与简化的常用手段?

多选题 · mid

  • A.边坍缩← 正确答案
  • B.顶点聚类← 正确答案
  • C.熵编码← 正确答案
  • D.光线追踪

答案:ABC。边坍缩和顶点聚类都是几何简化手段,熵编码则是把高频差值用短码存储的数据压缩手段,三者常组合使用。光线追踪是渲染算法,与“减小模型数据量”无关。

Q7. LOD切换距离一般与当前LOD级别的最大几何误差存在什么关系?

单选题 · hard

  • A.与误差平方成正比
  • B.与误差的平方根成正比← 正确答案
  • C.与误差成反比
  • D.与误差无关,只取决于模型面积

答案:B。\(d_{\text{switch}} = k \cdot \sqrt{\varepsilon_{\max}}\),误差越大,切换距离越远,这样大误差的低模在足够远的距离上才不会被察觉。反比或无关都说不通:低模精度差,不可能放得很近。

Q8. 为了让边坍缩结果保持拓扑正确,代价函数可以加入哪些惩罚项?

多选题 · hard

  • A.边界边长保持← 正确答案
  • B.法向翻转检测← 正确答案
  • C.三角形宽高比正则← 正确答案
  • D.顶点颜色饱和度

答案:ABC。边界保持防止模型轮廓向里收缩,法向翻转检测避免表面“穿帮”,宽高比正则抑制狭长三角面——这些都是拓扑与几何质量的约束。顶点颜色饱和度属于外观属性,与拓扑正确性无关。

Q9. 对称Hausdorff距离 \(d_H(A,B)\) 的定义是两个网格之间的?

单选题 · extreme

  • A.单向最大偏差,只从A看B
  • B.双向最大偏差取两者中的较大者← 正确答案
  • C.两个网格顶点数之差的绝对值
  • D.两个网格包围盒体积之差的平方根

答案:B。对称Hausdorff距离先计算 \(A\) 中每个点到 \(B\) 的最近距离并取最大值,再反向算一次,最后取更大者。它衡量的是“最坏情况的形变”,而不是统计平均或顶点数量差,所以A漏掉了反向,C、D是无关指标。

Q10. 网格压缩中“保持拓扑”的核心含义是?

单选题 · easy

  • A.保持模型的孔洞、边界和连通关系不变← 正确答案
  • B.保持顶点坐标的数值完全一致
  • C.保持三角形数量恒定
  • D.保持纹理贴图像素不变

答案:A。拓扑指的是几何对象的连通结构:一个杯子中间的洞、一条边上的开口、各部件是否相连。简化时如果洞被填平、把手断掉,拓扑就变了。保持拓扑不等于保持坐标或三角形数量,纹理分辨率也不属于拓扑范畴。