using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEditor.Rendering.HighDefinition;
using static UnityEngine.Rendering.HighDefinition.HDRenderQueue;
using static UnityEngine.Rendering.HighDefinition.HDMaterialProperties;
namespace UnityEngine.Rendering.HighDefinition
{
// Note: There is another SurfaceType in ShaderGraph (AlphaMode.cs) which conflicts in HDRP shader graph files
enum SurfaceType
{
Opaque,
Transparent
}
enum DisplacementMode
{
None,
Vertex,
Pixel,
Tessellation
}
enum DoubleSidedNormalMode
{
Flip,
Mirror,
None
}
enum DoubleSidedGIMode
{
Auto,
On,
Off
}
enum TessellationMode
{
None,
Phong
}
enum NormalMapSpace
{
TangentSpace,
ObjectSpace,
}
enum HeightmapMode
{
Parallax,
Displacement,
}
enum VertexColorMode
{
None,
Multiply,
Add
}
internal enum UVDetailMapping
{
UV0,
UV1,
UV2,
UV3
}
internal enum UVBaseMapping
{
UV0,
UV1,
UV2,
UV3,
Planar,
Triplanar
}
internal enum UVEmissiveMapping
{
UV0,
UV1,
UV2,
UV3,
Planar,
Triplanar,
SameAsBase
}
internal enum HeightmapParametrization
{
MinMax = 0,
Amplitude = 1
}
internal enum TransparentCullMode
{
// Off is double sided and a different setting so we don't have it here
Back = CullMode.Back,
Front = CullMode.Front,
}
internal enum OpaqueCullMode
{
// Off is double sided and a different setting so we don't have it here
Back = CullMode.Back,
Front = CullMode.Front,
}
/// This enum describes the different "Material Types" supported by the HDRP Lit shader and Lit ShaderGraph.
public enum MaterialId
{
/// Sub-surface scattering.
LitSSS = 0,
/// Standard. This is the default mode.
LitStandard = 1,
/// Anisotropic.
LitAniso = 2,
/// Iridescence.
LitIridescence = 3,
/// Specular Color.
LitSpecular = 4,
/// Translucent
LitTranslucent = 5,
/// Colored Translucent
LitColoredTranslucent = 6,
}
/// Emissive Intensity Unit
public enum EmissiveIntensityUnit
{
/// Nits
Nits,
/// EV100
EV100,
}
internal static class MaterialExtension
{
public static SurfaceType GetSurfaceType(this Material material)
=> material.HasProperty(kSurfaceType) ? (SurfaceType)material.GetFloat(kSurfaceType) : SurfaceType.Opaque;
public static BlendingMode GetBlendMode(this Material material)
=> material.HasProperty(kBlendMode) ? (BlendingMode)material.GetFloat(kBlendMode) : BlendingMode.Additive;
public static int GetLayerCount(this Material material)
=> material.HasProperty(kLayerCount) ? material.GetInt(kLayerCount) : 1;
public static bool GetZWrite(this Material material)
=> material.HasProperty(kZWrite) ? material.GetInt(kZWrite) == 1 : false;
public static bool GetTransparentZWrite(this Material material)
=> material.HasProperty(kTransparentZWrite) ? material.GetInt(kTransparentZWrite) == 1 : false;
public static CullMode GetTransparentCullMode(this Material material)
=> material.HasProperty(kTransparentCullMode) ? (CullMode)material.GetInt(kTransparentCullMode) : CullMode.Back;
public static CullMode GetOpaqueCullMode(this Material material)
=> material.HasProperty(kOpaqueCullMode) ? (CullMode)material.GetInt(kOpaqueCullMode) : CullMode.Back;
public static CompareFunction GetTransparentZTest(this Material material)
=> material.HasProperty(kZTestTransparent) ? (CompareFunction)material.GetInt(kZTestTransparent) : CompareFunction.LessEqual;
public static bool GetAddPrecomputedVelocity(this Material material)
=> material.HasProperty(kAddPrecomputedVelocity) ? material.GetInt(kAddPrecomputedVelocity) == 1 : false;
public static bool ReceiveSSRTransparent(this Material material)
=> material.HasProperty(kReceivesSSRTransparent) ? material.GetFloat(kReceivesSSRTransparent) > 0.0f : false;
public static ScreenSpaceRefraction.RefractionModel GetRefractionModel(this Material material)
{
var canHaveRefraction = material.GetSurfaceType() == SurfaceType.Transparent && !k_RenderQueue_PreRefraction.Contains(material.renderQueue);
return canHaveRefraction && material.HasProperty(kRefractionModel) ? (ScreenSpaceRefraction.RefractionModel)material.GetFloat(kRefractionModel) : ScreenSpaceRefraction.RefractionModel.None;
}
public static void ResetMaterialCustomRenderQueue(this Material material)
{
// using GetOpaqueEquivalent / GetTransparentEquivalent allow to handle the case when we switch surfaceType
HDRenderQueue.RenderQueueType targetQueueType;
switch (material.GetSurfaceType())
{
case SurfaceType.Opaque:
targetQueueType = HDRenderQueue.GetOpaqueEquivalent(HDRenderQueue.GetTypeByRenderQueueValue(material.renderQueue));
break;
case SurfaceType.Transparent:
targetQueueType = HDRenderQueue.GetTransparentEquivalent(HDRenderQueue.GetTypeByRenderQueueValue(material.renderQueue));
break;
default:
throw new ArgumentException("Unknown SurfaceType");
}
float sortingPriority = material.HasProperty(kTransparentSortPriority) ? material.GetFloat(kTransparentSortPriority) : 0.0f;
bool alphaTest = material.HasProperty(kAlphaCutoffEnabled) && material.GetFloat(kAlphaCutoffEnabled) > 0.0f;
bool decalEnable = material.HasProperty(kEnableDecals) && material.GetFloat(kEnableDecals) > 0.0f;
#if UNITY_EDITOR
// Since we are gonna override the renderQueue value, we revert it before to avoid keeping an out of date property override
// Render queue value should not be accessed between the next two lines
HDMaterial.RevertRenderQueueOverride(material);
#endif
material.renderQueue = HDRenderQueue.ChangeType(targetQueueType, (int)sortingPriority, alphaTest, decalEnable);
}
public static void UpdateEmissiveColorFromIntensityAndEmissiveColorLDR(this Material material)
{
const string kEmissiveColorLDR = "_EmissiveColorLDR";
const string kEmissiveColor = "_EmissiveColor";
const string kEmissiveIntensity = "_EmissiveIntensity";
if (material.HasProperty(kEmissiveColorLDR) && material.HasProperty(kEmissiveIntensity) && material.HasProperty(kEmissiveColor))
{
// Important: The color picker for kEmissiveColorLDR is LDR and in sRGB color space but Unity don't perform any color space conversion in the color
// picker BUT only when sending the color data to the shader... So as we are doing our own calculation here in C#, we must do the conversion ourselves.
Color emissiveColorLDR = material.GetColor(kEmissiveColorLDR);
Color emissiveColorLDRLinear = new Color(Mathf.GammaToLinearSpace(emissiveColorLDR.r), Mathf.GammaToLinearSpace(emissiveColorLDR.g), Mathf.GammaToLinearSpace(emissiveColorLDR.b));
material.SetColor(kEmissiveColor, emissiveColorLDRLinear * material.GetFloat(kEmissiveIntensity));
}
}
public static DisplacementMode GetFilteredDisplacementMode(this Material material, DisplacementMode displacementMode)
{
if (material.HasProperty(kTessellationMode))
{
if (displacementMode == DisplacementMode.Pixel || displacementMode == DisplacementMode.Vertex)
return DisplacementMode.None;
}
else
{
if (displacementMode == DisplacementMode.Tessellation)
return DisplacementMode.None;
}
return displacementMode;
}
public static bool HasPass(this Material material, string pass)
{
for (int i = 0, passCount = material.passCount; i < passCount; ++i)
{
if (material.GetPassName(i).Equals(pass, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
}
///
/// Utility class for setting properties, keywords and passes on a material to ensure it is in a valid state for rendering with HDRP.
///
public static partial class HDMaterial
{
//enum representing all shader and shadergraph that we expose to user
internal enum ShaderID
{
Lit,
LitTesselation,
LayeredLit,
LayeredLitTesselation,
Unlit,
Decal,
TerrainLit,
AxF,
Count_Standard,
SG_Unlit = Count_Standard,
SG_Lit,
SG_Hair,
SG_Fabric,
SG_StackLit,
SG_Decal,
SG_Eye,
SG_Water,
SG_FogVolume,
SG_SixWay,
SG_PBRSky,
Count_All,
Count_ShaderGraph = Count_All - Count_Standard,
SG_External = -1, // material packaged outside of HDRP
}
// exposed shader, for reference while searching the ShaderID
internal static readonly string[] s_ShaderPaths =
{
"HDRP/Lit",
"HDRP/LitTessellation",
"HDRP/LayeredLit",
"HDRP/LayeredLitTessellation",
"HDRP/Unlit",
"HDRP/Decal",
"HDRP/TerrainLit",
"HDRP/AxF",
};
internal static readonly string[] s_SubTargetIds =
{
"HDUnlitSubTarget",
"HDLitSubTarget",
"HairSubTarget",
"FabricSubTarget",
"StackLitSubTarget",
"DecalSubTarget",
"EyeSubTarget",
"WaterSubTarget",
"FogVolumeSubTarget",
"HDSixWaySubTarget",
};
// list of methods for resetting keywords
internal delegate void MaterialResetter(Material material);
internal static Dictionary k_PlainShadersMaterialResetters = new Dictionary()
{
{ ShaderID.Lit, LitAPI.ValidateMaterial },
{ ShaderID.LitTesselation, LitAPI.ValidateMaterial },
{ ShaderID.LayeredLit, LayeredLitAPI.ValidateMaterial },
{ ShaderID.LayeredLitTesselation, LayeredLitAPI.ValidateMaterial },
{ ShaderID.Unlit, UnlitAPI.ValidateMaterial },
{ ShaderID.Decal, DecalAPI.ValidateMaterial },
{ ShaderID.TerrainLit, TerrainLitAPI.ValidateMaterial },
{ ShaderID.AxF, AxFAPI.ValidateMaterial },
{ ShaderID.SG_Unlit, ShaderGraphAPI.ValidateUnlitMaterial },
{ ShaderID.SG_Lit, ShaderGraphAPI.ValidateLightingMaterial },
{ ShaderID.SG_Hair, ShaderGraphAPI.ValidateLightingMaterial },
{ ShaderID.SG_Fabric, ShaderGraphAPI.ValidateLightingMaterial },
{ ShaderID.SG_StackLit, ShaderGraphAPI.ValidateLightingMaterial },
{ ShaderID.SG_Decal, ShaderGraphAPI.ValidateDecalMaterial },
{ ShaderID.SG_Eye, ShaderGraphAPI.ValidateLightingMaterial },
{ ShaderID.SG_FogVolume, ShaderGraphAPI.ValidateFogVolumeMaterial },
{ ShaderID.SG_SixWay, ShaderGraphAPI.ValidateSixWayMaterial },
};
internal static ShaderID GetShaderID(Material material)
{
if (!IsShaderGraph(material))
{
var shaderName = material.shader.name;
return (ShaderID)Array.FindIndex(s_ShaderPaths, m => m == shaderName);
}
var subTarget = material.GetTag("ShaderGraphTargetId", false, null);
int index = Array.FindIndex(s_SubTargetIds, m => m == subTarget);
return index == -1 ? ShaderID.SG_External : index + ShaderID.Count_Standard;
}
internal static void RemoveMaterialKeyword(Material material, ShaderID shaderID)
{
// To avoid keeping unused keywords when switching shader on a material, we want to clear the list
// But we can only do that on our standard shaders because ShaderGraphs may define their own keywords
if (0 <= (int)shaderID && shaderID < ShaderID.Count_Standard)
material.shaderKeywords = null;
}
///
/// Setup properties, keywords and passes on a material to ensure it is in a valid state for rendering with HDRP. This function is only for materials using HDRP Shaders or ShaderGraphs.
///
/// The target material.
/// False if the material doesn't have an HDRP Shader.
public static bool ValidateMaterial(Material material)
{
var shaderID = GetShaderID(material);
k_PlainShadersMaterialResetters.TryGetValue(shaderID, out var resetter);
if (resetter == null)
return false;
RemoveMaterialKeyword(material, shaderID);
resetter(material);
return true;
}
}
public static partial class HDMaterial
{
/// Rendering Pass
public enum RenderingPass
{
/// Before Refraction. Only for transparent materials
BeforeRefraction,
/// Default
Default,
/// After Post Process
AfterPostProcess,
/// Low Resolution. Only for transparent materials
LowResolution,
}
static RenderQueueType RenderingPassToQueue(RenderingPass pass, bool isTransparent)
{
switch (pass)
{
case RenderingPass.Default:
return isTransparent ? RenderQueueType.Transparent : RenderQueueType.Opaque;
case RenderingPass.AfterPostProcess:
return isTransparent ? RenderQueueType.AfterPostprocessTransparent : RenderQueueType.AfterPostProcessOpaque;
case RenderingPass.BeforeRefraction:
return isTransparent ? RenderQueueType.PreRefraction : RenderQueueType.Opaque;
case RenderingPass.LowResolution:
return isTransparent ? RenderQueueType.LowTransparent : RenderQueueType.Opaque;
default:
return RenderQueueType.Unknown;
}
}
/// Set the Surface Type of a HDRP material.
/// The material to change.
/// Controls if the material has an opaque or transparent Surface Type.
public static void SetSurfaceType(Material material, bool transparent)
{
var type = transparent ? SurfaceType.Transparent : SurfaceType.Opaque;
material.SetFloat(kSurfaceType, (float)type);
HDMaterial.ValidateMaterial(material);
}
/// Set the Rendering Pass on Lit, Unlit and Shadergraph shaders.
/// The material to change.
/// The rendering pass to set.
public static void SetRenderingPass(Material material, RenderingPass value)
{
bool isTransparent = (SurfaceType)material.GetFloat(kSurfaceType) == SurfaceType.Transparent;
var type = RenderingPassToQueue(value, isTransparent);
int sortingPriority = material.HasProperty(kTransparentSortPriority) ? (int)material.GetFloat(kTransparentSortPriority) : 0;
bool alphaClipping = material.HasProperty(kAlphaCutoffEnabled) && material.GetFloat(kAlphaCutoffEnabled) > 0.0f;
bool receiveDecals = material.HasProperty(kEnableDecals) && material.GetFloat(kEnableDecals) > 0.0f;
material.renderQueue = ChangeType(type, sortingPriority, alphaClipping, receiveDecals);
}
/// Set the Emissive Color on Lit, Unlit and Decal shaders.
/// The material to change.
/// The emissive color. In LDR if the material uses a separate emissive intensity value, in HDR otherwise.
public static void SetEmissiveColor(Material material, Color value)
{
if (material.GetFloat(kUseEmissiveIntensity) > 0.0f)
{
material.SetColor(kEmissiveColorLDR, value);
material.SetColor(kEmissiveColor, value.linear * material.GetFloat(kEmissiveIntensity));
}
else
{
if (material.HasProperty(kEmissiveColorHDR))
material.SetColor(kEmissiveColorHDR, value);
material.SetColor(kEmissiveColor, value);
}
}
/// Set to true to use a separate LDR color and intensity value for the emission color. Compatible with Lit, Unlit and Decal shaders.
/// The material to change.
/// True to use separate color and intensity values.
public static void SetUseEmissiveIntensity(Material material, bool value)
{
material.SetFloat(kUseEmissiveIntensity, value ? 1.0f : 0.0f);
if (value)
material.UpdateEmissiveColorFromIntensityAndEmissiveColorLDR();
else if (material.HasProperty(kEmissiveColorHDR))
material.SetColor(kEmissiveColor, material.GetColor(kEmissiveColorHDR));
}
/// Compares a material's color and intensity values to determine if they are different. Works with Lit, Unlit and Decal shaders.
/// The material to change.
/// True if the material uses different color and intensity values.
public static bool GetUseEmissiveIntensity(Material material)
{
return material.GetFloat(kUseEmissiveIntensity) > 0.0f;
}
/// Set the Emissive Intensity on Lit, Unlit and Decal shaders. If the material doesn't use emissive intensity, this won't have any effect.
/// The material to change.
/// The emissive intensity.
/// The unit of the intensity parameter.
public static void SetEmissiveIntensity(Material material, float intensity, EmissiveIntensityUnit unit)
{
if (unit == EmissiveIntensityUnit.EV100)
intensity = LightUnitUtils.Ev100ToNits(intensity);
material.SetFloat(kEmissiveIntensity, intensity);
material.SetFloat(kEmissiveIntensityUnit, (float)unit);
if (material.GetFloat(kUseEmissiveIntensity) > 0.0f)
material.SetColor(kEmissiveColor, material.GetColor(kEmissiveColorLDR).linear * intensity);
}
/// Set Alpha Clipping on Lit and Unlit shaders.
/// The material to change.
/// True to enable alpha clipping.
public static void SetAlphaClipping(Material material, bool value)
{
material.SetFloat(kAlphaCutoffEnabled, value ? 1.0f : 0.0f);
material.SetupBaseUnlitKeywords();
}
/// Set Alpha Cutoff on Lit and Unlit shaders.
/// The material to change.
/// The alpha cutoff value between 0 and 1.
public static void SetAlphaCutoff(Material material, float cutoff)
{
material.SetFloat(kAlphaCutoff, cutoff);
material.SetFloat(kCutoff, cutoff);
}
/// Set the Diffusion profile on Lit shaders.
/// The material to change.
/// The Diffusion Profile Asset.
public static void SetDiffusionProfile(Material material, DiffusionProfileSettings profile)
{
float hash = profile != null ? HDShadowUtils.Asfloat(profile.profile.hash) : 0;
material.SetFloat(HDShaderIDs._DiffusionProfileHash, hash);
#if UNITY_EDITOR
SetDiffusionProfileAsset(material, profile, HDShaderIDs._DiffusionProfileAsset);
#endif
}
/// Set a Diffusion profile on a Shader Graph material.
/// The material to change.
/// The Diffusion Profile Asset.
/// The reference name of the Diffusion Profile property in the Shader Graph.
public static void SetDiffusionProfileShaderGraph(Material material, DiffusionProfileSettings profile, string referenceName)
{
float hash = profile != null ? HDShadowUtils.Asfloat(profile.profile.hash) : 0;
material.SetFloat(referenceName, hash);
#if UNITY_EDITOR
SetDiffusionProfileAsset(material, profile, Shader.PropertyToID(referenceName + "_Asset"));
#endif
}
///
/// Gets the "Material Type" of the material. Returns MaterialId.LitStandard in case the material doesn't have a Type.
///
/// The material used to get the type.
/// The "Material Type" of the material if the value exists, MaterialId.LitStandard otherwise.
public static MaterialId GetMaterialType(this Material material)
=> material.HasProperty(kMaterialID) ? (MaterialId)material.GetFloat(kMaterialID) : MaterialId.LitStandard;
///
/// Tries to set the "Material Type" property of the material. The function returns true if it have successfully
/// updated the material type. The function can fail if the material doesn't have a _MaterialID property or
/// if you're trying to set a type that wasn't exposed on the ShaderGraph shader of this material.
///
/// The material to change.
/// The new "Material Type" value to set on the material.
/// True if the function has successfully changed the material. False otherwise.
public static bool SetMaterialType(this Material material, MaterialId type)
{
if (material.HasProperty(kMaterialID))
{
// Only SG have TypeMask
if (material.HasProperty(kMaterialTypeMask))
{
int index = material.shader.FindPropertyIndex(kMaterialTypeMask);
int materialTypeMask = (int)material.shader.GetPropertyDefaultFloatValue(index);
if ((materialTypeMask & (1 << (int)type)) == 0)
return false;
}
material.SetFloat(kMaterialID, (int)type);
HDMaterial.ValidateMaterial(material);
return true;
}
return false;
}
#if UNITY_EDITOR
internal static void SetDiffusionProfileAsset(Material material, DiffusionProfileSettings profile, int assetPropertyId, int index = 0)
{
Vector4 guid = Vector3.zero;
if (profile != null)
guid = HDUtils.ConvertGUIDToVector4(UnityEditor.AssetDatabase.AssetPathToGUID(UnityEditor.AssetDatabase.GetAssetPath(profile)));
material.SetVector(assetPropertyId, guid);
var externalRefs = MaterialExternalReferences.GetMaterialExternalReferences(material);
externalRefs.SetDiffusionProfileReference(index, profile);
}
/// Get the Diffusion profile on Lit shaders.
/// The material to access.
/// The Diffusion Profile Asset.
public static DiffusionProfileSettings GetDiffusionProfile(Material material)
{
return GetDiffusionProfileAsset(material, HDShaderIDs._DiffusionProfileAsset);
}
/// Get the Diffusion profile on a Shader Graph material.
/// The material to access.
/// The reference name of the Diffusion Profile property in the Shader Graph.
/// The Diffusion Profile Asset.
public static DiffusionProfileSettings GetDiffusionProfileShaderGraph(Material material, string referenceName)
{
return GetDiffusionProfileAsset(material, Shader.PropertyToID(referenceName + "_Asset"));
}
internal static DiffusionProfileSettings GetDiffusionProfileAsset(Material material, int assetPropertyId)
{
string guid = HDUtils.ConvertVector4ToGUID(material.GetVector(assetPropertyId));
return UnityEditor.AssetDatabase.LoadAssetAtPath(UnityEditor.AssetDatabase.GUIDToAssetPath(guid));
}
internal static IEnumerable GetShaderDiffusionProfileProperties(Shader shader)
{
if (shader.FindPropertyIndex("_DiffusionProfileAsset") != -1)
yield return HDShaderIDs._DiffusionProfileAsset;
if (shader.FindPropertyIndex("_DiffusionProfileAsset0") != -1)
yield return Shader.PropertyToID("_DiffusionProfileAsset0");
if (shader.FindPropertyIndex("_DiffusionProfileAsset1") != -1)
yield return Shader.PropertyToID("_DiffusionProfileAsset1");
if (shader.FindPropertyIndex("_DiffusionProfileAsset2") != -1)
yield return Shader.PropertyToID("_DiffusionProfileAsset2");
if (shader.FindPropertyIndex("_DiffusionProfileAsset3") != -1)
yield return Shader.PropertyToID("_DiffusionProfileAsset3");
int propertyCount = UnityEditor.ShaderUtil.GetPropertyCount(shader);
for (int propIdx = 0; propIdx < propertyCount; ++propIdx)
{
var attributes = shader.GetPropertyAttributes(propIdx);
foreach (var attribute in attributes)
{
if (attribute == "DiffusionProfile")
{
propIdx++;
var type = UnityEditor.ShaderUtil.GetPropertyType(shader, propIdx);
if (type == UnityEditor.ShaderUtil.ShaderPropertyType.Vector)
yield return shader.GetPropertyNameId(propIdx);
break;
}
}
}
}
static System.Reflection.MethodInfo s_RevertRenderQueue = typeof(Material).GetMethod("RevertPropertyOverride_Serialized",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
internal static void RevertRenderQueueOverride(Material material) => s_RevertRenderQueue.Invoke(material, new object[] { 1 << 4 });
#endif
// this will work on ALL shadergraph-built shaders, in memory or asset based
// duplicated from GraphUtil.cs in shadergraph package, because it's not available outside the editor
internal static bool IsShaderGraph(Material material)
{
var shaderGraphTag = material.GetTag("ShaderGraphShader", false, null);
return !string.IsNullOrEmpty(shaderGraphTag);
}
}
}