编程语言
首页 > 编程语言> > c# – 在MonoGame(XNA)中绘制Bezier曲线会产生沙哑的线条

c# – 在MonoGame(XNA)中绘制Bezier曲线会产生沙哑的线条

作者:互联网

我最近开始使用MonoGame,我喜欢这个库.
但是,我似乎在绘制贝塞尔曲线时遇到了一些问题

我的代码生成的结果看起来像这样

看起来不好,不是吗?
线条根本不光滑.

我来告诉你一些代码:

//This is what I call to get all points between which to draw.
public static List<Point> computeCurvePoints(int steps)
{   
    List<Point> curvePoints = new List<Point>();
    for (float x = 0; x < 1; x += 1 / (float)steps)
    {
        curvePoints.Add(getBezierPointRecursive(x, pointsQ));
    }   
    return curvePoints; 
}

//Calculates a point on the bezier curve based on the timeStep.
private static Point getBezierPointRecursive(float timeStep, Point[] ps)
{   
    if (ps.Length > 2)
    {
        List<Point> newPoints = new List<Point>();
        for (int x = 0; x < ps.Length-1; x++)
        {
            newPoints.Add(interpolatedPoint(ps[x], ps[x + 1], timeStep));
        }
        return getBezierPointRecursive(timeStep, newPoints.ToArray());
    }
    else
    {
        return interpolatedPoint(ps[0], ps[1], timeStep);
    }
}

//Gets the linearly interpolated point at t between two given points (without manual rounding).
//Bad results!
private static Point interpolatedPoint(Point p1, Point p2, float t)
{
    Vector2 roundedVector = (Vector2.Multiply(p2.ToVector2() - p1.ToVector2(), t) + p1.ToVector2());          
    return new Point((int)roundedVector.X, (int)roundedVector.Y);   
}

//Method used to draw a line between two points.
public static void DrawLine(this SpriteBatch spriteBatch, Texture2D pixel, Vector2 begin, Vector2 end, Color color, int width = 1)
{
    Rectangle r = new Rectangle((int)begin.X, (int)begin.Y, (int)(end - begin).Length() + width, width);
    Vector2 v = Vector2.Normalize(begin - end);
    float angle = (float)Math.Acos(Vector2.Dot(v, -Vector2.UnitX));
    if (begin.Y > end.Y) angle = MathHelper.TwoPi - angle;
    spriteBatch.Draw(pixel, r, null, color, angle, Vector2.Zero, SpriteEffects.None, 0);
}

//DrawLine() is called as following. "pixel" is just a Texture2D with a single black pixel.
protected override void Draw(GameTime gameTime)
{
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();          
            for(int x = 0; x < curvePoints.Count-1; x++)
            {
                DrawExtenstion.DrawLine(spriteBatch, pixel, curvePoints[x].ToVector2(), curvePoints[x + 1].ToVector2(), Color.Black, 2);
            }
            spriteBatch.End();

            base.Draw(gameTime);
}

通过向我的interpolatedPoint方法添加一些手动Math.Round()调用,我设法让线条更平滑

//Gets the linearly interpolated point at t between two given points (with manual rounding).
//Better results (but still not good).
private static Point interpolatedPoint(Point p1, Point p2, float t)
{
    Vector2 roundedVector = (Vector2.Multiply(p2.ToVector2() - p1.ToVector2(), t) + p1.ToVector2());          
    return new Point((int)Math.Round(roundedVector.X), (int)Math.Round(roundedVector.Y));   
}

这会产生以下结果:

我不得不删除一张图片,因为Stackoverflow不允许我使用两个以上的链接

有什么方法可以让这条曲线绝对平滑吗?
也许DrawLine方法存在问题?

提前致谢.

编辑:

好吧,我设法通过使用Vector2Ds进行所有计算并且仅在需要绘制时将其转换为Point,使曲线看起来更好

它仍然不完美:/

解决方法:

正如Mike’Pomax’Kamermans所说,
这似乎是2D表面不允许子像素绘制并因此导致舍入问题的问题

按照工艺游戏的建议,我调整了算法,使用BasicEffect在3D中绘制曲线.这也允许抗锯齿,这使曲线平滑很多.

结果如下:

好了很多!

非常感谢你的建议!

编辑:

这是我用来做这个的代码.

我还想补充一点,这个网页(http://gamedevelopment.tutsplus.com/tutorials/create-a-glowing-flowing-lava-river-using-bezier-curves-and-shaders–gamedev-919)在编写这段代码时给了我很多帮助.

另外,请注意我用于定义方法的一些名称可能没有意义或可能令人困惑.这是我在一个晚上快速拼凑起来的东西.

//Used for generating the mesh for the curve
//First object is vertex data, second is indices (both as arrays)
public static object[] computeCurve3D(int steps)
{
    List<VertexPositionTexture> path = new List<VertexPositionTexture>();
    List<int> indices = new List<int>();

    List<Vector2> curvePoints = new List<Vector2>();
    for (float x = 0; x < 1; x += 1 / (float)steps)
    {
        curvePoints.Add(getBezierPointRecursive(x, points3D));
    }

    float curveWidth = 0.003f;

    for(int x = 0; x < curvePoints.Count; x++)
    {
        Vector2 normal;

        if(x == 0)
        {
            //First point, Take normal from first line segment
            normal = getNormalizedVector(getLineNormal(curvePoints[x+1] - curvePoints[x]));
        }
        else if (x + 1 == curvePoints.Count)
        {
            //Last point, take normal from last line segment
            normal = getNormalizedVector(getLineNormal(curvePoints[x] - curvePoints[x-1]));
        } else
        {
            //Middle point, interpolate normals from adjacent line segments
            normal = getNormalizedVertexNormal(getLineNormal(curvePoints[x] - curvePoints[x - 1]), getLineNormal(curvePoints[x + 1] - curvePoints[x]));
        }

        path.Add(new VertexPositionTexture(new Vector3(curvePoints[x] + normal * curveWidth, 0), new Vector2()));
        path.Add(new VertexPositionTexture(new Vector3(curvePoints[x] + normal * -curveWidth, 0), new Vector2()));
    } 

    for(int x = 0; x < curvePoints.Count-1; x++)
    {
        indices.Add(2 * x + 0);
        indices.Add(2 * x + 1);
        indices.Add(2 * x + 2);

        indices.Add(2 * x + 1);
        indices.Add(2 * x + 3);
        indices.Add(2 * x + 2);
    }

    return new object[] {
        path.ToArray(),
        indices.ToArray()
    };
}

//Recursive algorithm for getting the bezier curve points 
private static Vector2 getBezierPointRecursive(float timeStep, Vector2[] ps)
{

    if (ps.Length > 2)
    {
        List<Vector2> newPoints = new List<Vector2>();
        for (int x = 0; x < ps.Length - 1; x++)
        {
            newPoints.Add(interpolatedPoint(ps[x], ps[x + 1], timeStep));
        }
        return getBezierPointRecursive(timeStep, newPoints.ToArray());
    }
    else
    {
        return interpolatedPoint(ps[0], ps[1], timeStep);
    }
}

//Gets the interpolated Vector2 based on t
private static Vector2 interpolatedPoint(Vector2 p1, Vector2 p2, float t)
{
    return Vector2.Multiply(p2 - p1, t) + p1;
}

//Gets the normalized normal of a vertex, given two adjacent normals (2D)
private static Vector2 getNormalizedVertexNormal(Vector2 v1, Vector2 v2) //v1 and v2 are normals
{
    return getNormalizedVector(v1 + v2);
}

//Normalizes the given Vector2
private static Vector2 getNormalizedVector(Vector2 v)
{
    Vector2 temp = new Vector2(v.X, v.Y);
    v.Normalize();
    return v;
}

//Gets the normal of a given Vector2
private static Vector2 getLineNormal(Vector2 v)
{
    Vector2 normal = new Vector2(v.Y, -v.X);            
    return normal;
}

//Drawing method in main Game class
//curveData is a private object[] that is initialized in the constructor (by calling computeCurve3D)
protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    var camPos = new Vector3(0, 0, 0.1f);
    var camLookAtVector = Vector3.Forward;
    var camUpVector = Vector3.Up;

    effect.View = Matrix.CreateLookAt(camPos, camLookAtVector, camUpVector);
    float aspectRatio = graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight;
    float fieldOfView = MathHelper.PiOver4;
    float nearClip = 0.1f;
    float farClip = 200f;

    //Orthogonal
    effect.Projection = Matrix.CreateOrthographic(480 * aspectRatio, 480, nearClip, farClip);

    foreach (var pass in effect.CurrentTechnique.Passes)
    {
        pass.Apply();
        effect.World = Matrix.CreateScale(200);

        graphics.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, 
            (VertexPositionTexture[])curveData[0],
            0,
            ((VertexPositionTexture[])curveData[0]).Length,
            (int[])curveData[1],
            0,
            ((int[])curveData[1]).Length/3);            
    }

    base.Draw(gameTime);
}

此外,此图像可能能够显示代码做得更好一些

Wireframe of curve

标签:c,rounding,xna,bezier,monogame
来源: https://codeday.me/bug/20190528/1168420.html