Getting Started with Graphics: Graphics Spraying (1)

      Recently, I want to realize a series of spraying and drawing functions, which feels quite useful.
      The first is ordinary color drawing, using material texture for drawing operations, as follows:
insert image description here
      Assuming that the above is texture uv, our mouse ray hits muv, calculates the drawn circumscribed rectangle rect according to radius, and then judges the circle coloring according to the distance from the arc to the center of the circle. The only thing to pay attention to here is the boundary problem of the texture pixel, do not cross the boundary, as follows:

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;
    }
}

      Test it, the maintex of the material is set to readwrite/rgba32, as follows:
insert image description here

      But there is a small problem. It is recommended to copy the material picture, otherwise Apply will overwrite the original data, as follows:

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);
    }
}

      In this way, the objects we spray first clone a material and texture.
      At the same time, there is still a biggest problem with this solution, that is, the uv seam cannot be processed, as follows:
insert image description here

      Because the uv at the seam of the model grid is discontinuous and random (according to the artist uvmapping), it is impossible to deal with the continuity of spraying. If it is more realistic, we must think of another spraying scheme.
      Of course, the current solution also has some uses, such as brushing terrain and objects, which I use.
      There are also brush pattern textures, as follows:

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;
    }
}

      The core is to mix the drawing rect sampling pattern with the background color array, the effect is as follows:
insert image description here      pay special attention to the pattern sampling coordinate problem when the rectangle "breaks through" the boundary.
      Of course, there are still many questions that need to be in-depth, such as:
      1. How to deal with the UV joints of the model and the spraying of the joints between models?
      2. Spraying + grid calculation work together, similar to zbrush operation?
      3. Can you handle 3D textures and model coloring?
      There will be time to realize it slowly.
      By the way, by the way, this kind of drawing operation is not suitable for using ComputeShader. I wrote a test. Although ComputeShader is faster than CPU in calculating the color matrix, it is too time-consuming in data bus exchange and is not suitable for this kind of spraying operation.

Guess you like

Origin blog.csdn.net/yinhun2012/article/details/122877271