using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; public class OvrAvatarMaterialManager : MonoBehaviour { private Renderer TargetRenderer; private AvatarTextureArrayProperties[] TextureArrays; public enum TextureType { DiffuseTextures = 0, NormalMaps, RoughnessMaps, Count } // Material properties required to render a single component public struct AvatarComponentMaterialProperties { public ovrAvatarBodyPartType TypeIndex; public Color Color; public Texture2D[] Textures; public float DiffuseIntensity; public float RimIntensity; public float ReflectionIntensity; } // Texture arrays public struct AvatarTextureArrayProperties { public Texture2D[] Textures; public Texture2DArray TextureArray; } // Material property arrays that are pushed to the shader public struct AvatarMaterialPropertyBlock { public Vector4[] Colors; public float[] DiffuseIntensities; public float[] RimIntensities; public float[] ReflectionIntensities; } private readonly string[] TextureTypeToShaderProperties = { "_MainTex", // TextureType.DiffuseTextures = 0 "_NormalMap", // TextureType.NormalMaps "_RoughnessMap" // TextureType.RoughnessMaps }; // Container class for all the data relating to an avatar material description [System.Serializable] public class AvatarMaterialConfig { public AvatarComponentMaterialProperties[] ComponentMaterialProperties; public AvatarMaterialPropertyBlock MaterialPropertyBlock; } // Local config that this manager instance will render public AvatarMaterialConfig LocalAvatarConfig = new AvatarMaterialConfig(); public List ReflectionProbes = new List(); // Cache the previous shader when swapping in the loading shader. private Shader CombinedShader; // Shader properties public static string AVATAR_SHADER_LOADER = "OvrAvatar/Avatar_Mobile_Loader"; public static string AVATAR_SHADER_MAINTEX = "_MainTex"; public static string AVATAR_SHADER_NORMALMAP = "_NormalMap"; public static string AVATAR_SHADER_ROUGHNESSMAP = "_RoughnessMap"; public static string AVATAR_SHADER_COLOR = "_BaseColor"; public static string AVATAR_SHADER_DIFFUSEINTENSITY = "_DiffuseIntensity"; public static string AVATAR_SHADER_RIMINTENSITY = "_RimIntensity"; public static string AVATAR_SHADER_REFLECTIONINTENSITY = "_ReflectionIntensity"; public static string AVATAR_SHADER_CUBEMAP = "_Cubemap"; public static string AVATAR_SHADER_ALPHA = "_Alpha"; public static string AVATAR_SHADER_LOADING_DIMMER = "_LoadingDimmer"; public static string AVATAR_SHADER_IRIS_COLOR = "_MaskColorIris"; public static string AVATAR_SHADER_LIP_COLOR = "_MaskColorLips"; public static string AVATAR_SHADER_BROW_COLOR = "_MaskColorBrows"; public static string AVATAR_SHADER_LASH_COLOR = "_MaskColorLashes"; public static string AVATAR_SHADER_SCLERA_COLOR = "_MaskColorSclera"; public static string AVATAR_SHADER_GUM_COLOR = "_MaskColorGums"; public static string AVATAR_SHADER_TEETH_COLOR = "_MaskColorTeeth"; public static string AVATAR_SHADER_LIP_SMOOTHNESS = "_LipSmoothness"; // Diffuse Intensity constants: body, clothes, eyewear, hair, beard public static float[] DiffuseIntensities = new[] {0.3f, 0.1f, 0f, 0.15f, 0.15f}; // Rim Intensity constants: body, clothes, eyewear, hair, beard public static float[] RimIntensities = new[] {5f, 2f, 2.84f, 4f, 4f}; // Reflection Intensity constants: body, clothes, eyewear, hair, beard public static float[] ReflectionIntensities = new[] {0f, 0.3f, 0.4f, 0f, 0f}; // Loading animation private const float LOADING_ANIMATION_AMPLITUDE = 0.5f; private const float LOADING_ANIMATION_PERIOD = 0.35f; private const float LOADING_ANIMATION_CURVE_SCALE = 0.25f; private const float LOADING_ANIMATION_DIMMER_MIN = 0.3f; public void CreateTextureArrays() { const int componentCount = (int)ovrAvatarBodyPartType.Count; const int textureTypeCount = (int)TextureType.Count; LocalAvatarConfig.ComponentMaterialProperties = new AvatarComponentMaterialProperties[componentCount]; LocalAvatarConfig.MaterialPropertyBlock.Colors = new Vector4[componentCount]; LocalAvatarConfig.MaterialPropertyBlock.DiffuseIntensities = new float[componentCount]; LocalAvatarConfig.MaterialPropertyBlock.RimIntensities = new float[componentCount]; LocalAvatarConfig.MaterialPropertyBlock.ReflectionIntensities = new float[componentCount]; for (int i = 0; i < LocalAvatarConfig.ComponentMaterialProperties.Length; ++i) { LocalAvatarConfig.ComponentMaterialProperties[i].Textures = new Texture2D[textureTypeCount]; } TextureArrays = new AvatarTextureArrayProperties[textureTypeCount]; } public void SetRenderer(Renderer renderer) { TargetRenderer = renderer; TargetRenderer.GetClosestReflectionProbes(ReflectionProbes); } public void OnCombinedMeshReady() { InitTextureArrays(); SetMaterialPropertyBlock(); // Callback to delete texture set once the avatar is fully loaded StartCoroutine(RunLoadingAnimation(DeleteTextureSet)); } // Add a texture ID so that it's managed for deletion public void AddTextureIDToTextureManager(ulong assetID, bool isSingleComponent) { OvrAvatarSDKManager.Instance.GetTextureCopyManager().AddTextureIDToTextureSet( GetInstanceID(), assetID, isSingleComponent); } // Once avatar loading is completed trigger the texture set for deletion private void DeleteTextureSet() { OvrAvatarSDKManager.Instance.GetTextureCopyManager().DeleteTextureSet(GetInstanceID()); } // Prepare texture arrays and copy to GPU public void InitTextureArrays() { var localProps = LocalAvatarConfig.ComponentMaterialProperties[0]; for (int i = 0; i < TextureArrays.Length && i < localProps.Textures.Length; i++) { TextureArrays[i].TextureArray = new Texture2DArray( localProps.Textures[0].height, localProps.Textures[0].width, LocalAvatarConfig.ComponentMaterialProperties.Length, localProps.Textures[0].format, true, QualitySettings.activeColorSpace == ColorSpace.Gamma ? false : true ) { filterMode = FilterMode.Trilinear, //Can probably get away with 4 for roughness maps as well, once we switch //to BC7/ASTC4x4 texture compression. anisoLevel = (TextureType)i == TextureType.RoughnessMaps ? 16 : 4 }; //So a name shows up in Renderdoc TextureArrays[i].TextureArray.name = string.Format("Texture Array Type: {0}", (TextureType)i); TextureArrays[i].Textures = new Texture2D[LocalAvatarConfig.ComponentMaterialProperties.Length]; for (int j = 0; j < LocalAvatarConfig.ComponentMaterialProperties.Length; j++) { TextureArrays[i].Textures[j] = LocalAvatarConfig.ComponentMaterialProperties[j].Textures[i]; //So a name shows up in Renderdoc TextureArrays[i].Textures[j].name = string.Format("Texture Type: {0} Component: {1}", (TextureType)i, j); } ProcessTexturesWithMips( TextureArrays[i].Textures, localProps.Textures[i].height, TextureArrays[i].TextureArray); } } private void ProcessTexturesWithMips( Texture2D[] textures, int texArrayResolution, Texture2DArray texArray) { for (int i = 0; i < textures.Length; i++) { int currentMipSize = texArrayResolution; int correctNumberOfMips = textures[i].mipmapCount - 1; // Add mips to copyTexture queue in low-high order from correctNumberOfMips..0 for (int mipLevel = correctNumberOfMips; mipLevel >= 0; mipLevel--) { int mipSize = texArrayResolution / currentMipSize; OvrAvatarSDKManager.Instance.GetTextureCopyManager().CopyTexture( textures[i], texArray, mipLevel, mipSize, i, false); currentMipSize /= 2; } } } private void SetMaterialPropertyBlock() { if (TargetRenderer != null) { for (int i = 0; i < LocalAvatarConfig.ComponentMaterialProperties.Length; i++) { LocalAvatarConfig.MaterialPropertyBlock.Colors[i] = LocalAvatarConfig.ComponentMaterialProperties[i].Color; LocalAvatarConfig.MaterialPropertyBlock.DiffuseIntensities[i] = DiffuseIntensities[i]; LocalAvatarConfig.MaterialPropertyBlock.RimIntensities[i] = RimIntensities[i]; LocalAvatarConfig.MaterialPropertyBlock.ReflectionIntensities[i] = ReflectionIntensities[i]; } } } private void ApplyMaterialPropertyBlock() { MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock(); materialPropertyBlock.SetVectorArray(AVATAR_SHADER_COLOR, LocalAvatarConfig.MaterialPropertyBlock.Colors); materialPropertyBlock.SetFloatArray(AVATAR_SHADER_DIFFUSEINTENSITY, LocalAvatarConfig.MaterialPropertyBlock.DiffuseIntensities); materialPropertyBlock.SetFloatArray(AVATAR_SHADER_RIMINTENSITY, LocalAvatarConfig.MaterialPropertyBlock.RimIntensities); materialPropertyBlock.SetFloatArray(AVATAR_SHADER_REFLECTIONINTENSITY, LocalAvatarConfig.MaterialPropertyBlock.ReflectionIntensities); TargetRenderer.GetClosestReflectionProbes(ReflectionProbes); if (ReflectionProbes != null && ReflectionProbes.Count > 0 && ReflectionProbes[0].probe.texture != null) { materialPropertyBlock.SetTexture(AVATAR_SHADER_CUBEMAP, ReflectionProbes[0].probe.texture); } for (int i = 0; i < TextureArrays.Length; i++) { materialPropertyBlock.SetTexture(TextureTypeToShaderProperties[i], TextureArrays[(int)(TextureType)i].TextureArray); } TargetRenderer.SetPropertyBlock(materialPropertyBlock); } // Return a component type based on name public static ovrAvatarBodyPartType GetComponentType(string objectName) { if (objectName.Contains("0")) { return ovrAvatarBodyPartType.Body; } else if (objectName.Contains("1")) { return ovrAvatarBodyPartType.Clothing; } else if (objectName.Contains("2")) { return ovrAvatarBodyPartType.Eyewear; } else if (objectName.Contains("3")) { return ovrAvatarBodyPartType.Hair; } else if (objectName.Contains("4")) { return ovrAvatarBodyPartType.Beard; } return ovrAvatarBodyPartType.Count; } UInt64 GetTextureIDForType(ovrAvatarPBSMaterialState materialState, TextureType type) { if (type == TextureType.DiffuseTextures) { return materialState.albedoTextureID; } else if (type == TextureType.NormalMaps) { return materialState.normalTextureID; } else if (type == TextureType.RoughnessMaps) { return materialState.metallicnessTextureID; } return 0; } public void ValidateTextures(ovrAvatarPBSMaterialState[] materialStates) { var props = LocalAvatarConfig.ComponentMaterialProperties; int[] heights = new int[(int)TextureType.Count]; TextureFormat[] formats = new TextureFormat[(int)TextureType.Count]; for (var propIndex = 0; propIndex < props.Length; propIndex++) { for (var index = 0; index < props[propIndex].Textures.Length; index++) { if (props[propIndex].Textures[index] == null) { throw new System.Exception( props[propIndex].TypeIndex.ToString() + "Invalid: " + ((TextureType)index).ToString()); } heights[index] = props[propIndex].Textures[index].height; formats[index] = props[propIndex].Textures[index].format; } } for (int textureIndex = 0; textureIndex < (int)TextureType.Count; textureIndex++) { for (var propIndex = 1; propIndex < props.Length; propIndex++) { if (props[propIndex - 1].Textures[textureIndex].height != props[propIndex].Textures[textureIndex].height) { throw new System.Exception( props[propIndex].TypeIndex.ToString() + " Mismatching Resolutions: " + ((TextureType)textureIndex).ToString() + " " + props[propIndex - 1].Textures[textureIndex].height + " (ID: " + GetTextureIDForType(materialStates[propIndex - 1], (TextureType)textureIndex) + ") vs " + props[propIndex].Textures[textureIndex].height + " (ID: " + GetTextureIDForType(materialStates[propIndex], (TextureType)textureIndex) + ") Ensure you are using ASTC texture compression on Android or turn off CombineMeshes"); } if (props[propIndex - 1].Textures[textureIndex].format != props[propIndex].Textures[textureIndex].format) { throw new System.Exception( props[propIndex].TypeIndex.ToString() + " Mismatching Formats: " + ((TextureType)textureIndex).ToString() + " " + props[propIndex - 1].Textures[textureIndex].format + " (ID: " + GetTextureIDForType(materialStates[propIndex - 1], (TextureType)textureIndex) + ") vs " + props[propIndex].Textures[textureIndex].format + " (ID: " + GetTextureIDForType(materialStates[propIndex], (TextureType)textureIndex) + ") Ensure you are using ASTC texture compression on Android or turn off CombineMeshes"); } } } } // Loading animation on the Dimmer properyt // Smooth sine lerp every 0.3 seconds between 0.25 and 0.5 private IEnumerator RunLoadingAnimation(Action callBack) { // Set the material to single component while the avatar loads CombinedShader = TargetRenderer.sharedMaterial.shader; // Save shader properties int srcBlend = TargetRenderer.sharedMaterial.GetInt("_SrcBlend"); int dstBlend = TargetRenderer.sharedMaterial.GetInt("_DstBlend"); string lightModeTag = TargetRenderer.sharedMaterial.GetTag("LightMode", false); string renderTypeTag = TargetRenderer.sharedMaterial.GetTag("RenderType", false); string renderQueueTag = TargetRenderer.sharedMaterial.GetTag("Queue", false); string ignoreProjectorTag = TargetRenderer.sharedMaterial.GetTag("IgnoreProjector", false); int renderQueue = TargetRenderer.sharedMaterial.renderQueue; bool transparentQueue = TargetRenderer.sharedMaterial.IsKeywordEnabled("_ALPHATEST_ON"); // Swap in loading shader TargetRenderer.sharedMaterial.shader = Shader.Find(AVATAR_SHADER_LOADER); TargetRenderer.sharedMaterial.SetColor(AVATAR_SHADER_COLOR, Color.white); while (OvrAvatarSDKManager.Instance.GetTextureCopyManager().GetTextureCount() > 0) { float distance = (LOADING_ANIMATION_AMPLITUDE * Mathf.Sin(Time.timeSinceLevelLoad / LOADING_ANIMATION_PERIOD) + LOADING_ANIMATION_AMPLITUDE) * (LOADING_ANIMATION_CURVE_SCALE) + LOADING_ANIMATION_DIMMER_MIN; TargetRenderer.sharedMaterial.SetFloat(AVATAR_SHADER_LOADING_DIMMER, distance); yield return null; } // Swap back main shader TargetRenderer.sharedMaterial.SetFloat(AVATAR_SHADER_LOADING_DIMMER, 1f); TargetRenderer.sharedMaterial.shader = CombinedShader; // Restore shader properties TargetRenderer.sharedMaterial.SetInt("_SrcBlend", srcBlend); TargetRenderer.sharedMaterial.SetInt("_DstBlend", dstBlend); TargetRenderer.sharedMaterial.SetOverrideTag("LightMode", lightModeTag); TargetRenderer.sharedMaterial.SetOverrideTag("RenderType", renderTypeTag); TargetRenderer.sharedMaterial.SetOverrideTag("Queue", renderQueueTag); TargetRenderer.sharedMaterial.SetOverrideTag("IgnoreProjector", ignoreProjectorTag); if (transparentQueue) { TargetRenderer.sharedMaterial.EnableKeyword("_ALPHATEST_ON"); TargetRenderer.sharedMaterial.EnableKeyword("_ALPHABLEND_ON"); TargetRenderer.sharedMaterial.EnableKeyword("_ALPHAPREMULTIPLY_ON"); } else { TargetRenderer.sharedMaterial.DisableKeyword("_ALPHATEST_ON"); TargetRenderer.sharedMaterial.DisableKeyword("_ALPHABLEND_ON"); TargetRenderer.sharedMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON"); } TargetRenderer.sharedMaterial.renderQueue = renderQueue; ApplyMaterialPropertyBlock(); if (callBack != null) { callBack(); } } }