Unity テクスチャに落書きする

2022/1/2
Unity今さら触り始めました。
メモ程度ですが残していこうと思います。

# 概要

`unity Version 2020.3.25f1`

テクスチャにマウスで落書きするだけです。とりあえず動けばヨシの内容です。
RenderTextureというものを使いました。


# 流れ

## Unity Editor上の作業

### 2Dでプロジェクトを作成

- 特に言うことが無い

### Assetsへ[Create]->[RenderTexture]を追加

![renderTexture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/d65a7ca6-4d45-c178-2171-75556b4cf6c1.png)

Inspector
![renderTexture_insp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/7fcb42d1-f333-7a4e-19ff-e84b6d250f58.png)

- Sizeは512x512としている
- FormatはR32G32B32A32_UINT
- 名前は適当に。（画像は"rtex_canvas"にしている）

### Hierarchyへ[UI]->[Raw Image]を追加

![rowImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/5fcf1636-a726-0a2b-d8aa-a1692efb5ce9.png)


### 追加したRawImageへ追加したRenderTextureを設定

![rowImage_insp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/e83e3960-45bf-7375-cf13-33444476aaba.png)

- ついでにWidthとHeightを512x512と変更してRenderTextureと合わせておく

### 適当なオブジェクトへ適当にスクリプトを追加

![obj_insp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/416c32cc-bab6-82c5-7f48-a1fbbc41a192.png)

- 名前は適当に。（画像は"DrawSystem"にしている）

ここまでのHierarchyの状況
![hierarchy .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/78e4a7e1-5158-4d38-f7c3-c13ec1281632.png)

ここまでのAssetsの状況
![assets.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/87772ef1-897d-cad8-5d90-ab9fe06f0845.png)

## スクリプト

左クリックが押されたら座標を取ってそのままテクスチャのバッファへピクセルを書き込むだけ。

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DrawSystem: MonoBehaviour
{
    public RenderTexture renderTexture;

    protected Texture2D texture;
    protected Color[] buffers;

    // Start is called before the first frame update
    void Start()
    {
        // テクスチャを生成して白で塗る
        texture = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.ARGB32, false);
        int length = texture.GetPixels().Length;
        buffers = new Color[length];
        for (int i = 0; i < length; i++)
        {
            buffers[i] = Color.white;
        }

        Flush();
    }

    // Update is called once per frame
    void Update()
    {
        UpdateDrawSystem();
    }

    void UpdateDrawSystem()
    {
        if (Input.GetMouseButtonDown(0) || Input.GetMouseButton(0))
        {
            // タッチされているので落書き
            Vector3 v = Input.mousePosition;

            if (v.x >= renderTexture.width || v.y >= renderTexture.height)
            {
                // キャンバス外
            }
            else
            {
                SetPixel(v);

                // テクスチャ反映
                Flush();
            }
        }
    }

    void SetPixel(Vector3 v)
    {
        int index = (int)(v.y * renderTexture.width + v.x);
        if (index < 0) index = 0;
        if (index >= buffers.Length) index = buffers.Length - 1;

        buffers[index] = Color.black;
    }

    void Flush()
    {
        texture.SetPixels(buffers);
        texture.Apply();
        Graphics.Blit(texture, renderTexture);
   }

}
```

- InspectorからRenderTextureを参照させておく
- とりあえずTexture2Dを経由させていてそこでの面倒ごとを避けるため、Texture2D生成時のサイズとフォーマットはRenderTextureに合わせてある

実行画面
![pixel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/a0471882-cd4c-369b-c92e-cfac15ff219a.png)


## ちゃんと線を書きたいスクリプト

`UpdateDrawSystem()` を破棄して `UpdateDrawSystem2()` を追加

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DrawSystem: MonoBehaviour
{
    public RenderTexture renderTexture;

    protected Texture2D texture;
    protected Color[] buffers;
    protected Vector3 vec;

    // Start is called before the first frame update
    void Start()
    {
        // テクスチャを生成して白で塗る
        texture = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.ARGB32, false);
        int length = texture.GetPixels().Length;
        buffers = new Color[length];
        for (int i = 0; i < length; i++)
        {
            buffers[i] = Color.white;
        }

        Flush();
    }

    // Update is called once per frame
    void Update()
    {
        UpdateDrawSystem2();
    }

    void UpdateDrawSystem2()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // タッチされているので落書き
            Vector3 v = Input.mousePosition;

            // 筆を下ろした直後なので同じ座標を設定
            vec = v;

            if (v.x >= renderTexture.width || v.y >= renderTexture.height)
            {
                // キャンバス外
            }
            else
            {
                DrawLine(vec, v);

                // テクスチャ反映
                Flush();
            }
        }
        else if (Input.GetMouseButton(0))
        {
            // タッチされているので落書き
            Vector3 v = Input.mousePosition;
            if (v.x >= renderTexture.width || v.y >= renderTexture.height)
            {
                // キャンバス外
            }
            else
            {
                DrawLine(vec, v);

                // 前回座標から線で繋ぐために保管
                vec = v;

                // テクスチャ反映
                Flush();
            }
        }
    }

    void DrawLine(Vector3 v1, Vector3 v2)
    {
        int step;
        Vector3 d;
        Vector3 sub = v2 - v1;
        if (System.Math.Abs(sub.x) > System.Math.Abs(sub.y))
        {
            step = (int)System.Math.Abs(sub.x);
            d.x = sub.x / step;
            d.y = sub.y / step;
            d.z = 0;
        }
        else
        {
            step = (int)System.Math.Abs(sub.y);
            d.x = sub.x / step;
            d.y = sub.y / step;
            d.z = 0;
        }

        int i = 0;
        while (i < step)
        {
            Vector3 dest = new Vector3(v1.x + d.x * i, v1.y + d.y * i, v1.z + d.z * i);
            PutBrush(dest);
            i++;
        }
    }

    void PutBrush(Vector3 v)
    {
        // ここは結構適当
        int[,] brush = {
            { 0, 0, 1, 0, 0},
            { 0, 1, 1, 1, 0},
            { 1, 1, 1, 1, 1},
            { 0, 1, 1, 1, 0},
            { 0, 0, 1, 0, 0},
        };

        int x = (int)v.x - 2;
        int y = (int)v.y - 2;
        int z = 0;

        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < 5; j++)
            {
                if (brush[i, j] == 1)
                {
                    SetPixel(new Vector3(x + i, y + j, z));
                }
            }
        }
    }

    void SetPixel(Vector3 v)
    {
        int index = (int)(v.y * renderTexture.width + v.x);
        if (index < 0) index = 0;
        if (index >= buffers.Length) index = buffers.Length - 1;

        buffers[index] = Color.black;
    }

    void Flush()
    {
        texture.SetPixels(buffers);
        texture.Apply();
        Graphics.Blit(texture, renderTexture);
   }
}
```

- どこかバグってたらすみません<(_ _)>

実行画面
![line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355418/ab02a6e7-5b57-a01a-8175-af17d1cb0ff6.png)

## 最後

課題

- `Flush()` 関数内のバッファのコピーが多い気がする
- テクスチャのサイズいぢったら駄目になると思う