Files
Fishing2/Packages/com.jbooth.microsplat.mesh-workflow/Scripts/MicroSplatVertexMesh.cs
2025-06-04 09:09:39 +08:00

403 lines
11 KiB
C#

//////////////////////////////////////////////////////
// MicroSplat
// Copyright (c) Jason Booth
//////////////////////////////////////////////////////
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
#if __MICROSPLAT__
using JBooth.MicroSplat;
[ExecuteInEditMode]
[DisallowMultipleComponent]
public class MicroSplatVertexMesh : MicroSplatObject
{
public delegate void MaterialSyncAll ();
public delegate void MaterialSync (Material m);
public static event MaterialSyncAll OnMaterialSyncAll;
public event MaterialSync OnMaterialSync;
static List<MicroSplatVertexMesh> sInstances = new List<MicroSplatVertexMesh> ();
[HideInInspector]
public MeshRenderer rend;
// this is for things per submesh
[System.Serializable]
public class SubMeshOverride
{
public string materialName;
public bool active;
public bool bUVOverride = false;
public Vector4 UVOverride = new Vector4 (1, 1, 0, 0);
public long GetHash ()
{
long h = 3;
unchecked
{
if (bUVOverride)
{
h = h + UVOverride.GetHashCode () * 3;
}
}
if (h == 0)
{
Debug.Log ("Submesh override hash returned 0, this should not happen");
}
return h;
}
}
[System.Serializable]
public class SubMeshEntry
{
public CombinedOverride combinedOverride = new CombinedOverride ();
public SubMeshOverride subMeshOverride = new SubMeshOverride ();
public Material matInstance = null;
public long oldKey = 0;
public long GetHash ()
{
unchecked
{
return combinedOverride.GetHash () * 7 + subMeshOverride.GetHash () * 3;
}
}
}
public List<SubMeshEntry> subMeshEntries = new List<SubMeshEntry> ();
public struct MaterialInstranceEntry
{
public int refCount;
public Material matInstance;
}
static Dictionary<long, MaterialInstranceEntry> materialRegistry = new Dictionary<long, MaterialInstranceEntry> ();
public static int GetRegistrySize () { return materialRegistry.Count; }
public static void ClearMaterialCache (bool sync = true)
{
foreach (var k in materialRegistry)
{
if (k.Value.matInstance != null)
{
GameObject.DestroyImmediate (k.Value.matInstance);
}
}
materialRegistry.Clear ();
if (sync)
MicroSplatVertexMesh.SyncAll ();
}
long GetMaterialInstanceHash (int subMesh)
{
if (templateMaterial == null)
return 0;
long h = 3;
unchecked
{
h = h + templateMaterial.GetInstanceID ();
h = h + templateMaterial.shader.GetInstanceID ();
if (subMesh < subMeshEntries.Count)
{
h = h + subMeshEntries [subMesh].GetHash ();
}
h = h + GetOverrideHash ();
if (h == 0)
{
Debug.LogError ("Material instance hash is 0");
}
}
return h;
}
void Cleanup (long key, int subMesh)
{
if (subMesh >= subMeshEntries.Count)
{
return;
}
if (key == 0)
return;
MaterialInstranceEntry e;
if (materialRegistry.TryGetValue (key, out e))
{
e.refCount--;
//Debug.Log ("Releasing matInstance " + e.refCount + " " + e.matInstance + " " + key);
subMeshEntries [subMesh].matInstance = null;
if (e.refCount < 0)
{
Debug.LogError ("Reference count < 0, something is broken");
}
if (e.refCount == 0)
{
if (e.matInstance != null)
{
//Debug.Log ("Destroying mat instance " + e.matInstance.name + " " + key);
DestroyImmediate (e.matInstance);
e.matInstance = null;
}
materialRegistry.Remove (key);
}
else
{
materialRegistry [key] = e; // struct, so have to put it back
}
}
subMeshEntries [subMesh].oldKey = 0;
}
void Cleanup ()
{
for (int i = 0; i < subMeshEntries.Count; ++i)
{
Cleanup (subMeshEntries [i].oldKey, i);
}
}
#if UNITY_EDITOR
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
// bug in editor when compiling shaders. Brute force, but shouldn't affect runtime.
ClearMaterialCache();
}
#endif
Material GetMaterialInstance (int subMesh)
{
long key = GetMaterialInstanceHash (subMesh);
if (key == 0)
{
Debug.LogError ("0 key found, check hashing functions");
}
if (subMesh >= subMeshEntries.Count)
{
Debug.LogError ("SubMesh out of range");
}
var sub = subMeshEntries [subMesh];
if (sub.oldKey != key)
{
Cleanup(sub.oldKey, subMesh);
}
sub.oldKey = key;
MaterialInstranceEntry e;
if (materialRegistry.TryGetValue (key, out e))
{
e.refCount++;
//Debug.Log ("++refCount : " + e.refCount + " " + e.matInstance.name + " " + key);
if (e.matInstance == null)
{
e.matInstance = new Material (templateMaterial);
}
materialRegistry [key] = e; // struct, so have to assign back..
}
else
{
e = new MaterialInstranceEntry ();
e.matInstance = new Material (templateMaterial);
e.refCount = 1;
materialRegistry.Add (key, e);
//Debug.Log ("Creating entry : " + e.matInstance.name + " " + key);
}
sub.matInstance = e.matInstance;
return e.matInstance;
}
void Awake ()
{
rend = GetComponent<MeshRenderer> ();
}
void OnEnable ()
{
sInstances.Add (this);
Sync ();
}
void OnDisable ()
{
sInstances.Remove (this);
Cleanup ();
}
public void Sync ()
{
if (templateMaterial == null)
return;
if (this == null) // object is deleted
return;
if (keywordSO == null)
{
RevisionFromMat ();
}
if (keywordSO == null)
return;
if (rend == null)
{
rend = GetComponent<MeshRenderer> ();
}
if (rend == null)
{
Debug.LogError ("No renderer found on MicroSplatVertexMesh component's game object, cannot sync");
return;
}
if (keywordSO.IsKeywordEnabled ("_MESHOVERLAYSPLATS") == false && rend.sharedMaterials.Length != subMeshEntries.Count)
{
var shared = rend.sharedMaterials;
System.Array.Resize (ref shared, subMeshEntries.Count);
rend.sharedMaterials = shared;
}
ApplySharedData(templateMaterial);
for (int subMeshIdx = 0; subMeshIdx < subMeshEntries.Count; ++subMeshIdx)
{
var subMesh = subMeshEntries [subMeshIdx];
if (subMesh.subMeshOverride.active == false)
continue;
Material m = GetMaterialInstance (subMeshIdx);
if (m == null)
{
m = GetMaterialInstance (subMeshIdx);
}
matInstance = m;
// copy latest, as it may have changed as we don't hash the material data
m.CopyPropertiesFromMaterial (templateMaterial);
if (keywordSO.IsKeywordEnabled ("_MESHOVERLAYSPLATS"))
{
var materials = rend.sharedMaterials;
bool hasBlendMat = false;
// since out instance is hide and don't save, we accept the first null
// as a slot if our shader isn't found in use already.
int nullIndex = -1;
for (int i = 0; i < materials.Length; ++i)
{
if (materials [i] == null)
{
nullIndex = i;
}
else if (materials [i].shader == matInstance.shader)
{
materials [i] = matInstance;
hasBlendMat = true;
}
}
if (!hasBlendMat)
{
if (nullIndex > -1)
{
materials [nullIndex] = matInstance;
}
else
{
System.Array.Resize<Material> (ref materials, materials.Length + 1);
materials [materials.Length - 1] = matInstance;
}
rend.sharedMaterials = materials;
}
}
else
{
var shared = rend.sharedMaterials;
shared [subMeshIdx] = m;
rend.sharedMaterials = shared;
}
m.hideFlags = HideFlags.HideAndDontSave;
ApplyMaps (m);
if (keywordSO.IsKeywordEnabled ("_MESHCOMBINED"))
{
SetMap (m, "_StandardDiffuse", subMesh.combinedOverride.standardAlbedoOverride);
SetMap (m, "_StandardNormal", subMesh.combinedOverride.standardNormalOverride);
SetMap (m, "_StandardSmoothMetal", subMesh.combinedOverride.standardMetalSmoothOverride);
SetMap (m, "_StandardHeight", subMesh.combinedOverride.standardHeightOverride);
SetMap (m, "_StandardEmission", subMesh.combinedOverride.standardEmissionOverride);
SetMap (m, "_StandardOcclusion", subMesh.combinedOverride.standardOcclusionOverride);
SetMap (m, "_StandardPackedMap", subMesh.combinedOverride.standardPackedOverride);
SetMap (m, "_StandardSSS", subMesh.combinedOverride.standardSSS);
if (subMesh.combinedOverride.bStandardColorOverride && m.HasProperty ("_StandardDiffuseTint"))
{
m.SetColor ("_StandardDiffuseTint", subMesh.combinedOverride.standardColorOverride);
}
if (subMesh.combinedOverride.bStandardUVOverride && m.HasProperty ("_StandardUVScaleOffset"))
{
m.SetVector ("_StandardUVScaleOffset", subMesh.combinedOverride.standardUVOverride);
}
}
if (subMesh.subMeshOverride.bUVOverride && m.HasProperty ("_UVScale"))
{
m.SetVector ("_UVScale", subMesh.subMeshOverride.UVOverride);
}
if (subMesh.subMeshOverride.bUVOverride && m.HasProperty ("_TriplanarUVScale"))
{
m.SetVector ("_TriplanarUVScale", subMesh.subMeshOverride.UVOverride);
}
if (OnMaterialSync != null)
{
OnMaterialSync (m);
}
}
}
public override Bounds GetBounds () { return rend.bounds; }
public static new void SyncAll ()
{
#if UNITY_EDITOR
ClearMaterialCache(false); // work around issue where editor goes pink on compile
#endif
for (int i = 0; i < sInstances.Count; ++i)
{
sInstances [i].Sync ();
}
if (OnMaterialSyncAll != null)
{
OnMaterialSyncAll ();
}
}
}
#else
public class MicroSplatVertexMesh : MonoBehaviour
{
}
#endif