using System; using UnityEngine; using System.Linq; using UnityEngine.Rendering; // Include material common properties names using static UnityEngine.Rendering.HighDefinition.HDMaterialProperties; namespace UnityEditor.Rendering.HighDefinition { // Needed for json serialization to work [Serializable] internal struct SerializeableGUIDs { public string[] GUIDArray; public bool[] withUV; } /// /// GUI for HDRP Layered Lit materials (and tesselation), does not include shader graph + function to setup material keywords for Lit /// class LayeredLitGUI : HDShaderGUI { const LitSurfaceInputsUIBlock.Features commonLitSurfaceInputsFeatures = LitSurfaceInputsUIBlock.Features.LayerOptions; // Layered lit shaders don't support emission (realtime or baked) const EmissionUIBlock.Features emissionFeatures = EmissionUIBlock.Features.All ^ EmissionUIBlock.Features.EnableEmissionForGI; MaterialUIBlockList uiBlocks = new MaterialUIBlockList { new SurfaceOptionUIBlock(MaterialUIBlock.ExpandableBit.Base, 4, SurfaceOptionUIBlock.Features.Lit), new TessellationOptionsUIBlock(MaterialUIBlock.ExpandableBit.Tessellation), new LitSurfaceInputsUIBlock(MaterialUIBlock.ExpandableBit.Input, kMaxLayerCount, features: commonLitSurfaceInputsFeatures), new LayerListUIBlock(MaterialUIBlock.ExpandableBit.MaterialReferences), new LayersUIBlock(), new EmissionUIBlock(MaterialUIBlock.ExpandableBit.Emissive, features: emissionFeatures), new AdvancedOptionsUIBlock(MaterialUIBlock.ExpandableBit.Advance), }; protected override void OnMaterialGUI(MaterialEditor materialEditor, MaterialProperty[] props) { using (var changed = new EditorGUI.ChangeCheckScope()) { uiBlocks.OnGUI(materialEditor, props); ApplyKeywordsAndPassesIfNeeded(changed.changed, uiBlocks.materials); } } // Material property name for Layered Lit keyword setup const string kLayerCount = "_LayerCount"; const string kLayerInfluenceMaskMap = "_LayerInfluenceMaskMap"; const string kVertexColorMode = "_VertexColorMode"; const string kUVBlendMask = "_UVBlendMask"; const string kkUseMainLayerInfluence = "_UseMainLayerInfluence"; const string kUseHeightBasedBlend = "_UseHeightBasedBlend"; const string kObjectScaleAffectTile = "_ObjectScaleAffectTile"; // Density/opacity mode const string kOpacityAsDensity = "_OpacityAsDensity"; const string kMaskMap = "_MaskMap"; const string kNormalMap = "_NormalMap"; const string kNormalMapOS = "_NormalMapOS"; const string kBentNormalMap = "_BentNormalMap"; const string kBentNormalMapOS = "_BentNormalMapOS"; const string kNormalMapSpace = "_NormalMapSpace"; const string kHeightMap = "_HeightMap"; const string kSubsurfaceMaskMap = "_SubsurfaceMaskMap"; const string kThicknessMap = "_ThicknessMap"; const string kUVDetail = "_UVDetail"; const string kDetailMap = "_DetailMap"; const string kEmissiveColorMap = "_EmissiveColorMap"; const string kUVEmissive = "_UVEmissive"; const string kSpecularOcclusionMode = "_SpecularOcclusionMode"; protected override void SetupMaterialKeywordsAndPass(Material material) => SetupLayeredLitKeywordsAndPass(material); static public void SetupLayersMappingKeywords(Material material) { // object scale affect tile CoreUtils.SetKeyword(material, "_LAYER_TILING_COUPLED_WITH_UNIFORM_OBJECT_SCALE", material.GetFloat(kObjectScaleAffectTile) > 0.0f); // Blend mask UVBaseMapping UVBlendMaskMapping = (UVBaseMapping)material.GetFloat(kUVBlendMask); CoreUtils.SetKeyword(material, "_LAYER_MAPPING_PLANAR_BLENDMASK", UVBlendMaskMapping == UVBaseMapping.Planar); CoreUtils.SetKeyword(material, "_LAYER_MAPPING_TRIPLANAR_BLENDMASK", UVBlendMaskMapping == UVBaseMapping.Triplanar); int numLayer = (int)material.GetFloat(kLayerCount); // Layer if (numLayer == 4) { CoreUtils.SetKeyword(material, "_LAYEREDLIT_4_LAYERS", true); CoreUtils.SetKeyword(material, "_LAYEREDLIT_3_LAYERS", false); } else if (numLayer == 3) { CoreUtils.SetKeyword(material, "_LAYEREDLIT_4_LAYERS", false); CoreUtils.SetKeyword(material, "_LAYEREDLIT_3_LAYERS", true); } else { CoreUtils.SetKeyword(material, "_LAYEREDLIT_4_LAYERS", false); CoreUtils.SetKeyword(material, "_LAYEREDLIT_3_LAYERS", false); } const string kLayerMappingPlanar = "_LAYER_MAPPING_PLANAR"; const string kLayerMappingTriplanar = "_LAYER_MAPPING_TRIPLANAR"; // We have to check for each layer if the UV2 or UV3 is needed. bool needUV3 = false; bool needUV2 = false; for (int i = 0; i < numLayer; ++i) { string layerUVBaseParam = string.Format("{0}{1}", kUVBase, i); UVBaseMapping layerUVBaseMapping = (UVBaseMapping)material.GetFloat(layerUVBaseParam); string currentLayerMappingPlanar = string.Format("{0}{1}", kLayerMappingPlanar, i); CoreUtils.SetKeyword(material, currentLayerMappingPlanar, layerUVBaseMapping == UVBaseMapping.Planar); string currentLayerMappingTriplanar = string.Format("{0}{1}", kLayerMappingTriplanar, i); CoreUtils.SetKeyword(material, currentLayerMappingTriplanar, layerUVBaseMapping == UVBaseMapping.Triplanar); string uvBase = string.Format("{0}{1}", kUVBase, i); string uvDetail = string.Format("{0}{1}", kUVDetail, i); if (((UVDetailMapping)material.GetFloat(uvDetail) == UVDetailMapping.UV2) || ((UVBaseMapping)material.GetFloat(uvBase) == UVBaseMapping.UV2)) { needUV2 = true; } if (((UVDetailMapping)material.GetFloat(uvDetail) == UVDetailMapping.UV3) || ((UVBaseMapping)material.GetFloat(uvBase) == UVBaseMapping.UV3)) { needUV3 = true; break; // If we find it UV3 let's early out } } if (needUV3) { material.DisableKeyword("_REQUIRE_UV2"); material.EnableKeyword("_REQUIRE_UV3"); } else if (needUV2) { material.EnableKeyword("_REQUIRE_UV2"); material.DisableKeyword("_REQUIRE_UV3"); } else { material.DisableKeyword("_REQUIRE_UV2"); material.DisableKeyword("_REQUIRE_UV3"); } } // All Setup Keyword functions must be static. It allow to create script to automatically update the shaders with a script if code change static public void SetupLayeredLitKeywordsAndPass(Material material) { BaseLitGUI.SetupBaseLitKeywords(material); BaseLitGUI.SetupBaseLitMaterialPass(material); SetupLayersMappingKeywords(material); bool receiveSSR = material.GetSurfaceType() == SurfaceType.Opaque ? (material.HasProperty(kReceivesSSR) ? material.GetInt(kReceivesSSR) != 0 : false) : (material.HasProperty(kReceivesSSRTransparent) ? material.GetInt(kReceivesSSRTransparent) != 0 : false); BaseLitGUI.SetupStencil(material, receiveSSR, material.GetMaterialId() == MaterialId.LitSSS); if (material.HasProperty(kAddPrecomputedVelocity)) { CoreUtils.SetKeyword(material, "_ADD_PRECOMPUTED_VELOCITY", material.GetInt(kAddPrecomputedVelocity) != 0); } for (int i = 0; i < kMaxLayerCount; ++i) { NormalMapSpace normalMapSpace = ((NormalMapSpace)material.GetFloat(kNormalMapSpace + i)); CoreUtils.SetKeyword(material, "_NORMALMAP_TANGENT_SPACE" + i, normalMapSpace == NormalMapSpace.TangentSpace); if (normalMapSpace == NormalMapSpace.TangentSpace) { CoreUtils.SetKeyword(material, "_NORMALMAP" + i, material.GetTexture(kNormalMap + i) || material.GetTexture(kDetailMap + i)); CoreUtils.SetKeyword(material, "_BENTNORMALMAP" + i, material.GetTexture(kBentNormalMap + i)); } else { CoreUtils.SetKeyword(material, "_NORMALMAP" + i, material.GetTexture(kNormalMapOS + i) || material.GetTexture(kDetailMap + i)); CoreUtils.SetKeyword(material, "_BENTNORMALMAP" + i, material.GetTexture(kBentNormalMapOS + i)); } CoreUtils.SetKeyword(material, "_MASKMAP" + i, material.GetTexture(kMaskMap + i)); CoreUtils.SetKeyword(material, "_DETAIL_MAP" + i, material.GetTexture(kDetailMap + i)); CoreUtils.SetKeyword(material, "_HEIGHTMAP" + i, material.GetTexture(kHeightMap + i)); CoreUtils.SetKeyword(material, "_SUBSURFACE_MASK_MAP" + i, material.GetTexture(kSubsurfaceMaskMap + i)); CoreUtils.SetKeyword(material, "_THICKNESSMAP" + i, material.GetTexture(kThicknessMap + i)); } CoreUtils.SetKeyword(material, "_INFLUENCEMASK_MAP", material.GetTexture(kLayerInfluenceMaskMap) && material.GetFloat(kkUseMainLayerInfluence) != 0.0f); CoreUtils.SetKeyword(material, "_EMISSIVE_MAPPING_PLANAR", ((UVEmissiveMapping)material.GetFloat(kUVEmissive)) == UVEmissiveMapping.Planar && material.GetTexture(kEmissiveColorMap)); CoreUtils.SetKeyword(material, "_EMISSIVE_MAPPING_TRIPLANAR", ((UVEmissiveMapping)material.GetFloat(kUVEmissive)) == UVEmissiveMapping.Triplanar && material.GetTexture(kEmissiveColorMap)); CoreUtils.SetKeyword(material, "_EMISSIVE_MAPPING_BASE", ((UVEmissiveMapping)material.GetFloat(kUVEmissive)) == UVEmissiveMapping.SameAsBase && material.GetTexture(kEmissiveColorMap)); CoreUtils.SetKeyword(material, "_EMISSIVE_COLOR_MAP", material.GetTexture(kEmissiveColorMap)); // For migration of specular occlusion to specular mode we remove previous keyword // _ENABLESPECULAROCCLUSION is deprecated CoreUtils.SetKeyword(material, "_ENABLESPECULAROCCLUSION", false); int specOcclusionMode = material.GetInt(kSpecularOcclusionMode); CoreUtils.SetKeyword(material, "_SPECULAR_OCCLUSION_NONE", specOcclusionMode == 0); CoreUtils.SetKeyword(material, "_SPECULAR_OCCLUSION_FROM_BENT_NORMAL_MAP", specOcclusionMode == 2); CoreUtils.SetKeyword(material, "_MAIN_LAYER_INFLUENCE_MODE", material.GetFloat(kkUseMainLayerInfluence) != 0.0f); VertexColorMode VCMode = (VertexColorMode)material.GetFloat(kVertexColorMode); if (VCMode == VertexColorMode.Multiply) { CoreUtils.SetKeyword(material, "_LAYER_MASK_VERTEX_COLOR_MUL", true); CoreUtils.SetKeyword(material, "_LAYER_MASK_VERTEX_COLOR_ADD", false); } else if (VCMode == VertexColorMode.Add) { CoreUtils.SetKeyword(material, "_LAYER_MASK_VERTEX_COLOR_MUL", false); CoreUtils.SetKeyword(material, "_LAYER_MASK_VERTEX_COLOR_ADD", true); } else { CoreUtils.SetKeyword(material, "_LAYER_MASK_VERTEX_COLOR_MUL", false); CoreUtils.SetKeyword(material, "_LAYER_MASK_VERTEX_COLOR_ADD", false); } bool useHeightBasedBlend = material.GetFloat(kUseHeightBasedBlend) != 0.0f; CoreUtils.SetKeyword(material, "_HEIGHT_BASED_BLEND", useHeightBasedBlend); bool useDensityModeEnable = false; for (int i = 0; i < material.GetInt(kLayerCount); ++i) { useDensityModeEnable |= material.GetFloat(kOpacityAsDensity + i) != 0.0f; } CoreUtils.SetKeyword(material, "_DENSITY_MODE", useDensityModeEnable); MaterialId materialId = material.GetMaterialId(); CoreUtils.SetKeyword(material, "_MATERIAL_FEATURE_SUBSURFACE_SCATTERING", materialId == MaterialId.LitSSS); CoreUtils.SetKeyword(material, "_MATERIAL_FEATURE_TRANSMISSION", materialId == MaterialId.LitTranslucent || (materialId == MaterialId.LitSSS && material.GetFloat(kTransmissionEnable) > 0.0f)); } // This function is call by a script to help artists to have up to date material // that why it is static public static void SynchronizeAllLayers(Material material) { int layerCount = (int)material.GetFloat(kLayerCount); AssetImporter materialImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(material.GetInstanceID())); Material[] layers = null; bool[] withUV = null; // Material importer can be null when the selected material doesn't exists as asset (Material saved inside the scene) if (materialImporter != null) InitializeMaterialLayers(material, ref layers, ref withUV); // We could have no userData in the assets, so test if we have load something if (layers != null && withUV != null) { for (int i = 0; i < layerCount; ++i) { SynchronizeLayerProperties(material, i, layers[i], withUV[i]); } } } // This function will look for all referenced lit material, and assign value from Lit to layered lit layers. // This is based on the naming of the variables, i.E BaseColor will match BaseColor0, if a properties shouldn't be override // put the name in the exclusionList below public static void SynchronizeLayerProperties(Material material, int layerIndex, Material layerMaterial, bool includeUVMappingProperties) { string[] exclusionList = { kTexWorldScale, kUVBase, kUVMappingMask, kUVDetail, kUVDetailsMappingMask }; if (layerMaterial != null) { Shader layerShader = layerMaterial.shader; int propertyCount = ShaderUtil.GetPropertyCount(layerShader); for (int i = 0; i < propertyCount; ++i) { string propertyName = ShaderUtil.GetPropertyName(layerShader, i); string layerPropertyName = propertyName + layerIndex; if (includeUVMappingProperties || !exclusionList.Contains(propertyName)) { if (material.HasProperty(layerPropertyName)) { ShaderUtil.ShaderPropertyType type = ShaderUtil.GetPropertyType(layerShader, i); switch (type) { case ShaderUtil.ShaderPropertyType.Color: { material.SetColor(layerPropertyName, layerMaterial.GetColor(propertyName)); break; } case ShaderUtil.ShaderPropertyType.Float: case ShaderUtil.ShaderPropertyType.Range: { material.SetFloat(layerPropertyName, layerMaterial.GetFloat(propertyName)); break; } case ShaderUtil.ShaderPropertyType.Vector: { material.SetVector(layerPropertyName, layerMaterial.GetVector(propertyName)); break; } case ShaderUtil.ShaderPropertyType.TexEnv: { material.SetTexture(layerPropertyName, layerMaterial.GetTexture(propertyName)); if (includeUVMappingProperties) { material.SetTextureOffset(layerPropertyName, layerMaterial.GetTextureOffset(propertyName)); material.SetTextureScale(layerPropertyName, layerMaterial.GetTextureScale(propertyName)); } break; } } } } } } } // We use the user data to save a string that represent the referenced lit material // so we can keep reference during serialization public static void InitializeMaterialLayers(Material material, ref Material[] layers, ref bool[] withUV) { AssetImporter materialImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(material.GetInstanceID())); InitializeMaterialLayers(materialImporter, ref layers, ref withUV); } public static void InitializeMaterialLayers(AssetImporter materialImporter, ref Material[] layers, ref bool[] withUV) { if (materialImporter.userData != string.Empty) { SerializeableGUIDs layersGUID = JsonUtility.FromJson(materialImporter.userData); if (layersGUID.GUIDArray.Length > 0) { layers = new Material[layersGUID.GUIDArray.Length]; for (int i = 0; i < layersGUID.GUIDArray.Length; ++i) { layers[i] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(layersGUID.GUIDArray[i]), typeof(Material)) as Material; } } if (layersGUID.withUV != null && layersGUID.withUV.Length > 0) { withUV = new bool[layersGUID.withUV.Length]; for (int i = 0; i < layersGUID.withUV.Length; ++i) withUV[i] = layersGUID.withUV[i]; } } else { if (layers != null) { for (int i = 0; i < layers.Length; ++i) layers[i] = null; } if (withUV != null) { for (int i = 0; i < withUV.Length; ++i) withUV[i] = true; } } } public static void SaveMaterialLayers(Material material, Material[] materialLayers, bool[] withUV) { AssetImporter materialImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(material.GetInstanceID())); SerializeableGUIDs layersGUID; layersGUID.GUIDArray = new string[materialLayers.Length]; layersGUID.withUV = new bool[withUV.Length]; for (int i = 0; i < materialLayers.Length; ++i) { if (materialLayers[i] != null) layersGUID.GUIDArray[i] = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(materialLayers[i].GetInstanceID())); layersGUID.withUV[i] = withUV[i]; } materialImporter.userData = JsonUtility.ToJson(layersGUID); } } } // namespace UnityEditor