Files
2026-06-29 21:52:59 +02:00

356 lines
15 KiB
C#

using System;
using UnityEngine;
namespace UnityEditor
{
internal class UVFreePBRShaderGUI : ShaderGUI
{
private enum WorkflowMode
{
Specular,
Metallic,
Dielectric
}
private static class Styles
{
public static GUIStyle optionsButton = "PaneOptions";
public static string emptyTootip = "";
public static GUIContent triplanarSpace = new GUIContent("Triplanar Space", "Whether the triplanar projection should be in local or global space");
public static GUIContent texturePowerText = new GUIContent("Texture Power", "How strong textures on different axes are differentiated. 1 for smooth blending, 10+ for sharper contrasts");
public static GUIContent vertexColorStrengthText = new GUIContent("Vertex Color Strength", "0 for no vertex color (off), 1 for full vertex color tinting");
public static GUIContent albedoText = new GUIContent("Albedo", "Albedo (RGB) and Transparency (A)");
public static GUIContent alphaCutoffText = new GUIContent("Alpha Cutoff", "Threshold for alpha cutoff");
public static GUIContent specularMapText = new GUIContent("Specular", "Specular (RGB) and Smoothness (A)");
public static GUIContent metallicMapText = new GUIContent("Metallic", "Metallic (R) and Smoothness (A)");
public static GUIContent smoothnessText = new GUIContent("Smoothness", "");
public static GUIContent normalMapText = new GUIContent("Normal Map", "Normal Map");
public static GUIContent heightMapText = new GUIContent("Height Map", "Height Map (G)");
public static GUIContent occlusionText = new GUIContent("Occlusion", "Occlusion (G)");
public static GUIContent emissionText = new GUIContent("Emission", "Emission (RGB)");
public static GUIContent detailMaskText = new GUIContent("Detail Mask", "Mask for Secondary Maps (A)");
public static GUIContent detailAlbedoText = new GUIContent("Detail Albedo x2", "Albedo (RGB) multiplied by 2");
public static GUIContent detailNormalMapText = new GUIContent("Normal Map", "Normal Map");
public static string whiteSpaceString = " ";
public static string primaryMapsText = "Main Maps";
public static string secondaryMapsText = "Secondary Maps";
public static GUIContent emissiveWarning = new GUIContent ("Emissive value is animated but the material has not been configured to support emissive. Please make sure the material itself has some amount of emissive.");
public static GUIContent emissiveColorWarning = new GUIContent ("Ensure emissive color is non-black for emission to have effect.");
public static readonly string[] spaceNames = Enum.GetNames (typeof(Space));
public static string advancedText = "Advanced Options";
}
MaterialProperty vertexColorStrength = null;
MaterialProperty albedoMap = null;
MaterialProperty albedoColor = null;
MaterialProperty specularMap = null;
MaterialProperty specularColor = null;
MaterialProperty metallicMap = null;
MaterialProperty metallic = null;
MaterialProperty smoothness = null;
MaterialProperty bumpScale = null;
MaterialProperty bumpMap = null;
MaterialProperty occlusionStrength = null;
MaterialProperty occlusionMap = null;
MaterialProperty heigtMapScale = null;
MaterialProperty heightMap = null;
MaterialProperty emissionScaleUI = null;
MaterialProperty emissionColorUI = null;
MaterialProperty emissionColorForRendering = null;
MaterialProperty emissionMap = null;
MaterialProperty detailMask = null;
MaterialProperty detailAlbedoMap = null;
MaterialProperty detailNormalMapScale = null;
MaterialProperty detailNormalMap = null;
MaterialProperty texturePower = null;
MaterialProperty triplanarSpace = null;
MaterialEditor m_MaterialEditor;
WorkflowMode m_WorkflowMode = WorkflowMode.Specular;
bool m_FirstTimeApply = true;
public void FindProperties (MaterialProperty[] props)
{
triplanarSpace = FindProperty("_TriplanarSpace", props, false);
texturePower = FindProperty("_TexPower", props, false);
vertexColorStrength = FindProperty("_VertexColorStrength", props, false);
albedoMap = FindProperty ("_MainTex", props);
albedoColor = FindProperty ("_Color", props);
specularMap = FindProperty ("_SpecGlossMap", props, false);
specularColor = FindProperty ("_SpecColor", props, false);
metallicMap = FindProperty ("_MetallicGlossMap", props, false);
metallic = FindProperty ("_Metallic", props, false);
if (specularMap != null && specularColor != null)
m_WorkflowMode = WorkflowMode.Specular;
else if (metallicMap != null && metallic != null)
m_WorkflowMode = WorkflowMode.Metallic;
else
m_WorkflowMode = WorkflowMode.Dielectric;
smoothness = FindProperty ("_Glossiness", props);
bumpScale = FindProperty ("_BumpScale", props);
bumpMap = FindProperty ("_BumpMap", props);
heigtMapScale = FindProperty ("_Parallax", props);
heightMap = FindProperty("_ParallaxMap", props);
occlusionStrength = FindProperty ("_OcclusionStrength", props);
occlusionMap = FindProperty ("_OcclusionMap", props);
emissionScaleUI = FindProperty ("_EmissionScaleUI", props);
emissionColorUI = FindProperty ("_EmissionColorUI", props);
emissionColorForRendering = FindProperty ("_EmissionColor", props);
emissionMap = FindProperty ("_EmissionMap", props);
detailMask = FindProperty ("_DetailMask", props);
detailAlbedoMap = FindProperty ("_DetailAlbedoMap", props);
detailNormalMapScale = FindProperty ("_DetailNormalMapScale", props);
detailNormalMap = FindProperty ("_DetailNormalMap", props);
}
public override void OnGUI (MaterialEditor materialEditor, MaterialProperty[] props)
{
FindProperties (props); // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly
m_MaterialEditor = materialEditor;
Material material = materialEditor.target as Material;
ShaderPropertiesGUI (material);
// Make sure that needed keywords are set up if we're switching some existing
// material to a standard shader.
if (m_FirstTimeApply)
{
SetMaterialKeywords (material, m_WorkflowMode);
m_FirstTimeApply = false;
}
}
public void ShaderPropertiesGUI (Material material)
{
// Use default labelWidth
EditorGUIUtility.labelWidth = 0f;
// Detect any changes to the material
EditorGUI.BeginChangeCheck();
{
TriplanarSpacePopup();
m_MaterialEditor.RangeProperty (texturePower, "Texture Power");
if (vertexColorStrength != null)
{
m_MaterialEditor.RangeProperty (vertexColorStrength, Styles.vertexColorStrengthText.text);
}
// Primary properties
GUILayout.Label (Styles.primaryMapsText, EditorStyles.boldLabel);
DoAlbedoArea(material);
DoSpecularMetallicArea();
m_MaterialEditor.TexturePropertySingleLine(Styles.normalMapText, bumpMap, bumpMap.textureValue != null ? bumpScale : null);
m_MaterialEditor.TexturePropertySingleLine(Styles.heightMapText, heightMap, heightMap.textureValue != null ? heigtMapScale : null);
m_MaterialEditor.TexturePropertySingleLine(Styles.occlusionText, occlusionMap, occlusionMap.textureValue != null ? occlusionStrength : null);
DoEmissionArea(material);
m_MaterialEditor.TexturePropertySingleLine(Styles.detailMaskText, detailMask);
EditorGUI.BeginChangeCheck();
m_MaterialEditor.TextureScaleOffsetProperty(albedoMap);
if (EditorGUI.EndChangeCheck())
emissionMap.textureScaleAndOffset = albedoMap.textureScaleAndOffset; // Apply the main texture scale and offset to the emission texture as well, for Enlighten's sake
EditorGUILayout.Space();
// Secondary properties
GUILayout.Label(Styles.secondaryMapsText, EditorStyles.boldLabel);
m_MaterialEditor.TexturePropertySingleLine(Styles.detailAlbedoText, detailAlbedoMap);
m_MaterialEditor.TexturePropertySingleLine(Styles.detailNormalMapText, detailNormalMap, detailNormalMapScale);
m_MaterialEditor.TextureScaleOffsetProperty(detailAlbedoMap);
}
if (EditorGUI.EndChangeCheck())
{
MaterialChanged(material, m_WorkflowMode);
}
EditorGUILayout.Space();
GUILayout.Label(Styles.advancedText, EditorStyles.boldLabel);
m_MaterialEditor.RenderQueueField();
m_MaterialEditor.EnableInstancingField();
m_MaterialEditor.DoubleSidedGIField();
}
//internal
void DetermineWorkflow(MaterialProperty[] props)
{
if (FindProperty("_SpecGlossMap", props, false) != null && FindProperty("_SpecColor", props, false) != null)
m_WorkflowMode = WorkflowMode.Specular;
else if (FindProperty("_MetallicGlossMap", props, false) != null && FindProperty("_Metallic", props, false) != null)
m_WorkflowMode = WorkflowMode.Metallic;
else
m_WorkflowMode = WorkflowMode.Dielectric;
}
public override void AssignNewShaderToMaterial (Material material, Shader oldShader, Shader newShader)
{
base.AssignNewShaderToMaterial(material, oldShader, newShader);
return;
}
void TriplanarSpacePopup()
{
EditorGUI.showMixedValue = triplanarSpace.hasMixedValue;
var space = (Space)triplanarSpace.floatValue;
EditorGUI.BeginChangeCheck();
space = (Space)EditorGUILayout.Popup(Styles.triplanarSpace.text, (int)space, Styles.spaceNames);
if (EditorGUI.EndChangeCheck())
{
m_MaterialEditor.RegisterPropertyChangeUndo("Triplanar Space");
triplanarSpace.floatValue = (float)space;
}
EditorGUI.showMixedValue = false;
}
void DoAlbedoArea(Material material)
{
m_MaterialEditor.TexturePropertySingleLine(Styles.albedoText, albedoMap, albedoColor);
}
void DoEmissionArea(Material material)
{
bool showEmissionColorAndGIControls = emissionScaleUI.floatValue > 0f;
bool hadEmissionTexture = emissionMap.textureValue != null;
// Do controls
m_MaterialEditor.TexturePropertySingleLine(Styles.emissionText, emissionMap, showEmissionColorAndGIControls ? emissionColorUI : null, emissionScaleUI);
// Set default emissionScaleUI if texture was assigned
if (emissionMap.textureValue != null && !hadEmissionTexture && emissionScaleUI.floatValue <= 0f)
emissionScaleUI.floatValue = 1.0f;
// Dynamic Lightmapping mode
if (showEmissionColorAndGIControls)
{
bool shouldEmissionBeEnabled = ShouldEmissionBeEnabled(EvalFinalEmissionColor(material));
EditorGUI.BeginDisabledGroup(!shouldEmissionBeEnabled);
m_MaterialEditor.LightmapEmissionProperty (MaterialEditor.kMiniTextureFieldLabelIndentLevel + 1);
EditorGUI.EndDisabledGroup();
}
if (!HasValidEmissiveKeyword(material))
{
EditorGUILayout.HelpBox(Styles.emissiveWarning.text, MessageType.Warning);
}
}
void DoSpecularMetallicArea()
{
if (m_WorkflowMode == WorkflowMode.Specular)
{
if (specularMap.textureValue == null)
m_MaterialEditor.TexturePropertyTwoLines(Styles.specularMapText, specularMap, specularColor, Styles.smoothnessText, smoothness);
else
m_MaterialEditor.TexturePropertySingleLine(Styles.specularMapText, specularMap);
}
else if (m_WorkflowMode == WorkflowMode.Metallic)
{
if (metallicMap.textureValue == null)
m_MaterialEditor.TexturePropertyTwoLines(Styles.metallicMapText, metallicMap, metallic, Styles.smoothnessText, smoothness);
else
m_MaterialEditor.TexturePropertySingleLine(Styles.metallicMapText, metallicMap);
}
}
// Calculate final HDR _EmissionColor (gamma space) from _EmissionColorUI (LDR, gamma) & _EmissionScaleUI (gamma)
static Color EvalFinalEmissionColor(Material material)
{
return material.GetColor("_EmissionColorUI") * material.GetFloat("_EmissionScaleUI");
}
static bool ShouldEmissionBeEnabled (Color color)
{
return color.grayscale > (0.1f / 255.0f);
}
static void SetMaterialKeywords(Material material, WorkflowMode workflowMode)
{
// Note: keywords must be based on Material value not on MaterialProperty due to multi-edit & material animation
// (MaterialProperty value might come from renderer material property block)
if (workflowMode == WorkflowMode.Specular)
SetKeyword (material, "_SPECGLOSSMAP", material.GetTexture ("_SpecGlossMap"));
else if (workflowMode == WorkflowMode.Metallic)
SetKeyword (material, "_METALLICGLOSSMAP", material.GetTexture ("_MetallicGlossMap"));
SetKeyword (material, "_PARALLAXMAP", material.GetTexture ("_ParallaxMap"));
SetKeyword (material, "_DETAIL", material.GetTexture ("_DetailAlbedoMap") || material.GetTexture ("_DetailNormalMap"));
SetKeyword (material, "_OCCLUSION", material.GetTexture ("_OcclusionMap"));
bool shouldEmissionBeEnabled = ShouldEmissionBeEnabled (material.GetColor("_EmissionColor"));
SetKeyword (material, "_EMISSION", shouldEmissionBeEnabled);
// Triplanar texture space"
SetKeyword(material, "_UVFREE_LOCAL", material.GetFloat ("_TriplanarSpace") == (float) Space.Self);
// Setup lightmap emissive flags
MaterialGlobalIlluminationFlags flags = material.globalIlluminationFlags;
if ((flags & (MaterialGlobalIlluminationFlags.BakedEmissive | MaterialGlobalIlluminationFlags.RealtimeEmissive)) != 0)
{
flags &= ~MaterialGlobalIlluminationFlags.EmissiveIsBlack;
if (!shouldEmissionBeEnabled)
flags |= MaterialGlobalIlluminationFlags.EmissiveIsBlack;
material.globalIlluminationFlags = flags;
}
}
bool HasValidEmissiveKeyword (Material material)
{
// Material animation might be out of sync with the material keyword.
// So if the emission support is disabled on the material, but the property blocks have a value that requires it, then we need to show a warning.
// (note: (Renderer MaterialPropertyBlock applies its values to emissionColorForRendering))
bool hasEmissionKeyword = material.IsKeywordEnabled ("_EMISSION");
if (!hasEmissionKeyword && ShouldEmissionBeEnabled (emissionColorForRendering.colorValue))
return false;
else
return true;
}
static void MaterialChanged(Material material, WorkflowMode workflowMode)
{
// Clamp EmissionScale to always positive
if (material.GetFloat("_EmissionScaleUI") < 0.0f)
material.SetFloat("_EmissionScaleUI", 0.0f);
// Apply combined emission value
Color emissionColorOut = EvalFinalEmissionColor (material);
material.SetColor("_EmissionColor", emissionColorOut);
if (workflowMode == WorkflowMode.Metallic)
{
material.SetFloat ("_UsingMetallicGlossMap", (material.GetTexture ("_MetallicGlossMap") == null) ? 0.0f : 1.0f);
}
else if (workflowMode == WorkflowMode.Specular)
{
material.SetFloat ("_UsingSpecGlossMap", (material.GetTexture ("_SpecGlossMap") == null) ? 0.0f : 1.0f);
}
SetMaterialKeywords(material, workflowMode);
}
static void SetKeyword(Material m, string keyword, bool state)
{
//Debug.Log(keyword + ": " + state);
if (state)
m.EnableKeyword (keyword);
else
m.DisableKeyword (keyword);
}
}
} // namespace UnityEditor