Created
February 1, 2018 13:28
-
-
Save jagt/2d8fa490ceb5dfb0a2c61ec6d26ac57a to your computer and use it in GitHub Desktop.
InstaLOD binding example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
using InstaLOD; | |
using System.Runtime.InteropServices; | |
public static class InstaLODEditorUtils | |
{ | |
class NativeMeshWrapper : IDisposable | |
{ | |
private GCHandle _verticesHandle; | |
private GCHandle _trianglesHandle; | |
private GCHandle _texCoordsHandle; | |
private GCHandle _tangentsHandle; | |
private GCHandle _normalsHandle; | |
private GCHandle _colorsHandle; | |
private GCHandle _materialIDsHandle; | |
private GCHandle _subMeshIDsHandle; | |
public InstaLODMesh nativeMesh; | |
public NativeMeshWrapper(Mesh mesh, int[] textureIDs, Transform transform) | |
{ | |
var verticesRaw = mesh.vertices; | |
var trianglesRaw = mesh.triangles; | |
var triangles = new uint[trianglesRaw.Length]; // stores the indices actually | |
var materialIDs = new int[trianglesRaw.Length / 3]; // as we're combining multiple textures together, this maps which texture this uv is sampling | |
var submeshIDs = new uint[trianglesRaw.Length / 3]; // stores triangle belongs to which submesh | |
// populate triangles and submeshIDs | |
Debug.Assert(mesh.subMeshCount >= 1); | |
Debug.Assert(textureIDs.Length == mesh.subMeshCount); | |
uint vertexIx = 0; | |
for (int subMeshId = 0; subMeshId < mesh.subMeshCount; subMeshId++) | |
{ | |
int[] subMeshTriangles = mesh.GetTriangles(subMeshId); | |
for (int subId = 0; subId < subMeshTriangles.Length; subId++) | |
{ | |
triangles[vertexIx] = (uint)subMeshTriangles[subId]; | |
submeshIDs[vertexIx / 3u] = (uint)subMeshId; | |
++vertexIx; | |
} | |
} | |
// populate textureIDs | |
for (int faceID = 0; faceID < materialIDs.Length; faceID++) | |
{ | |
uint subMeshID = submeshIDs[faceID]; | |
materialIDs[subMeshID] = textureIDs[subMeshID]; | |
} | |
// populate normals and tangents | |
var colorsRaw = mesh.colors; | |
var normalsRaw = mesh.normals; | |
var tangentsRaw = mesh.tangents; | |
if (normalsRaw.Length == 0) | |
{ | |
mesh.RecalculateNormals(); | |
normalsRaw = mesh.normals; | |
} | |
if (tangentsRaw.Length == 0) | |
{ | |
mesh.RecalculateTangents(); | |
tangentsRaw = mesh.tangents; | |
} | |
// transform using unity Transform, or it will put all objects at local zero | |
Matrix4x4 localToWorldMatrix = transform.localToWorldMatrix; | |
for (uint ix = 0; ix < verticesRaw.Length; ix++) | |
{ | |
verticesRaw[ix] = localToWorldMatrix.MultiplyPoint(verticesRaw[ix]); | |
normalsRaw[ix] = localToWorldMatrix.MultiplyPoint(normalsRaw[ix]); | |
// seems tangent translate needs to keep the last w component | |
Vector4 tangentLocal = tangentsRaw[ix]; | |
Vector3 tangentWorld = new Vector3(tangentLocal.x, tangentLocal.y, tangentLocal.z); | |
tangentWorld = localToWorldMatrix.MultiplyVector(tangentLocal); | |
tangentsRaw[ix] = new Vector4(tangentWorld.x, tangentWorld.y, tangentWorld.z, tangentLocal.w); | |
} | |
_verticesHandle = GCHandle.Alloc(verticesRaw, GCHandleType.Pinned); | |
_trianglesHandle = GCHandle.Alloc(triangles, GCHandleType.Pinned); | |
_texCoordsHandle = GCHandle.Alloc(mesh.uv, GCHandleType.Pinned); | |
_tangentsHandle = GCHandle.Alloc(tangentsRaw, GCHandleType.Pinned); | |
_normalsHandle = GCHandle.Alloc(normalsRaw, GCHandleType.Pinned); | |
if (colorsRaw.Length != 0) | |
_colorsHandle = GCHandle.Alloc(colorsRaw, GCHandleType.Pinned); | |
_materialIDsHandle = GCHandle.Alloc(_materialIDsHandle, GCHandleType.Pinned); | |
_subMeshIDsHandle = GCHandle.Alloc(_subMeshIDsHandle, GCHandleType.Pinned); | |
// populate native mesh | |
nativeMesh = new InstaLODMesh() | |
{ | |
VertexCount = (uint)verticesRaw.Length, | |
IndexCount = (uint)trianglesRaw.Length, | |
Vertices = _verticesHandle.AddrOfPinnedObject(), | |
Indices = _trianglesHandle.AddrOfPinnedObject(), | |
TexCoords0 = _texCoordsHandle.AddrOfPinnedObject(), | |
TexCoords1 = IntPtr.Zero, | |
TexCoords2 = IntPtr.Zero, | |
TexCoords3 = IntPtr.Zero, | |
Tangents = _tangentsHandle.AddrOfPinnedObject(), | |
Normals = _normalsHandle.AddrOfPinnedObject(), | |
Colors = colorsRaw.Length == 0 ? IntPtr.Zero : _colorsHandle.AddrOfPinnedObject(), | |
MaterialIDs = _materialIDsHandle.AddrOfPinnedObject(), | |
SubmeshIDs = _subMeshIDsHandle.AddrOfPinnedObject(), | |
}; | |
} | |
public void Dispose() | |
{ | |
_verticesHandle.Free(); | |
_trianglesHandle.Free(); | |
_texCoordsHandle.Free(); | |
_tangentsHandle.Free(); | |
_normalsHandle.Free(); | |
_colorsHandle.Free(); | |
_materialIDsHandle.Free(); | |
_subMeshIDsHandle.Free(); | |
} | |
} | |
class NativeTextureWrapper : IDisposable | |
{ | |
GCHandle _colorsHandle; | |
public InstaLODTexturePage nativeTexturePage; | |
public static Color32[] GetTextureDataSafe(Texture texture) | |
{ | |
RenderTexture temporary = RenderTexture.GetTemporary(texture.width, texture.height); | |
temporary.filterMode = FilterMode.Point; | |
RenderTexture.active = temporary; | |
// TODO normal map is actually handled here, drop it for now | |
Graphics.Blit(texture, temporary); | |
Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false); | |
texture2D.ReadPixels(new Rect(0f, 0f, (float)texture.width, (float)texture.height), 0, 0); | |
texture2D.Apply(); | |
RenderTexture.active = null; | |
return texture2D.GetPixels32(); | |
} | |
public NativeTextureWrapper(Texture texture) | |
{ | |
Color32[] textureData = GetTextureDataSafe(texture); | |
_colorsHandle = GCHandle.Alloc(textureData, GCHandleType.Pinned); | |
nativeTexturePage = new InstaLODTexturePage() | |
{ | |
Width = (uint)texture.width, | |
Height = (uint)texture.height, | |
Name = Marshal.StringToHGlobalAnsi(texture.name), | |
IsNormalMap = new NativeBool(false), | |
Data = _colorsHandle.AddrOfPinnedObject(), | |
ComponentType = TexturePageComponentType.ComponentTypeUInt8, | |
PixelType = TexturePagePixelType.PixelTypeRGBA, | |
DefaultColor = Color.white, | |
}; | |
return; | |
} | |
public void Dispose() | |
{ | |
_colorsHandle.Free(); | |
} | |
} | |
public static void OptimizeRenderers(List<MeshRenderer> renderers) | |
{ | |
// setup mesh and renderer things | |
List<Texture> textures = new List<Texture>(); | |
foreach (var renderer in renderers) | |
{ | |
var sharedMaterials = renderer.sharedMaterials; | |
foreach (var mat in sharedMaterials) | |
{ | |
var mainTex = mat.mainTexture; | |
Debug.Assert(mainTex != null); | |
if (!textures.Contains(mainTex)) | |
textures.Add(mainTex); | |
} | |
} | |
List<NativeMeshWrapper> nativeMeshes = new List<NativeMeshWrapper>(); | |
foreach (var renderer in renderers) | |
{ | |
var meshFilter = renderer.gameObject.GetComponent<MeshFilter>(); | |
Debug.Assert(meshFilter != null); | |
var mesh = meshFilter.sharedMesh; | |
Debug.Assert(mesh != null); | |
var sharedMats = renderer.sharedMaterials; | |
int[] textureIDs = new int[mesh.subMeshCount]; | |
for (int ix = 0; ix < mesh.subMeshCount; ix++) | |
{ | |
var drawMat = ix >= sharedMats.Length ? sharedMats[sharedMats.Length - 1] : sharedMats[ix]; | |
var mainTex = drawMat.mainTexture; | |
Debug.Assert(mainTex != null); | |
textureIDs[ix] = textures.IndexOf(mainTex); | |
Debug.Assert(textureIDs[ix] >= 0); | |
} | |
nativeMeshes.Add(new NativeMeshWrapper(mesh, textureIDs, renderer.transform)); | |
} | |
// native textures | |
List<NativeTextureWrapper> nativeTextures = new List<NativeTextureWrapper>(); | |
foreach (var texture in textures) | |
nativeTextures.Add(new NativeTextureWrapper(texture)); | |
InstaLODNative.Initialize(); | |
if (!InstaLODNative.IsInitialized()) | |
{ | |
Debug.LogErrorFormat("InstaLOD not initialized"); | |
return; | |
} | |
if (!InstaLODNative.IsAuthorized()) | |
{ | |
Debug.LogErrorFormat("InstaLOD not authorized"); | |
return; | |
} | |
// setup material data and call native api, see what happens | |
InstaLODNative.ClearErrorLog(); | |
InstaLODNative.EnsureAssetDataFolder(); | |
// native meshes | |
InstaLODNative.InstaLODMesh_Initialize(); | |
foreach (var nativeWrapper in nativeMeshes) | |
InstaLODNative.InstaLODMesh_Append(ref nativeWrapper.nativeMesh); | |
// native materials | |
InstaLODNative.InstaLODMaterialData_Initialize(); | |
for (int ix = 0; ix < nativeTextures.Count; ix++) | |
{ | |
var nativeTexture = nativeTextures[ix]; | |
InstaLODNative.InstaLODMaterial_Begin(ix); | |
InstaLODNative.InstaLODMaterial_AddTexturePage(ref nativeTexture.nativeTexturePage); | |
} | |
InstaLODNative.InstaLODMaterialData_Finalize(); | |
// remesh | |
{ | |
var settings = new InstaLODRemeshingSettings() | |
{ | |
BakeOutput = new InstaLODBakeOutputSettings(1024, 1024), | |
MaximumTriangles = 10000, | |
}; | |
InstaLODNative.Remesh(ref settings); | |
} | |
//{ | |
// var settings = new InstaLODMeshMergeSettings() | |
// { | |
// BakeOutput = new InstaLODBakeOutputSettings(1024, 1024), | |
// }; | |
// InstaLODNative.MeshMerge(ref settings); | |
//} | |
// extract | |
Mesh resultMesh; | |
using (InstaLODMeshWrapper instaLODMeshWrapper = new InstaLODMeshWrapper(null, null, null, null, null, null, null)) | |
{ | |
InstaLODMesh nativeMesh = default(InstaLODMesh); | |
InstaLODNative.InstaLODMesh_Get(ref nativeMesh); | |
resultMesh = instaLODMeshWrapper.ConvertNativeMesh(nativeMesh, true); | |
} | |
SECTR_Asset.Create(null, resultMesh.name, resultMesh); | |
uint extractCount = InstaLODNative.InstaLODMaterial_GetTexturePageCount(); | |
Debug.LogFormat("extracting {0} textures", extractCount); | |
for (uint ix = 0; ix < extractCount; ix++) | |
{ | |
InstaLODTexturePage page = default(InstaLODTexturePage); | |
InstaLODNative.InstaLODMaterial_GetTexturePageAtIndex(ix, ref page); | |
var fullPath = Path.Combine(Application.dataPath, string.Format("extract{0}.png", ix)); | |
Debug.LogFormat("extracting {0} to {1}", ix, fullPath); | |
InstaLODNative.InstaLODMaterial_WriteTexturePageAtIndexAsPNG(ix, fullPath); | |
} | |
Debug.Log("Done"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment