贝塞尔曲线,Loop和Blinn风格

11
几天前,我开始研究高效绘制贝塞尔曲线,并发现了由Charles Loop和Jim Blinn开发的这种方法,看起来非常有趣。然而,经过大量试验他们的算法后,我似乎无法使其能够呈现立方曲线。二次曲线很好,没有问题。
目前我找到的唯一资源如下: GPU Gems 3第25章 Curvy Blues 使用可编程图形硬件进行分辨率独立曲线渲染 为了快速进行测试,我在XNA中完成此任务。基本上,我将纹理坐标与我的顶点一起传递给GPU,应用透视变换,并在像素着色器中使用所有文章中提到的公式来呈现最终结果。然而,问题(我认为)在于我如何计算纹理坐标。请查看此代码:
public void Update()
{
    float a1 = Vector3.Dot(p1, Vector3.Cross(p4, p3));
    float a2 = Vector3.Dot(p2, Vector3.Cross(p1, p4));
    float a3 = Vector3.Dot(p3, Vector3.Cross(p2, p2));

    float d1 = a1 - 2 * a2 + 3 * a3;
    float d2 = -a2 + 3 * a3;
    float d3 = 3 * a3;

    float discr = d1 * d1 * (3 * d2 * d2 - 4 * d1 * d3);

    if (discr > 0)
    {
        Type = CurveTypes.Serpentine;

        float ls = 3 * d2 - (float)Math.Sqrt(9 * d2 * d2 - 12 * d1 * d3);
        float lt = 6 * d1;
        float ms = 3 * d2 + (float)Math.Sqrt(9 * d2 * d2 - 12 * d1 * d3);
        float mt = 6 * d1;

        TexCoord1 = new Vector3(ls * ms, (float)Math.Pow(ls, 3), (float)Math.Pow(ms, 3));
        TexCoord2 = new Vector3((3 * ls * ms - ls * mt - lt * ms) / 3, ls * ls * (ls - lt), ms * ms * (ms - mt));
        TexCoord3 = new Vector3((lt * (mt - 2 * ms) + ls * (3 * ms - 2 * mt)) / 3, (float)Math.Pow(lt - ls, 2) * ls, (float)Math.Pow(mt - ms, 2) * ms);
        TexCoord4 = new Vector3((lt - ls) * (mt - ms), -(float)Math.Pow(lt - ls, 3), -(float)Math.Pow(mt - ms, 3));
    }
    else if (discr == 0)
    {
        Type = CurveTypes.Cusp;
    }
    else if (discr < 0)
    {
        Type = CurveTypes.Loop;
    }
}

抱歉代码比较混乱,这只是测试代码。 p1 ... p4 是世界空间中的控制点,而TexCoord1 ... TexCoord4是相应的纹理坐标。 这是 GPU Gems 文章中所说内容的复制。

这里有几个问题,首先在计算 a3 时,我们使用 p2 作为两个参数,这当然会始终得到一个 (0,0,0) 的向量,并将其和 p3 的点积总是得到 0。这非常无用,那么为什么他们要在文章中提到呢?

这当然会使 discr 不正确,而且我们甚至无法确定它是什么类型的曲线。

在尝试了一段时间的代码之后,我决定尝试按照 Loop 和 Blinn 论文中的方式来做。从那里得到了这样的东西:

public void Update()
{
    Matrix m1 = new Matrix(
        p4.X, p4.Y, 1, 0,
        p3.X, p3.Y, 1, 0,
        p2.X, p2.Y, 1, 0,
        0, 0, 0, 1);
    Matrix m2 = new Matrix(
        p4.X, p4.Y, 1, 0,
        p3.X, p3.Y, 1, 0,
        p1.X, p1.Y, 1, 0,
        0, 0, 0, 1);
    Matrix m3 = new Matrix(
        p4.X, p4.Y, 1, 0,
        p2.X, p2.Y, 1, 0,
        p1.X, p1.Y, 1, 0,
        0, 0, 0, 1);
    Matrix m4 = new Matrix(
        p3.X, p3.Y, 1, 0,
        p2.X, p2.Y, 1, 0,
        p1.X, p1.Y, 1, 0,
        0, 0, 0, 1);

    float det1 = m1.Determinant();
    float det2 = -m2.Determinant();
    float det3 = m3.Determinant();
    float det4 = -m4.Determinant();

    float tet1 = det1 * det3 - det2 * det2;
    float tet2 = det2 * det3 - det1 * det4;
    float tet3 = det2 * det4 - det3 * det3;

    float discr = 4 * tet1 * tet3 - tet2 * tet2;

    if (discr > 0)
    {
        Type = CurveTypes.Serpentine;

        float ls = 2 * det2;
        float lt = det3 + (float)((1 / Math.Sqrt(3)) * Math.Sqrt(3 * det3 * det3 - 4 * det2 * det4));
        float ms = 2 * det2;
        float mt = det3 - (float)((1 / Math.Sqrt(3)) * Math.Sqrt(3 * det3 * det3 - 4 * det2 * det4));

        TexCoord1 = new Vector3(lt * mt, (float)Math.Pow(lt, 3), (float)Math.Pow(mt, 3));
        TexCoord2 = new Vector3(-ms * lt - ls * mt, -3 * ls * lt * lt, -3 * ms * mt * mt);
        TexCoord3 = new Vector3(ls * ms, 3 * ls * ls * lt, 3 * ms * ms * mt);
        TexCoord4 = new Vector3(0, -ls * ls * ls, -ms * ms * ms);
    }
    else if (discr == 0)
    {
        Type = CurveTypes.Cusp;
    }
    else if (discr < 0)
    {
        Type = CurveTypes.Loop;
    }
}

猜猜看,那也没有起作用。然而,现在discr似乎至少更正确了一点。至少它有正确的符号,并且当控制点排列成尖角时,它为零。我仍然得到相同的视觉效果,除了曲线会随机消失一段时间(像素着色器公式始终大于零),并在我将控制点移回更接近正方形的形状后返回。顺便说一句,这是像素着色器代码:

 PixelToFrame PixelShader(VertexToPixel PSIn)
 {
     PixelToFrame Output = (PixelToFrame)0;

     if(pow(PSIn.TexCoords.x, 3) - PSIn.TexCoords.y * PSIn.TexCoords.z > 0)
     {
     Output.Color = float4(0,0,0,0.1);
     }
     else
     {
     Output.Color = float4(0,1,0,1);
     }

    return Output;
}

目前我能想到的所有有用信息就是这些。有人知道发生了什么吗?因为我的想法已经用尽了。


我也遇到了这个问题。你解决了吗?你能回答我的问题吗?https://dev59.com/9W_Xa4cB1Zd3GeqP6ONt - scippie
1个回答

7
我看了你的论文和代码,似乎你漏掉了将矩阵M3乘以一个数的步骤。
在使用p1、p2、p3、p4坐标计算行列式之前,应该将这些坐标放入一个矩阵中,并将其与矩阵M3相乘。
例如:
Matrix M3 = Matrix(
    1, 0, 0, 0,
    -3, 3, 0, 0,
    3, -6, 3, 0,
    -1, 3, -3, 1);
Matrix B = Matrix(
    p1.X, p1.Y, 0, 1,
    p2.X, p2.Y, 0, 1,
    p3.X, p3.Y, 0, 1,
    p4.X, p4.Y, 0, 1);
Matrix C = M3*B;

然后您可以将C矩阵的每一行用作代码中m1到m4矩阵的坐标。其中行的第一个和第二个值是x、y坐标,最后一个是w坐标。

最后,纹理坐标矩阵需要与M3的逆矩阵相乘 例如:

Matrix invM3 = Matrix(
    1, 0, 0, 0,
    1, 0.3333333, 0, 0,
    1, 0.6666667, 0.333333, 0,
    1, 1, 1, 1);
Matrix F = Matrix(
    TexCoord1,
    TexCoord2,
    TexCoord3,
    TexCoord4);
Matrix result = invM3*F;

生成的每一行矩阵对应于着色器所需的纹理坐标。

我自己还没有实现过,所以不能保证它能解决你的问题。只是在阅读论文后,我注意到你的实现中缺少了这个。

希望这可以帮到你,如果我错了,请告诉我,因为我很快就会尝试这个。


太棒了!简直不敢相信我会搞砸得这么离谱 :D 问题是,det1总是为0,这绝对不对。明天我会进行更多测试,看看能否找到问题所在。如果你有任何想法,请告诉我。 - Roliga
1
在论文的积分三次曲线部分(4.4),第一段中确实提到对于积分三次曲线,它的第一个行列式将等于零。这将把着色器代码方程简化为k^3-lm。 - rdsgalvao
好的。我认为有问题的原因是因为我只认为曲线实际上带有一个循环才应该使用循环方程,但事实并非如此,所以在实现循环方程后一切正常了。现在我需要对其进行结构化处理,以便实际使用:D 如果遇到更多问题,我会再次发表评论。 - Roliga

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