2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Unity(C#)】Lerpで滑らかな線を描く

Last updated at Posted at 2020-09-20

はじめに

平面上に線を滑らかに描く方法を調べました。

VR空間でもぬるぬる動いている例を参考にしました(↓すごい)

【参考リンク】:ホワイトボードで線がヌルヌル描ける仕組み

描く実装

"描く部分"のコードは下記リンクからまるまる拝借しました。

【参考リンク】:Unityでテクスチャにお絵描きしよう

デモ

まずは補間することなくそのまま動かしてみたサンプルです。
Paint2DForQiita1.gif

線がかすれてしまって不格好です。


次にLerpによる補間を施したものです。
Paint2DForQiita2.gif

しっかりと線の間を補間してくれました。

【参考リンク】:[Unity] Vector3.Lerpの使い方

コード

下記全文です。

using UnityEngine;

//使い方↓
//https://nn-hokuson.hatenablog.com/entry/2016/12/08/200133
public class SmoothPaint : MonoBehaviour
{
    Texture2D drawTexture;
    Color[] buffer;

    private Vector2 _prevPosition;

    void Start()
    {
        Texture2D mainTexture = (Texture2D) GetComponent<Renderer>().material.mainTexture;
        Color[] pixels = mainTexture.GetPixels();

        buffer = new Color[pixels.Length];
        pixels.CopyTo(buffer, 0);

        drawTexture = new Texture2D(mainTexture.width, mainTexture.height, TextureFormat.RGBA32, false);
        drawTexture.filterMode = FilterMode.Point;
    }

    public void Draw(Vector2 p)
    {
        for (int x = 0; x < 256; x++)
        {
            for (int y = 0; y < 256; y++)
            {
                if ((p - new Vector2(x, y)).magnitude < 5)
                {
                    buffer.SetValue(Color.black, x + 256 * y);
                }
            }
        }
    }

    void Update()
    {
        if (Input.GetMouseButton(0))
        {
            //前回値がまだないなら現在の値を前回値として扱う
            if (_prevPosition == Vector2.zero)
            {
                _prevPosition = Input.mousePosition;
            }

            //線形補間に使う入力の終点座標
            Vector2 endPosition = Input.mousePosition;
            //1フレームの線の距離
            float lineLength = Vector2.Distance(_prevPosition, endPosition);
            //線の長さに応じて変わる補間値 CeilToIntは小数点以下を切り上げ
            int lerpCountAdjustNum = 5;
            int lerpCount = Mathf.CeilToInt(lineLength / lerpCountAdjustNum);

            for (int i = 1; i <= lerpCount; i++)
            {
                //Lerpの割合値を "現在の回数/合計回数" で出す
                float lerpWeight = (float) i / lerpCount;

                //前回の入力座標、現在の入力座標、割合を渡して補間する座標を算出
                Vector3 lerpPosition = Vector2.Lerp(_prevPosition, Input.mousePosition, lerpWeight);
                
                Ray ray = Camera.main.ScreenPointToRay(lerpPosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 100.0f))
                {
                    Draw(hit.textureCoord * 256);
                }

                drawTexture.SetPixels(buffer);
                drawTexture.Apply();
                GetComponent<Renderer>().material.mainTexture = drawTexture;
            }
            
            //前回の入力座標を記録
            _prevPosition = Input.mousePosition;
        }
        else
        {
            //前回の入力座標をリセット
            _prevPosition = Vector2.zero;
        }
    }
}

ロジックとしては下記です
①前フレームのマウスの入力座標を保持
②次フレームにて①と現在の入力座標の距離を算出
③距離に応じて補間値(補間回数)を算出
④補間値を利用しLerpで補間

線が繋がる

実装していてそこそこ時間を使ってしまったのが、
線が繋がってしまう という現象でした。

下記GIFのように新しく描き始めた箇所と
前回の終了地点が繋がってしまっていました。

Paint2DForQiita3.gif

これは描き終えた際の値を前回値として保持したまま
新しい入力箇所での補間を行っていることが原因でした。

ですので、下記箇所で入力が無い状態になった際に前回値のリセットを行っています。
さらに、入力値のリセットにより前回値が存在しないフレーム、
すなわち描き始めのフレームにおいては補間を行う必要が無いので
前回の値=現在の値として扱うようにしています。


    if(Input.GetMouseButton(0))
    { 
        //前回値がまだないなら現在の値を前回値として扱う
        if (_prevPosition == Vector2.zero)
        {
            _prevPosition = Input.mousePosition;
        }
    }
    else
    {
        //前回の入力座標をリセット
        _prevPosition = Vector2.zero;
    }

これにより描き始めた箇所に線を新しく描画することができました。
Paint2DForQiita4.gif

おわりに

線の太さ(大きさ)も考慮した補間値を算出すれば
もっと最適な補間ができるみたいです。

そのうち書けたら書きます。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?