啥也不说先上代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using Unity.Jobs;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
public class OutLineBaker : AssetPostprocessor
{
static bool bExecuting = false;
[MenuItem("Tools/BakeNormal", false, 100)]
public static void BakeNormal()
{
Object obj = Selection.activeObject;
string path = AssetDatabase.GetAssetPath(obj);
string ext = Path.GetExtension(path);
if(ext == ".fbx")
{
bExecuting = true;
string newPath = Path.GetDirectoryName(path) + "/copy_@@@" + Path.GetFileName(path);
Debug.Log($"new Path= {newPath}");
AssetDatabase.CopyAsset(path, newPath);
//AssetDatabase.ImportAsset(newPath);
}
else
{
Debug.Log($"必须对.fbx文件进行此操作");
}
//Debug.Log($"EXT name = {ext}");
}
static void OnPostOver(string path,bool isCopy)
{
if (isCopy)
{
string srcPath = path.Replace("copy_@@@", "");
Debug.Log($"复制体后处理后,本体的path={srcPath}");
AssetDatabase.ImportAsset(srcPath);
}
else
{
Debug.Log($"处理完毕,关闭开关");
bExecuting = false;
}
}
void OnPreprocessModel()
{
if (!bExecuting) return;
//Input Settings
ModelImporter importer = assetImporter as ModelImporter;
if (!assetImporter.assetPath.Contains("copy_@@@")) return;
importer.importNormals = ModelImporterNormals.Calculate;
importer.normalCalculationMode = ModelImporterNormalCalculationMode.AngleWeighted;
importer.normalSmoothingAngle = 180f;
importer.importAnimation = false;//no animation imported
importer.materialImportMode = ModelImporterMaterialImportMode.None;//no material imported
Debug.Log($"1");
}
void OnPostprocessModel(GameObject go)
{
if (!bExecuting) return;
//AfterInputing Settings
if(go.name.Contains("copy_@@@"))
{
//Debug.Log($"Enter the copy obj postprocess");
OnPostOver(assetPath, true);
}
else
{
// Debug.Log($"Self");
string copyPath = Path.GetDirectoryName(assetPath) + "/copy_@@@" + Path.GetFileName(assetPath);
Debug.Log($"复制体的Path = {copyPath}");
GameObject cGo = AssetDatabase.LoadAssetAtPath<GameObject>(copyPath);
Dictionary<string, Mesh> dic_name2mesh_src = GetMesh(go);//原本模型的各个子Mesh,通过节点名索引
Dictionary<string, Mesh> dic_name2mesh_copy = GetMesh(cGo);//平滑模型各个子Mesh,通过节点名索引
foreach (var item in dic_name2mesh_src)
{
item.Value.colors = GetColor(item.Value,dic_name2mesh_copy[item.Key]);
}
OnPostOver("", false);
}
}
Dictionary<string,Mesh>GetMesh(GameObject go)
{
Dictionary<string, Mesh> dic = new();
foreach (var item in go.GetComponentsInChildren<MeshFilter>())
{
string n = item.name.Replace("copy_@@@", "");
dic.Add(n, item.sharedMesh);
}
if (dic.Count == 0)
{
foreach (var item in go.GetComponentsInChildren<SkinnedMeshRenderer>())
{
string n = item.name.Replace("copy_@@@", "");
dic.Add(n, item.sharedMesh);
}
}
return dic;
}
Color[] GetColor(Mesh srcMesh,Mesh smthMesh)
{
//按照顶点位置索引,烘焙信息
int lenSrc = srcMesh.vertices.Length;
int lenSmth = smthMesh.vertices.Length;
int maxOverlap = 10;
NativeArray<Vector3> arr_vert_smth = new(smthMesh.vertices,Allocator.Persistent);//Allocator为资源分配的方式,一般选择Persistent
NativeArray<Vector3> arr_normal_smth = new(smthMesh.normals, Allocator.Persistent);
NativeArray<UnsafeHashMap<Vector3, Vector3>> arr_vert2normal = new(maxOverlap, Allocator.Persistent);
NativeArray<UnsafeHashMap<Vector3, Vector3>.ParallelWriter> arr_vert2normal_writer = new(maxOverlap, Allocator.Persistent);
for (int i = 0; i < maxOverlap; i++)
{
arr_vert2normal[i] = new UnsafeHashMap<Vector3, Vector3>(lenSmth, Allocator.Persistent);
arr_vert2normal_writer[i] = arr_vert2normal[i].AsParallelWriter();
}
CollectNormalJob collectJob = new()
{
verts = arr_vert_smth,
normals = arr_normal_smth,
arr_vert2normal= arr_vert2normal_writer
};
JobHandle collectHandle = collectJob.Schedule(lenSmth, 128);
collectHandle.Complete();
NativeArray<Vector3> arr_vert_src = new(srcMesh.vertices, Allocator.Persistent);
NativeArray<Vector3> arr_nor_src = new(srcMesh.normals, Allocator.Persistent);
NativeArray<Vector4> arr_tgt_src = new(srcMesh.tangents, Allocator.Persistent);
NativeArray<Color> arr_color = new(lenSrc, Allocator.Persistent);
BakeNormalJob bakeJob = new()
{
normals = arr_nor_src,
verts = arr_vert_src,
tangents = arr_tgt_src,
bakedNormals = arr_vert2normal,
colors = arr_color
};
JobHandle bakeHandel = bakeJob.Schedule(lenSrc,128);
bakeHandel.Complete();//烘焙完成
Color[] cols = new Color[lenSrc];
arr_color.CopyTo(cols);
foreach (var item in arr_vert2normal)
{
item.Dispose();
}
arr_vert_smth.Dispose();
arr_normal_smth.Dispose();
arr_vert2normal.Dispose();
arr_vert2normal_writer.Dispose();
arr_vert_src.Dispose();
arr_nor_src.Dispose();
arr_tgt_src.Dispose();
arr_color.Dispose();
return cols;
}
struct CollectNormalJob : IJobParallelFor
{
[ReadOnly] public NativeArray<Vector3> verts;
[ReadOnly] public NativeArray<Vector3> normals;
//顶点-法线的映射
[NativeDisableContainerSafetyRestriction]//解除安全锁定
//顶点-法线
public NativeArray<UnsafeHashMap<Vector3, Vector3>.ParallelWriter> arr_vert2normal;//NativeArray是一个平行字典,有一个安全封装,一般是无法写入的,所以需要一个.ParallelWriter的写入器
public void Execute(int index)
{
//每次执行
for (int i = 0; i < arr_vert2normal.Length; i++)
{
if (i==arr_vert2normal.Length)
{
Debug.Log($"超出顶点数量");
break;
}
if (arr_vert2normal[i].TryAdd(verts[index], normals[index]))
{
Debug.Log($"当前添加:顶点:{verts[index]},法线:{normals[index]}");
break;
}
}
}
}
struct BakeNormalJob : IJobParallelFor
{
//切线空间 切线 法线 顶点位置 烘焙好的法线 输出-颜色数组
[ReadOnly] public NativeArray<Vector3> normals;
[ReadOnly] public NativeArray<Vector3> verts;
[ReadOnly] public NativeArray<Vector4> tangents;
[ReadOnly][NativeDisableContainerSafetyRestriction] public NativeArray<UnsafeHashMap<Vector3, Vector3>> bakedNormals;
public NativeArray<Color> colors;
public void Execute(int index)
{
Vector3 newNormal = Vector3.zero;
for (int i = 0; i < bakedNormals.Length; i++)
{
if (bakedNormals[i][verts[index]]!= Vector3.zero){
newNormal += bakedNormals[i][verts[index]];
}
else
{
break;
}
}
newNormal = newNormal.normalized;
//tangent作为VEC4数值,里面的W存储的是+1或者-1,因为opengl于DX坐标一个左手一个右手,所以拿Z区分
Vector3 bitangent = (Vector3.Cross(normals[index], tangents[index]) * tangents[index].w).normalized;
Matrix4x4 tbn = new(
tangents[index],
bitangent,
normals[index],
Vector4.zero
);
tbn = tbn.transpose;
Vector3 finalNormal = tbn.MultiplyVector(newNormal).normalized;
Color col = new(
finalNormal.x*0.5f+0.5f,
finalNormal.y*0.5f+0.5f,
finalNormal.z*0.5f+0.5f,
1
);
colors[index] = col;
}
}
}
使用之前,现在unity中载入Collection,如果Unity版本大于2022,需要将UnsafeHashMap替代成UnsafeParallelHashMap