// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors // SPDX-License-Identifier: Apache-2.0 #if USING_URP || USING_HDRP || (UNITY_SHADER_GRAPH_12_OR_NEWER && GLTFAST_BUILTIN_SHADER_GRAPH) #define GLTFAST_SHADER_GRAPH #else #define GLTFAST_BUILTIN_RP #endif using System; using GLTFast.Schema; using UnityEngine; using Unity.Mathematics; using UnityEngine.Assertions; #if USING_URP using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; #endif namespace GLTFast.Materials { using Logging; /// /// Common base class for implementations of IMaterialGenerator /// public abstract class MaterialGenerator : IMaterialGenerator { /// /// Type of material /// protected enum MaterialType { // Unknown /// /// Metallic-Roughness material /// MetallicRoughness, /// /// Specular-Glossiness material /// SpecularGlossiness, /// /// Unlit material /// Unlit, } // ReSharper disable MemberCanBeProtected.Global // ReSharper disable MemberCanBePrivate.Global /// When a glTF mesh(primitive) has no material assigned, /// a default material, has to get created and assigned. This default material should get named after this /// field's value to ensure future round-trip workflows are functioning. public const string DefaultMaterialName = "glTF-Default-Material"; /// Render type key public const string RenderTypeTag = "RenderType"; /// Render type TransparentCutout value public const string TransparentCutoutRenderType = "TransparentCutout"; /// Render type Opaque value public const string OpaqueRenderType = "Opaque"; /// Render type Fade value public const string FadeRenderType = "Fade"; /// Render type Transparent value public const string TransparentRenderType = "Transparent"; /// Shader keyword _ALPHATEST_ON public const string AlphaTestOnKeyword = "_ALPHATEST_ON"; /// Shader keyword _TEXTURE_TRANSFORM public const string TextureTransformKeyword = "_TEXTURE_TRANSFORM"; /// Shader keyword _UV_CHANNEL_SELECT public const string UVChannelSelectKeyword = "_UV_CHANNEL_SELECT"; /// [Obsolete("Use MaterialProperty.AlphaCutoff instead.")] public static readonly int AlphaCutoffProperty = MaterialProperty.AlphaCutoff; /// [Obsolete("Use MaterialProperty.BaseColor instead.")] public static readonly int BaseColorProperty = MaterialProperty.BaseColor; /// [Obsolete("Use MaterialProperty.BaseColorTexture instead.")] public static readonly int BaseColorTextureProperty = MaterialProperty.BaseColorTexture; /// [Obsolete("Use MaterialProperty.BaseColorTextureRotation instead.")] public static readonly int BaseColorTextureRotationProperty = MaterialProperty.BaseColorTextureRotation; /// [Obsolete("Use MaterialProperty.BaseColorTextureScaleTransform instead.")] public static readonly int BaseColorTextureScaleTransformProperty = MaterialProperty.BaseColorTextureScaleTransform; /// [Obsolete("Use MaterialProperty.BaseColorTextureTexCoord instead.")] public static readonly int BaseColorTextureTexCoordProperty = MaterialProperty.BaseColorTextureTexCoord; /// [Obsolete("Use MaterialProperty.CullMode instead.")] public static readonly int CullModeProperty = MaterialProperty.CullMode; /// [Obsolete("Use MaterialProperty.Cull instead.")] public static readonly int CullProperty = MaterialProperty.Cull; /// [Obsolete("Use MaterialProperty.DstBlend instead.")] public static readonly int DstBlendProperty = MaterialProperty.DstBlend; /// [Obsolete("Use MaterialProperty.DiffuseFactor instead.")] public static readonly int DiffuseFactorProperty = MaterialProperty.DiffuseFactor; /// [Obsolete("Use MaterialProperty.DiffuseTexture instead.")] public static readonly int DiffuseTextureProperty = MaterialProperty.DiffuseTexture; /// [Obsolete("Use MaterialProperty.DiffuseTextureScaleTransform instead.")] public static readonly int DiffuseTextureScaleTransformProperty = MaterialProperty.DiffuseTextureScaleTransform; /// [Obsolete("Use MaterialProperty.DiffuseTextureRotation instead.")] public static readonly int DiffuseTextureRotationProperty = MaterialProperty.DiffuseTextureRotation; /// [Obsolete("Use MaterialProperty.DiffuseTextureTexCoord instead.")] public static readonly int DiffuseTextureTexCoordProperty = MaterialProperty.DiffuseTextureTexCoord; /// [Obsolete("Use MaterialProperty.EmissiveFactor instead.")] public static readonly int EmissiveFactorProperty = MaterialProperty.EmissiveFactor; /// [Obsolete("Use MaterialProperty.EmissiveTexture instead.")] public static readonly int EmissiveTextureProperty = MaterialProperty.EmissiveTexture; /// [Obsolete("Use MaterialProperty.EmissiveTextureRotation instead.")] public static readonly int EmissiveTextureRotationProperty = MaterialProperty.EmissiveTextureRotation; /// [Obsolete("Use MaterialProperty.EmissiveTextureScaleTransform instead.")] public static readonly int EmissiveTextureScaleTransformProperty = MaterialProperty.EmissiveTextureScaleTransform; /// [Obsolete("Use MaterialProperty.EmissiveTextureTexCoord instead.")] public static readonly int EmissiveTextureTexCoordProperty = MaterialProperty.EmissiveTextureTexCoord; /// [Obsolete("Use MaterialProperty.GlossinessFactor instead.")] public static readonly int GlossinessFactorProperty = MaterialProperty.GlossinessFactor; /// [Obsolete("Use MaterialProperty.NormalTexture instead.")] public static readonly int NormalTextureProperty = MaterialProperty.NormalTexture; /// [Obsolete("Use MaterialProperty.NormalTextureRotation instead.")] public static readonly int NormalTextureRotationProperty = MaterialProperty.NormalTextureRotation; /// [Obsolete("Use MaterialProperty.NormalTextureScaleTransform instead.")] public static readonly int NormalTextureScaleTransformProperty = MaterialProperty.NormalTextureScaleTransform; /// [Obsolete("Use MaterialProperty.NormalTextureTexCoord instead.")] public static readonly int NormalTextureTexCoordProperty = MaterialProperty.NormalTextureTexCoord; /// [Obsolete("Use MaterialProperty.NormalTextureScale instead.")] public static readonly int NormalTextureScaleProperty = MaterialProperty.NormalTextureScale; /// [Obsolete("Use MaterialProperty.Metallic instead.")] public static readonly int MetallicProperty = MaterialProperty.Metallic; /// [Obsolete("Use MaterialProperty.MetallicRoughnessMap instead.")] public static readonly int MetallicRoughnessMapProperty = MaterialProperty.MetallicRoughnessMap; /// [Obsolete("Use MaterialProperty.MetallicRoughnessMapScaleTransform instead.")] public static readonly int MetallicRoughnessMapScaleTransformProperty = MaterialProperty.MetallicRoughnessMapScaleTransform; /// [Obsolete("Use MaterialProperty.MetallicRoughnessMapRotation instead.")] public static readonly int MetallicRoughnessMapRotationProperty = MaterialProperty.MetallicRoughnessMapRotation; /// [Obsolete("Use MaterialProperty.MetallicRoughnessMapTexCoord instead.")] public static readonly int MetallicRoughnessMapUVChannelProperty = MaterialProperty.MetallicRoughnessMapTexCoord; /// [Obsolete("Use MaterialProperty.OcclusionTexture instead.")] public static readonly int OcclusionTextureProperty = MaterialProperty.OcclusionTexture; /// [Obsolete("Use MaterialProperty.OcclusionTextureStrength instead.")] public static readonly int OcclusionTextureStrengthProperty = MaterialProperty.OcclusionTextureStrength; /// [Obsolete("Use MaterialProperty.OcclusionTextureRotation instead.")] public static readonly int OcclusionTextureRotationProperty = MaterialProperty.OcclusionTextureRotation; /// [Obsolete("Use MaterialProperty.OcclusionTextureScaleTransform instead.")] public static readonly int OcclusionTextureScaleTransformProperty = MaterialProperty.OcclusionTextureScaleTransform; /// [Obsolete("Use MaterialProperty.OcclusionTextureTexCoord instead.")] public static readonly int OcclusionTextureTexCoordProperty = MaterialProperty.OcclusionTextureTexCoord; /// [Obsolete("Use MaterialProperty.RoughnessFactor instead.")] public static readonly int RoughnessFactorProperty = MaterialProperty.RoughnessFactor; /// [Obsolete("Use MaterialProperty.SpecularFactor instead.")] public static readonly int SpecularFactorProperty = MaterialProperty.SpecularFactor; /// [Obsolete("Use MaterialProperty.SpecularGlossinessTexture instead.")] public static readonly int SpecularGlossinessTextureProperty = MaterialProperty.SpecularGlossinessTexture; /// [Obsolete("Use MaterialProperty.SpecularGlossinessTextureScaleTransform instead.")] public static readonly int SpecularGlossinessTextureScaleTransformProperty = MaterialProperty.SpecularGlossinessTextureScaleTransform; /// [Obsolete("Use MaterialProperty.SpecularGlossinessTextureRotation instead.")] public static readonly int SpecularGlossinessTextureRotationProperty = MaterialProperty.SpecularGlossinessTextureRotation; /// [Obsolete("Use MaterialProperty.SpecularGlossinessTextureTexCoord instead.")] public static readonly int SpecularGlossinessTextureTexCoordProperty = MaterialProperty.SpecularGlossinessTextureTexCoord; /// [Obsolete("Use MaterialProperty.SrcBlend instead.")] public static readonly int SrcBlendProperty = MaterialProperty.SrcBlend; /// [Obsolete("Use MaterialProperty.ZWrite instead.")] public static readonly int ZWriteProperty = MaterialProperty.ZWrite; // ReSharper restore MemberCanBeProtected.Global // ReSharper restore MemberCanBePrivate.Global static IMaterialGenerator s_DefaultMaterialGenerator; static bool s_DefaultMaterialGenerated; static UnityEngine.Material s_DefaultMaterial; /// /// Provides the default material generator that's being used if no /// custom material generator was provided. The result depends on /// the currently used render pipeline. /// /// The default material generator /// Is thrown when the default material generator couldn't be determined based on the current render pipeline. public static IMaterialGenerator GetDefaultMaterialGenerator() { if (s_DefaultMaterialGenerator != null) return s_DefaultMaterialGenerator; var renderPipeline = RenderPipelineUtils.RenderPipeline; switch (renderPipeline) { #if UNITY_SHADER_GRAPH_12_OR_NEWER && GLTFAST_BUILTIN_SHADER_GRAPH case RenderPipeline.BuiltIn: s_DefaultMaterialGenerator = new BuiltInShaderGraphMaterialGenerator(); return s_DefaultMaterialGenerator; #elif GLTFAST_BUILTIN_RP || UNITY_EDITOR case RenderPipeline.BuiltIn: s_DefaultMaterialGenerator = new BuiltInMaterialGenerator(); return s_DefaultMaterialGenerator; #endif #if USING_URP case RenderPipeline.Universal: var urpAsset = (UniversalRenderPipelineAsset) (QualitySettings.renderPipeline ? QualitySettings.renderPipeline : GraphicsSettings.defaultRenderPipeline); s_DefaultMaterialGenerator = new UniversalRPMaterialGenerator(urpAsset); return s_DefaultMaterialGenerator; #endif case RenderPipeline.HighDefinition: #if USING_HDRP s_DefaultMaterialGenerator = new HighDefinitionRPMaterialGenerator(); return s_DefaultMaterialGenerator; #endif default: throw new InvalidOperationException($"Could not determine default MaterialGenerator (render pipeline {renderPipeline})"); } } /// /// Logger to be used for messaging. Can be null! /// protected ICodeLogger Logger { get; private set; } /// public UnityEngine.Material GetDefaultMaterial(bool pointsSupport = false) { if (pointsSupport) { Logger?.Warning(LogCode.TopologyPointsMaterialUnsupported); } if (!s_DefaultMaterialGenerated) { s_DefaultMaterial = GenerateDefaultMaterial(pointsSupport); s_DefaultMaterialGenerated = true; } return s_DefaultMaterial; } /// /// Creates a fallback material to be assigned to nodes without a material. /// /// If true, material has to support meshes with points topology /// fallback material protected abstract UnityEngine.Material GenerateDefaultMaterial(bool pointsSupport = false); /// /// Tries to load a shader and covers error handling. /// /// The requested shader's name. /// Logger used for reporting errors. /// Requested shader or null if it couldn't be loaded. protected static Shader FindShader(string shaderName, ICodeLogger logger) { var shader = Shader.Find(shaderName); if (shader == null) { logger?.Error(LogCode.ShaderMissing, shaderName); } return shader; } /// public abstract UnityEngine.Material GenerateMaterial( MaterialBase gltfMaterial, IGltfReadable gltf, bool pointsSupport = false ); /// public void SetLogger(ICodeLogger logger) { this.Logger = logger; } /// /// Attempts assigning a glTF texture to a Unity material. /// /// glTF source texture /// target material /// Context glTF /// Target texture property /// Scale/transform (_ST) property /// Rotation property /// UV channel selection property /// True if texture assignment was successful, false otherwise. protected bool TrySetTexture( TextureInfoBase textureInfo, UnityEngine.Material material, IGltfReadable gltf, int texturePropertyId, int scaleTransformPropertyId = -1, int rotationPropertyId = -1, int uvChannelPropertyId = -1 ) { if (textureInfo != null && textureInfo.index >= 0) { int textureIndex = textureInfo.index; var srcTexture = gltf.GetSourceTexture(textureIndex); if (srcTexture != null) { var texture = gltf.GetTexture(textureIndex); if (texture != null) { material.SetTexture(texturePropertyId, texture); // TODO: Implement texture transform and UV channel selection for all texture types and remove // this condition if (scaleTransformPropertyId >= 0 && rotationPropertyId >= 0 && uvChannelPropertyId >= 0) { var flipY = gltf.IsTextureYFlipped(textureIndex); TrySetTextureTransform( textureInfo, material, texturePropertyId, scaleTransformPropertyId, rotationPropertyId, uvChannelPropertyId, flipY ); } return true; } #if UNITY_IMAGECONVERSION Logger?.Error(LogCode.TextureLoadFailed,textureIndex.ToString()); #endif } else { Logger?.Error(LogCode.TextureNotFound, textureIndex.ToString()); } } return false; } // protected static bool DifferentIndex(TextureInfo a, TextureInfo b) { // return a != null && b != null && a.index>=0 && b.index>=0 && a.index != b.index; // } void TrySetTextureTransform( TextureInfoBase textureInfo, UnityEngine.Material material, int texturePropertyId, int scaleTransformPropertyId = -1, int rotationPropertyId = -1, int uvChannelPropertyId = -1, bool flipY = false ) { var hasTransform = false; // Scale (x,y) and Transform (z,w) var textureScaleTranslation = new float4( 1, 1,// scale 0, 0 // translation ); var texCoord = textureInfo.texCoord; if (textureInfo.Extensions?.KHR_texture_transform != null) { hasTransform = true; var tt = textureInfo.Extensions.KHR_texture_transform; if (tt.texCoord >= 0) { texCoord = tt.texCoord; } if (tt.offset != null) { textureScaleTranslation.z = tt.offset[0]; textureScaleTranslation.w = 1 - tt.offset[1]; } if (tt.scale != null) { textureScaleTranslation.x = tt.scale[0]; textureScaleTranslation.y = tt.scale[1]; } if (math.abs(tt.rotation) >= float.Epsilon) { var cos = math.cos(tt.rotation); var sin = math.sin(tt.rotation); var newRot = new Vector2(textureScaleTranslation.x * sin, textureScaleTranslation.y * -sin); Assert.IsTrue(rotationPropertyId >= 0, "Texture rotation property invalid!"); material.SetVector(rotationPropertyId, newRot); textureScaleTranslation.x *= cos; textureScaleTranslation.y *= cos; textureScaleTranslation.z -= newRot.y; // move offset to move rotation point (horizontally) } else { // Make sure the rotation is properly zeroed material.SetVector(rotationPropertyId, Vector4.zero); } textureScaleTranslation.w -= textureScaleTranslation.y; // move offset to move flip axis point (vertically) } if (texCoord != 0) { if (uvChannelPropertyId >= 0 && texCoord < 2f) { material.EnableKeyword(UVChannelSelectKeyword); material.SetFloat(uvChannelPropertyId, texCoord); } else { Logger?.Error(LogCode.UVMulti, texCoord.ToString()); } } if (flipY) { hasTransform = true; textureScaleTranslation.w = 1 - textureScaleTranslation.w; // flip offset in Y textureScaleTranslation.y = -textureScaleTranslation.y; // flip scale in Y } if (hasTransform) { material.EnableKeyword(TextureTransformKeyword); } material.SetTextureOffset(texturePropertyId, textureScaleTranslation.zw); material.SetTextureScale(texturePropertyId, textureScaleTranslation.xy); Assert.IsTrue(scaleTransformPropertyId >= 0, "Texture scale/transform property invalid!"); material.SetVector(scaleTransformPropertyId, textureScaleTranslation); } /// /// Approximates Transmission material effect for Render Pipelines / Shaders where filtering the /// backbuffer is not possible. /// /// glTF transmission extension data /// BaseColor reference. Alpha will be altered according to transmission /// True when the transmission can be approximated with Premultiply mode. False if blending is better protected static bool TransmissionWorkaroundShaderMode(Transmission transmission, ref Color baseColorLinear) { var min = Mathf.Min(Mathf.Min(baseColorLinear.r, baseColorLinear.g), baseColorLinear.b); var max = baseColorLinear.maxColorComponent; if (max - min < .1f) { // R/G/B components don't diverge too much // -> white/grey/black-ish color // -> Approximation via Transparent mode should be close to real transmission baseColorLinear.a *= 1 - transmission.transmissionFactor; return true; } else { // Color is somewhat saturated // -> Fallback to Blend mode // -> Dial down transmissionFactor by 50% to avoid material completely disappearing // Shows at least some color tinting baseColorLinear.a *= 1 - transmission.transmissionFactor * 0.5f; // Premultiply color? Decided not to. I preferred vivid (but too bright) colors over desaturation effect. // baseColorLinear.r *= baseColorLinear.a; // baseColorLinear.g *= baseColorLinear.a; // baseColorLinear.b *= baseColorLinear.a; return false; } } } }