在Unity中画出流畅的线条真的很难吗?

时间:2017-04-21 16:37:52

标签: unity3d drawing

我一直试图在Unity中绘制平滑的线条,但是使用线条渲染器我只获得锯齿状线条,其边角不圆,特别是当曲率角非常小时。我在质量设置中增加了抗锯齿的值并尝试了不同的材料,但没有任何改变。我还尝试在每次鼠标移动时实例化一个球体,但它会在各个球体之间产生一些间隙,特别是当鼠标快速移动时。我知道有一个名为Vectrosity的插件,但有一种方法可以实现这一点吗?

6 个答案:

答案 0 :(得分:21)

通过从一组点生成网格,可以获得一些好的结果。

它的算法如下:

  1. 你有一组点,可以用贝塞尔曲线生成。
  2. enter image description here

    1. 对于每个点,将方向向量移至下一个点v = (p2 - p1) (标记为蓝色)。然后将该矢量旋转90度normal = v.y, -v.x 标记为红色
    2. enter image description here

      1. 这说明我们将从点位置使用每个法线。现在,您可以将此向量在两个方向上乘以所需的线宽。
      2. enter image description here

        1. 在这些位置创建顶点。
        2. enter image description here

          1. 添加索引以形成三角形。它类似[i, w/2 + i, w/2 + i + 1],其中i是当前索引,w是顶点总数。
          2. enter image description here

            1. 创建其他三角形。再次像[i, w/2 * i + 1, i + 1]
            2. enter image description here

              1. 最后的结果。您可以添加更多点以使线条更平滑。
              2. enter image description here

答案 1 :(得分:8)

  

我只获得锯齿状的线条,其中的圆角没有圆角   特别是当曲率角非常小时。

这是 Unity 5.4 及以下的问题。在完全重新设计LineRenderer后, Unity 5.5 及以上版本已修复此问题。

您所要做的就是更新到 Unity 5.5 或上面的版本,这个问题就会消失。

有一个名为LineRenderer.numCornerVertices的新变量。您可以使用它来设置您希望线条的平滑程度。 5 的值似乎很好。

还有另一个名为LineRenderer.numCapVertices的新变量,可用于设置该行的 end 的平滑程度。

这是在 5.4 5.5 之间演示更改的屏幕截图:

enter image description here

答案 2 :(得分:2)

由于@Iggy在catlikecoding.com上的灵感和教程(我使用的样条代码来自此),我创建了一个组件,该组件将根据给定宽度和采样频率的样条创建网格。更高的采样频率=当然曲线更平滑。

using UnityEngine;

[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer), typeof(BezierSpline))]
public class SplineMesh : MonoBehaviour {

  [Range(1, 20)]
  public int sampleFrequency = 5;

  [Range(0, 5f)]
  public float lineWidth = 0.3f;

  BezierSpline spline;
  Mesh mesh;

  private void Awake () {
    spline = GetComponent<BezierSpline>();
    mesh = GetComponent<Mesh>();
  }

  void Update()
  {
    /*
    for(int i = 0; i <= sampleFrequency; i++){
      float interval = i / (float)sampleFrequency;

      var point = spline.GetPoint(interval);
      var direction = spline.GetDirection(interval);

      var perpendicularLeftVec = PerpendicularLeft(direction) * lineWidth;
      var perpendicularRightVec = PerpendicularRight(direction) * lineWidth;

      Debug.DrawLine(point, point + (Vector3)perpendicularLeftVec, Color.magenta, 0.5f, false);
      Debug.DrawLine(point, point + (Vector3)perpendicularRightVec, Color.cyan, 0.5f, false);
    }
    */
  }

    Vector2 PerpendicularRight(Vector2 orig){
        var vec = new Vector2(orig.y, -orig.x);
        vec.Normalize();
        return vec;
    }
    Vector2 PerpendicularLeft(Vector2 orig){
        var vec = new Vector2(orig.y, -orig.x);
        vec.Normalize();
        return vec * -1;
    }

  private Vector3[] vertices;

  public void GenerateMesh(){
    vertices = new Vector3[(sampleFrequency + 1) * 2];

    //iterate over our samples adding two vertices for each one
    for(int s = 0, i = 0; s <= sampleFrequency; s++, i += 2){
      float interval = s / (float)sampleFrequency;

      //get point along spline, and translate to local coords from world
      var point = transform.InverseTransformPoint(spline.GetPoint(interval));
      var direction = spline.GetDirection(interval);

      var perpendicularLeftVec = PerpendicularLeft(direction) * lineWidth;
      var perpendicularRightVec = PerpendicularRight(direction) * lineWidth;
      // var perpendicularVec = turnLeft ? PerpendicularLeft(diffVector) : PerpendicularRight(diffVector);

      vertices[i] = point + (Vector3)perpendicularLeftVec;
      vertices[i + 1] = point + (Vector3)perpendicularRightVec;
    }

    GetComponent<MeshFilter>().mesh = mesh = new Mesh();
    mesh.name = "Spline Mesh";

    mesh.vertices = vertices;

    //now figure out our triangles
    int [] triangles = new int[sampleFrequency * 6];
    for(int s = 0, ti = 0, vi = 0; s < sampleFrequency; s++, ti += 6, vi += 2){
      //first tri
      triangles[ti] = vi;
      triangles[ti + 1] = vi + 3;
      triangles[ti + 2] = vi + 1;
      //second matching tri
      triangles[ti + 3] = vi;
      triangles[ti + 4] = vi + 2;
      triangles[ti + 5] = vi + 3;
    }

    mesh.triangles = triangles;
    mesh.RecalculateNormals();

    Debug.Log("Generated Spline Mesh");
  }


}

答案 3 :(得分:0)

我在Unity 2017.2中遇到过这个问题。我尝试将我的AA设置更改为最大值以摆脱线条渲染锯齿。没有工作,这令人沮丧。

我的解决方案是解决了相机关闭MSAA的问题,因为渲染延迟了。相机有一个“使用图形设置”的设置,它应该从来没有搞砸过,但我是初学者 - 我不太了解。我将设置改为“前进”,我的锯齿消失在雾中。

如果我更勤奋,我会在图像之前和之后发布。

答案 4 :(得分:0)

由于@Iggy的出色回答,我终于可以工作了。

在画布上创建一个新图像,删除图像脚本并将其替换为UICubicBezier。

enter image description here

提供:

enter image description here

using UnityEngine;
using UnityEngine.UI;

[ExecuteInEditMode]
public class UiCubicBezier : MaskableGraphic
{
    public float thickness = 2;

    public int anchors = 20;

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        // draws a cubic bezier curve from the lower left hand corner (start)
        // to the upper right hand corner (end).
        vh.Clear();

        var rt = this.rectTransform;
        var rect = rt.rect;

        var start = new Vector2(-rect.width / 2, -rect.height / 2);
        var cp1 = new Vector2(-rect.width / 6, -rect.height / 2);
        var cp2 = new Vector2(rect.width / 6, rect.height / 2);
        var end = new Vector2(rect.width / 2, rect.height / 2);
        var data = new BezierData(start, cp1, cp2, end);

        // all you need to know is that data.GetPoint generates a sequence of points
        // between the start and end points.
        var points = new Vector2[this.anchors];
        for (var anchor = 0; anchor < points.Length; anchor++)
        {
            var t = (float)anchor / this.anchors;
            points[anchor] = data.GetPoint(t);
        }

        // because the normals are at the mid-points between vertexes the start and end
        // points don't touch the bounding box. to fix this some vertexes are added to
        // the start and end that touch the bounding box.
        this.DrawStartVertexes(vh, start);

        for (var anchor = 0; anchor < points.Length - 1; anchor++)
        {
            this.DrawVertexes(vh, points[anchor], points[anchor + 1]);
        }

        this.DrawEndVertexes(vh, end);

        for (var v = 0; v + 2 < vh.currentVertCount; v += 2)
        {
            vh.AddTriangle(v, v + 1, v + 2);
        }

        for (var v = 0; v + 3 < vh.currentVertCount; v += 2)
        {
            vh.AddTriangle(v + 1, v + 2, v + 3);
        }
    }

    private void DrawStartVertexes(VertexHelper vh, Vector2 start)
    {
        // d = thickness * \sqrt{2}, so the distance between the vertexes
        // is equal to the thickness (https://en.wikipedia.org/wiki/Triangle#Right_triangles)
        var d = this.thickness * 0.70710678118f;

        var vertex = UIVertex.simpleVert;
        vertex.color = this.color;

        vertex.position = new Vector2(start.x, start.y + d);
        vh.AddVert(vertex);

        vertex.position = new Vector2(start.x + d, start.y);
        vh.AddVert(vertex);
    }

    private void DrawEndVertexes(VertexHelper vh, Vector2 end)
    {
        // d = thickness * \sqrt{2}, so the distance between the vertexes
        // is equal to the thickness (https://en.wikipedia.org/wiki/Triangle#Right_triangles)
        var d = this.thickness * 0.70710678118f;

        var vertex = UIVertex.simpleVert;
        vertex.color = this.color;

        vertex.position = new Vector2(end.x - d, end.y);
        vh.AddVert(vertex);

        vertex.position = new Vector2(end.x, end.y - d);
        vh.AddVert(vertex);
    }

    private void DrawVertexes(VertexHelper vh, Vector2 start, Vector2 end)
    {
        var v = end - start;
        var mid = start + v / 2; // the mid-point between start and end.
        var perp = Vector2.Perpendicular(v.normalized); // vector of length 1 perpendicular to v.

        var vertex = UIVertex.simpleVert;
        vertex.color = this.color;

        // move half the thickness away from the mid-point.
        vertex.position = mid + (perp * this.thickness / 2); 
        vh.AddVert(vertex);

        // move half the thickness away from the mid-point in the opposite direction.
        vertex.position = mid - (perp * this.thickness / 2);
        vh.AddVert(vertex);
    }

    private struct BezierData
    {
        private readonly Vector2 start;
        private readonly float cx;
        private readonly float bx;
        private readonly float ax;
        private readonly float cy;
        private readonly float by;
        private readonly float ay;

        public BezierData(Vector2 start, Vector2 cp1, Vector2 cp2, Vector2 end)
        {
            // cribbed from here: https://www.codeproject.com/articles/25237/bezier-curves-made-simple

            this.start = start;
            this.cx = 3 * (cp1.x - start.x);
            this.bx = 3 * (cp2.x - cp1.x) - this.cx;
            this.ax = end.x - start.x - this.cx - this.bx;

            this.cy = 3 * (cp1.y - start.y);
            this.by = 3 * (cp2.y - cp1.y) - this.cy;
            this.ay = end.y - start.y - this.cy - this.by;
        }

        public Vector2 GetPoint(float t)
        {
            var tSquared = t * t;
            var tCubed = tSquared * t;

            return new Vector2(
                (this.ax * tCubed) + (this.bx * tSquared) + (this.cx * t) + this.start.x,
                (this.ay * tCubed) + (this.by * tSquared) + (this.cy * t) + this.start.y);
        }
    }
}

答案 5 :(得分:0)

TLDR: 考虑使用ShapesFreya Holmér

免责声明:我还没有尝试过,但是结果看起来很棒。

过去,我创建了自定义解决方案,其中将创建自定义网格(类似于此处描述的其他解决方案),用于抵消屏幕空间中的顶点的顶点着色器和具有alpha混合功能的片段着色器,以实现出色的AA。我从来没有时间来创建一个合适的,可重复使用的程序包,但是Freya的解决方案似乎要先进得多。

如果我再次需要类似的东西,我会先检查她的图书馆。