A tool for the Unity engine to modify the vertex color of the model

Hello everyone, I am Zhao.
  I shared how to modify the vertex color of the model in 3DsMax through MaxScript. However, since the editing of vertex colors often needs to be dynamically adjusted according to the actual situation in the game engine and the shader, it will be much more convenient if you can directly modify the vertex colors of the model in the engine. So I wrote the following tool to modify the vertex color of the model in the Unity engine.

1. Function introduction

1. Model selection and mesh generation

This is the interface of the tool, select one or more GameObjects with mesh models, and click to start editing
insert image description here

If the selected grid model is of a type that cannot be edited, such as unity's own grid, the grid in fbx, the tool will prompt to generate an editable grid
insert image description here

The tool will copy the mesh model and save it as a resource file in asset format.

2. Brush function

After starting editing, put the mouse on the model, and a brush will appear, and the size, attenuation, and intensity of the brush can be adjusted
insert image description here
insert image description here

The radius of the brush can be controlled by the shortcut keys "[" and "]", and the attenuation can be controlled by the shortcut keys "-" and "="

3. Vertex color display

In order to know the effect of drawing when using the tool, it is necessary to display the vertex color.
insert image description here

However, if the model has colors by default, it may make the brushing process difficult to see clearly, so you can also enter the wireframe mode to view
insert image description here
insert image description here

If the model has a lot of vertices, it will take time to display the vertices. The performance is that it is a bit stuck when brushing the color, so a display mode is also provided, which refers to the points that display the coverage of the brush.
insert image description here

In order to facilitate the observation of the current value of a single channel, in the single channel mode, you can choose monochrome display or black and white display, which will look more intuitive
insert image description here
insert image description here

4. Brush color

  Adjust the intensity, which is actually the transparency of the brush, and you can paint the vertex color on the model. If you are brushing all channels, you can directly select the color. If it is a single channel, you can choose to increase or decrease the color of this channel.
insert image description here

  The option of weight mode is added here. The so-called weight mode is to ensure that the color values ​​of RGBA4 channels add up to 1. If the color of a certain channel is increased, the colors of other channels will be correspondingly reduced. This weighting mode is useful when doing some blending functions with vertex colors.

2. Technical points

1. Determine the position of the brush

  It is a very simple thing, the mouse emits rays at the screen position, and sums the collision points of the model grid. However, if you use the Physics.Raycast method, you need to have a collision body on the model grid to detect it. In the environment of this tool, what I need is to detect the collision point of the mesh model without a collision body.
  In fact, this method comes with Unity:

bool HandleUtility.IntersectRayMesh(Ray ray,Mesh mesh,Matrix4x4 matrix,out RaycastHit hit)

It is a pity that this method is actually hidden by Unity and has not been exposed for us to use. So you can only use reflection to call:

private bool IntersectRayMesh(Ray ray,Mesh sharedMesh,Matrix4x4 matrix,out RaycastHit hit)
{
    System.Type type = typeof(HandleUtility);
    System.Reflection.MethodInfo method = type.GetMethod("IntersectRayMesh", (BindingFlags.Static | BindingFlags.NonPublic));
    RaycastHit tempHit = new RaycastHit();

    object[] objArr = new object[4];
    objArr[0] = ray;
    objArr[1] = sharedMesh;
    objArr[2] = matrix;
    objArr[3] = null;
    bool isSuccess = (bool)method.Invoke(null,objArr);
    hit = (RaycastHit)objArr[3];      
    return isSuccess;
}

2. Lock operation

When brushing the vertex color, we definitely don’t want the mouse to still have the original operation behavior, such as box selection, displacement rotation and other operations, so we need to lock the operation: use this method to lock the mouse operation: HandleUtility.AddDefaultControl
(
GUIUtility .GetControlID(FocusType.Passive));
Then record the currently used tool and set the current tool to None. In this way, there will be no moving coordinate axes and the like, and the currently used tool is recorded so that it can be restored when exiting editing.

LastTool = Tools.current;
Tools.current = Tool.None;

3. Draw brushes and vertices

To draw graphics in the Scene view, you need to add duringSceneGui processing

SceneView.duringSceneGui += OnSceneGUI;
void OnSceneGUI(SceneView sceneView)
{
}

Then the drawing commands are all written in OnSceneGUI.
Three commands are mainly used here:
draw lines

Handles.DrawLine

draw disc

Handles.DrawWireDisc

draw points

Handles.DotHandleCap

Then changing the drawable color is done with

Handles.color

4. Modify the color without affecting the mesh itself

If it is just to change the vertex color of the mesh, then it is enough to modify Mesh.colors directly, but this is only an editing process after all, and we may undo the modification, or even not save this modification directly. So Mesh.colors cannot be modified directly.
Here I use a data class MeshEditData to record all the vertex colors on each mesh, and then only change the color in the MeshEditData object during the process of brushing the vertex colors. When the save is confirmed, the color data is written into the mesh.

5. Withdraw operation

Unity itself has its own Undo operation. It is reasonable to call Undo-related methods to realize the undo operation.
However, whether it is Undo.RecordObject or Undo.RecordObjects, you need to pass in Object as a parameter, which is the object of Unity. And what I record is a custom object, not an Object, so it cannot be used directly.
So you can do this:
by registering the undoRedoPerformed method, you can customize the method executed when you undo
Undo.undoRedoPerformed += this.OnUndo;
before each brush starts to brush, that is, when the left mouse button is clicked, record all current The color value of the grid and store it in an array.
Then in the OnUndo method, check the retracted array, if there is, take out the color value last pushed into the stack, and assign it to the current data.
There are 2 points worth noting:
1. The undoRedoPerformed method will be executed regardless of whether it is ctrl+z or ctrl+y. 2.
The undoRedoPerformed method requires previous operations before recording the stack. One operation is recorded, so it can only be rolled back once. Here, after each stroke is drawn, that is, when the left mouse button is lifted, manually call the Undo.RegisterCompleteObjectUndo method

3. Tool source code

A total of 3 c# scripts

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

using UnityEngine;
using UnityEditor;
using System.IO;
using System.Reflection;

namespace azhao.tools.VertexColorPainer
{
    public enum BrushChannelType
    {
        ALL = 0,
        RED = 1,
        GREEN = 2,
        BLUE = 3,
        ALPHA = 4
    }
    public enum SetColorMode
    {
        ADD = 0,
        WEIGHT = 1
    }

    public enum OperatorType
    {
        ADD = 0,
        REDUCE = 1
    }
    public class VertexColorPainerWin : EditorWindow
    {
        static private VertexColorPainerWin _instance;

        public static VertexColorPainerWin Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = (VertexColorPainerWin)EditorWindow.GetWindow(typeof(VertexColorPainerWin));
                    _instance.titleContent = new GUIContent("顶点颜色刷");
                    _instance.maxSize = _instance.minSize = new Vector2(600, 600);
                }
                return _instance;
            }
        }
        [MenuItem("Tools/顶点颜色刷")]
        static void ShowWin()
        {
            VertexColorPainerWin.Instance.Show();
        }
        #region 生命周期
        // Start is called before the first frame update
        void Start()
        {

        }
        private void OnEnable()
        {
            SceneView.duringSceneGui += OnSceneGUI;
            Undo.undoRedoPerformed -= this.OnUndo;
            Undo.undoRedoPerformed += this.OnUndo;
        }

        private void OnDisable()
        {
            SceneView.duringSceneGui -= OnSceneGUI;
            Undo.undoRedoPerformed -= this.OnUndo;
            Tools.current = LastTool;
        }
        void OnDestroy()
        {
            SceneView.duringSceneGui -= this.OnSceneGUI;
            Undo.undoRedoPerformed -= this.OnUndo;
        }
        // Update is called once per frame
        void Update()
        {
            
        }
        #endregion

        Tool LastTool = Tool.None;
        private bool isEditMode = false;
        private List<GameObject> meshObjList;
        List<MeshEditData> meshEditList;

        private string val = "";
        private float brushSize = 0.5f;
        private float brushFalloff = 1;
        private Color brushColor = Color.white;
        private bool isShowPointColor = false;
        private string[] channelSelectStr = new string[] { "所有通道", "R通道", "G通道", "B通道", "A通道" };
        private int channelSelectIndex = 0;
        private string[] showPointStr = new string[] { "不显示", "笔刷范围显示", "全部显示" };
        private int showPointIndex = 0;
        private string[] showColorType = new string[] { "原色", "单色","黑白" };
        private int showColorIndex = 0;
        string[] drawTypeStr = new string[] { "颜色叠加", "权重模式" };
        int drawTypeIndex = 0;
        string[] drawAddTypeStr = new string[] { "增加", "减少" };
        int drawAddTypeIndex = 0;
        private float brushSizeScale = 0.01f;
        private float brushAlpha = 1;
        private bool isPaint = false;
        void OnGUI()
        {
            ShowHelp();
            ShowSelectobj();
            if(isEditMode)
            {
                if(HasSelectObj() == false)
                {
                    CleanMeshList();
                    isEditMode = false;
                }
                ShowObjInfo();
                ShowTitle();
                ShowCtrl();
                ShowContent();
                
            }


        }

        #region 说明
        private bool isShowHelp = true;
        private void ShowHelp()
        {
            if(GUILayout.Button("来自阿赵的使用说明",GUILayout.Height(40)))
            {
                isShowHelp = !isShowHelp;
            }
            if(isShowHelp == false)
            {
                return;
            }
            GUILayout.Label("1、在场景中想编辑的模型,点击开始编辑");
            GUILayout.Label("2、如果模型中的网格不可编辑,会生成课编辑网格,路径在Assets/meshes/");
            GUILayout.Label("3、根据情况选择刷所有通道颜色还是单个通道颜色");
            GUILayout.Label("4、可以选择叠加模式或者权重模式");
            GUILayout.Label("\t叠加模式是直接绘制指定颜色");
            GUILayout.Label("\t权重模式是保证RGBA通道加起来等于1");
            GUILayout.Label("5、绘制不合适可以按Ctrl+Z撤回");
        }
        #endregion

        #region 物体选择编辑
        private bool HasSelectObj()
        {
            if(meshEditList == null||meshEditList.Count==0)
            {
                return false;
            }
            if(meshObjList == null||meshObjList.Count==0)
            {
                return false;
            }
            for(int i = 0;i<meshObjList.Count;i++)
            {
                if(meshObjList[i]==null)
                {
                    return false;
                }
            }
            return true;
        }
        
        private void ShowSelectobj()
        {
            ShowLine("编辑模型顶点色工具");


            if (isEditMode == true)
            {
                if (GUILayout.Button("保存编辑", GUILayout.Width(600), GUILayout.Height(40)))
                {
                    SaveMeshes(); 
                }
                if (GUILayout.Button("退出编辑",GUILayout.Width(600),GUILayout.Height(40)))
                {
                    OnEditEnd();
                }
            }
            else
            {
                if (GUILayout.Button("开始编辑", GUILayout.Width(600), GUILayout.Height(40)))
                {
                    OnEditBegin();
                }
            }
        }

        private void OnEditEnd()
        {
            Tools.current = LastTool;
            if (HasEditableMesh())
            {
                if(ShowSelectTips("是否保存网格?", "保存", "退出"))
                {
                    SaveMeshes();
                }
                isEditMode = false;
            }
        }

        private bool HasEditableMesh()
        {
            if(meshEditList != null&& meshEditList.Count>0)
            {
                return true;
            }
            return false;
        }

        private void OnEditBegin()
        {
            LastTool = Tools.current;
            Tools.current = Tool.None;
            meshObjList = new List<GameObject>();
            meshEditList = new List<MeshEditData>();
            GameObject[] gos = Selection.gameObjects;
            if(gos!=null)
            {
                for(int i = 0;i<gos.Length;i++)
                {
                    Transform[] trs = gos[i].GetComponentsInChildren<Transform>();
                    for(int j = 0;j<trs.Length;j++)
                    {
                        if (trs[j].gameObject.activeSelf == true)
                        {
                            if (meshObjList.IndexOf(trs[j].gameObject) < 0)
                            {
                                meshObjList.Add(trs[j].gameObject);
                            }
                        }
                    }

                }
            }

            if(meshObjList == null||meshObjList.Count==0)
            {
                ShowTips("没有可以编辑的模型,请用鼠标选中场景中的模型");
                return;
            }
            if(CheckMeshCanEdit()==true)
            {
                isEditMode = true;
            }
        }

        private void CleanMeshList()
        {
            meshEditList = null;
        }

        private bool CheckMeshCanEdit(int count = 0)
        {
            count++;
            if(count>=3)
            {
                return false;
            }
            if(meshObjList == null)
            {
                return false;
            }
            meshEditList = new List<MeshEditData>();
            for(int i = 0;i<meshObjList.Count;i++)
            {
                if(meshObjList[i].activeSelf == false)
                {
                    continue;
                }
                MeshEditData data = new MeshEditData(meshObjList[i]);
                if(data.mesh!=null)
                {
                    meshEditList.Add(data);
                }
            }

            if(HasEditableMesh()==false)
            {
;
                CleanMeshList();
                ShowTips("选择的物体里面没有可以编辑的网格模型");
                return false;
            }
            bool hasOrigMesh = false;
            if(meshEditList != null&& meshEditList.Count>0)
            {
                for(int i = 0;i< meshEditList.Count;i++)
                {
                    Mesh mesh = meshEditList[i].mesh;
                    string path = AssetDatabase.GetAssetPath(mesh);
                    if(path.EndsWith(".asset")==false)
                    {
                        hasOrigMesh = true;
                        break;
                    }
                }
            }
            if(hasOrigMesh == true)
            {                
                if (ShowSelectTips("当前模型里面有不可以直接编辑的网格,是否生成可编辑的网格?", "生成", "取消编辑"))
                {
                    bool successCreate = CreateCopyMeshAsset();
                    if(successCreate == false)
                    {
                        CleanMeshList();

                        return false;
                    }
                    else
                    {
                        CleanMeshList();
                        return CheckMeshCanEdit(count);
                    }
                }
                else
                {
                    CleanMeshList();
                    return false;
                }
            }
            else
            {
                return true;
            }
        }
        private void SaveMeshes()
        {
            if (meshEditList != null && meshEditList.Count > 0)
            {
                for (int i = 0; i < meshEditList.Count; i++)
                {
                    meshEditList[i].SaveMesh();

                }
            }
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        #endregion

        #region 笔刷操作编辑

        private void ShowObjInfo()
        {

            GUILayout.Label("当前编辑的对象:", GUILayout.Width(120));
            if(meshEditList != null&& meshEditList.Count>0)
            {
                for(int i = 0;i< meshEditList.Count;i++)
                {
                    EditorGUILayout.ObjectField(meshEditList[i].gameObject, typeof(Object), true, GUILayout.Width(200));
                }
            }

        }

        private void ShowCtrl()
        {

            GUILayout.BeginHorizontal();
            GUILayout.Label("是否显示顶点色", GUILayout.Width(120));
            //isShowPointColor = EditorGUILayout.Toggle(isShowPointColor, GUILayout.Width(40));
            showPointIndex = GUILayout.SelectionGrid(showPointIndex, showPointStr, 4, GUILayout.Width(500));
            GUILayout.EndHorizontal();
            if(showPointIndex>0&& channelSelectIndex>0)
            {
                GUILayout.BeginHorizontal();
                GUILayout.Label("显示颜色类型:", GUILayout.Width(120));
                showColorIndex = GUILayout.SelectionGrid(showColorIndex, showColorType, 3, GUILayout.Width(300));                
                GUILayout.EndHorizontal();
            }
            GUILayout.BeginHorizontal();
            GUILayout.Label("半径:", GUILayout.Width(80));
            brushSize = EditorGUILayout.Slider(brushSize, 0.1f, 2, GUILayout.Width(400));
            if(brushSize<0.1f)
            {
                brushSize = 0.1f;
            }
            if(brushSize>2)
            {
                brushSize = 10;
            }
            GUILayout.EndHorizontal();
            GUILayout.BeginHorizontal();
            GUILayout.Label("衰减:", GUILayout.Width(80));
            brushFalloff = EditorGUILayout.Slider(brushFalloff, 0, 1, GUILayout.Width(200));
            if(brushFalloff<0)
            {
                brushFalloff = 0;
            }
            if(brushFalloff>1)
            {
                brushFalloff = 1;
            }
            GUILayout.EndHorizontal();
            GUILayout.BeginHorizontal();
            GUILayout.Label("强度:", GUILayout.Width(80));
            brushAlpha = EditorGUILayout.Slider(brushAlpha, 0.1f, 1, GUILayout.Width(200));
            GUILayout.EndHorizontal();
        }

        private void ShowTitle()
        {
            channelSelectIndex = GUILayout.SelectionGrid(channelSelectIndex, channelSelectStr, 5, GUILayout.Width(500));
        }

        private void ShowContent()
        {
            if(channelSelectIndex == (int)BrushChannelType.ALL)
            {
                ShowAllColorCtrl();
            }
            else
            {
                ShowOneChannelColorCtrl();
            }
        }

        private void ShowAllColorCtrl()
        {
            GUILayout.BeginHorizontal();
            GUILayout.Label("笔刷颜色:", GUILayout.Width(100));
            brushColor = EditorGUILayout.ColorField(brushColor, GUILayout.Width(100));
            GUILayout.EndHorizontal();
        }


        private void ShowOneChannelColorCtrl()
        {
            GUILayout.BeginHorizontal();
            GUILayout.Label("绘制模式:", GUILayout.Width(100));
            drawTypeIndex = GUILayout.SelectionGrid(drawTypeIndex, drawTypeStr, 2, GUILayout.Width(200));
            GUILayout.EndHorizontal();
            GUILayout.BeginHorizontal();
            GUILayout.Label("颜色叠加:", GUILayout.Width(100));
            drawAddTypeIndex = GUILayout.SelectionGrid(drawAddTypeIndex, drawAddTypeStr, 2, GUILayout.Width(200));
            GUILayout.EndHorizontal();

        }
        #endregion





        #region 显示顶点色
        private void ShowPointColorFunc()
        {
            if(meshEditList != null&& meshEditList.Count>0)
            {
                for (int i = 0; i < meshEditList.Count; i++)
                {
                    ShowOneMeshPointColor(meshEditList[i]);
                }
            }

        }

        private void ShowPointColorFunc(Vector3 center,Vector3 normal)
        {
            if (meshEditList != null && meshEditList.Count > 0)
            {
                for (int i = 0; i < meshEditList.Count; i++)
                {

                    ShowOneMeshPointColor(meshEditList[i], center,normal);
                }
            }

        }



        private void ShowOneMeshPointColor(MeshEditData data)
        {
            ShowOneMeshPointColor(data, Vector3.zero, Vector3.zero,false);
        }
        private void ShowOneMeshPointColor(MeshEditData data, Vector3 center,Vector3 normal,bool needCheckDistance = true)
        {

            Vector3[] verts = data.mesh.vertices;
            float r = (brushSize + brushSize * brushFalloff);
            r = r * r;
            //Debug.Log(mesh.vertices.Length + "," + mesh.colors.Length);
            Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
            for (int j = 0; j < verts.Length; j++)
            {
                Vector3 pos = data.transform.TransformPoint(verts[j]);
                if (needCheckDistance==true)
                {                    
                    float tempR = (pos.x - center.x) * (pos.x - center.x) + (pos.y - center.y) * (pos.y - center.y) + (pos.z - center.z) * (pos.z - center.z);
                    if (tempR > r)
                    {
                        continue;
                    }
                }

                if (data.colors.Length > j)
                {
                    if (channelSelectIndex == (int)BrushChannelType.ALL||showColorIndex ==0)
                    {
                        Handles.color = data.colors[j];
                    }
                    else
                    {
                        if (channelSelectIndex == (int)BrushChannelType.RED)
                        {
                            if (showColorIndex == 1)
                            {
                                Handles.color = new Color(data.colors[j].r, 0, 0, 1);
                            }
                            else if (showColorIndex == 2)
                            {
                                Handles.color = new Color(data.colors[j].r, data.colors[j].r, data.colors[j].r, 1);
                            }

                        }
                        else if (channelSelectIndex == (int)BrushChannelType.GREEN)
                        {
                            if (showColorIndex == 1)
                            {
                                Handles.color = new Color(0, data.colors[j].g, 0, 1);
                            }
                            else if (showColorIndex == 2)
                            {
                                Handles.color = new Color(data.colors[j].g, data.colors[j].g, data.colors[j].g, 1);
                            }
                        }
                        else if (channelSelectIndex == (int)BrushChannelType.BLUE)
                        {
                            if (showColorIndex == 1)
                            {
                                Handles.color = new Color(0, 0, data.colors[j].b, 1);
                            }
                            else if (showColorIndex == 2)
                            {
                                Handles.color = new Color(data.colors[j].b, data.colors[j].b, data.colors[j].b, 1);
                            }
                        }
                        else if (channelSelectIndex == (int)BrushChannelType.ALPHA)
                        {
                            Handles.color = new Color(data.colors[j].a, data.colors[j].a, data.colors[j].a, 1);
                        }

                    }
                }
                
                Handles.DotHandleCap(0, pos, Quaternion.identity, HandleUtility.GetHandleSize(pos) * 0.03f, EventType.Repaint);
            }
        }

        #endregion

        #region 绘制顶点色
        private void PaintPointColor(Vector3 center, Vector3 normal)
        {
            if (meshEditList != null && meshEditList.Count > 0)
            {
                for (int i = 0; i < meshEditList.Count; i++)
                {

                    PaintOneMeshPoint(meshEditList[i], center, normal);
                }
            }
        }

        private void PaintOneMeshPoint(MeshEditData data, Vector3 center, Vector3 normal)
        {
            Vector3[] verts = data.mesh.vertices;
            float r = (brushSize + brushSize * brushFalloff);
            r = r * r;
            //Debug.Log(mesh.vertices.Length + "," + mesh.colors.Length);
            Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
            for (int j = 0; j < verts.Length; j++)
            {
                Vector3 pos = data.transform.TransformPoint(verts[j]);

                float tempR = (pos.x - center.x) * (pos.x - center.x) + (pos.y - center.y) * (pos.y - center.y) + (pos.z - center.z) * (pos.z - center.z);
                if (tempR > r)
                {
                    continue;
                }
                float falloff = 1;
                if(brushFalloff>0)
                {
                    falloff = Vector3.Distance(pos, center) - brushSize / (brushSize + brushSize * brushFalloff*0.1f);
                    falloff = 1 - falloff;
                    if(falloff<0)
                    {
                        falloff = 0;
                    }
                    if(falloff>1)
                    {
                        falloff = 1;
                    }
                }
                

                if(channelSelectIndex == (int)BrushChannelType.ALL)
                {
                    data.DrawVertexColor(j, brushColor, falloff* brushAlpha * Time.deltaTime);
                }
                else
                {
                    data.DrawVertexChannelColor(j, channelSelectIndex, drawTypeIndex, drawAddTypeIndex, falloff* brushAlpha * Time.deltaTime*0.1f);
                }
               
            }
        }

        #endregion

        void OnSceneGUI(SceneView sceneView)
        {
            if (isEditMode == false)
            {
                return;
            }
            HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
            if (meshObjList == null)
            {
                return;
            }

            if (HasEditableMesh() == false)
            {
                return;
            }

            Event e = Event.current;
            if (e.isKey)
            {
                //Debug.Log(e.keyCode);
                if (e.keyCode == KeyCode.RightBracket)
                {
                    brushSize += brushSizeScale;
                    if (brushSize > 2)
                    {
                        brushSize = 2;
                    }
                }
                else if (e.keyCode == KeyCode.LeftBracket)
                {
                    brushSize -= brushSizeScale;
                    if (brushSize < 0.1f)
                    {
                        brushSize = 0.1f;
                    }
                }
                else if (e.keyCode == KeyCode.Minus)
                {
                    brushFalloff -= 0.1f;
                    if (brushFalloff < 0)
                    {
                        brushFalloff = 0;
                    }
                }
                else if (e.keyCode == KeyCode.Equals)
                {
                    brushFalloff += 0.1f;
                    if (brushFalloff > 1)
                    {
                        brushFalloff = 1;
                    }
                }
            }
            if (e.rawType == EventType.MouseDown && e.button == 0)
            {
                AddToUndo();
                isPaint = true;

            }
            if (e.rawType == EventType.MouseUp && e.button == 0)
            {
                isPaint = false;
            }
            Vector3 mousePos = e.mousePosition;
            Camera camera = SceneView.currentDrawingSceneView.camera;
            float mult = 1;
#if UNITY_5_4_OR_NEWER
            mult = EditorGUIUtility.pixelsPerPoint;
#endif
            mousePos.y = camera.pixelHeight - mousePos.y * mult;
            mousePos.x *= mult;
            //Vector3 fakePoint = mousePos;
            //fakePoint.z = 20;
            //Vector3 point = sceneView.camera.ScreenToWorldPoint(fakePoint);
            float minDis = 99999;
            bool isHit = false;
            Vector3 center = Vector3.zero;
            Vector3 normal = Vector3.zero;

            Ray ray = camera.ScreenPointToRay(mousePos);
            float num = 1000;
            if (meshEditList != null && meshEditList.Count > 0)
            {
                for (int i = 0; i < meshEditList.Count; i++)
                {
                    Mesh sharedMesh = meshEditList[i].mesh;
                    RaycastHit hit;
                    bool hasHit = IntersectRayMesh(ray, sharedMesh, meshEditList[i].transform.localToWorldMatrix, out hit);
                    if (hasHit == false || hit.distance > num)
                    {
                        continue;
                    }
                    isHit = true;
                    if (hit.distance < minDis)
                    {
                        center = hit.point;
                        normal = hit.normal;
                        minDis = hit.distance;
                    }

                }
            }

            if (isHit == true)
            {
                Color tempColor;
                if (channelSelectIndex == (int)BrushChannelType.RED)
                {
                    tempColor = Color.red;
                }
                else if (channelSelectIndex == (int)BrushChannelType.GREEN)
                {
                    tempColor = Color.green;
                }
                else if (channelSelectIndex == (int)BrushChannelType.BLUE)
                {
                    tempColor = Color.blue;
                }
                else if (channelSelectIndex == (int)BrushChannelType.ALPHA)
                {
                    tempColor = Color.gray;
                }
                else
                {
                    tempColor = brushColor;
                }
                DrawBrush(tempColor, center, normal, brushSize, brushFalloff);
                if (showPointIndex == 1)
                {
                    ShowPointColorFunc(center, normal);
                }
                if (isPaint)
                {
                    PaintPointColor(center, normal);
                }
                sceneView.Repaint();
                HandleUtility.Repaint();
            }

            if (showPointIndex == 2)
            {
                ShowPointColorFunc();
            }

        }
        #region 辅助方法
        private bool IntersectRayMesh(Ray ray,Mesh sharedMesh,Matrix4x4 matrix,out RaycastHit hit)
        {
            System.Type type = typeof(HandleUtility);
            System.Reflection.MethodInfo method = type.GetMethod("IntersectRayMesh", (BindingFlags.Static | BindingFlags.NonPublic));
            RaycastHit tempHit = new RaycastHit();

            object[] objArr = new object[4];
            objArr[0] = ray;
            objArr[1] = sharedMesh;
            objArr[2] = matrix;
            objArr[3] = null;
            bool isSuccess = (bool)method.Invoke(null,objArr);
            hit = (RaycastHit)objArr[3];      
            return isSuccess;
        }

        private void DrawBrush(Color col, Vector3 center, Vector3 normal, float radius,float falloff)
        {
            if(col.a<0.5f)
            {
                col.a = 0.5f;
            }
            Handles.color = col;
            Handles.DrawWireDisc(center,HandleUtility.GetHandleSize(center)* normal,  radius);            
            Handles.DrawLine(center, center + radius * normal );
            if(falloff>0)
            {
                Handles.color = col * 0.7f;
                Handles.DrawWireDisc(center, HandleUtility.GetHandleSize(center) * normal, (radius + radius* falloff));
            }

        }

        private bool CreateCopyMeshAsset()
        {
            if(meshObjList==null)
            {
                return false;
            }


            if(HasEditableMesh() == false)
            {
                return false;
            }
            string path = Application.dataPath + "/meshes";
            if(Directory.Exists(path)==false)
            {
                Directory.CreateDirectory(path);
                AssetDatabase.Refresh();
            }
            bool hasError = false;
            Dictionary<Mesh, Mesh> dict = new Dictionary<Mesh, Mesh>();
            for(int i = 0;i<meshEditList.Count;i++)
            {
                Mesh mesh = meshEditList[i].mesh;
                string meshPath = AssetDatabase.GetAssetPath(mesh);
                if(meshPath.EndsWith(".asset"))
                {
                    continue;
                }
                if(dict.ContainsKey(mesh))
                {
                    meshEditList[i].SetShareMesh(dict[mesh]);
                }
                else
                {
                    Mesh newMesh = CopyMesh(mesh);
                    string savePath = GetNewSavePath(mesh.name);


                    AssetDatabase.CreateAsset(newMesh, savePath);
                    AssetDatabase.Refresh();
                    Mesh loadMesh = (Mesh)AssetDatabase.LoadAssetAtPath(savePath, typeof(Mesh));
                    dict.Add(mesh, loadMesh);
                    meshEditList[i].SetShareMesh(loadMesh); 

                }
            }

           
            if (hasError == true)
            {
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
                return false;
            }

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            return true;
        }

        private string GetNewSavePath(string assetName)
        {
            string savePath = "Assets/meshes/" + assetName + ".asset";
            if(AssetDatabase.LoadAssetAtPath(savePath,typeof(Object))==null)
            {
                return savePath;
            }
            int i = 1;
            while(true)
            {
                i++;
                savePath = "Assets/meshes/" + assetName+i + ".asset";
                if (AssetDatabase.LoadAssetAtPath(savePath, typeof(Object)) == null)
                {
                    return savePath;
                }
            }
        }

        private Mesh CopyMesh(Mesh mesh)
        {
            Mesh newMesh = new Mesh();
            newMesh.vertices = mesh.vertices;
            newMesh.uv = mesh.uv;
            newMesh.triangles = mesh.triangles;
            newMesh.normals = mesh.normals;
            if(mesh.colors.Length==0)
            {
                newMesh.colors = new Color[mesh.vertices.Length];
                Color[] cols = new Color[mesh.vertices.Length];
                for (int i = 0;i<mesh.vertices.Length; i++)
                {
                    cols[i] = Color.black;
                }
                newMesh.SetColors(cols);
            }
            else
            {
                newMesh.colors = mesh.colors;
            }
            return newMesh;
        }
        #endregion
        #region Undo
        private List<DrawColorUndoData> undoList;
        private void AddToUndo()
        {
            if(meshEditList == null||meshEditList.Count == 0)
            {
                return;
            }
            Undo.RegisterCompleteObjectUndo(this, "paint color");
            if (undoList == null)
            {
                undoList = new List<DrawColorUndoData>();
            }
            DrawColorUndoData data = new DrawColorUndoData(meshEditList);
            undoList.Add(data);
        }

        private void OnUndo()
        {
            if(undoList == null||undoList.Count==0)
            {
                return;
            }
            DrawColorUndoData data = undoList[undoList.Count - 1];
            undoList.RemoveAt(undoList.Count-1);
            if(meshEditList!=null&&meshEditList.Count>0)
            {
                data.Undo(meshEditList);
            }            
        }
        #endregion

        #region 其他选项

        private void ShowTips(string str)
        {
            Debug.Log(str);
            EditorUtility.DisplayDialog("提示", str, "确定");
        }
        private void ShowErrorTips(string str)
        {
            Debug.Log(str);
            EditorUtility.DisplayDialog("提示", str, "确定");
            throw new System.Exception("errorTips:" + str);
        }

        private bool ShowSelectTips(string str,string okStr = "确定",string cancelStr = "取消")
        {
            return EditorUtility.DisplayDialog("提示", str, okStr,cancelStr);
        }

        private void ShowProgress(string title, string content, float rate)
        {
            EditorUtility.DisplayProgressBar(title, content, rate);
        }

        private void HideProgress()
        {
            EditorUtility.ClearProgressBar();
        }

        private void ShowLine(string str = "", int w = -1, int h = 20)
        {
            if(w<0)
            {
                w = Mathf.FloorToInt(this.maxSize.x);
            }
            if (string.IsNullOrEmpty(str))
            {
                h = 5;
            }
            GUILayout.Box(str, GUILayout.Width(w), GUILayout.Height(h));
        }

        private string SelectFolder(string str)
        {
            return EditorUtility.OpenFolderPanel("选择文件夹", str, "");
        }

        private string SelectFile(string str, string ex = "")
        {
            return EditorUtility.OpenFilePanel("选择文件", str, ex);
        }
        private void ShowTextArea(string str, int w = 500, int h = 60)
        {
            ShowLine();
            GUILayout.Label(str, GUILayout.Width(w), GUILayout.Height(h));
            ShowLine();
        }
        #endregion


    }

    
}
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace azhao.tools.VertexColorPainer
{
    public class MeshEditData
    {
        public Transform transform;
        public GameObject gameObject;
        private SkinnedMeshRenderer skinnedMeshRenderer;
        private MeshFilter meshFilter;
        public Mesh mesh;
        public Color[] colors;
        public MeshEditData(GameObject go)
        {
            transform = go.transform;
            gameObject = go;
            skinnedMeshRenderer = go.GetComponent<SkinnedMeshRenderer>();
            if (skinnedMeshRenderer != null)
            {
                mesh = skinnedMeshRenderer.sharedMesh;
            }
            else
            {
                meshFilter = go.GetComponent<MeshFilter>();
                if (meshFilter != null)
                {
                    mesh = meshFilter.sharedMesh;
                }
            }
            if (mesh != null)
            {
                InitMeshColor();
            }
        }

        public void SetShareMesh(Mesh m)
        {
            mesh = m;
            if(skinnedMeshRenderer!=null)
            {
                skinnedMeshRenderer.sharedMesh = mesh;
            }
            if(meshFilter!=null)
            {
                meshFilter.sharedMesh = mesh;
            }
        }

        private void InitMeshColor()
        {
            if (mesh == null)
            {
                return;
            }

            int vertCount = mesh.vertexCount;
            colors = new Color[vertCount];
            for (int i = 0; i < vertCount; i++)
            {
                if (mesh.colors != null && i < mesh.colors.Length)
                {
                    colors[i] = mesh.colors[i];
                }
                else
                {
                    colors[i] = Color.black;
                }
            }

        }

        public void DrawVertexColor(int index,Color col,float alpha)
        {
            if(colors == null||colors.Length<=index)
            {
                return;
            }
            Color newColor = colors[index] * (1 - alpha) + col * alpha;
            colors[index] = newColor;
;        }

        public void DrawVertexChannelColor(int index,int channel,int mode,int operatorType,float alpha)
        {
            if (colors == null || colors.Length <= index)
            {
                return;
            }
            float val = 0;
            if(channel== (int)BrushChannelType.RED)
            {
                val = colors[index].r;
            }
            else if(channel == (int)BrushChannelType.GREEN)
            {
                val = colors[index].g;
            }
            else if (channel == (int)BrushChannelType.BLUE)
            {
                val = colors[index].b;
            }
            else if (channel == (int)BrushChannelType.ALPHA)
            {
                val = colors[index].a;
            }

            if(operatorType == (int)OperatorType.ADD)
            {
                val += alpha;
                if(val>1)
                {
                    val = 1;
                }
            }
            else
            {
                val -= alpha;
                if(val<0)
                {
                    val = 0;
                }
            }

            if (channel == (int)BrushChannelType.RED)
            {
                if(mode == (int)SetColorMode.WEIGHT)
                {
                    float leftVal = 1 - val;
                    float totalVal = colors[index].g + colors[index].b + colors[index].a;
                    if(totalVal==0)
                    {
                        colors[index].g = colors[index].b = colors[index].a = leftVal / 3;
                    }
                    else
                    {
                        colors[index].g = leftVal * colors[index].g / totalVal;
                        colors[index].b = leftVal * colors[index].b / totalVal;
                        colors[index].a = leftVal * colors[index].a / totalVal;
                    }

                }
                colors[index].r = val;
            }
            else if (channel == (int)BrushChannelType.GREEN)
            {
                if (mode == (int)SetColorMode.WEIGHT)
                {
                    float leftVal = 1 - val;
                    float totalVal = colors[index].r + colors[index].b + colors[index].a;
                    if (totalVal == 0)
                    {
                        colors[index].r = colors[index].b = colors[index].a = leftVal / 3;
                    }
                    else
                    {
                        colors[index].r = leftVal * colors[index].r / totalVal;
                        colors[index].b = leftVal * colors[index].b / totalVal;
                        colors[index].a = leftVal * colors[index].a / totalVal;
                    }
                }
                colors[index].g = val;
            }
            else if (channel == (int)BrushChannelType.BLUE)
            {
                if (mode == (int)SetColorMode.WEIGHT)
                {
                    float leftVal = 1 - val;
                    float totalVal = colors[index].r + colors[index].g + colors[index].a;
                    if (totalVal == 0)
                    {
                        colors[index].r = colors[index].g = colors[index].a = leftVal / 3;
                    }
                    else
                    {
                        colors[index].r = leftVal * colors[index].r / totalVal;
                        colors[index].g = leftVal * colors[index].g / totalVal;
                        colors[index].a = leftVal * colors[index].a / totalVal;
                    }
                }
                colors[index].b = val;
            }
            else if (channel == (int)BrushChannelType.ALPHA)
            {
                if (mode == (int)SetColorMode.WEIGHT)
                {
                    float leftVal = 1 - val;
                    float totalVal = colors[index].r + colors[index].g + colors[index].b;
                    if (totalVal == 0)
                    {
                        colors[index].r = colors[index].g = colors[index].b = leftVal / 3;
                    }
                    else
                    {
                        colors[index].r = leftVal * colors[index].r / totalVal;
                        colors[index].g = leftVal * colors[index].g / totalVal;
                        colors[index].b = leftVal * colors[index].b / totalVal;
                    }
                }
                colors[index].a = val;
            }

        }

        public void SaveMesh()
        {
            if(mesh==null)
            {
                return;
            }
            mesh.colors = colors;            
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace azhao.tools.VertexColorPainer
{
    public class DrawColorUndoData
    {
        private List<Color[]> colors;
        public DrawColorUndoData(List<MeshEditData> data)
        {
            colors = new List<Color[]>();
            for(int i = 0;i<data.Count;i++)
            {
                colors.Add((Color[])data[i].colors.Clone());
            }
        }

        public void Undo(List<MeshEditData> data)
        {
            if(colors == null||colors.Count!=data.Count)
            {
                Debug.Log("xxxUndo  return");
                return;
            }
            for(int i = 0;i<data.Count;i++)
            {
                data[i].colors = colors[i];
            }
        }
}
}

Guess you like

Origin blog.csdn.net/liweizhao/article/details/132571529