using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using UnityEngine; // Todo: Change this into a editor window namespace Misaki.HdrpToon.Editor { public class ModelOutlineJobs { public struct CollectNormalJob : IJobParallelFor { [ReadOnly] public NativeArray normals, vertrx; [NativeDisableContainerSafetyRestriction] public NativeArray.ParallelWriter> result; public CollectNormalJob(NativeArray normals, NativeArray vertrx, NativeArray.ParallelWriter> result) { this.normals = normals; this.vertrx = vertrx; this.result = result; } void IJobParallelFor.Execute(int index) { for (var i = 0; i < result.Length + 1; i++) { if (i == result.Length) { Debug.LogError($"Overlapping vertices count({i})has out of bound!"); break; } if (result[i].TryAdd(vertrx[index], normals[index])) { break; } } } } public struct BakeNormalJob : IJobParallelFor { [ReadOnly] public NativeArray vertrx, normals; [ReadOnly] public NativeArray tangents; [NativeDisableContainerSafetyRestriction] [ReadOnly] public NativeArray> result; [WriteOnly] public NativeArray uv2; public BakeNormalJob(NativeArray vertrx, NativeArray normals, NativeArray tangents, NativeArray> result, NativeArray uv2) { this.vertrx = vertrx; this.normals = normals; this.tangents = tangents; this.result = result; this.uv2 = uv2; } void IJobParallelFor.Execute(int index) { var smoothedNormals = Vector3.zero; for (var i = 0; i < result.Length; i++) { if (result[i][vertrx[index]] != Vector3.zero) smoothedNormals += result[i][vertrx[index]]; else break; } smoothedNormals = smoothedNormals.normalized; var bitangent = (Vector3.Cross(normals[index], tangents[index]) * tangents[index].w).normalized; var tbn = new Matrix4x4( tangents[index], bitangent, normals[index], Vector4.zero); tbn = tbn.transpose; var bakedNormal = tbn.MultiplyVector(smoothedNormals).normalized; var newUV = new Vector2(bakedNormal.x, bakedNormal.y); uv2[index] = newUV; } } //[ContextMenu("Generate smooth normal to uv2")] //void ProcessingModel() //{ // var go = Selection.activeGameObject; // if (go == null) // { // Debug.LogError("Select a GameObject first!"); // return; // } // Dictionary originalMesh = GetMesh(go), smoothedMesh = GetMesh(go); // foreach (var item in originalMesh) // { // var m = item.Value; // ComputeSmoothedNormalByJob(smoothedMesh[item.Key], m); // } //} //void OnPreprocessModel() //{ // if (assetPath.Contains("@@@")) // { // ModelImporter model = assetImporter as ModelImporter; // model.importNormals = ModelImporterNormals.Calculate; // model.normalCalculationMode = ModelImporterNormalCalculationMode.AngleWeighted; // model.normalSmoothingAngle = 180.0f; // model.importAnimation = false; // model.materialImportMode = ModelImporterMaterialImportMode.None; // } //} //void OnPostprocessModel(GameObject g) //{ // if (!g.name.Contains("_ol") || g.name.Contains("@@@")) // return; // ModelImporter model = assetImporter as ModelImporter; // string src = model.assetPath; // string dst = Path.GetDirectoryName(src) + "/@@@" + Path.GetFileName(src); // if (!File.Exists(Application.dataPath + "/" + dst.Substring(7))) // { // AssetDatabase.CopyAsset(src, dst); // AssetDatabase.ImportAsset(dst); // } // else // { // var go = AssetDatabase.LoadAssetAtPath(dst); // Dictionary originalMesh = GetMesh(g), smoothedMesh = GetMesh(go); // foreach (var item in originalMesh) // { // var m = item.Value; // ComputeSmoothedNormalByJob(smoothedMesh[item.Key], m); // } // AssetDatabase.DeleteAsset(dst); // } // AssetDatabase.Refresh(); //} } public static class UTSNormalBakerHelper { public static Dictionary GetMesh(GameObject go) { static void AddMesh(Dictionary dictionary, string name, Mesh mesh) { if (dictionary.ContainsKey(name)) Debug.LogWarning($"Model:'{name}'is duplicate!"); else dictionary.Add(name, mesh); } var dic = new Dictionary(); foreach (var item in go.GetComponentsInChildren()) AddMesh(dic, item.name, item.sharedMesh); if (go.TryGetComponent(out var mf)) { AddMesh(dic, mf.name.Replace("@", ""), mf.sharedMesh); } foreach (var item in go.GetComponentsInChildren()) { AddMesh(dic, item.name, item.sharedMesh); } if (go.TryGetComponent(out var smr)) { AddMesh(dic, smr.name.Replace("@", ""), smr.sharedMesh); } return dic; } public static void ComputeSmoothedNormalByJob(Mesh smoothedMesh, Mesh originalMesh, int maxOverlapvertices = 20) { int svc = smoothedMesh.vertexCount, ovc = originalMesh.vertexCount; // CollectNormalJob Data var normals = new NativeArray(smoothedMesh.normals, Allocator.Persistent); var vertrx = new NativeArray(smoothedMesh.vertices, Allocator.Persistent); var smoothedNormals = new NativeArray(svc, Allocator.Persistent); var result = new NativeArray>(maxOverlapvertices, Allocator.Persistent); var resultParallel = new NativeArray.ParallelWriter>(result.Length, Allocator.Persistent); // NormalBakeJob Data NativeArray normalsO = new NativeArray(originalMesh.normals, Allocator.Persistent), vertrxO = new NativeArray(originalMesh.vertices, Allocator.Persistent); var tangents = new NativeArray(originalMesh.tangents, Allocator.Persistent); var uv2 = new NativeArray(ovc, Allocator.Persistent); for (var i = 0; i < result.Length; i++) { result[i] = new UnsafeParallelHashMap(svc, Allocator.Persistent); resultParallel[i] = result[i].AsParallelWriter(); } var collectNormalJob = new ModelOutlineJobs.CollectNormalJob(normals, vertrx, resultParallel); var normalBakeJob = new ModelOutlineJobs.BakeNormalJob(vertrxO, normalsO, tangents, result, uv2); normalBakeJob.Schedule(ovc, 8, collectNormalJob.Schedule(svc, 100)).Complete(); var _uv2 = new Vector2[ovc]; uv2.CopyTo(_uv2); originalMesh.uv2 = _uv2; normals.Dispose(); vertrx.Dispose(); result.Dispose(); smoothedNormals.Dispose(); resultParallel.Dispose(); normalsO.Dispose(); vertrxO.Dispose(); tangents.Dispose(); uv2.Dispose(); } } }