Skip to main content

Chapter 12: Surface and Curve Representations

一件曲面资产进入实时渲染时,会经历一条容易被混在一起的数据路径:建模软件中看到的是连续曲线、曲面或细分控制笼,运行时提交给 GPU 的通常是三角形、顶点属性、索引、贴图坐标、法线、切线、骨骼权重和变形增量。本章讨论的主问题是:当一个模型在画面中出现曲面不顺、贴图拉伸、接缝发亮或动画变形破面时,如何判断当前问题来自曲面表示、参数化、几何近似、导入属性,还是动画变形路径。

贯穿本章的材料是一帧角色头部特写:美术在 DCC 工具中用细分曲面控制脸部轮廓,用曲线控制嘴角和眼睑边缘,用 UV 展开绘制皮肤纹理,用 normal map 表达毛孔细节,运行时通过 skinning 和 smile morph target 让角色露出笑容。中立表情时画面正常,笑容播放时嘴角到脸颊出现一条亮线,棋盘格纹理在脸颊被拉成长条,侧光下法线接缝闪烁。读完本章后,读者应能沿着这条路径定位表示层级、判断误差来源、区分 UV 与几何问题,并给出稳定的检查顺序。

曲线与曲面表示服务的是“可编辑形状”,运行时 mesh 服务的是“可绘制数据”。这两个目标经常使用同一个模型名,但它们处在不同阶段。DCC 里的 NURBS 面、B-Spline 曲线或 subdivision cage 允许艺术家用少量控制点组织形状;GPU draw call 中的 vertex buffer 需要有限个顶点和明确属性。理解这层转换,才能解释为什么同一张脸在建模视口里连续,在引擎中却可能因为 UV seam、tangent split 或 morph delta 不一致而出现断裂。

这条资源路径可以压缩成下图。图中每个节点都可能制造可见问题,也都对应不同的证据:控制曲面决定形状意图,tessellation 决定几何近似,UV chart 决定贴图采样,顶点属性决定 shader 输入,动画通道决定每帧顶点如何移动。

12.1 曲线与曲面的数学定义

曲线是由一个参数驱动的一维几何对象,常写作 C(t)C(t)。这里的 tt 是曲线内部的参数,取值范围通常被归一化到 [0,1][0,1],输出是模型空间中的点、切线或其它可插值属性。曲面是由两个参数驱动的二维几何对象,常写作 S(u,v)S(u,v)。这里的 uuvv 表示曲面参数,输出通常是模型空间 position,也可以进一步求 normal、tangent 或曲率。图形工程里关心这些定义,是因为它们决定了形状怎样被采样成顶点,以及 shader 最终拿到什么输入。

Bézier 曲线使用一组控制点和 Bernstein 权重求值,可写作 C(t)=iBin(t)PiC(t)=\sum_i B_i^n(t)P_i。公式里的 PiP_i 是模型空间控制点,Bin(t)B_i^n(t) 是参数 tt 下的权重,输出 C(t)C(t) 是曲线上一个位置。它适合短段、可美术控制的轮廓,例如眼睑边缘、嘴角弧线、路径动画和字体轮廓。Bézier 的端点可控,曲线对控制点移动的响应直观,所以它常作为局部设计工具,而实时渲染阶段通常会把曲线采样成线段或三角面边界。

B-Spline 曲线和曲面把控制点、基函数和 knot vector 组合起来。它的核心价值是局部控制:移动一个控制点通常只影响附近几个 span。角色脸部、汽车外壳、工业设计曲面常需要长距离连续变化,B-Spline 允许把大曲面分成多个局部片段,同时维持可预测的连续性。对渲染管线来说,B-Spline 的输出仍需要被求值成可绘制的顶点、patch 或三角形。

NURBS 是带权重的有理 B-Spline。权重让它能精确表达圆、圆弧和许多 CAD 场景中的规则曲面。它在工程建模中常见,因为设计人员需要精确尺寸、光顺曲面和可制造边界。游戏或 Web 运行时通常把 NURBS 转换成 polygon mesh,再把 position、normal、UV 等属性上传到 GPU。转换阶段一旦使用过粗的采样密度,屏幕中的轮廓会出现折线;采样密度过高,又会增加顶点带宽、索引缓存压力和导入资源体积。

Subdivision surface 使用粗控制笼和细分规则描述光滑极限曲面。Catmull-Clark 这类规则适合四边形控制网格,常用于角色、布料和有机模型。Pixar 的 OpenSubdiv 将细分曲面求值组织成可在 CPU 或 GPU 上执行的库,适合静态拓扑、动态变形的曲面绘制。工程判断中要把“控制笼可编辑”与“运行时可绘制”分开:控制笼决定艺术家如何修改形状,运行时 draw call 处理的是求值后的 patch、tessellated mesh 或缓存好的顶点数据。

表示方式控制结构求值输出常见图形用途运行时边界
Bézier控制点和 Bernstein 权重曲线上位置、切线或采样点局部轮廓、路径、字体、短曲线长曲线需要分段管理,过多采样会增加几何量
B-Spline控制点、阶数、knot vector局部光顺曲线或曲面点长距离连续曲线、自由曲面导出前需要确定采样密度和边界缝合
NURBSB-Spline 加权重精确曲线或曲面点CAD、工业曲面、规则圆弧实时引擎常转换为三角 mesh,误差受采样策略控制
Subdivision surface粗控制笼和细分规则极限曲面或有限细分 mesh角色、布料、有机形体拓扑、crease、extraordinary vertex 会影响高光连续性

本节要建立的判断是:数学曲面负责描述连续形状,GPU 输入负责承载有限采样结果。遇到脸颊高光出现折线时,先问当前画面实际使用的是 control surface、tessellated patch,还是离线导出的 triangle mesh。这个问题会直接决定排查入口:若运行时已经只剩三角 mesh,修 NURBS 控制点不会改变引擎里的顶点密度;若运行时有硬件 tessellation 或 subdivision 求值,屏幕误差和 patch 边界才是首要证据。

12.2 参数化方法与贴图坐标的计算

参数化把曲面上的点映射到二维参数域。贴图坐标 UV 是实时渲染中最常见的参数化结果,它作为 vertex attribute 随 position 一起进入 vertex buffer,再在光栅化阶段被插值到 fragment shader。shader 用插值后的 UV 采样 base color、normal、roughness、metallic、occlusion 等纹理。Khronos 的 glTF 2.0 specification 使用 TEXCOORD_n 表示纹理坐标语义,并把 POSITIONNORMALTANGENTJOINTS_nWEIGHTS_n 等属性作为 mesh primitive 的输入语义。

在角色头部案例中,UV 展开的目标是把三维脸部曲面剪开成若干 2D chart。seam 是剪开的位置,通常放在耳后、头发遮挡区、口腔内侧或衣领下方。stretch 是三维曲面到二维 UV 后发生的长度或面积失真。texel density 表示单位模型长度对应多少纹理像素,脸部特写需要更高密度,后脑勺或遮挡区可以分配较低密度。chart packing 则把多个 UV chart 放入同一张纹理,保留 padding 来减少 mipmap 下的颜色串扰。

UV 的工程难点来自一个事实:同一个几何位置在不同 chart 中可能需要不同 UV。mesh 数据结构通常通过复制顶点来表达这种分裂:两个顶点 position 相同,但 TEXCOORD_0 不同,normal 或 tangent 也可能不同。这个复制在资源层面是正常现象,GPU 看到的是两个不同 vertex。渲染结果是否连续,取决于这些重复顶点的 position、normal、tangent、morph delta 和 skin weight 是否符合当前材质与动画需求。

贴图坐标进入 shader 后,还会影响 mipmap 选择和 anisotropic filtering。UV 在屏幕上变化过快时,硬件会估计纹理坐标导数,选择合适 mip level。若 UV chart 被强烈压缩,一个屏幕像素可能覆盖纹理中的大范围区域,高频纹理会模糊或闪烁;若 chart 被拉伸,纹理像素被摊到更大表面,皮肤细节会变粗。棋盘格测试纹理能直接暴露这种问题:正方形变成长条,说明局部长度比例失真;棋盘格面积忽大忽小,说明 texel density 分配失衡。

参数化还会影响 tangent space。normal map 中的 RGB 存储的是相对切线空间的法线方向,shader 需要用 vertex tangent、normal 和 handedness 还原局部坐标系。UV seam 处若 tangent 基不同,侧光下会出现亮线。此时颜色纹理可能完全对齐,几何 position 也完全连续,但 normal map 的解释空间已经发生跳变。定位这类问题时,先切换到 flat normal 或关闭 normal map;若亮线消失,问题集中在 tangent、normal map、UV orientation 或导入器的 tangent 生成规则。

本节的可复用判断顺序是:先确认 UV 是否作为顶点属性存在,再看 seam 是否导致 vertex split,然后用 checker texture 检查 stretch 和 texel density,最后用 normal map debug 检查 tangent space。这样能把“贴图画错了”“UV 展开拉伸”“normal seam”“geometry seam”分到不同证据上,减少把所有可见线条都归因到纹理文件的误判。

12.3 等距映射与几何误差控制

等距映射指映射前后的长度保持一致。曲面展开到平面时,理想目标是让三维表面长度、面积和角度在 UV 空间中尽量稳定;真实资产中,这个目标需要按视觉需求取舍。球面、头部、弯曲衣褶这类曲面很难被完整摊平成一张二维图而保持全部长度与面积关系,所以 UV seam、stretch 和 chart 分割是参数化中的结构性代价。

误差控制需要区分两类误差。第一类是几何近似误差:连续曲面 S(u,v)S(u,v) 被三角 mesh T(u,v)T(u,v) 近似,位置误差可以写成 ep=S(u,v)T(u,v)e_p=\|S(u,v)-T(u,v)\|。这里 S(u,v)S(u,v) 是原始曲面在模型空间中的目标位置,T(u,v)T(u,v) 是三角化后同一参数位置对应的近似位置。第二类是参数化误差:三维边长与 UV 边长比例发生变化,纹理采样密度和方向被改变。两类误差都会进入画面,但症状不同:几何误差影响 silhouette、specular highlight 和 shadow;参数化误差影响纹理清晰度、纹理方向和 normal map 细节。

长度失真可以通过边长比例观察。若模型空间中一条边长为 lml_m,UV 空间中对应长度为 luvl_{uv},纹理分辨率为 RR,那么局部 texel density 可用 Rluv/lmR \cdot l_{uv} / l_m 估算。这个值在脸颊区域突然下降,皮肤纹理会变粗;突然升高,纹理资源会集中到局部,别处会牺牲清晰度。面积失真同理,可比较三角形在模型空间和 UV 空间中的面积比例。角色脸部特写通常优先保证眼睛、嘴唇、鼻翼和脸颊前侧的密度,耳后 seam 区域可以承受更高失真。

曲率变化决定三角化需要多少采样。平面区域使用较少三角形也能保持视觉稳定;高曲率区域需要更多顶点来保持轮廓和高光连续。高光对法线变化敏感,金属、湿润皮肤、车漆和玻璃表面会放大几何误差。若同一个头部模型在 matte material 下看起来平滑,在强侧光和高 roughness 对比材质下出现折线,证据优先指向 normal、tessellation 或 smoothing,albedo 纹理的优先级降低。

屏幕误差把模型空间误差转换成像素影响。远处角色脸部的 0.50.5 毫米几何误差可能低于一个像素;近景特写中同样误差会覆盖多个像素并破坏高光。LOD 与 tessellation 的阈值应按屏幕占比、材质敏感度和动画幅度设置。对于头部特写,嘴角、眼睑和鼻翼区域需要更严格的屏幕误差;对于远处群演,同样曲面可以使用更低采样密度。

排查几何误差时,先固定相机和材质,分别输出 wireframe、flat normal、smooth normal、checker UV 和 normal map 版本。wireframe 能观察三角化密度与曲率分布;flat normal 会暴露真实三角面方向;smooth normal 能检查顶点法线插值;checker UV 能显示参数化失真;normal map 版本则验证 tangent space 是否稳定。若只有 wireframe 稀疏处出现高光折线,重点在几何采样;若 wireframe 密度足够但 normal map 版本出现 seam,重点在 UV 与 tangent;若动画播放后才出现问题,重点转向 morph delta、skin weight 和动画后的法线更新。

本节的核心结论是:等距只是参数化的理想目标,工程上要控制的是可见误差。可见误差由曲率、材质、相机距离、纹理频率和动画幅度共同决定。把误差放回屏幕像素和 shader 输入,才能给出可执行的资产修改建议。

12.4 在模型导入中处理 UV 坏接缝

UV 坏接缝的可见症状通常有五类:颜色纹理断开、normal map 出现亮线、roughness 或 metallic 在 seam 两侧跳变、mipmap 后出现脏边、动画后 seam 从隐藏区移动到可见区。处理这类问题时,先判断 seam 是正常的 chart 边界,还是导入阶段制造了属性不一致。正常 seam 允许 UV 分裂;坏 seam 的证据是分裂后的重复顶点在 position 连续时,normal、tangent、morph delta、skin weight 或贴图边界没有按同一意图组织。

导入器处理 mesh 时,position 相同的点可能因为 UV、normal、tangent、color、joint、weight 或 material split 被拆成多个 vertex。下面的简化 glTF 片段展示了一个 mesh primitive 的关键属性集合。它说明 shader 看到的是多个 attribute stream 的组合,UV seam 问题通常需要同时检查 TEXCOORD_0NORMALTANGENT,并且在动画资产中继续检查 JOINTS_0WEIGHTS_0 与 morph target 数据。

{
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TANGENT": 2,
"TEXCOORD_0": 3,
"JOINTS_0": 4,
"WEIGHTS_0": 5
},
"indices": 6,
"material": 0
}

第一步是确认重复顶点是否符合预期。若同一空间位置对应两个 UV chart,顶点复制合理;若同一 chart 内出现重复顶点,并且 normal 或 tangent 不一致,就会产生可见断裂。检查时可以按 position 近似聚类,把同一位置的顶点列出来,比较 TEXCOORD_0NORMALTANGENT、joint index、weight 和 morph target delta。若 UV 相同但 normal 不同,症状更像 smoothing group 或导出 normal 问题;若 normal 相同但 tangent 不同,症状更像 tangent basis 或 normal map 方向问题。

第二步是检查 tangent space 的生成规则。normal map 的可见连续性要求 DCC、烘焙工具、导入器和运行时 shader 使用兼容的 tangent basis。很多实时管线使用 MikkTSpace 风格的 tangent 生成。若烘焙时用一种 tangent basis,运行时重新生成另一种 basis,seam 与硬边会在侧光下显现。glTF 的 tangent 属性还使用 W 分量保存 handedness 符号,导入器丢失或翻转这个符号时,normal map 的局部坐标会发生镜像,画面中常表现为一侧凹凸方向反转。

第三步是清理退化三角形和零面积 UV。退化三角形在模型空间面积接近零,零面积 UV 在纹理空间中三个 UV 点重叠或几乎重叠。它们会让 tangent 求解矩阵病态,导致一小块三角形产生极端 tangent 或 NaN,随后在法线贴图下形成黑斑、亮线或闪烁。清理策略是按导入阈值移除退化面,修复重叠 UV,重新三角化高风险 n-gon,并在导出前冻结变换与应用尺度。

第四步是检查贴图边界。mipmap 会在较低层级把相邻 texel 混合,UV chart 边缘若缺少 padding,seam 会在远距离或斜视角下出现颜色污染。解决路径是扩大 chart padding、对纹理边界做 dilation、让 chart packing 保留足够间距,并验证纹理采样模式。颜色 seam 在关闭 normal map 后仍存在时,优先检查 padding、颜色空间和纹理边界;normal seam 在纯色材质下才显现时,优先检查 normal、tangent 和 handedness。

第五步是复查导出设置。三角化顺序、法线导出、切线导出、坐标轴转换、单位缩放、材质拆分和纹理色彩空间都会改变最终属性。稳定做法是让同一资产在 DCC、离线查看器和引擎中使用一致的三角化结果,并明确决定由谁生成 normal 与 tangent。若引擎统一生成 tangent,就在导出阶段保留足够的 UV 和 normal 信息;若资产携带 tangent,就在导入阶段验证 W handedness、归一化和坐标系转换。

本节的排查顺序可以固定为:先判断 seam 是否来自合法 vertex split,再检查重复顶点的 attribute 一致性,然后验证 tangent basis,接着清理退化几何与零面积 UV,最后复查 mipmap padding 和导出设置。这个顺序把“视觉线条”拆回 mesh 数据和 shader 输入,能减少在贴图、模型和材质之间来回试错。

12.5 在动画与变形中的曲面表示

动画会把静态曲面问题放大。中立表情下隐藏的 seam,播放 smile morph 后可能进入强光区域;静态姿态下稳定的 skin weight,在嘴角拉伸时可能让脸颊出现折痕;原本均匀的 UV density,在大幅表情变形后会呈现动态拉伸。这里要把曲面表示、顶点属性和动画通道放到同一条顺序中观察:先有 base mesh 和 UV,再有 morph delta 或 skinning 权重,随后 vertex shader 或 CPU deformation 更新 position、normal、tangent,最后 shader 使用这些结果采样纹理并输出颜色。

Morph target 描述的是相对于 base mesh 的属性增量。以 glTF 为例,morph target 会把 base attribute 加上带权重的 target attribute delta;POSITIONNORMALTANGENT 等属性可以参与变形,位移通常在 skinning 和 node transform 前应用。这个顺序会影响排查结论:若嘴角 seam 只在 smile weight 增大时出现,应比较 seam 两侧重复顶点的 morph delta。position delta 不一致会形成几何裂缝,normal 或 tangent delta 不一致会形成光照裂缝。

Skinning 描述的是骨骼矩阵对顶点的加权影响。每个顶点通常携带 joint index 和 weight,vertex shader 或 CPU skinning 路径将 bind pose 中的点变换到当前姿态。skin weight 在关节、嘴角、眼睑和肩部这些高变形区域需要平滑过渡。若 seam 两侧重复顶点的 skin weight 不一致,静态模型仍然闭合,动画后两个顶点会被不同骨骼矩阵拉向不同位置,裂缝随动作出现。此时焊接 position 只能暂时掩盖问题,真正的证据在权重差异和动画后的顶点位置。

Blend shape 在许多 DCC 和引擎中与 morph target 指向同类思想:用多个目标形状按权重混合出当前形状。面部动画常用 blend shape 表达笑、眨眼、皱眉、鼓腮等局部表情,再叠加骨骼控制头颈和下颌。对曲面连续性来说,blend shape 需要保证相邻区域的 delta 平滑,重复顶点的 delta 一致,法线与切线在混合后可重新计算或可正确插值。若只更新 position,不更新 normal 或 tangent,强光下会出现“形状动了,光照仍按旧表面解释”的症状。

UV 在多数实时管线中随顶点固定,不会随表情重新展开。大幅面部变形会改变模型空间长度,但 UV 坐标仍沿用 bind pose 的参数化结果,所以嘴角、眼皮和鼻翼周围的纹理可能被拉伸。解决方向分三类:调整 base topology 让变形沿自然纹理方向分布;修改 UV chart 与 density,让高变形区域保留足够纹理余量;在 shader 或材质层使用 wrinkle map、flow map 或 expression-specific mask 补偿局部细节。选择哪一类,取决于问题是几何折痕、采样密度不足,还是表情细节缺少动态纹理。

动画曲面的检查顺序应从静态到动态推进。先在 bind pose 下检查 mesh、UV、normal、tangent 和 texture padding;再播放单个 morph target,比较 seam 两侧 delta;接着播放 skinning 关节,检查重复顶点的 joint 和 weight;最后组合表情与骨骼,观察 normal map 与高光是否稳定。若单独 morph 正常、单独 skinning 正常,组合后出错,问题常在变形顺序、normal/tangent 更新或 shader 对变形后属性的解释。

回到本章贯穿的角色头部特写,嘴角到脸颊的亮线可以按证据分流:棋盘格拉伸说明 UV 参数化或 texel density 存在局部失衡;normal map 侧光闪烁说明 tangent space 或变形后的 normal/tangent 不稳定;笑容播放后才出现说明 morph delta 或 skin weight 参与问题。把三个证据按顺序放回管线,最终检查目标就会从“修一条线”变成“修复 seam 两侧重复顶点在 UV、tangent、morph 和 skinning 上的一致性”。

最小自检任务

你导入一个角色头部模型。中立表情下 base color 没有明显断裂;播放 smile morph 后,嘴角到脸颊出现一条亮线;切换棋盘格纹理后,脸颊格子被拉成长条;关闭 normal map 后亮线明显减弱。你暂时没有 RenderDoc 或引擎帧捕获,只能检查导出的 mesh 属性、UV 图和几种基础材质 debug。请给出排查顺序,并说明每一步的证据对应哪个管线阶段。

答案要点

先把问题拆成三条证据。棋盘格被拉长指向 UV 参数化和 texel density;关闭 normal map 后亮线减弱,指向 tangent space、normal map 或变形后的法线与切线;问题只在 smile morph 后出现,说明 morph delta 或动画后的属性更新参与了结果。

第一步检查 bind pose 下的 UV chart。查看嘴角到脸颊是否位于 seam 附近,比较该区域棋盘格大小、方向和 chart padding。若同一区域 texel density 明显低于脸部主体,应调整 UV relax、重新分配 chart 面积或移动 seam 到遮挡区。

第二步按 position 聚类检查重复顶点。对 seam 两侧 position 接近的顶点,比较 TEXCOORD_0NORMALTANGENTJOINTS_0WEIGHTS_0 和 smile morph 的 POSITION delta。UV 分裂合理,但 normal、tangent、weight 或 morph delta 的异常差异会在动画后形成光照线或几何裂缝。

第三步验证 tangent basis。确认烘焙 normal map、导入器和运行时使用兼容的 tangent 规则,并检查 tangent W handedness 是否保留。若 flat material 稳定、normal map 材质出线,重点放在 tangent、normal map 方向、镜像 UV 和零面积 UV 上。

第四步单独播放 smile morph,再单独播放 skinning。若 smile morph 单独触发亮线,修复 morph target 中重复顶点的 delta,并重新生成或正确混合 normal/tangent。若 skinning 单独触发裂缝,统一 seam 两侧的 joint 和 weight。若组合后才出现,检查变形顺序和变形后的 normal/tangent 更新策略。

最终修复应让三类输出同时稳定:checker texture 的格子比例恢复到可接受范围,normal map 下侧光亮线消失或被压到不可见程度,播放 smile morph 时 seam 两侧 position、normal、tangent 和动画属性保持连续意图。

本章知识点总结

  • 曲面层级:DCC 中的连续曲面负责表达可编辑形状,运行时 draw call 通常处理求值后的有限顶点和属性。
  • 曲线参数C(t)C(t) 的参数 tt 驱动一维曲线求值,输出位置、切线或采样点可继续进入网格生成。
  • 曲面参数S(u,v)S(u,v) 的参数 uuvv 描述二维曲面位置,后续会被采样成 mesh、patch 或顶点属性。
  • 表示取舍:Bézier 适合局部可控曲线,B-Spline 适合长距离光顺控制,NURBS 适合精确工程曲面,subdivision surface 适合有机形体。
  • UV 本质:UV 是随顶点传入 GPU 的参数化结果,fragment shader 用插值后的 UV 采样纹理。
  • Seam 语义:UV seam 常通过重复顶点表达,重复顶点的 position 可相同,但 UV、normal、tangent 或动画属性可能分裂。
  • 失真判断:长度失真影响纹理方向和清晰度,面积失真影响 texel density,曲率误差影响轮廓与高光。
  • 屏幕误差:同一模型空间误差在不同相机距离下产生不同像素影响,LOD 和 tessellation 阈值应按屏幕占比判断。
  • 接缝排查:UV 坏接缝应按 vertex split、attribute 一致性、tangent basis、退化几何、texture padding 和导出设置逐项定位。
  • Tangent 风险:normal map 的连续性依赖 tangent space,tangent handedness 丢失或生成规则不一致会产生侧光亮线。
  • 动画放大:morph target 和 skinning 会放大静态 seam 问题,重复顶点的 delta、joint 和 weight 需要保持连续意图。
  • 最终判断:曲面问题要回到形状表示、UV 参数化、几何近似、顶点属性、动画通道和 shader 输入之间的因果链。