我在OpenGL程序中绘制了许多二次Bézier曲线。现在,曲线是一个像素的薄和软件生成的,因为我处于一个相当早的阶段,它足以看到什么有效。
简单地说,给定3个控制点( P 0 到 P 2 ),我评估以下内容 t 的等式在软件中从0变为1(步长为1/8)并使用GL_LINE_STRIP
将它们链接在一起:
B( t )=(1 - t ) 2 P 0 + 2(1 - t ) t P 1 + t 2 p <子> 2 子> 的
显然,B
导致二维向量。
这种方法“足够好”,因为即使我最大的曲线也不需要超过8个步骤才能看起来弯曲。不过,一条像素细的曲线很难看。
我想编写一个GLSL着色器,它接受控制点和一个统一的thickness
变量,以使曲线更粗。起初我想过只创建一个像素着色器,它只会为曲线的thickness / 2
距离内的像素着色,但这样做需要求解三次多项式,并且在着色器内部选择三个解决方案并不看喜欢有史以来最好的主意。
然后我试着查看其他人是否已经这样做了。我偶然发现了a white paper by Loop and Blinn from Microsoft Research,在那里,这些家伙展示了一种简单的方法来填充曲线下的区域。虽然它在这种程度上运作良好,但我无法将想法调整为在两条曲线之间绘制。
使用几何着色器查找与单条曲线匹配的边界曲线相当容易。片段着色器会出现问题,应该填满整个事物。他们的方法使用插值纹理坐标来确定片段是否落在曲线之上或之下;但我想办法用两条曲线来做这件事(我对着色器而言并不是数学专家,所以事实上我并没有弄清楚如何做到这一点当然并不意味着它是不可能的)。
我的下一个想法是将填充的曲线分成三角形,只在外部使用Bézier片段着色器。但是为此我需要将内部曲线和外部曲线分开到可变点,这意味着我必须再次解决方程式,这不是一个真正的选择。
是否有可行的算法用于使用着色器描边二次Bézier曲线?
答案 0 :(得分:3)
这部分延续了我之前的回答,但实际上是完全不同的,因为我在答案中遇到了一些错误的中心问题。
为了允许片段着色器仅在两条曲线之间进行着色,提供两组“纹理”坐标作为变量变量,应用Loop-Blinn技术。
varying vec2 texCoord1,texCoord2;
varying float insideOutside;
varying vec4 col;
void main()
{
float f1 = texCoord1[0] * texCoord1[0] - texCoord1[1];
float f2 = texCoord2[0] * texCoord2[0] - texCoord2[1];
float alpha = (sign(insideOutside*f1) + 1) * (sign(-insideOutside*f2) + 1) * 0.25;
gl_FragColor = vec4(col.rgb, col.a * alpha);
}
到目前为止,很容易。困难的部分是在几何着色器中设置纹理坐标。 Loop-Blinn为控制三角形的三个顶点指定它们,并且它们在三角形中被适当地插值。但是,这里我们需要在实际渲染不同的三角形时使用相同的插值。
对此的解决方案是找到从(x,y)坐标到内插/外推值的线性函数映射。然后,可以在渲染三角形时为每个顶点设置这些值。这是我的代码的关键部分。
vec2[3] tex = vec2[3]( vec2(0,0), vec2(0.5,0), vec2(1,1) );
mat3 uvmat;
uvmat[0] = vec3(pos2[0].x, pos2[1].x, pos2[2].x);
uvmat[1] = vec3(pos2[0].y, pos2[1].y, pos2[2].y);
uvmat[2] = vec3(1, 1, 1);
mat3 uvInv = inverse(transpose(uvmat));
vec3 uCoeffs = vec3(tex[0][0],tex[1][0],tex[2][0]) * uvInv;
vec3 vCoeffs = vec3(tex[0][1],tex[1][1],tex[2][1]) * uvInv;
float[3] uOther, vOther;
for(i=0; i<3; i++) {
uOther[i] = dot(uCoeffs,vec3(pos1[i].xy,1));
vOther[i] = dot(vCoeffs,vec3(pos1[i].xy,1));
}
insideOutside = 1;
for(i=0; i< gl_VerticesIn; i++){
gl_Position = gl_ModelViewProjectionMatrix * pos1[i];
texCoord1 = tex[i];
texCoord2 = vec2(uOther[i], vOther[i]);
EmitVertex();
}
EndPrimitive();
这里pos1和pos2包含两个控制三角形的坐标。此部分渲染由pos1定义的三角形,但texCoord2设置为pos2三角形的平移值。然后类似地需要渲染pos2三角形。然后需要填充每端的这两个三角形之间的间隙,两组坐标被适当地转换。
矩阵逆的计算需要GLSL 1.50或者需要手动编码。最好在不计算逆的情况下求解平移方程。无论哪种方式,我都不希望这部分在几何着色器中特别快。
答案 1 :(得分:0)
你应该能够在你提到的论文中使用Loop和Blinn的技巧。
基本上,您需要在两个方向上偏移法线方向上的每个控制点,以获得两条曲线(内部和外部)的控制点。然后按照Loop和Blinn的3.1节中的技术 - 这将分解曲线的各个部分以避免三角形重叠,然后对内部的主要部分进行三角测量(请注意,此部分需要CPU)。最后,填充这些三角形,并使用Loop和Blinn的技术(在第3节的开头和结尾)在GPU上渲染它们外面的小弯曲部分。
此处描述了可能适合您的替代技术: Thick Bezier Curves in OpenGL
编辑: 啊,你想避免CPU三角测量 - 我应该更仔细地阅读。
您遇到的一个问题是几何着色器和片段着色器之间的接口 - 几何着色器需要生成基元(最可能是三角形),然后单独栅格化并通过片段程序填充。
在你的厚度恒定的情况下,我认为非常简单的三角测量将起作用 - 使用Loop和Bling来处理所有“弯曲位”。当两个控制三角形不相交时,它很容易。当他们这样做时,交叉口外的部分很容易。所以唯一困难的部分是在交叉点内(应该是一个三角形)。
在交叉点内,只有当两个控制三角形都通过Loop和Bling对其进行着色时,才要对像素进行着色。因此片段着色器需要能够对两个三角形进行纹理查找。一个可以作为标准,你需要为第二组纹理坐标添加一个vec2变量变量,你需要为三角形的每个顶点适当地设置。同样,你需要一个统一的“sampler2D”变量用于纹理,然后你可以通过texture2D进行采样。然后,您只需遮蔽满足两个控制三角形(在交叉点内)的检查的片段。
我认为这适用于所有情况,但我可能错过了一些东西。
答案 2 :(得分:0)
我不知道如何解决这个问题,但这很有趣。我认为你需要GPU中的每个不同的处理单元:
顶点着色器
向顶点着色器抛出一条正常的点线。让顶点着色器将点移动到贝塞尔曲线。
几何着色器
让几何着色器为每个顶点创建一个额外的点。
foreach (point p in bezierCurve)
new point(p+(0,thickness,0)) // in tangent with p1-p2
片段着色器
要使用特殊笔划描边bezier,可以使用带alpha通道的纹理。您可以检查Alpha通道的值。如果它为零,则剪切像素。这样,你仍然可以让系统认为它是一条实线,而不是半透明的。您可以在Alpha通道中应用一些模式。
我希望这会对你有所帮助。你必须自己弄清楚一些事情,但我认为几何着色会加快你的速度。
我仍然选择创建GL_QUAD_STRIP
和alpha通道纹理。