Unity引擎修改模型顶点色的工具

大家好,我是阿赵。
  之前分享过怎样通过MaxScript在3DsMax里面修改模型的顶点色。不过由于很多时候顶点色的编辑需要根据在游戏引擎里面的实际情况和shader的情况来动态调整,所以如果能在引擎里面直接修改模型的顶点色,将会方便很多。于是我写了下面这个在Unity引擎里面修改模型顶点色的工具。

一、功能介绍

1、模型选择和网格生成

这是工具的界面,选择一个或者多个带有网格模型的GameObject,点击开始编辑
在这里插入图片描述

如果选择的网格模型是属于不能编辑的类型,比如unity自带网格,fbx里面的网格,工具会提示生成可编辑网格
在这里插入图片描述

工具将会把网格模型复制一份,保存成asset格式的资源文件。

2、笔刷功能

开始编辑之后,把鼠标放在模型上,会出现笔刷,笔刷的大小、衰减、强度等可以调节
在这里插入图片描述
在这里插入图片描述

其中笔刷的半径可以用快捷键”[“和”]”控制,衰减可以用快捷键”-”和”=”来控制

3、顶点色显示

为了能在使用工具的过程中知道绘制的效果,所以有必要把顶点色给显示出来。
在这里插入图片描述

不过如果模型默认有颜色,可能会导致刷的过程比较难看得清楚,所以也可以进入线框模式看
在这里插入图片描述
在这里插入图片描述

如果模型的顶点很多,那么显示顶点也会比较耗时,表现就是在刷颜色的时候有点卡,所以也提供了一个显示模式,是指显示笔刷覆盖范围的点
在这里插入图片描述

为了便于观察单个通道的当前值,所以在单个通道模式下,可以选择单色显示或者是黑白显示,这样看起来会更加直观
在这里插入图片描述
在这里插入图片描述

4、刷颜色

  调一下强度,其实就是笔刷的透明度,就可以在模型上面刷顶点颜色了。如果是刷所有通道,可以直接选择颜色。如果是单个通道,可以选择是增加或者减少该通道的颜色。
在这里插入图片描述

  这里加入了权重模式的选项。所谓的权重模式,是保证RGBA4个通道的颜色值加起来等于1,如果增加某个通道的颜色,就会相应的减少其他几个通道的颜色。在用顶点色做某些混合功能的时候,这个权重模式会比较的有用。

二、技术点

1、判断笔刷位置

  本来很简单的一件事情,鼠标在屏幕位置发射射线,求和模型网格的碰撞点就可以了。不过如果用Physics.Raycast方法,是需要模型网格上有碰撞体才能检测到的。而在这个工具的环境下,我需要的是没有碰撞体也能检测得到网格模型的碰撞点。
  其实这个方法是Unity自带了的:

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

可惜的是,这个方法Unity居然是隐藏的,并没有暴露出来给我们用。所以只能使用反射的手段去调用:

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、锁定操作

在刷顶点色的时候,我们肯定不希望鼠标还有原来的操作行为,比如框选、位移旋转之类的操作,所以需要对操作进行锁定:
通过这个方法来锁定鼠标操作:
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
然后记录一下当前使用的工具,并把当前工具置为None。这样就不会出现移动的坐标轴之类了,记录当前使用的工具是为了退出编辑的时候可以还原。

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

3、绘制笔刷和顶点

在Scene视图里面绘制图形,需要添加duringSceneGui 处理

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

然后绘制的命令都写在OnSceneGUI里面。
这里主要用到了3个命令:
绘制线条

Handles.DrawLine

绘制圆盘

Handles.DrawWireDisc

绘制点

Handles.DotHandleCap

然后改变绘制颜色是用

Handles.color

4、在不影响mesh本身的情况下修改颜色

如果只是为了改变mesh的顶点色,那么直接修改Mesh.colors就可以了,不过这个毕竟只是一个编辑的过程,我们也有可能撤销修改,甚至直接不保存这次的修改。所以不能直接修改Mesh.colors。
我这里是使用了一个数据类MeshEditData来记录了每个mesh上面的所有顶点颜色,然后在刷顶点色的过程中,只是改变这个MeshEditData对象里面的颜色。等确定保存的时候,才把color数据写入mesh。

5、撤回操作

Unity本身是自带了Undo的操作的,按道理只需要调用Undo相关的方法,就可以实现撤回操作了。
不过不论是Undo.RecordObject还是Undo.RecordObjects,都需要传入Object作为参数,也就是Unity的对象。而我记录的是自定义对象,并不是Object,所以不能直接这么用。
于是可以这样操作:
通过注册undoRedoPerformed 方法,来自定义撤回的时候执行的方法
Undo.undoRedoPerformed += this.OnUndo;
在每次笔刷开始刷之前,也就是鼠标左键点下的时候,记录一下当前所有网格的颜色值,并存到一个数组里面。
然后在OnUndo方法里面,检查回撤的数组,如果有,就把数组最后入栈的颜色值取出,并赋值给当前的数据。
值得注意的有2点:
1.undoRedoPerformed 方法不论是ctrl+z还是ctrl+y都会执行的
2.undoRedoPerformed 方法是需要前面有操作,才会记录堆栈,如果我们一直在Scene视图刷颜色,他只会记录一次操作,所以也只能回退一次,这里需要每次绘制完一笔之后,也就是鼠标左键抬起的时候,手动调用一下Undo.RegisterCompleteObjectUndo方法

三、工具源码

总共3个c#脚本

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 updatevoid 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 framevoid Update(){}#endregionTool 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);}}}#endregionvoid 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_NEWERmult = EditorGUIUtility.pixelsPerPoint;
#endifmousePos.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 Undoprivate 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];}}
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/58197.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【力扣每日一题】2023.8.24 统计参与通信的服务器

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目顾名思义&#xff0c;要我们统计参与通信的服务器&#xff0c;给我们一个二维矩阵&#xff0c;元素为1的位置则表示是一台服务器。 …

lnmp架构-nginx

6.nginx基础配置 证书 重定向&#xff08;80重定向到443&#xff09; 当访问http时 直接到 https 自动索引&#xff1a; 下载方便 Nginx缓存配置 &#xff1a;缓存可以降低网站带宽&#xff0c;加速用户访问 日志轮询 禁用不必要的日志记录 以节省磁盘IO的消耗 监控的信息 监…

Java实现根据关键词搜索1688商品新品数据方法,1688API节课申请指南

要通过1688的API获取商品新品数据&#xff0c;您可以使用1688开放平台提供的接口来实现。以下是一种使用Java编程语言实现的示例&#xff0c;展示如何通过1688开放平台API获取商品新品数据&#xff1a; 首先&#xff0c;确保您已注册成为1688开放平台的开发者&#xff0c;并创…

2.3 【MySQL】命令行和配置文件中启动选项的区别

在命令行上指定的绝大部分启动选项都可以放到配置文件中&#xff0c;但是有一些选项是专门为命令行设计的&#xff0c;比方说defaults-extra-file 、 defaults-file 这样的选项本身就是为了指定配置文件路径的&#xff0c;再放在配置文件中使用就没啥意义了。 如果同一个启动选…

HUT23级训练赛

目录 A - tmn学长的字符串1 B - 帮帮神君先生 C - z学长的猫 D - 这题用来防ak E - 这题考察FFT卷积 F - 这题考察二进制 G - 这题考察高精度 H - 这题考察签到 I - 爱派克斯&#xff0c;启动! J - tmn学长的字符串2 K - 秋奕来买瓜 A - tmn学长的字符串1 思路&#x…

Vue项目中app.js过大,导致web初始化加载过慢问题

1、删除多余不需要的库&#xff1a; npm uninstall xxx 如例如moment库文件是很大的可以直接放到index.html文件直接CDN引入 2、修改/config/index.js配置文件&#xff1a;将productionGzip设置为false ​ 3、设置vue-router懒加载 懒加载配置&#xff1a; ​ 非懒加载配置&…

Spring security报栈溢出几种可能的情况

今天在运行spring security的时候&#xff0c;发现出现了栈溢出的情况&#xff0c;总结可能性如下&#xff1a; 1.UserDetailsService的实现类没有加上Service注入到容器中&#xff0c;导致容器循环寻找UserDetailsService的实现类&#xff0c;最终发生栈溢出的现象。 解决方法…

JavaSE学习——异常

目录 一、异常概述 二、异常的体系结果 二、异常的处理&#xff1a;抓抛模型 三、try-catch-finally的使用 四、throws 异常类型 的使用 五、开发中如何选择使用try-catch-finally还是使用throws&#xff1f; 六、自定义异常 自定义异常步骤&#xff1a; 七、总结&a…

复原20世纪复古修仙游戏

前言 在本教程中&#xff0c;我突发奇想&#xff0c;想做一个复古的修仙游戏&#xff0c;考虑到以前的情怀决定做个古老的躺平修仙游戏 &#x1f4dd;个人主页→数据挖掘博主ZTLJQ的主页 个人推荐python学习系列&#xff1a; ☄️爬虫JS逆向系列专栏 - 爬虫逆向教学 ☄️python…

Elasticsearch 集成---Spark Streaming 框架集成

一.Spark Streaming 框架介绍 Spark Streaming 是 Spark core API 的扩展&#xff0c;支持实时数据流的处理&#xff0c;并且具有可扩展&#xff0c; 高吞吐量&#xff0c;容错的特点。 数据可以从许多来源获取&#xff0c;如 Kafka &#xff0c; Flume &#xff0c; Kin…

全景图像生成算法

摘要 全景图像生成是计算机视觉领域的一个重要研究方向。本文对五种经典的全景图像生成算法进行综述&#xff0c;包括基于相机运动估计的算法、基于特征匹配的算法、基于图像切割的算法、基于多项式拟合的算法和基于深度学习的算法。通过对这些算法的原理、优缺点、适用场景等…

【附源码】Axure RP Pro8.0安装教程|HTML|网页设计

软件下载 软件&#xff1a;Axure版本&#xff1a;8.0语言&#xff1a;简体中文大小&#xff1a;82.53M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨下载链接&#xff1a;https://pan.baidu.com/s/…

部署 ssm 项目到云服务器上(购买云服务器 + 操作远程云服务器 + 服务器中的环境搭建 + 部署项目到服务器)

部署 Web 项目 1、获取 Linux 环境1.1、如何去买一个云服务器1.2、远程操作云服务器1.3、在 Linux 系统中搭建 Java Web 的运行环境。1&#xff09;安装 JDK&#xff08;使用包管理器 yum 来安装&#xff09;2&#xff09; 安装Tomcat3&#xff09;安装 MySQL。 1.4、在云服务器…

【力扣每日一题】2023.8.28 插入区间

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 和昨天的题大差不差&#xff0c;我们仍然是有一堆区间&#xff0c;题目给我们一个新的区间&#xff0c;要我们把新区间插入到原本的区间数…

腾讯云便宜购买指南(腾讯云怎样购买划算)

腾讯云是国内知名的云计算服务商&#xff0c;拥有广泛的应用和用户群体。对于有需要的用户来说&#xff0c;怎样便宜购买腾讯云产品是一个值得关注的问题&#xff0c;下面给大家分享腾讯云便宜购买指南。 腾讯云便宜购买指南&#xff1a;1、新用户专属礼包&#xff1b;2、老用户…

Postman —— postman实现参数化

什么时候会用到参数化 比如&#xff1a;一个模块要用多组不同数据进行测试 验证业务的正确性 Login模块&#xff1a;正确的用户名&#xff0c;密码 成功&#xff1b;错误的用户名&#xff0c;正确的密码 失败 postman实现参数化 在实际的接口测试中&#xff0c;部分参数每…

远程连接虚拟机中ubuntu报错:Network error:Connection refused

ping检测一下虚拟机 可以ping通&#xff0c;说明主机是没问题 #检查ssh是否安装&#xff1a; ps -e |grep ssh发现ssh没有安装 #安装openssh-server sudo apt-get install openssh-server#启动ssh service ssh startps -e |grep ssh检查一下防火墙 #防火墙状态查看 sudo ufw…

使用 Transformer 和 Amazon OpenSearch Service 构建基于列的语义搜索引擎

在数据湖中&#xff0c;对于数据清理和注释、架构匹配、数据发现和跨多个数据来源进行分析等许多操作&#xff0c;查找相似的列有着重要的应用。如果不能从多个不同的来源准确查找和分析数据&#xff0c;就会严重拉低效率&#xff0c;不论是数据科学家、医学研究人员、学者&…

智慧化工地SaaS平台源码,PC端+APP端+智慧数据可视化大屏端,源码完全开源不封装,自主研发,支持二开,项目使用,微服务+Java++vue+mysql

智慧工地管理平台充分运用数字化技术&#xff0c;聚焦施工现场岗位一线&#xff0c;依托物联网、互联网、AI等技术&#xff0c;围绕施工现场管理的人、机、料、法、环五大维度&#xff0c;以及施工过程管理的进度、质量、安全三大体系为基础应用&#xff0c;实现全面高效的工程…

es和数据库同步方案

5.5 课程信息索引同步 5.5.1 技术方案 通过向索引中添加课程信息最终实现了课程的搜索&#xff0c;我们发现课程信息是先保存在关系数据库中&#xff0c;而后再写入索引&#xff0c;这个过程是将关系数据中的数据同步到elasticsearch索引中的过程&#xff0c;可以简单成为索引…