GPU渲染二次贝塞尔曲线

原理

Resolution Independent Curve Rendering using Programmable Graphics Hardware中作者指出,存在一个射影变换,使得任意一个二次参数曲线可以转换为如下标准的隐式方程

f ( u , v ) = u 2 − v f(u,v) = u^2 - v f(u,v)=u2v

对于曲线上的点 ( u , v ) (u,v) (u,v),必然有 f ( u , v ) = 0 f(u,v)=0 f(u,v)=0,所以在渲染时,我们如果有fragment对应的坐标 ( u , v ) (u,v) (u,v),只需代入这个隐式方程,判断是否小于一定的阈值,即认为该fragment在曲线上。

曲线的表示

若二次参数曲线 C ( t ) C(t) C(t)的控制点为 P i   ( 0 ≤ i ≤ 2 ) P_i \ (0 \le i \le 2) Pi (0i2),则该曲线可以表示为

B i 2 = ( 2 i ) ( 1 − t ) 2 − i t i C ( t ) = ∑ 0 2 B i 2 ( t ) P i = [ P 0 P 1 P 2 ] [ ( 1 − t ) 2 2 t ( 1 − t ) t 2 ] = [ P 0 P 1 P 2 ] [ 1 − 2 1 0 2 − 2 0 0 1 ] [ 1 t t 2 ] = P M t \begin{aligned} B_i^2 &= \binom{2}{i}(1-t)^{2-i}t^i \\ \mathbf{C}(t) &= \sum_0^2 B_i^2(t)P_i \\ &= \begin{bmatrix} P_0 & P_1 & P_2 \end{bmatrix} \begin{bmatrix} (1-t)^2\\ 2t(1-t) \\ t^2 \end{bmatrix}\\ &= \begin{bmatrix} P_0 & P_1 & P_2 \end{bmatrix} \begin{bmatrix} 1 & -2 & 1 \\ 0 & 2 & -2 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 \\ t \\ t^2 \end{bmatrix} \\ &= \mathbf{P M t} \end{aligned} Bi2C(t)=(i2)(1t)2iti=02Bi2(t)Pi=[P0P1P2](1t)22t(1t)t2=[P0P1P2]1002201211tt2=PMt

对于标准形式的二次参数曲线,可以表示为
F ( t ) = F t = [ 0 1 0 0 0 1 1 0 0 ] [ 1 t t 2 ] = [ t t 2 1 ] \begin{aligned} \mathbf{F}(t) &= \mathbf{Ft}\\ &=\begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{bmatrix} \begin{bmatrix} 1 \\ t \\ t^2 \end{bmatrix} \\ &= \begin{bmatrix} t \\ t^2 \\ 1 \end{bmatrix} \end{aligned} F(t)=Ft=0011000101tt2=tt21

这里令 u ( t ) = t u(t) = t u(t)=t v ( t ) = t 2 v(t) = t^2 v(t)=t2,转换为隐式表示就是 f ( u , v ) = u 2 − v = 0 f(u,v)=u^2-v=0 f(u,v)=u2v=0。对应的控制点矩阵 P \mathbf{P} P可以如下计算
F = P M P = F M − 1 = [ 0 1 2 1 0 0 1 1 1 1 ] \begin{aligned} \mathbf{F} &= \mathbf{PM} \\ \mathbf{P} &= \mathbf{FM^{-1}} \\ &=\begin{bmatrix} 0 & \frac{1}{2} & 1 \\ 0 & 0 & 1 \\ 1 & 1 & 1 \end{bmatrix} \end{aligned} FP=PM=FM1=0012101111
所以三个控制点分别为 ( 0 , 0 ) (0,0) (0,0) ( 1 2 , 0 ) (\frac{1}{2},0) (21,0) ( 1 , 1 ) (1,1) (1,1)

将曲线 C \mathbf{C} C变换为标准形式的射影变换 T \mathbf{T} T可以如下计算

F = T C T = F C − 1 \begin{aligned} \mathbf{F} &= \mathbf{TC} \\ \mathbf{T} &= \mathbf{FC^{-1}} \end{aligned} FT=TC=FC1

注意:上述的射影变换在代码中不需要做任何处理,因为已经由三个控制点的位置决定。

实现

由于已经有了三个对应的标准控制点,那么给定一个三角形,我们可以很容易地得到三角形中任意一点的 ( u , v ) (u,v) (u,v)坐标,只需要利用GPU插值即可。通过将三个控制点坐标分别赋给每个顶点,并在fragment shader求值即可得到隐式方程的值。

float aastep(float edge, float v) 
{
    float fwidth = 0.7 * length(float2(dfdx(v), dfdy(v)));
    return smoothstep(edge - fwidth , edge + fwidth, v);
}

fragment float4 curveFragmentFunc(VertexOut input [[ stage_in ]])
{
    float d = input.texcoord.x * input.texcoord.x - input.texcoord.y;
    // calculate the gradient vector
    float2 grad = float2(ddx(d), ddy(d));
    d /= length(grad);
    constexpr int width = 2;
    return float4(0, 1, 0, 1.0f - aastep(width / 2, abs(d)));
}

利用NSBezierPath进行验证

NSBezierPath可以画三次贝塞尔曲线,但是我们需要画二次贝塞尔曲线来验证实现的正确性,所以我们需要将二次曲线升次到三次,对于控制为 P i   ( 0 ≤ i ≤ 2 ) P_i \ (0 \le i \le 2) Pi (0i2),我们可以得到对应的三次曲线的控制点

Q 0 = P 0 Q 1 = ( 1 − 1 3 ) P 1 + 1 3 P 0 Q 2 = ( 1 − 2 3 ) P 2 + 2 3 P 1 Q 3 = P 2 \begin{aligned} Q_0 &= P_0 \\ Q_1 &= (1-\frac{1}{3})P_1 + \frac{1}{3}P_0 \\ Q_2 &= (1-\frac{2}{3})P_2 + \frac{2}{3}P_1 \\ Q_3 &= P_2 \end{aligned} Q0Q1Q2Q3=P0=(131)P1+31P0=(132)P2+32P1=P2

下面是测试的结果
在这里插入图片描述

三个控制点以圆圈表示,黑色为NSBezierPath渲染的结果,绿色为GPU渲染的结果。

改进

如果仔细观察上图会发现,在接近两端控制点部分的曲线宽度有点问题,原因是有一部分像素正好在曲线凸包所在的三角形外,解决办法是沿着垂直于两个切线方向适当地增大一点渲染用的三角形,使得正好能够覆盖曲线的宽度,即最终渲染时使用如下的mesh进行渲染

修正后
使用这个配置后,我们需要计算出新的顶点的 ( u , v ) (u,v) (u,v)坐标,这个可以通过屏幕位置利用上文的 T \mathbf{T} T进行计算得到。计算新的顶点位置需要考虑原始三角形是否翻转,要做相应的的处理。

具体实现见示例工程: 提取码: 5wct。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值