グラフィックス入門: グラフィックス スプレー (1)

      最近、スプレーと描画の一連の機能を実現したいと思っていますが、これはかなり便利だと感じています。
      1 つ目は、次のように描画操作にマテリアル テクスチャを使用する通常のカラー描画です。
ここに画像の説明を挿入
      上記がテクスチャ uv であると仮定すると、マウス レイが muv に当たり、半径に従って描画された外接四角形を計算し、円弧から円の中心までの距離に従って円の着色を判断します。ここで注意すべき唯一のことは、テクスチャ ピクセルの境界の問題です。次のように境界を越えないでください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestColorSpray : MonoBehaviour
{
    
    
    public Color color;         //颜色
    [Range(0, 1)]
    public float alpha;         //透明度
    public int radius;          //半径像素

    private Camera cam;

    private bool isStart = false;

    private Material mat;
    private Texture2D tex;
    private int texWidth;
    private int texHeight;

    void Start()
    {
    
    
        cam = Camera.main;
    }

    void FixedUpdate()
    {
    
    
        if (Input.GetMouseButtonDown(0))
        {
    
    
            Ray ray = cam.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
    
    
                StartSpray(hit.transform);
            }
        }
        if (Input.GetMouseButton(0))
        {
    
    
            if (isStart)
            {
    
    
                Ray ray = cam.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))
                {
    
    
#if UNITY_EDITOR
                    Debug.DrawLine(cam.transform.position, hit.point, Color.black);
#endif
                    //鼠标圆心
                    Vector2 muv = new Vector2(hit.textureCoord.x * texWidth, hit.textureCoord.y * texHeight);
                    int mx = Mathf.Clamp((int)muv.x, 0, texWidth - 1);
                    int my = Mathf.Clamp((int)muv.y, 0, texHeight - 1);
                    //外接矩形(不规则)
                    int left = Mathf.Clamp(mx - radius, 0, texWidth - 1);
                    int right = Mathf.Clamp(mx + radius, 0, texWidth - 1);
                    int bottom = Mathf.Clamp(my - radius, 0, texHeight - 1);
                    int top = Mathf.Clamp(my + radius, 0, texHeight - 1);
                    int rectwid = right - left;
                    int recthei = top - bottom;
                    //绘制
                    Color[] ogcols = tex.GetPixels(left, bottom, rectwid, recthei);
                    //逐行扫描
                    for (int y = 0; y < recthei; y++)
                    {
    
    
                        for (int x = 0; x < rectwid; x++)
                        {
    
    
                            int index = y * rectwid + x;
                            int px = x + left - mx;
                            int py = y + bottom - my;
                            float alphainten;
                            if (CheckInCircle(radius, px, py, out alphainten))
                            {
    
    
                                Color ocol = ogcols[index];
                                Color scol = color;
                                float inten = alphainten * alpha;
                                ogcols[index] = BlendColor(ocol, scol, inten);
                            }
                        }
                    }
                    tex.SetPixels(left, bottom, rectwid, recthei, ogcols);
                    tex.Apply();
                }
            }
        }
        if (Input.GetMouseButtonUp(0))
        {
    
    
            StopSpray();
        }
    }

    private void StartSpray(Transform obj)
    {
    
    
        isStart = true;
        mat = obj.GetComponent<MeshRenderer>().sharedMaterial;
        tex = (Texture2D)mat.GetTexture("_MainTex");
        texWidth = tex.width;
        texHeight = tex.height;
    }

    private void StopSpray()
    {
    
    
        isStart = false;
    }

    /// <summary>
    /// 检测xy在rad圆形中
    /// 相对坐标,圆心(0,0)
    /// </summary>
    /// <param name="rad"></param>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="inten"></param>
    /// <returns></returns>
    private bool CheckInCircle(int rad, int x, int y, out float inten)
    {
    
    
        float rad2 = rad * rad;
        float len2 = x * x + y * y;
        inten = 1f - len2 / rad2;
        return len2 <= rad2;
    }
    /// <summary>
    /// 混合颜色
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    private Color BlendColor(Color a, Color b, float k)
    {
    
    
        Color c = Color.Lerp(a, b, k);
        return c;
    }
}

      テストしてみると、マテリアルのメインテックスは次のように readwrite/rgba32 に設定されています。
ここに画像の説明を挿入

      ただし、小さな問題があり、素材画像をコピーすることをお勧めします。そうしないと、次のように適用によって元のデータが上書きされます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SprayObject : MonoBehaviour
{
    
    
    void Start()
    {
    
    
        MeshRenderer render = gameObject.GetComponent<MeshRenderer>();
        Material mat = render.material;
        Texture2D tex = (Texture2D)mat.GetTexture("_MainTex");
        Texture2D copytex = new Texture2D(tex.width, tex.height, tex.format, false);
        copytex.SetPixels(tex.GetPixels());
        copytex.Apply();
        mat.SetTexture("_MainTex", copytex);
    }
}

      このようにして、スプレーするオブジェクトは最初にマテリアルとテクスチャのクローンを作成します。
      同時に、この解決策には依然として最大の問題が残っています。それは、次のように UV シームを処理できないことです。
ここに画像の説明を挿入

      モデル メッシュの継ぎ目の UV は不連続かつランダムであるため (アーティストの UVMapping によると)、スプレーの連続性を扱うことは不可能です。より現実的な場合は、別のスプレー スキームを考える必要があります。
      もちろん、現在のスキームにも地形やオブジェクトのブラシなど、使用する特定の場所があり、それを使用しています。
      次のようなブラシ パターン テクスチャもあります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestGraphSpray : MonoBehaviour
{
    
    
    public Texture2D graphTex;  //图案
    [Range(0, 1)]
    public float alpha;         //透明度

    private Camera cam;

    private bool isStart = false;

    private Material mat;
    private Texture2D tex;
    private int texWidth;
    private int texHeight;

    private int gWid;           //图案宽
    private int gHei;           //图案高
    private int ghWid;          //图案半宽
    private int ghHei;          //图案半高

    void Start()
    {
    
    
        cam = Camera.main;

        gWid = graphTex.width;
        gHei = graphTex.height;
        ghWid = (int)(gWid * 0.5f);
        ghHei = (int)(graphTex.height * 0.5f);
    }

    void Update()
    {
    
    
        if (Input.GetMouseButtonDown(0))
        {
    
    
            Ray ray = cam.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
    
    
                StartSpray(hit.transform);
            }
        }
        if (Input.GetMouseButton(0))
        {
    
    
            if (isStart)
            {
    
    
                Ray ray = cam.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))
                {
    
    
#if UNITY_EDITOR
                    Debug.DrawLine(cam.transform.position, hit.point, Color.black);
#endif
                    //鼠标圆心
                    Vector2 muv = new Vector2(hit.textureCoord.x * texWidth, hit.textureCoord.y * texHeight);
                    int mx = Mathf.Clamp((int)muv.x, 0, texWidth - 1);
                    int my = Mathf.Clamp((int)muv.y, 0, texHeight - 1);
                    //外接矩形(不规则)
                    int left = Mathf.Clamp(mx - ghWid, 0, texWidth - 1);
                    int right = Mathf.Clamp(mx + ghWid, 0, texWidth - 1);
                    int bottom = Mathf.Clamp(my - ghHei, 0, texHeight - 1);
                    int top = Mathf.Clamp(my + ghHei, 0, texHeight - 1);
                    int rectwid = right - left;
                    int recthei = top - bottom;
                    //绘制
                    Color[] ocols = tex.GetPixels(left, bottom, rectwid, recthei);
                    //根据矩形是否越界,判断graphTex采样的起始点
                    int gx = mx > ghWid ? 0 : ghWid - mx;
                    int gy = my > ghHei ? 0 : ghHei - my;
                    Color[] gcols = graphTex.GetPixels(gx, gy, rectwid, recthei);
                    //逐行扫描
                    for (int y = 0; y < recthei; y++)
                    {
    
    
                        for (int x = 0; x < rectwid; x++)
                        {
    
    
                            int index = y * rectwid + x;
                            Color ocol = ocols[index];
                            Color gcol = gcols[index];
                            float k = alpha * gcol.a;
                            ocols[index] = BlendColor(ocol, gcol, k);
                        }
                    }
                    tex.SetPixels(left, bottom, rectwid, recthei, ocols);
                    tex.Apply();
                }
            }
        }
        if (Input.GetMouseButtonUp(0))
        {
    
    
            StopSpray();
        }
    }

    private void StartSpray(Transform obj)
    {
    
    
        isStart = true;
        mat = obj.GetComponent<MeshRenderer>().sharedMaterial;
        tex = (Texture2D)mat.GetTexture("_MainTex");
        texWidth = tex.width;
        texHeight = tex.height;
    }

    private void StopSpray()
    {
    
    
        isStart = false;
    }

    private Color BlendColor(Color a, Color b, float k)
    {
    
    
        Color c = Color.Lerp(a, b, k);
        return c;
    }
}

      中心となるのは、描画四角形のサンプリング パターンと背景色の配列を混合することです。その効果は次のとおりです。
ここに画像の説明を挿入      四角形が境界を「突破」するときのパターン サンプリング座標の問題に特に注意してください。
      もちろん、次のような、さらに深く掘り下げる必要がある多くの質問がまだあります。
      1. モデルの UV ジョイントとモデル間のジョイントのスプレーをどのように処理するか?
      2. スプレー + グリッド計算は zbrush 操作と同様に連動しますか?
      3. 3D テクスチャとモデルのカラーリングを処理できますか?
      ゆっくりと気づく時が来るでしょう。
      ちなみに、この手の描画操作は ComputeShader には向いていないのでテストを書いてみましたが、ComputeShader はカラーマトリクスの計算は CPU よりも速いのですが、データバスのやり取りに時間がかかりすぎて、この手の吹き付け操作には向いていません。

おすすめ

転載: blog.csdn.net/yinhun2012/article/details/122877271