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<ReflectionProbeBlendInfo> ReflectionProbes = new List<ReflectionProbeBlendInfo>();
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
}
|