在GPU上绘制二次曲线

16

我的任务是通过Stage3d(Adobe Flash)技术呈现二次贝塞尔曲线(路径),但此技术默认没有任何扩展来进行绘制(而我所知道的OpenGl有此功能)。是的,有一个Starling-Extension-Graphics,但它使用简单方法将曲线段分割为许多直线,这会为我的长曲线路径生成非常多的三角形。

所以...针对Loop和Blinn,有一种完美的方式来呈现分辨率独立的形状。我已经阅读了GPUGems3文章(gpugems3_ch25.html),并将其片段着色器移植到AGAL2:

二次曲线像素着色器

float4 QuadraticPS(float2 p : TEXCOORD0,  
  float4 color : COLOR0) : COLOR  
{  
  // Gradients  
   float2 px = ddx(p);  
   float2 py = ddy(p);  
  // Chain rule  
   float fx = (2*p.x)*px.x - px.y;  
   float fy = (2*p.x)*py.x - py.y;  
  // Signed distance  
   float sd = (p.x*p.x - p.y)/sqrt(fx*fx + fy*fy);  
  // Linear alpha  
   float alpha = thickness - abs(sd);  
  if (alpha > 1)       // Inside  
    color.a = 1;  
  else if (alpha < 0)  // Outside  
    clip(-1);  
  else                   
  // Near boundary  
    color.a = alpha;  
    return color;  
}  

它能工作。但是存在两个根本性问题:

  1. 我不了解那个算法:(。我阅读了有关有符号距离场、导数等的内容......我想了很多小时,又重新阅读了一遍——但没有结果!我的问题是:是否有人可以帮我解释一下该着色器中发生了什么(如果可能,逐行解释!)?

  2. 第二个问题是曲线在三角形的拐角处被剪裁,并且具有可变厚度。请看图片: https://monosnap.com/file/84EBOuQ1czNM5lprQ5VlnRUKP2mKmW 所以如果我画一条路径,它看起来像这样:https://monosnap.com/file/54Zs5Xui6s3BL6lNdZRCx9ibcV2bCF

我喜欢使用每个曲线段一个三角形的方法,因为不需要任何几何形状。我不需要非常厚的曲线(1-2像素很好),但是可变厚度是一个问题。有人可以帮助我吗?

(对不起我的英语。这不是我的母语。)

[Spektre的编辑1] 从评论和无效答案中移动

我计划使用每个曲线段一个三角形,就像图片上所示

  • the path
  • 路径由许多三角形组成
  • 每个路径段(二次曲线)一个三角形
  • 如果所有控制点共线(位于同一条直线上)或几乎共线,则如何处理此方法的问题?

添加细节,例如您使用的二次曲线是哪个(方程式),每个曲线补丁有多少控制点等等。看起来您正在使用三角形基元(厚度伪影的原因),也许QUAD_STRIP会更好(其中控制点以定义的方式位于四边形补丁内,如四边形是控制点四边形的恒定放大,但对于通用曲线来说这很棘手)或将控制点作为另一个属性布局单独提供。 - Spektre
谢谢,Spektre!我想画出由3个控制点描述的任何二次Bezier曲线。现在我认为最好的方法是:1.使用一个常数放大的四边形(2个三角形)作为“画布”,可以进行任何程序化绘制;2.查找具体的二次曲线方程(http://math.stackexchange.com/questions/1360891/find-quadratic-bezier-curve-equation-based-on-its-control-points),并将其传递给片段着色器,而不是Loop/Blinn默认方程(u^2 - v = 0);3.使用Loop/Blinn算法绘制新方程。 - Ilya
看一下这个有没有可能从三次贝塞尔曲线方程中表示“t”变量?你可以轻松地更改代码以满足您的低阶BEZIER... - Spektre
1个回答

14

对于三个控制点的贝塞尔曲线,我会:

  1. 使用三角形作为基元
  2. 扩大控制点以包括曲线周围的区域,以避免出现伪影

triangle enlargement

这种方法非常快速,并且从 A,B,C 计算 A',B',C'(反之亦然)没有问题。如果比例尺度是常数(例如 scale=1.25),则最大可用曲线 thickness<=2.0*min(|control_point-M|)*(scale-1.0)

为了更安全地扩大曲线,您可以计算所需的精确比例尺度(例如在几何着色器中),并将其传递给顶点和片段... 以上所有操作都可以通过几何着色器完成。您应该使用透明度来正确地连接曲线。平均中心点应保持相同 M=A+B+C=A'+B'+C'

如果透明度不是选项

那么您需要改变方法,将控制点和纹理内部位置一起传递。

  1. 创建一个二维 float 纹理,其中包含控制点
  • 类似于 float pnt[9][N]
  • pnt[0,1,2][] 是控制点 A(x,y,z)
  • pnt[3,4,5][] 是控制点 B(x,y,z)
  • pnt[6,7,8][] 是控制点 C(x,y,z)
  1. 还要创建一维颜色纹理
  • 类似于 rgba col[N]
  • 纹理的 x 轴分辨率 = N 是贝塞尔曲线的数量
  1. 现在绘制一个覆盖整个屏幕的单个四边形

在片段着色器中检查像素是否在任何曲线内。如果是,请输出其颜色...

对于高贝塞尔曲线数量 N,这可能会变得非常慢

[编辑1] 几乎共线的控制点

对于这些点,我将使用四边形

quad enlarge

  • D,E 是镜像点 A,B 关于 C
  • D=C+C-A
  • E=C+C-B
  • C 是中间点 M = (A+B+D+E)/4 = C = (A'+B'+C'+D')/4
  • 并且 A',B',C',D' 是增大的 控制 点A,B,D,E
  • A'=C+(A -C)*scale
  • B'=C+(B -C)*scale
  • A =C+(A'-C)/scale
  • B =C+(B'-C)/scale

这可用于任何Bezier,而不仅仅是几乎共线,但它使用更大的多边形,因此在性能上会更慢(比实际需要的碎片更多)

这里是更高级/优化的GLSL方法,完整实现了立方体BEZIER曲线:


非常感谢,Spektre!太棒了!!!这正是我想的,但我无法表达出来!它可能会非常快速运行! - Ilya
嗯...我不明白为什么需要使用透明度...你是什么意思?第二种方法现在也不是很清楚... - Ilya
非常感谢!现在我考虑另一种方法来解决这个问题,因为正如你所说,它会影响性能,而且也没有简单的方法来绘制直线。 - Ilya
@AlexVestin 转换为顶点...如果您将原始顶点发送到GPU,则必须通过缩放将其扩大,以使原始区域覆盖整个曲线。这可以在几何着色器中完成,同时计算曲线多项式系数。您也可以在CPU端执行此操作,然后在几何着色器中缩小... - Spektre
谢谢!我正在尝试进行WebGL实现(它没有几何着色器),所以我有点困惑如何在片段着色器中反转缩放。最终我在片段着色器中进行了UV缩放,并在您的帮助下成功了。 - AlexVestin
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接