Files
AR-Menu/Library/PackageCache/org.khronos.unitygltf@6b55d14e19c1/Editor/Scripts/ShaderGraph/ShaderConverters.cs
2025-11-30 08:35:03 +02:00

321 lines
14 KiB
C#

using System;
using System.Linq;
using GLTF.Schema;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityGLTF
{
public static class ShaderConverters
{
[InitializeOnLoadMethod]
static void InitShaderConverters()
{
GLTFMaterialHelper.RegisterMaterialConversionToGLTF(ConvertStandardAndURPLit);
GLTFMaterialHelper.RegisterMaterialConversionToGLTF(ConvertUnityGLTFGraphs);
}
private static bool ConvertUnityGLTFGraphs(Material material, Shader oldShader, Shader newShader)
{
// update legacy shaders that didn't have material overrides available
if (oldShader.name.StartsWith("Hidden/UnityGLTF/PBRGraph") || oldShader.name.StartsWith("Hidden/UnityGLTF/UnlitGraph"))
{
material.shader = newShader;
var meta = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(material.shader)) as IUnityGltfShaderUpgradeMeta;
if (meta != null)
{
// Debug.Log("Updating shader from " + material.shader + " to " + meta.SourceShader +
// " (transparent: " + meta.IsTransparent + ", double sided: " + meta.IsDoublesided + ")");
var isUnlit = meta.SourceShader.name.Contains("Unlit");
material.shader = meta.SourceShader;
var mapper = isUnlit ? (IUniformMap) new UnlitMap(material) : new PBRGraphMap(material);
if (meta.IsTransparent)
mapper.AlphaMode = AlphaMode.BLEND;
if (meta.IsDoublesided)
mapper.DoubleSided = true;
EditorUtility.SetDirty(material);
}
return true;
}
if (oldShader.name != "UnityGLTF/UnlitGraph" && oldShader.name != "UnityGLTF/PBRGraph") return false;
material.shader = newShader;
return true;
}
private static bool ConvertStandardAndURPLit(Material material, Shader oldShader, Shader newShader)
{
var allowedConversions = new[] {
StandardShader,
UnlitColorShader,
UnlitTextureShader,
UnlitTransparentShader,
UnlitTransparentCutoutShader,
URPLitShader,
URPUnlitShader,
};
var unlitSources = new[] {
UnlitColorShader,
UnlitTextureShader,
URPUnlitShader,
UnlitTransparentShader,
UnlitTransparentCutoutShader,
};
var birpShaders = new[] {
StandardShader,
UnlitColorShader,
UnlitTextureShader,
UnlitTransparentShader,
UnlitTransparentCutoutShader,
};
if (!allowedConversions.Contains(oldShader.name)) return false;
var sourceIsUnlit = unlitSources.Contains(oldShader.name);
var targetIsUnlit = newShader.name == URPUnlitShader;
var sourceIsTransparent = oldShader.name == UnlitTransparentShader || oldShader.name == UnlitTransparentCutoutShader;
var sourceIsBirp = birpShaders.Contains(oldShader.name);
var needsEmissiveColorSpaceConversion = sourceIsBirp && QualitySettings.activeColorSpace == ColorSpace.Linear;
var colorProp = sourceIsBirp ? _Color : _BaseColor;
var colorTexProp = sourceIsBirp ? _MainTex : _BaseMap;
var color = material.GetColor(colorProp, Color.white);
var albedo = material.GetTexture(colorTexProp, null);
var albedoOffset = material.GetTextureOffset(colorTexProp, Vector2.zero);
var albedoTiling = material.GetTextureScale(colorTexProp, Vector2.one);
var isTransparent = material.GetTag("RenderType", false) == "Transparent" || sourceIsTransparent;
var metallic = material.GetFloat(_Metallic, 0);
var smoothness = material.HasProperty(_Smoothness) ? material.GetFloat(_Smoothness, 0) :
material.HasProperty(_Glossiness) ? material.GetFloat(_Glossiness, 0) : 0.5f;
var metallicGloss = material.GetTexture(_MetallicGlossMap, null);
var normal = material.GetTexture(_BumpMap, null);
var normalStrength = material.GetFloat(_BumpScale, 1);
var occlusion = material.GetTexture(_OcclusionMap, null);
var occlusionStrength = material.GetFloat(_Strength, 1);
var emission = material.GetTexture(_EmissionMap, null);
var emissionColor = material.GetColor(_EmissionColor, Color.black);
// if emission is OFF we don't want to set it to ON during conversion
if ((oldShader.name == StandardShader || oldShader.name == URPLitShader) && !material.IsKeywordEnabled("_EMISSION"))
{
emission = null;
emissionColor = Color.black;
}
var cutoff = material.GetFloat(_Cutoff, 0.5f);
var isCutoff = material.IsKeywordEnabled("_ALPHATEST_ON") ||
material.IsKeywordEnabled("_BUILTIN_ALPHATEST_ON") ||
material.IsKeywordEnabled("_BUILTIN_AlphaClip") ||
oldShader.name == UnlitTransparentCutoutShader;
material.shader = newShader;
material.SetColor(baseColorFactor, color);
material.SetTexture(baseColorTexture, albedo);
material.SetTextureOffset(baseColorTexture, albedoOffset);
material.SetTextureScale(baseColorTexture, albedoTiling);
if (albedoOffset != Vector2.zero || albedoTiling != Vector2.one)
GLTFMaterialHelper.SetKeyword(material, "_TEXTURE_TRANSFORM", true);
material.SetFloat(metallicFactor, metallic);
material.SetFloat(roughnessFactor, 1 - smoothness);
const string ConversionWarning = "The Metallic (R) Smoothness (A) texture needs to be converted to Roughness (B) Metallic (G). ";
#if UNITY_EDITOR && UNITY_2022_1_OR_NEWER
var importerPath = AssetDatabase.GetAssetPath(metallicGloss);
var importer = AssetImporter.GetAtPath(importerPath) as TextureImporter;
if (importer && importer.swizzleG != TextureImporterSwizzle.OneMinusR) // can't really detect if this has been done on the texture already, this is just a heuristic...
{
if (EditorUtility.DisplayDialog("Texture Conversion",
ConversionWarning + "This is done by swizzling texture channels in the importer. Do you want to proceed?",
"OK", DialogOptOutDecisionType.ForThisSession, nameof(GLTFMaterialHelper) + "_texture_conversion"))
{
importer.swizzleR = TextureImporterSwizzle.B;
importer.swizzleG = TextureImporterSwizzle.OneMinusR;
importer.swizzleB = TextureImporterSwizzle.G;
Undo.RegisterImporterUndo(importerPath, "Texture Swizzles (M__G > _RM_)");
importer.SaveAndReimport();
}
}
else if (metallicGloss)
{
Debug.LogWarning(ConversionWarning + "This currently needs to be done manually. Please swap channels in an external software.", material);
}
#else
if (metallicGloss)
Debug.LogWarning(ConversionWarning + "This currently needs to be done manually. Please swap channels in an external software.", material);
#endif
// TODO: convert metallicGloss to metallicRoughnessTexture format: Metallic (R) + Smoothness (A) → Roughness (G) + Metallic (B)
// TODO: when smoothness is not 0 or 1 and there's a texture, need to convert to a texture and set roughness = 1, otherwise the math doesn't match
// TODO: figure out where to put the newly created texture, and how to avoid re-creating it several times when multiple materials may use it.
material.SetTexture(metallicRoughnessTexture, metallicGloss);
material.SetTexture(normalTexture, normal);
material.SetFloat(normalScale, normalStrength);
material.SetTexture(occlusionTexture, occlusion);
material.SetFloat(occlusionStrength1, occlusionStrength);
material.SetTexture(emissiveTexture, emission);
material.SetFloat(alphaCutoff, isCutoff ? cutoff : -cutoff); // bit hacky, but that avoids an additional keyword for determining alpha cutoff right now
var map = new PBRGraphMap(material);
map.AlphaMode = isCutoff ? AlphaMode.MASK : (isTransparent ? AlphaMode.BLEND : AlphaMode.OPAQUE);
material.SetColor(emissiveFactor, needsEmissiveColorSpaceConversion ? emissionColor.linear : emissionColor);
// set the flags on conversion, otherwise it's confusing why they're not on - can't easily replicate the magic that Unity does in their inspectors when changing emissive on/off
if (material.globalIlluminationFlags == MaterialGlobalIlluminationFlags.None || material.globalIlluminationFlags == MaterialGlobalIlluminationFlags.EmissiveIsBlack)
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive;
// ensure keywords are correctly set after conversion
GLTFMaterialHelper.ValidateMaterialKeywords(material);
return true;
}
// ReSharper disable InconsistentNaming
private const string StandardShader = "Standard";
private const string UnlitColorShader = "Unlit/Color";
private const string UnlitTextureShader = "Unlit/Texture";
private const string UnlitTransparentShader = "Unlit/Transparent";
private const string UnlitTransparentCutoutShader = "Unlit/Transparent Cutout";
private const string URPLitShader = "Universal Render Pipeline/Lit";
private const string URPUnlitShader = "Universal Render Pipeline/Unlit";
// Standard and URP-Lit property names
private static readonly int _Color = Shader.PropertyToID("_Color");
private static readonly int _BaseColor = Shader.PropertyToID("_BaseColor");
private static readonly int _MainTex = Shader.PropertyToID("_MainTex");
private static readonly int _BaseMap = Shader.PropertyToID("_BaseMap");
private static readonly int _Metallic = Shader.PropertyToID("_Metallic");
private static readonly int _Glossiness = Shader.PropertyToID("_Glossiness");
private static readonly int _Smoothness = Shader.PropertyToID("_Smoothness");
private static readonly int _MetallicGlossMap = Shader.PropertyToID("_MetallicGlossMap");
private static readonly int _BumpMap = Shader.PropertyToID("_BumpMap");
private static readonly int _BumpScale = Shader.PropertyToID("_BumpScale");
private static readonly int _OcclusionMap = Shader.PropertyToID("_OcclusionMap");
private static readonly int _Strength = Shader.PropertyToID("_OcclusionStrength");
private static readonly int _EmissionMap = Shader.PropertyToID("_EmissionMap");
private static readonly int _EmissionColor = Shader.PropertyToID("_EmissionColor");
private static readonly int _Cutoff = Shader.PropertyToID("_Cutoff");
// glTF property names
private static readonly int baseColorFactor = Shader.PropertyToID("baseColorFactor");
private static readonly int baseColorTexture = Shader.PropertyToID("baseColorTexture");
private static readonly int metallicFactor = Shader.PropertyToID("metallicFactor");
private static readonly int roughnessFactor = Shader.PropertyToID("roughnessFactor");
private static readonly int metallicRoughnessTexture = Shader.PropertyToID("metallicRoughnessTexture");
private static readonly int normalTexture = Shader.PropertyToID("normalTexture");
private static readonly int normalScale = Shader.PropertyToID("normalScale");
private static readonly int occlusionTexture = Shader.PropertyToID("occlusionTexture");
private static readonly int occlusionStrength1 = Shader.PropertyToID("occlusionStrength");
private static readonly int emissiveTexture = Shader.PropertyToID("emissiveTexture");
private static readonly int emissiveFactor = Shader.PropertyToID("emissiveFactor");
private static readonly int alphaCutoff = Shader.PropertyToID("alphaCutoff");
// ReSharper restore InconsistentNaming
private static readonly string[] emissivePropNames = new[] { "emissiveFactor", "_EmissionColor" };
[MenuItem("CONTEXT/Material/UnityGLTF Material Helpers/Convert Emissive Colors > sRGB - weaker, darker")]
private static void ConvertToSRGB(MenuCommand command)
{
if (!(command.context is Material mat)) return;
Undo.RegisterCompleteObjectUndo(mat, "Convert emissive colors to sRGB");
foreach(var propName in emissivePropNames)
if (mat.HasProperty(propName)) mat.SetColor(propName, mat.GetColor(propName).gamma);
}
[MenuItem("CONTEXT/Material/UnityGLTF Material Helpers/Convert Emissive Colors > Linear - brighter, stronger")]
private static void ConvertToLinear(MenuCommand command)
{
if (!(command.context is Material mat)) return;
Undo.RegisterCompleteObjectUndo(mat, "Convert emissive colors to sRGB");
foreach(var propName in emissivePropNames)
if (mat.HasProperty(propName)) mat.SetColor(propName, mat.GetColor(propName).linear);
}
[MenuItem("CONTEXT/Material/UnityGLTF Material Helpers/ Select all materials with this shader", false, -1000)]
private static void SelectAllMaterialsWithShader(MenuCommand command)
{
if (!(command.context is Material mat)) return;
var allMaterials = AssetDatabase.FindAssets("t:Material")
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<Material>)
.Where(x => !AssetDatabase.IsSubAsset(x))
.Where(x => x.shader == mat.shader)
.Cast<Object>()
.ToArray();
foreach (var obj in allMaterials)
EditorGUIUtility.PingObject(obj);
Selection.objects = allMaterials;
}
private static bool TryGetMetadataOfType<T>(Shader shader, out T obj) where T : ScriptableObject
{
obj = null;
var path = AssetDatabase.GetAssetPath(shader);
foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(path))
{
if (asset is T metadataAsset)
{
obj = metadataAsset;
return true;
}
}
return false;
}
}
static class MaterialHelper
{
public static float GetFloat(this Material material, int propertyIdx, float fallback)
{
if (material.HasProperty(propertyIdx))
return material.GetFloat(propertyIdx);
return fallback;
}
public static Color GetColor(this Material material, int propertyIdx, Color fallback)
{
if (material.HasProperty(propertyIdx))
return material.GetColor(propertyIdx);
return fallback;
}
public static Texture GetTexture(this Material material, int propertyIdx, Texture fallback)
{
if (material.HasProperty(propertyIdx))
return material.GetTexture(propertyIdx);
return fallback;
}
public static Vector2 GetTextureScale(this Material material, int propertyIdx, Vector2 fallback)
{
if (material.HasProperty(propertyIdx))
return material.GetTextureScale(propertyIdx);
return fallback;
}
public static Vector2 GetTextureOffset(this Material material, int propertyIdx, Vector2 fallback)
{
if (material.HasProperty(propertyIdx))
return material.GetTextureOffset(propertyIdx);
return fallback;
}
}
}