using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine.Rendering;
namespace UnityEngine.PathTracing.Core
{
internal enum MaterialPropertyType { None = 0, Color, Texture }
internal struct MaterialPropertyDesc
{
public MaterialPropertyType Type;
public float3 Color; // Unused if type == Textured
}
internal enum TransmissionChannels { None = 0, RGB, Alpha }
internal struct TransmissionDesc
{
public Texture SourceTexture;
public TransmissionChannels Channels;
public Vector2 Scale;
public Vector2 Offset;
}
internal static class MaterialAspectOracle
{
// See gi::HasBakedEmissive in Materials.cpp
public static MaterialPropertyDesc GetEmission(Material mat)
{
// If the material is not marked as baked emissive, or if it is black, there is no emission
var emissiveFlags = mat.globalIlluminationFlags;
bool isBlack = emissiveFlags.HasFlag(MaterialGlobalIlluminationFlags.EmissiveIsBlack);
bool isBaked = emissiveFlags.HasFlag(MaterialGlobalIlluminationFlags.BakedEmissive);
if (isBlack || !isBaked)
{
return new MaterialPropertyDesc { Type = MaterialPropertyType.None, Color = float3.zero };
}
// If the material has an emission keyword, but it is disabled, there is no emission
if (IsMaterialWithEmissionKeyword(mat) && !EnumerableArrayContains(mat.shaderKeywords, "_EMISSION"))
{
return new MaterialPropertyDesc { Type = MaterialPropertyType.None, Color = float3.zero };
}
// If we have reached this point, the material should be emissive.
// First we check for an emissive texture, and use that if it exists
if (HasEmissionMap(mat))
{
return new MaterialPropertyDesc { Type = MaterialPropertyType.Texture, Color = float3.zero };
}
// Otherwise, if the material only has an emissive color, we use that
if (mat.HasProperty(SID.EmissionColor))
{
return new MaterialPropertyDesc { Type = MaterialPropertyType.Color, Color = ToFloat3(mat.GetColor(SID.EmissionColor)) };
}
// If we found neither property, we assume that the material has an unusual meta pass implementation,
// which will render the emission - thus we use texture mode
return new MaterialPropertyDesc { Type = MaterialPropertyType.Texture, Color = float3.zero };
}
private static bool EnumerableArrayContains(IEnumerable<string> array, string value)
{
foreach (string element in array)
if (EqualityComparer<string>.Default.Equals(element, value))
return true;
return false;
}
// Check if a material has a property with a specific Shaderlab property flag
private static bool MaterialHasPropertyWithFlag(Material mat, ShaderPropertyFlags flag)
{
if (mat.shader == null)
return false;
int numProperties = mat.shader.GetPropertyCount();
for (int i = 0; i < numProperties; ++i)
{
var flags = mat.shader.GetPropertyFlags(i);
if (flags.HasFlag(flag))
{
return true;
}
}
return false;
}
// See CreateBakeMaterial in ExtractBakeMaterials.cpp
public static TransmissionDesc GetTransmission(Material mat)
{
// Full RGB transmission
bool hasRGBTransparencyTexture = mat.HasProperty(SID.TransparencyLm) && mat.GetTexture(SID.TransparencyLm) != null;
if (hasRGBTransparencyTexture)
{
return new TransmissionDesc
{
Channels = TransmissionChannels.RGB,
SourceTexture = mat.GetTexture(SID.TransparencyLm),
Scale = mat.GetTextureScale(SID.TransparencyLm),
Offset = mat.GetTextureOffset(SID.TransparencyLm)
};
}
// Alpha-only transmission, alpha from main texture (if exists)
bool isOnTransparentQueue = mat.renderQueue >= (int)RenderQueue.AlphaTestRenderQueue && mat.renderQueue < (int)RenderQueue.OverlayRenderQueue;
bool hasMainTexture = MaterialHasPropertyWithFlag(mat, ShaderPropertyFlags.MainTexture) || mat.HasProperty(SID.MainTex);
if (isOnTransparentQueue && hasMainTexture)
{
return new TransmissionDesc
{
Channels = TransmissionChannels.Alpha,
SourceTexture = mat.mainTexture,
Scale = mat.mainTextureScale,
Offset = mat.mainTextureOffset
};
}
// No transmission
return new TransmissionDesc { Channels = TransmissionChannels.None };
}
private static bool HasEmissionMap(Material mat)
{
return (mat.HasProperty(SID.EmissionMap) && mat.GetTexture(SID.EmissionMap) != null)
&& (!mat.HasProperty(SID.UseEmissiveMap) || mat.GetInt(SID.UseEmissiveMap) == 1);
}
private static bool IsMaterialWithEmissionKeyword(Material mat)
{
return EnumerableArrayContains(mat.shader.keywordSpace.keywordNames, "_EMISSION");
}
public static float GetAlpha(Material mat)
{
if (mat.HasProperty(SID.BaseColor))
{
return mat.GetColor(SID.BaseColor).a;
}
else if (mat.HasProperty(SID.Color))
{
return mat.GetColor(SID.Color).a;
}
else
{
for (int i = 0; i < mat.shader.GetPropertyCount(); i++)
{
if (mat.shader.GetPropertyFlags(i).HasFlag(ShaderPropertyFlags.MainColor))
{
return mat.GetColor(mat.shader.GetPropertyNameId(i)).a;
}
}
return 1.0f;
}
}
public static bool UsesAlphaCutoff(Material mat)
{
bool alphaTestQueue = mat.renderQueue >= (int)RenderQueue.AlphaTestRenderQueue && mat.renderQueue < (int)RenderQueue.TransparentRenderQueue;
if (!alphaTestQueue)
return false;
return mat.HasProperty(SID.Cutoff) || mat.HasProperty(SID.AlphaTestRef);
}
public static float GetAlphaCutoff(Material mat)
{
if (mat.HasProperty(SID.Cutoff))
{
return mat.GetFloat(SID.Cutoff);
}
else if (mat.HasProperty(SID.AlphaTestRef))
{
return mat.GetFloat(SID.AlphaTestRef);
}
return 0.0f;
}
private static float3 ToFloat3(Color col)
{
return new float3(col.r, col.g, col.b);
}
private enum RenderQueue
{
GeometryRenderQueue = 2000,
AlphaTestRenderQueue = 2450,
TransparentRenderQueue = 3000,
OverlayRenderQueue = 4000,
}
private static class SID
{
public static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
public static readonly int EmissionMap = Shader.PropertyToID("_EmissionMap");
public static readonly int UseEmissiveMap = Shader.PropertyToID("_UseEmissiveMap");
public static readonly int TransparencyLm = Shader.PropertyToID("_TransparencyLM");
public static readonly int Color = Shader.PropertyToID("_Color");
public static readonly int BaseColor = Shader.PropertyToID("_BaseColor");
public static readonly int Cutoff = Shader.PropertyToID("_Cutoff");
public static readonly int AlphaTestRef = Shader.PropertyToID("_AlphaTestRef");
public static readonly int MainTex = Shader.PropertyToID("_MainTex");
}
// TODO(Yvain) Bump, roughness, metalness ?
}
}