Skip to content

Instantly share code, notes, and snippets.

@jagt
Created February 1, 2018 13:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jagt/2d8fa490ceb5dfb0a2c61ec6d26ac57a to your computer and use it in GitHub Desktop.
Save jagt/2d8fa490ceb5dfb0a2c61ec6d26ac57a to your computer and use it in GitHub Desktop.
InstaLOD binding example
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