大家好,我是阿赵。
之前分享过怎样通过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方法文章来源:https://www.toymoban.com/news/detail-680347.html
三、工具源码
总共3个c#脚本文章来源地址https://www.toymoban.com/news/detail-680347.html
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];
}
}
}
}
到了这里,关于Unity引擎修改模型顶点色的工具的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!