共计 1571 个字符,预计需要花费 4 分钟才能阅读完成。
背景
这篇文章针对的是须要从 3D 硬件加速 API 自撸 2D 绘制引擎的状况。
在 3D 引擎中绘制比较复杂的 2D 线条,常见的办法是在 CPU 中先算出线条的具体轮廓(在此过程中思考简单的线条款式,如线条拐角与端点的形态等等)并三角化,而后将三角形图元发送到显卡管线进行渲染。显卡起的作用其实次要是光栅化器和执行着色逻辑。
线条地位,线条轮廓,三角化图元,光栅化。
那么咱们立刻就会遇到抗锯齿(antialias,以下简称 AA)的问题。3D 管线内置的 MSAA 成果比较稳定,然而计算代价较大,而更先进的 FXAA、深度学习抗锯齿更多地是为了 3D 场景渲染设计,如果利用到 2D 绘制,一是未必有它们须要的信息(比方深度、光照),二是这些近似算法可能不太适宜用在要求准确的 2D 绘制上。而且不论如何,咱们常常会遇到不能关上硬件内置 AA 的状况(硬件不反对 / 须要重开窗口而利用场景无奈重开窗口)。于是一个自制的 AA 总是有必要的。
此处介绍一种比拟简便的抗锯齿描线办法,它大体上是对一个传统的三次 draw call 描线办法(别离绘制线条核心和半透明的两个边际)的一个改良,次要有这些益处:
- 三角形数量大抵上只有原始形态的两倍,而不是三倍。
- 只须要一次 draw call。
- 思路比较简单。
- 计算代价绝对较低。
- 不依赖任何独有的硬件个性。
- 成果能够承受。
办法
在栅格化的时候,AA 操作的本质,是计算被矢量图形局部笼罩的像素该当是什么“强度”,比方:如果像素有一半的面积被笼罩,那它的“强度”该当是 50%;有三分之一被笼罩,它的“强度”就该当是 33%。那么如何搞出一个比拟适合的“强度”呢?这个办法的思路是:让像素依照本人核心间隔线条边界的间隔逐步淡出:处于边界外部 0.5 像素尺寸或更靠内的像素有齐全的“强度”,处于边界内部 0.5 像素尺寸的像素是齐全淡出的。这是对像素覆盖率的一个近似。
为了达到这个目标,首先咱们在生成线条的几何轮廓的时候,进行下列两点修改:
- 光栅化的时候,不思考任何硬件 AA,那么只有核心在几何体之内的像素才会参加渲染。为了保障仅局部笼罩的像素也参加渲染,线条的几何轮廓须要略微搞大一点。如果线条宽度为 w,半径 r = w/2,那么改为生成 r + 1 半径的轮廓(实际上该当 +0.707 像素尺寸就够了)。这是为了保障淡出范畴的像素被笼罩。
- 生成轮廓的时候,劈成左右两半边,并且赋予一个顶点属性记录本人离线条核心有多远:在线条核心的顶点设置为 0,在边缘的顶点设置为 r +1,让它插值到 fragment shader。这样天然就晓得每个像素离核心多远了。
这个过程相当于构建了一个矢量的有向间隔场。
粗实线为线条核心,深灰色标识原始线条宽度,浅灰色标识“扩大”的线条宽度。
最终在 fragment shader 中,根据这个属性计算出片元离边界的间隔,归一化之后间接给片元色彩额定的 alpha 系数,两者关系如下图所示:
下图为应用了这个办法进行 AA 的理论渲染成果。其中绿色折线宽度为 2 像素,蓝色曲线宽度为 1 像素。
一些显然的改良
DPI-aware
其实非常简单,算 alpha 的时候假如像素尺寸不是 1 就好了:如果 DPI 缩放是 2,那么像素尺寸就是 0.5,并依此计算像素“强度”。于是 alpha 值和核心间隔的关系稍作批改,如下图所示:
DPI 缩放值没法间接从 shader 中取得,须要从另外的中央拿到(比方窗口零碎),并在调用时塞到 uniform 里。
更正确的像素“强度”
上述简化的 AA 算法的像素“强度”估算,其实只在线条方向平行于像素边际时才是正确的,其它方向都会有一些偏差,会导致细曲线看上去有些奇怪。
齐全准确的像素“强度”其实也并不那么艰难,但须要额定的信息:在生成几何体的时候,把线条的切线方向传进顶点,并插值到片元。这样一来,片元同时晓得本人离边缘的间隔和切线的方向,是能正确计算出本人的覆盖率的,依此算出的像素“强度”基本上是准确的。