如何在绘制线条的同时将点均匀分布到LineRenderer的宽度曲线上?

3
我正在使用线渲染器创建一个“绘画”应用程序,现在我正在尝试启用笔压力,并使用LineRenderer的宽度曲线。问题是AnimationCurve的“时间”值(水平轴)从0到1归一化,所以我不能每次添加位置时仅仅向其末尾添加一个值。除非有我不知道的函数,我唯一能想到的解决办法是,找到一种方法来在绘制线条时精确地按百分比移动所有先前的数百个值,并在添加位置时执行此操作。这似乎过于复杂。

我不知道该怎么办。

这是每帧添加一个点的基本线条。

curve.AddKey(1.0f, penPressureValue);

"1.0f" 是曲线上的位置(1 表示最后一个点),所以这会在每一帧末尾添加一个点,随着绘制过程中的变化而改变整条线的宽度。
1个回答

2

很遗憾,我认为没有任何一种方法可以实现这种不怪异且不影响性能的效果。

当然,你可以像下面这样计算(假设LineRenderer的起始位置数量为0):

public LineRenderer line;
private int positionCount;
private float totalLengthOld;

private void AddPoint(Vector3 position, float width)
{
    // increase the position count by one
    positionCount++;

    // set the count back to the line
    line.positionCount = positionCount;

    // add our new point
    line.SetPosition(positionCount - 1, position);

    // now get the current width curve
    var curve = line.widthCurve;

    // Is this the beginning of the line?
    if (positionCount == 1)
    {
        // First point => simply set the first keyframe 
        curve.MoveKey(0, new Keyframe(0f, width));
    }
    else
    {
        // otherwise get all positions
        var positions = new Vector3[positionCount];
        line.GetPositions(positions);

        // sum up the distances between positions to obtain the length of the line
        var totalLengthNew = 0f;
        for (var i = 1; i < positionCount; i++)
        {
            totalLengthNew += Vector3.Distance(positions[i - 1], positions[i]);
        }

        // calculate the time factor we have to apply to all already existing keyframes
        var factor = totalLengthOld / totalLengthNew;

        // then store for the next added point
        totalLengthOld = totalLengthNew;

        // now move all existing keys which are currently based on the totalLengthOld to according positions based on the totalLengthNew
        // we can skip the first one as it will stay at 0 always
        var keys = curve.keys;
        for (var i = 1; i < keys.Length; i++)
        {
            var key = keys[i];
            key.time *= factor;

            curve.MoveKey(i, key);
        }

        // add the new last keyframe
        curve.AddKey(1f, width);
    }

    // finally write the curve back to the line
    line.widthCurve = curve;
}

仅用作示范

public class Example : MonoBehaviour
{
    public LineRenderer line;
    public Transform pen;
    [Range(0.01f, 0.5f)] public float width;
    public float drawThreshold = 0.1f;

    private int positionCount;
    private float totalLengthOld;
    private Vector3 lastPenPosition;
    
    private void Awake()
    {
        line = GetComponent<LineRenderer>();
        line.useWorldSpace = true;
        line.positionCount = 0;

        lastPenPosition = pen.position;
    }

    private void Update()
    {
        // just for the demo simply ping-pong the width over time
        width = Mathf.Lerp(0.01f, 0.8f, Mathf.PingPong(Time.time, 1f));

        var currentPenPosition = pen.position;
        
        if (Vector3.Distance(lastPenPosition, currentPenPosition) >= drawThreshold)
        {
            lastPenPosition = currentPenPosition;
            AddPoint(currentPenPosition, width);
        }
    }
    
    private void AddPoint(Vector3 position, float width)
    {
        positionCount++;

        line.positionCount = positionCount;
        line.SetPosition(positionCount - 1, position);

        var curve = line.widthCurve;

        if (positionCount == 1)
        {
            curve.MoveKey(0, new Keyframe(0f, width));
        }
        else
        {
            var positions = new Vector3[positionCount];
            line.GetPositions(positions);

            var totalLengthNew = 0f;
            for (var i = 1; i < positionCount; i++)
            {
                totalLengthNew += Vector3.Distance(positions[i - 1], positions[i]);
            }

            var factor = totalLengthOld / totalLengthNew;

            totalLengthOld = totalLengthNew;

            var keys = curve.keys;
            for (var i = 1; i < keys.Length; i++)
            {
                var key = keys[i];
                key.time *= factor;

                curve.MoveKey(i, key);
            }

            curve.AddKey(1f, width);
        }

        line.widthCurve = curve;
    }
}

在此输入图片描述


当然,使用LineRenderer绘制一定数量的点后,性能会受到限制。但我认为目前来说这已经是你可以做到的最好了。否则,LineRenderer可能不是绘图的正确工具。

当然,你可以采取巧妙的方法,在绘制一定数量的点之后,使用LineRenderer.BakeMesh将现有的线路烘焙成单独的固定网格,并以最后一个点作为起点开始绘制新的线路。

这样,只有尚未烘焙的部分才会受到宽度曲线关键帧移动的影响。

类似于:

public int meshBakeThreshold = 50;

private void AddPoint(Vector3 position, float width)
{
    ......

    if (positionCount >= meshBakeThreshold)
    {
        CreateSnapShotAndStartOver(position, width);
    }
}

private void CreateSnapShotAndStartOver(Vector3 position, float width)
{
    // create a new GameObject that will receive the line snapsho mesh
    var snapshotObject = new GameObject("LineSnapshot", typeof(MeshRenderer), typeof(MeshFilter));

    // set the material
    var renderer = snapshotObject.GetComponent<Renderer>();
    renderer.material = line.material;

    // bake and set the mesh
    var meshFilter = snapshotObject.GetComponent<MeshFilter>();
    var mesh = new Mesh();
    line.BakeMesh(mesh, Camera.main, true);
    meshFilter.mesh = mesh;

    // start with a new line at the same current position
    positionCount = 0;
    AddPoint(position, width);
}

您需要稍微调整一下阈值,50可能有点低,只是为了演示而使用。您需要在迭代所有关键帧和烘焙网格的性能成本之间找到一个平衡;)

输入图像描述


哇!我醒来发现了最详细的答案,感觉好像需要付费给你哈哈。非常感谢你提供的所有解释和示例。由于我还在敬畏中,所以还没有时间尝试它,但我会在这里告诉你/大家它的效果如何。 - KeepCool
我遇到的唯一令我困惑的事情是,在使用“totalLengthOld”之前,它没有被赋值。这是一个错误还是我没有看到的东西? - KeepCool
没事了,它可以工作了!我将在我的应用程序中绘制短线,因此“烘焙”可能不是必需的,但为了以后安全起见,我可能会实现它。目前,性能非常流畅。 - KeepCool
@KeepCool 当线路一开始没有点时,totalLengthOld 最初为 0 ;) 稍后在线路至少有 2 个点后,您始终会在 totalLengthOld = totalLengthNew; 中更新它 ;) - derHugo

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