// SPDX-FileCopyrightText: 2024 Unity Technologies and the glTFast authors // SPDX-License-Identifier: Apache-2.0 using System; using GLTFast.Logging; using GLTFast.Materials; using GLTFast.Schema; using Unity.Mathematics; using UnityEngine; using UnityEngine.Rendering; using Material = UnityEngine.Material; namespace GLTFast.Export { /// /// Converts Unity Materials that use a glTFast shader to glTF materials /// public abstract class GltfMaterialExporter : MaterialExportBase { /// public override bool ConvertMaterial( Material unityMaterial, out GLTFast.Schema.Material material, IGltfWritable gltf, ICodeLogger logger) { material = new GLTFast.Schema.Material { name = unityMaterial.name, pbrMetallicRoughness = new PbrMetallicRoughness(), doubleSided = IsDoubleSided(unityMaterial) }; var alphaMode = GetAlphaMode(unityMaterial); material.SetAlphaMode(alphaMode); if (alphaMode == MaterialBase.AlphaMode.Mask) { material.alphaCutoff = GetAlphaCutoff(unityMaterial); } material = HandlePbrMetallicRoughness(gltf, material, unityMaterial); material = HandleNormal(gltf, material, unityMaterial); material = HandleOcclusion(gltf, material, unityMaterial); material = HandleEmission(gltf, material, unityMaterial); return true; } /// /// Extracts the glTF alpha mode from a Unity material. /// /// /// Unity material. /// glTF alpha mode. protected abstract MaterialBase.AlphaMode GetAlphaMode(Material material); /// /// Returns that material's alpha cutoff threshold. /// /// Unity material. /// /// Alpha cutoff threshold value. protected abstract float GetAlphaCutoff(Material material); /// /// Indicates whether (back-face) culling should be disabled. /// /// /// Unity material. /// True if material does not do back-face culling. False otherwise. protected abstract bool IsDoubleSided(Material material); static GLTFast.Schema.Material HandlePbrMetallicRoughness( IGltfWritable gltf, GLTFast.Schema.Material material, Material unityMaterial) { if (TryGetValue(unityMaterial, MaterialProperty.BaseColorTexture, out Texture2D texture2D)) { if (MaterialExport.AddImageExport(gltf, new ImageExport(texture2D), out var textureId)) { var textureInfo = new TextureInfo { index = textureId, texCoord = GetValue(unityMaterial, MaterialProperty.BaseColorTextureTexCoord) }; material.pbrMetallicRoughness.baseColorTexture = textureInfo; if (TryCreateTextureTransform( gltf, unityMaterial, MaterialProperty.BaseColorTextureScaleTransform, MaterialProperty.BaseColorTextureRotation, out var textureTransform )) { material.pbrMetallicRoughness.baseColorTexture.extensions = new TextureInfoExtensions { KHR_texture_transform = textureTransform }; } } } if (TryGetValue(unityMaterial, MaterialProperty.BaseColor, out Color baseColor)) { material.pbrMetallicRoughness.BaseColor = baseColor.linear; } material = HandleMetallicRoughness(gltf, material, unityMaterial); return material; } static GLTFast.Schema.Material HandleMetallicRoughness( IGltfWritable gltf, GLTFast.Schema.Material material, Material unityMaterial) { if (TryGetValue(unityMaterial, MaterialProperty.MetallicRoughnessMap, out Texture2D texture2D) && MaterialExport.AddImageExport(gltf, new ImageExport(texture2D), out var textureId)) { var textureInfo = new TextureInfo { index = textureId, texCoord = GetValue(unityMaterial, MaterialProperty.MetallicRoughnessMapTexCoord) }; if (TryCreateTextureTransform( gltf, unityMaterial, MaterialProperty.MetallicRoughnessMapScaleTransform, MaterialProperty.MetallicRoughnessMapRotation, out var textureTransform) ) { textureInfo.extensions = new TextureInfoExtensions { KHR_texture_transform = textureTransform }; } material.pbrMetallicRoughness.metallicRoughnessTexture = textureInfo; } if (TryGetValue(unityMaterial, MaterialProperty.Metallic, out float metallicFactor)) { material.pbrMetallicRoughness.metallicFactor = metallicFactor; } if (TryGetValue(unityMaterial, MaterialProperty.RoughnessFactor, out float roughnessFactor)) { material.pbrMetallicRoughness.roughnessFactor = roughnessFactor; } return material; } static GLTFast.Schema.Material HandleNormal( IGltfWritable gltf, GLTFast.Schema.Material material, Material unityMaterial) { if (!TryGetValue(unityMaterial, MaterialProperty.NormalTexture, out Texture2D texture2D)) { return material; } if (!MaterialExport.AddImageExport(gltf, new NormalImageExport(texture2D), out var textureId)) { return material; } TryGetValue(unityMaterial, MaterialProperty.NormalTextureScale, out float normalScale); var textureInfo = new NormalTextureInfo { index = textureId, texCoord = GetValue(unityMaterial, MaterialProperty.NormalTextureTexCoord), scale = normalScale }; material.normalTexture = textureInfo; if (TryCreateTextureTransform( gltf, unityMaterial, MaterialProperty.NormalTextureScaleTransform, MaterialProperty.NormalTextureRotation, out var textureTransform )) { material.normalTexture.extensions = new TextureInfoExtensions { KHR_texture_transform = textureTransform }; } return material; } static GLTFast.Schema.Material HandleOcclusion( IGltfWritable gltf, GLTFast.Schema.Material material, Material unityMaterial) { if (!TryGetValue(unityMaterial, MaterialProperty.OcclusionTexture, out Texture2D texture2D)) { return material; } if (!MaterialExport.AddImageExport(gltf, new ImageExport(texture2D), out var textureId)) { return material; } TryGetValue(unityMaterial, MaterialProperty.OcclusionTextureStrength, out float occlusionStrength); var info = new OcclusionTextureInfo { index = textureId, texCoord = GetValue(unityMaterial, MaterialProperty.OcclusionTextureTexCoord), strength = occlusionStrength }; material.occlusionTexture = info; if (TryCreateTextureTransform( gltf, unityMaterial, MaterialProperty.OcclusionTextureScaleTransform, MaterialProperty.OcclusionTextureRotation, out var textureTransform )) { material.occlusionTexture.extensions = new TextureInfoExtensions { KHR_texture_transform = textureTransform }; } return material; } static GLTFast.Schema.Material HandleEmission( IGltfWritable gltf, GLTFast.Schema.Material material, Material unityMaterial) { if (TryGetValue(unityMaterial, MaterialProperty.EmissiveTexture, out Texture2D texture2D)) { if (MaterialExport.AddImageExport(gltf, new ImageExport(texture2D), out var textureId)) { var info = new TextureInfo { index = textureId, texCoord = GetValue(unityMaterial, MaterialProperty.EmissiveTextureTexCoord) }; material.emissiveTexture = info; if (TryCreateTextureTransform( gltf, unityMaterial, MaterialProperty.EmissiveTextureScaleTransform, MaterialProperty.EmissiveTextureRotation, out var textureTransform )) { material.emissiveTexture.extensions = new TextureInfoExtensions { KHR_texture_transform = textureTransform }; } } } if (TryGetValue(unityMaterial, MaterialProperty.EmissiveFactor, out Color emissiveFactor)) { material.Emissive = emissiveFactor; } return material; } internal static bool TryCreateTextureTransform( IGltfWritable gltf, Material uMaterial, int scaleTransformPropertyId, int rotationPropertyId, out TextureTransform result ) { result = null; if (!uMaterial.IsKeywordEnabled("_TEXTURE_TRANSFORM")) { return false; } var st = uMaterial.GetVector(scaleTransformPropertyId); var r = uMaterial.GetVector(rotationPropertyId); if (math.abs(st.z) >= float.Epsilon || math.abs(st.w) >= float.Epsilon) { result ??= new TextureTransform(); result.offset = new[] { st.z, st.w }; } var uvTransform = UvTransform.FromMatrix(new float2x2(st.x, st.y, r.x, r.y)); if (math.abs(uvTransform.rotation) > float.Epsilon) { result ??= new TextureTransform(); result.rotation = uvTransform.rotation; } if (math.abs(uvTransform.scale.x - 1) > math.EPSILON || math.abs(uvTransform.scale.y - 1) > math.EPSILON) { result ??= new TextureTransform(); result.scale = new[] { uvTransform.scale[0], uvTransform.scale[1] }; } if (result != null) { gltf.RegisterExtensionUsage(Extension.TextureTransform); return true; } return false; } internal static bool TryGetValue(Material material, int propertyId, out float value) { if (!material.HasProperty(propertyId)) { value = default; return false; } value = material.GetFloat(propertyId); return true; } internal static bool TryGetValue(Material material, int propertyId, out int value) { if (TryGetValue(material, propertyId, out float floatValue)) { value = (int)floatValue; return true; } value = default; return false; } internal static int GetValue(Material material, int propertyId) { return !TryGetValue(material, propertyId, out int value) ? default : value; } internal static bool TryGetValue(Material material, int propertyId, out Color value) { if (!material.HasProperty(propertyId)) { value = default; return false; } value = material.GetColor(propertyId); return true; } internal static bool TryGetValue(Material material, int propertyId, out Texture2D value) { if (!material.HasProperty(propertyId)) { value = default; return false; } value = (Texture2D)material.GetTexture(propertyId); return !(value is null); } } }