在MonoGame(XNA)中绘制Bezier曲线会产生刮擦线 [英] Drawing Bezier curves in MonoGame (XNA) produces scratchy lines

查看:79
本文介绍了在MonoGame(XNA)中绘制Bezier曲线会产生刮擦线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I recently got into using MonoGame, and I love the library. However, I seem to be having some issues with drawing bezier curves

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

The result that my code produces looks something like this

不好意思,不是吗? 线条根本不平滑.

Look bad, no? The lines aren't smooth at all.

让我为您展示一些代码:

Let me show you some of the code:

//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()调用,我设法使行变得更平滑

I managed to make the line a bit smoother by adding some manual Math.Round() calls to my interpolatedPoint method

//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方法有问题?

Are there any ways I can get this curve to be absolutely smooth? Perhaps there is a problem with the DrawLine method?

谢谢.

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

Okay, I managed to make the curve look a lot better by doing all the calculations with Vector2Ds and only converting it to a Point at the moment that it needs to be drawn

尽管如此,它仍然不是很完美:/

It still isn't perfect though :/

推荐答案

正如Mike'Pomax'Kamermans所说, 二维曲面似乎不允许子像素绘制,从而导致舍入问题,这似乎是一个问题

As Mike 'Pomax' Kamermans said, it seems to have been a problem with the 2D surface not allowing subpixel drawing and thus causing rounding issues

根据Craftworkgames的建议,我对算法进行了修改,以使用BasicEffect在3D中绘制曲线.这样还可以进行抗锯齿,从而使曲线平滑很多.

Following craftworkgames' advice, I adapted the algorithm to draw the curve in 3D using a BasicEffect. This also allows for antialiasing, which smoothes out the curve a lot.

结果如下:

好多了!

非常感谢您的有用建议!

Thank you very much for the helpful advice!

这是我执行此操作的代码.

Here is the code I used for doing this.

我还要补充一点,该网页(

I would also like to add that this webpage (http://gamedevelopment.tutsplus.com/tutorials/create-a-glowing-flowing-lava-river-using-bezier-curves-and-shaders--gamedev-919) helped me a lot while writing this code.

另外,请注意,我用于定义方法的某些名称可能并没有真正意义,或者可能会造成混淆.这是我很快在一个晚上整理的东西.

Also, please note that some of the names I used for defining the methods might not really make sense or can be confusing. This was something I quickly put together on an evening.

//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);
}

此外,此图像可能能够显示代码的功能要好一些

Also, this image may be able to show what the code does a little bit better

这篇关于在MonoGame(XNA)中绘制Bezier曲线会产生刮擦线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆