GPU Gems 3 · Chapter 25. Rendering Vector Art on the GPU

从 Bézier 控制点出发,解释如何把向量曲线隐式化,让 GPU 用凸包、插值和片元判断直接渲染可缩放的二次与三次曲线。

学习目标

  • 能解释向量曲线为什么可以只提交控制点凸包,而不必把轮廓预先细分成大量三角形
  • 能修改 Vector Art on the GPU Lab 的曲线阶数、视角、边界模式与控制点形状,比较隐式判断和抗锯齿结果
  • 能回答:为什么二次曲线使用 u² − v,而三次曲线需要先分类再使用统一的隐式方程

先问:为什么放大字体不该先变成一堆小三角形

想象一条用几枚钉子和一根软尺描述的曲线。无论把它印在名片还是广告牌上,钉子的位置没有变;如果你先把软尺切成很多小直线,放大后就会看见切口,换一个角度还要重新切一遍。

屏幕上的字形、图标和路径也有同样的取舍:像素最终必须一个个决定是否被曲线覆盖,但曲线本身不必先变成密集网格。本章把这两个工作分开,让 GPU 负责覆盖判断和边界平滑。

没有这条路径,矢量图要么依赖高密度镶嵌来避免锯齿,要么在缩放和透视后暴露采样接缝;有了它,几何数据保持小而稳定,分辨率交给片元阶段处理。

1. 让控制点凸包承担几何工作

向量图由闭合路径和曲线片段组成。渲染时,我们不把每条曲线切成很多小线段,而是为它的控制点构造一个足以覆盖曲线的凸包三角形。内部区域可以像普通三角形一样填充,真正需要特殊处理的是包含曲线边界的那几块。

这让几何量随边界描述增长,而不是随目标分辨率增长。像素着色器拿到三角形覆盖下的插值坐标后,才决定当前像素在曲线内、曲线外,还是处在需要平滑的边界附近。

describe the boundary, rasterize the hull, shade the curvecontrol pointsfew points, no dense tessellationGPU interpolationprocedural coordinates interpolatepixel testsign of f(x,y)inside / outsidegeometry stores the boundary; the fragment stage decides coverage

2. 二次 Bézier 可以变成一个简单的隐式测试

TrueType 等字形数据常把轮廓拆成二次样条。把相邻控制点转换成二次 Bézier 后,为凸包三个顶点赋予特殊的 uv 坐标,GPU 会在透视变换后继续做正确插值。像素阶段不必查纹理,只要计算 u² − v 的符号:对于凸曲线,负值代表曲线内,正值代表曲线外;凹曲线则按方向翻转。

关键不是“用公式画一条曲线”,而是把公式的坐标安排到三角形顶点,让硬件插值器替我们求出每个像素对应的参数。这样任意 3D 投影都只改变插值后的坐标分布,不会把曲线变成有固定分辨率的贴图。

quadratic coverage: u² − v = 0assign procedural coordinates(u,v)=(0,0)(u,v)=(1/2,0)(u,v)=(1,1)interpolate u and v across the hullfragment shaderf = u² − vf < 0: inside · f > 0: outsideno texture lookup, no tessellation error

3. 隐式化把参数曲线换成像素可问的问题

参数曲线回答的是“参数走到哪里”;像素着色器更适合回答“这个坐标在边界哪一侧”。隐式化就是在两种提问方式之间搭桥:把 x(t), y(t) 变成 f(x,y)=0,边界是零,符号代表两侧。

二次曲线的方程很短,因此可以把同一套片元逻辑用于字形、图标和简单路径。它也解释了为什么算法不需要先把曲线烘成高分辨率纹理:真正的采样发生在最终屏幕像素上。

4. 三次曲线先分类,再共用一个 shader

三次曲线比二次曲线灵活,但也不能像二次曲线那样全部投影自同一个原型。官方算法先通过拐点多项式的判别式分类:正值是 serpentine,负值是 loop,零值是 cusp。分类在预处理阶段完成,运行时仍保持紧凑的片元表达。

对积分三次曲线,函数坐标可以让像素着色器计算 k³ − lm。其中 klm 是控制点对应的线性函数值,GPU 插值后直接代入。loop 若在参数区间内有双重点,还要在预处理时细分并修正子曲线方向,避免凸包中出现错误的穿透区域。

cubic curves share one shader, not one shapeserpentinediscriminant > 0loopdiscriminant < 0cuspdiscriminant = 0classify offline; interpolate k,l,m at runtime; evaluate k³ − lm in every fragment

5. 凸包重叠是几何阶段的真正陷阱

一条闭合路径通常还需要填充曲线内部。每段二次曲线的凸包是一个三角形;三次曲线的凸包可能是三角形或四边形,需要再三角化。若相邻凸包重叠,错误区域可能透出来,三角化器也会难以判断应该保留哪一侧。

实际做法是离线检测重叠并细分其中一条曲线,使局部凸包互不遮挡,再对整个闭合轮廓的内部做一次全局三角化。复杂洞和凹形路径也可以借助 stencil 的奇偶规则处理,但那是填充路径的另一层,不应混入曲线片元方程。

6. 用屏幕梯度把内外判断变成抗锯齿边界

单纯按符号丢弃片元会得到硬边。为了平滑边缘,像素着色器用硬件 ddx()ddy() 估算函数在屏幕相邻像素间的变化,再用链式法则把隐式值除以梯度长度,得到近似屏幕距离。距离落在半个像素附近时,把 alpha 从 1 线性过渡到 0。

这不是昂贵的精确距离求解;精确距离需要解更高阶方程,片元阶段不划算。梯度近似的价值在于:它随着投影缩放自动改变边缘带宽,字形在不同尺寸和视角下仍然稳定。

gradients turn an implicit value into a soft edgeneighboring pixelsddx(f), ddy(f)signed distancesd ≈ f / |∇f|screen-space scalealpha rampalpha = 0.5 − sdsmooth edge
float2 px = ddx(p);
float2 py = ddy(p);
float f = p.x * p.x - p.y;
float fx = 2.0 * p.x * px.x - px.y;
float fy = 2.0 * p.x * py.x - py.y;
float sd = f / sqrt(fx * fx + fy * fy);
float alpha = saturate(0.5 - sd);

这里的 p 是插值后的程序化坐标。真实实现还会根据曲线方向处理 inside/outside,代码片段只突出“隐式函数 → 梯度 → alpha”这条关键路径。

动手走一遍:让一条曲线穿过 GPU

分步1 / 3

先覆盖控制点凸包

把曲线控制点组成最小覆盖三角形或四边形,内部填充和曲线边界分开管理。

describe the boundary, rasterize the hull, shade the curvecontrol pointsfew points, no dense tessellationGPU interpolationprocedural coordinates interpolatepixel testsign of f(x,y)inside / outsidegeometry stores the boundary; the fragment stage decides coverage
GPU Gems 3 · Chapter 25

Vector Art on the GPU Lab

可交互

切换曲线阶数、透视变换、边界模式和控制点形状,观察同一套隐式覆盖逻辑如何处理不同曲线。

cubic Bézier · k³ − lm · perspective-correct interpolation keeps the curve stableprojective transform · hull triangles 2 · edge pixels 23gradient-based alpha softens the boundary
控制点4
凸包三角形2
隐式方程k³ − lm
边界像素23

小结

  • 向量图保存边界描述,像素阶段决定覆盖,因此不必绑定某个输出分辨率。
  • Bézier 凸包提供最小几何覆盖,GPU 插值器提供每个像素的程序化坐标。
  • 二次曲线可用 u² − v 判断内外,三次曲线先分类再用 k³ − lm
  • 凸包不能失控重叠,loop 双重点和路径内部填充需要预处理与三角化策略。
  • ddx/ddy 近似 signed distance,能把硬符号测试变成随投影变化的抗锯齿 alpha。

练习

练习

问题 1|修改 Demo 代码。 在 Vector Art on the GPU Lab 中依次切换 quadratic、cubic 和 binary boundary,记录控制点数量、隐式方程和边界像素的变化。

问题 2|诊断透视下的曲线变形。 一条二次曲线在 flat view 正常,切换到 arbitrary projective view 后边缘偏移。请检查坐标赋值、插值方式和 inside/outside 方向。

问题 3|场景选型。 需要渲染一个可缩放字体图标、一条包含自交的 cubic 路径和一个复杂多轮廓填充区域。分别说明应优先关注哪个阶段。

名词解释

本章出现的专业名词,用大白话再讲一遍。

vector art

用路径和曲线描述边界的图形表示,放大时可以重新计算像素覆盖。

quadratic Bézier curve

由两个端点和一个控制点定义的抛物线片段。

implicitization

把带参数的曲线改写成像素可直接代入的平面方程。

cubic Bézier curve

由四个控制点定义、可按形状分类的三次曲线片段。

Bézier convex hull

包住曲线控制点的最小凸多边形,GPU 先光栅化它来覆盖曲线。

signed distance

表示像素离边界多远且在哪一侧的近似值,用来生成平滑 alpha。

资料与写作方式声明

本章以GPU Gems 3 · Chapter 25. Rendering Vector Art on the GPU权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…