using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Adobe.Substance { public static class MaterialUtils { /// /// Default shader for the HDRP pipeline. /// private const string HDRPShaderName = "HDRP/Lit"; /// /// Default shader for the URP pipeline. /// private const string URPShaderName = "Universal Render Pipeline/Lit"; /// /// Default shader for the Standard unity render pipeline. /// private const string StandardShaderName = "Standard"; /// /// Table for converting substance output names to textures inputs in HDRP shaders. /// private static readonly IReadOnlyDictionary HDRPOutputTable = new Dictionary() { { "basecolor", "_BaseColorMap"}, { "diffuse", "_BaseColorMap" }, { "mask", "_MaskMap" }, { "normal", "_NormalMap" }, { "height", "_HeightMap" }, { "emissive", "_EmissiveColorMap" }, { "specular", "_SpecularColorMap" }, { "detailMask", "_DetailMask" }, { "opacity", "_OpacityMap" }, { "glossiness", "_GlossinessMap" }, { "ambientocclusion", "_OcclusionMap" }, { "metallic", "_MetallicGlossMap" }, { "roughness", "_RoughnessMap" } }; /// /// Table for converting substance output names to textures inputs in URP shaders. /// private static readonly IReadOnlyDictionary URPOutputTable = new Dictionary() { { "basecolor" , "_BaseMap" }, { "diffuse", "_BaseMap" }, { "normal" , "_BumpMap" }, { "height" ,"_ParallaxMap" }, { "emissive" , "_EmissionMap" }, { "specular" , "_SpecGlossMap" }, { "ambientocclusion" , "_OcclusionMap" }, { "metallic" , "_MetallicGlossMap" }, { "mask" , "_MaskMap" }, { "detailMask" , "_DetailMask" }, { "opacity" ,"_OpacityMap" }, { "glossiness" ,"_GlossinessMap" }, { "roughness" ,"_RoughnessMap" } }; /// /// Table for converting substance output names to textures inputs in unity Standard pipeline shaders. /// private static readonly IReadOnlyDictionary StandardOutputTable = new Dictionary() { { "basecolor", "_MainTex" }, { "diffuse", "_MainTex" }, { "normal" , "_BumpMap" }, { "height" ,"_ParallaxMap" }, { "emissive" ,"_EmissionMap" }, { "specular" ,"_SpecGlossMap" }, { "specularlevel" ,"_SpecularLevelMap" }, { "opacity", "_OpacityMap" }, { "glossiness" ,"_GlossinessMap" }, { "ambientocclusion" ,"_OcclusionMap" }, { "detailmask" ,"_DetailMask" }, { "metallic" ,"_MetallicGlossMap" }, { "roughness" ,"_RoughnessMap" } }; private static IReadOnlyDictionary GetTextureMappingDictionary() { if (PluginPipelines.IsHDRP()) return HDRPOutputTable; if (PluginPipelines.IsURP()) return URPOutputTable; // for now return StandardOutputTable; } public static void AssignOutputTexturesToMaterial(SubstanceGraphSO graph) { foreach (var output in graph.Output) { if (output.OutputTexture == null) continue; Texture2D texture = output.OutputTexture; var shaderTextureName = output.MaterialTextureTarget; EnableShaderKeywords(graph.OutputMaterial, shaderTextureName); graph.OutputMaterial.SetTexture(shaderTextureName, texture); } var smoothnessChannel = GetSmoothnessChannelAssignment(graph); graph.OutputMaterial.SetInt("_SmoothnessTextureChannel", smoothnessChannel); graph.OutputMaterial.SetFloat("_Glossiness", 1.0f); graph.OutputMaterial.SetFloat("_Smoothness", 1.0f); graph.OutputMaterial.SetFloat("_OcclusionStrength", 1.0f); var opacityOutput = graph.Output.FirstOrDefault(a => a.IsOpacity()); } public static string GetUnityTextureName(SubstanceOutputDescription description) { var dictionary = GetTextureMappingDictionary(); if (dictionary.TryGetValue(description.Channel.ToLower(), out string result)) return result; return string.Empty; } private static void EnableShaderKeywords(Material material, string shaderTextureName) { if (shaderTextureName == "_BumpMap") { material.EnableKeyword("_NORMALMAP"); } else if (shaderTextureName == "_EmissionMap") { // Enables emission for the material, and make the material use // realtime emission. material.EnableKeyword("_EMISSION"); material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive; // Update the emission color and intensity of the material. material.SetColor("_EmissionColor", Color.white); // Inform Unity's GI system to recalculate GI based on the new emission map. DynamicGI.UpdateEnvironment(); } else if (shaderTextureName == "_ParallaxMap") { material.EnableKeyword("_PARALLAXMAP"); } else if (shaderTextureName == "_MetallicGlossMap") { if (PluginPipelines.IsURP()) material.EnableKeyword("_METALLICSPECGLOSSMAP"); else material.EnableKeyword("_METALLICGLOSSMAP"); } } /// /// Returns 1 if smoothness is assigned to _MainTex alpha channel and 0 if it is assigned to metallic map alpha channel. /// /// Target substance graph. /// 0 or 1 depending on the smoothness channel assignment. private static int GetSmoothnessChannelAssignment(SubstanceGraphSO graph) { var baseColorOutput = graph.Output.FirstOrDefault(a => a.IsBaseColor()); //Check if smoothness is assigned to baseColor. if (baseColorOutput != null) if (baseColorOutput.AlphaChannel == "roughness" || baseColorOutput.AlphaChannel == "glossiness") return 1; //Check if smoothness is assigned to diffuse. var diffuseOutput = graph.Output.FirstOrDefault(a => a.IsDiffuse()); if (diffuseOutput != null) if (diffuseOutput.AlphaChannel == "roughness" || diffuseOutput.AlphaChannel == "glossiness") return 1; //Assumes it is assinged to metallic map. return 0; } public static bool CheckIfStandardOutput(SubstanceOutputDescription description) { if (PluginPipelines.IsHDRP()) { return CheckIfHRPStandardOutput(description); } else if (PluginPipelines.IsURP()) { return CheckIfURPStandardOutput(description); } //Unity Standard render pipeline. return CheckIfStandardPipelineOutput(description); } private static bool CheckIfURPStandardOutput(SubstanceOutputDescription description) { if (description == null) return false; if (string.IsNullOrEmpty(description.Channel)) return false; if (string.Equals(description.Channel, "baseColor", StringComparison.OrdinalIgnoreCase) || string.Equals(description.Channel, "diffuse", StringComparison.OrdinalIgnoreCase)) { return true; } if (!URPOutputTable.TryGetValue(description.Channel, out string shaderValue)) return false; var material = new Material(GetStandardShader()); return material.HasProperty(shaderValue); } private static bool CheckIfHRPStandardOutput(SubstanceOutputDescription description) { if (description == null) return false; if (string.IsNullOrEmpty(description.Channel)) return false; switch (description.Channel) { case "baseColor": return true; case "normal": return true; case "mask": return true; case "height": return true; case "emissive": return true; default: return false; } } private static bool CheckIfStandardPipelineOutput(SubstanceOutputDescription description) { if (description == null) return false; if (string.IsNullOrEmpty(description.Channel)) return false; var channel = description.Channel.ToLower(); if ("basecolor" == channel) return true; if (!StandardOutputTable.TryGetValue(channel, out string shaderValue)) return false; var material = new Material(GetStandardShader()); return material.HasProperty(shaderValue); } public static Shader GetStandardShader() { if (PluginPipelines.IsHDRP()) return Shader.Find(HDRPShaderName); else if (PluginPipelines.IsURP()) return Shader.Find(URPShaderName); return Shader.Find(StandardShaderName); } public static bool CheckIfBGRA(SubstanceOutputDescription description) { if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer) { return false; } if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer) { return false; } switch (description.Channel) { case "baseColor": case "diffuse": case "emissive": case "normal": return true; default: return false; } } #region PhysicalSize public static void ApplyPhysicalSize(Material material, Vector3 physicalSize, bool enablePhysicalSize) { if (PluginPipelines.IsHDRP()) ApplyPhysicalSizeHDRP(material, physicalSize, enablePhysicalSize); } public static Vector3 GetPhysicalSizePositionOffset(Material material) { if (PluginPipelines.IsHDRP()) return GetPhysicalSizePositionOffsetHDRP(material); return new Vector3(0, 0, 0); } public static void SetPhysicalSizePositionOffset(Material material, Vector3 offset) { if (PluginPipelines.IsHDRP()) SetPhysicalSizePositionOffsetHDRP(material, offset); } private static void ApplyPhysicalSizeHDRP(Material material, Vector3 physicalSize, bool enablePhysicalSize) { if (enablePhysicalSize) { material.SetFloat("_UVBase", 5); material.SetFloat("_UVEmissive", 5); material.SetFloat("_TexWorldScale", 100); material.EnableKeyword("_MAPPING_PLANAR"); material.EnableKeyword("_EMISSIVE_MAPPING_TRIPLANAR"); material.mainTextureScale = new Vector2(1f / physicalSize.x, 1f / physicalSize.y); } else { material.SetFloat("_UVBase", 0); material.SetFloat("_UVEmissive", 0); material.SetFloat("_TexWorldScale", 1); material.DisableKeyword("_MAPPING_PLANAR"); material.DisableKeyword("_EMISSIVE_MAPPING_TRIPLANAR"); material.mainTextureScale = new Vector2(1, 1); } } public static Vector3 GetPhysicalSizePositionOffsetHDRP(Material material) { return material.GetTextureOffset("_BaseColorMap"); } public static void SetPhysicalSizePositionOffsetHDRP(Material material, Vector3 offset) { material.SetTextureOffset("_BaseColorMap", offset); material.SetTextureOffset("_EmissiveColorMap", offset); } #endregion PhysicalSize #region Live output assignment public static void UpdateTextureTarget(Material material, Texture2D texture, string oldstring, string newString) { material.SetTexture(oldstring, null); if (!string.IsNullOrEmpty(newString)) material.SetTexture(newString, texture); } public static void EnableEmissionIfAssigned(Material material) { var emissionTextureName = GetTextureMappingDictionary()["emissive"]; if (material.GetTexture(emissionTextureName) != null) { material.EnableKeyword("_EMISSION"); material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive; material.SetColor("_EmissionColor", Color.white); } else { material.DisableKeyword("_EMISSION"); material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.EmissiveIsBlack; material.SetColor("_EmissionColor", Color.black); } } #endregion Live output assignment } }