Files
2025-11-30 08:35:03 +02:00

429 lines
17 KiB
C#

// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors
// SPDX-License-Identifier: Apache-2.0
using System;
using GLTFast.Materials;
using Unity.Mathematics;
using UnityEngine;
namespace GLTFast.Export
{
using Logging;
using Schema;
/// <summary>
/// Converts URP/HDRP Lit and Built-In Standard shader based materials to glTF materials
/// </summary>
public class StandardMaterialExport : MaterialExportBase
{
const string k_KeywordBumpMap = "_BUMPMAP";
const string k_KeywordEmission = "_EMISSION";
const string k_KeywordMetallicGlossMap = "_METALLICGLOSSMAP"; // Built-In Standard
#if USING_URP || USING_HDRP
const string k_KeywordMetallicSpecGlossMap = "_METALLICSPECGLOSSMAP"; // URP Lit
#endif
const string k_KeywordSmoothnessTextureAlbedoChannelA = "_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A";
static readonly int k_EmissionColor = Shader.PropertyToID("_EmissionColor");
static readonly int k_EmissionMap = Shader.PropertyToID("_EmissionMap");
static readonly int k_BumpMap = Shader.PropertyToID("_BumpMap");
static readonly int k_BumpScale = Shader.PropertyToID("_BumpScale");
static readonly int k_OcclusionMap = Shader.PropertyToID("_OcclusionMap");
static readonly int k_OcclusionStrength = Shader.PropertyToID("_OcclusionStrength");
static readonly int k_BaseMap = Shader.PropertyToID("_BaseMap");
static readonly int k_ColorTexture = Shader.PropertyToID("_ColorTexture");
static readonly int k_TintColor = Shader.PropertyToID("_TintColor");
static readonly int k_MetallicGlossMap = Shader.PropertyToID("_MetallicGlossMap");
static readonly int k_Glossiness = Shader.PropertyToID("_Glossiness");
static readonly int k_GlossMapScale = Shader.PropertyToID("_GlossMapScale");
/// <summary>
/// Converts a Unity material to a glTF material.
/// </summary>
/// <param name="uMaterial">Source material</param>
/// <param name="material">Resulting material</param>
/// <param name="gltf">Associated IGltfWriter. Is used for adding images and textures.</param>
/// <param name="logger">Logger used for reporting</param>
/// <returns>True if no errors occured, false otherwise</returns>
public override bool ConvertMaterial(UnityEngine.Material uMaterial, out Material material, IGltfWritable gltf, ICodeLogger logger)
{
var success = true;
material = new Material
{
name = uMaterial.name,
pbrMetallicRoughness = new PbrMetallicRoughness
{
metallicFactor = 0,
roughnessFactor = 1.0f
}
};
SetAlphaModeAndCutoff(uMaterial, material);
material.doubleSided = IsDoubleSided(uMaterial, MaterialProperty.Cull);
if (uMaterial.IsKeywordEnabled(k_KeywordEmission))
{
if (uMaterial.HasProperty(k_EmissionColor))
{
var emissionColor = uMaterial.GetColor(k_EmissionColor);
// Clamp emissionColor to 0..1
var maxFactor = math.max(emissionColor.r, math.max(emissionColor.g, emissionColor.b));
if (maxFactor > 1f)
{
emissionColor.r /= maxFactor;
emissionColor.g /= maxFactor;
emissionColor.b /= maxFactor;
// TODO: use maxFactor as emissiveStrength (KHR_materials_emissive_strength)
}
material.Emissive = emissionColor;
}
if (uMaterial.HasProperty(k_EmissionMap))
{
var emissionTex = uMaterial.GetTexture(k_EmissionMap);
if (emissionTex != null)
{
if (emissionTex is Texture2D)
{
material.emissiveTexture = ExportTextureInfo(emissionTex, gltf);
if (material.emissiveTexture != null)
{
ExportTextureTransform(material.emissiveTexture, uMaterial, k_EmissionMap, gltf);
}
}
else
{
logger?.Error(LogCode.TextureInvalidType, "emission", material.name);
success = false;
}
}
}
}
if (
uMaterial.HasProperty(k_BumpMap)
&& (uMaterial.IsKeywordEnabled(Materials.Constants.NormalMapKeyword)
|| uMaterial.IsKeywordEnabled(k_KeywordBumpMap))
)
{
var normalTex = uMaterial.GetTexture(k_BumpMap);
if (normalTex != null)
{
if (normalTex is Texture2D)
{
material.normalTexture = ExportNormalTextureInfo(normalTex, uMaterial, gltf, k_BumpScale);
if (material.normalTexture != null)
{
ExportTextureTransform(material.normalTexture, uMaterial, k_BumpMap, gltf);
}
}
else
{
logger?.Error(LogCode.TextureInvalidType, "normal", uMaterial.name);
success = false;
}
}
}
var isPbrMetallicRoughness = IsPbrMetallicRoughness(uMaterial);
var needsMetalRoughTexture =
isPbrMetallicRoughness &&
(
HasMetallicGlossMap(uMaterial)
|| uMaterial.IsKeywordEnabled(k_KeywordSmoothnessTextureAlbedoChannelA)
);
Texture2D occlusionTexture = null;
Texture2D metalGlossTexture = null;
Texture2D smoothnessTexture = null;
var mainTexProperty = MainTexProperty;
if (uMaterial.HasProperty(k_BaseMap))
{
mainTexProperty = k_BaseMap;
}
else if (uMaterial.HasProperty(k_ColorTexture))
{
mainTexProperty = k_ColorTexture;
}
if (IsUnlit(uMaterial))
{
ExportUnlit(material, uMaterial, mainTexProperty, gltf, logger);
}
else if (isPbrMetallicRoughness)
{
success &= ExportPbrMetallicRoughness(
uMaterial,
material,
mainTexProperty,
gltf,
logger,
out metalGlossTexture,
out smoothnessTexture
);
}
else if (uMaterial.HasProperty(mainTexProperty))
{
var mainTex = uMaterial.GetTexture(mainTexProperty);
material.pbrMetallicRoughness = new PbrMetallicRoughness
{
metallicFactor = 0,
roughnessFactor = 1.0f,
BaseColor = uMaterial.HasProperty(BaseColorProperty)
? uMaterial.GetColor(BaseColorProperty).linear
: Color.white
};
if (mainTex != null)
{
material.pbrMetallicRoughness.baseColorTexture = ExportTextureInfo(mainTex, gltf);
if (material.pbrMetallicRoughness.baseColorTexture != null)
{
ExportTextureTransform(material.pbrMetallicRoughness.baseColorTexture, uMaterial, mainTexProperty, gltf);
}
}
if (uMaterial.HasProperty(k_TintColor))
{
//particles use _TintColor instead of _Color
material.pbrMetallicRoughness.BaseColor = uMaterial.GetColor(k_TintColor).linear;
}
}
if (uMaterial.HasProperty(k_OcclusionMap))
{
var occTex = uMaterial.GetTexture(k_OcclusionMap);
if (occTex != null)
{
if (occTex is Texture2D occTex2d)
{
if (!needsMetalRoughTexture)
{
material.occlusionTexture = ExportOcclusionTextureInfo(occTex2d, gltf);
}
else
{
material.occlusionTexture = new OcclusionTextureInfo();
occlusionTexture = occTex2d;
}
if (material.occlusionTexture != null)
{
ExportTextureTransform(
material.occlusionTexture,
uMaterial,
mainTexProperty, // Standard and Lit re-use main texture transform
gltf
);
}
}
else
{
logger?.Error(LogCode.TextureInvalidType, "occlusion", material.name);
success = false;
}
}
}
if (needsMetalRoughTexture && material.pbrMetallicRoughness != null)
{
var ormImageExport = new OrmImageExport(metalGlossTexture, occlusionTexture, smoothnessTexture);
if (MaterialExport.AddImageExport(gltf, ormImageExport, out var ormTextureId))
{
if (material.pbrMetallicRoughness.MetallicRoughnessTexture != null)
{
material.PbrMetallicRoughness.MetallicRoughnessTexture.index = ormTextureId;
ExportTextureTransform(material.PbrMetallicRoughness.MetallicRoughnessTexture, uMaterial, k_MetallicGlossMap, gltf);
}
if (ormImageExport.HasOcclusion)
{
material.occlusionTexture.index = ormTextureId;
}
}
#if UNITY_IMAGECONVERSION
else {
logger?.Error(LogCode.ExportImageFailed);
}
#endif
}
if (material.occlusionTexture != null)
{
if (uMaterial.HasProperty(k_OcclusionStrength))
{
material.occlusionTexture.strength = uMaterial.GetFloat(k_OcclusionStrength);
}
}
return success;
}
static bool IsPbrMetallicRoughness(UnityEngine.Material material)
{
return material.HasProperty(MetallicProperty)
&& (
HasMetallicGlossMap(material)
|| material.HasProperty(k_Glossiness)
|| material.HasProperty(SmoothnessProperty)
);
}
static bool ExportPbrMetallicRoughness(
UnityEngine.Material uMaterial,
Material material,
int mainTexProperty,
IGltfWritable gltf,
ICodeLogger logger,
out Texture2D metalGlossTexture,
out Texture2D smoothnessTexture
)
{
metalGlossTexture = null;
smoothnessTexture = null;
var success = true;
var pbr = new PbrMetallicRoughness { metallicFactor = 0, roughnessFactor = 1.0f };
var hasAlphaSmoothness = uMaterial.IsKeywordEnabled(k_KeywordSmoothnessTextureAlbedoChannelA);
if (uMaterial.HasProperty(BaseColorProperty))
{
pbr.BaseColor = uMaterial.GetColor(BaseColorProperty).linear;
}
else
if (uMaterial.HasProperty(ColorProperty))
{
pbr.BaseColor = uMaterial.GetColor(ColorProperty).linear;
}
if (uMaterial.HasProperty(k_TintColor))
{
//particles use _TintColor instead of _Color
float white = 1;
if (uMaterial.HasProperty(ColorProperty))
{
var c = uMaterial.GetColor(ColorProperty);
white = (c.r + c.g + c.b) / 3.0f; //multiply alpha by overall whiteness of TintColor
}
pbr.BaseColor = (uMaterial.GetColor(k_TintColor) * white).linear;
}
if (uMaterial.HasProperty(mainTexProperty))
{
// TODO if additive particle, render black into alpha
// TODO use private Material.GetFirstPropertyNameIdByAttribute here, supported from 2020.1+
var mainTex = uMaterial.GetTexture(mainTexProperty);
if (mainTex)
{
if (mainTex is Texture2D)
{
pbr.baseColorTexture = ExportTextureInfo(
mainTex,
gltf,
// Force RGB for the baseColor, so that the alpha (which is smoothness)
// is not used for alpha-opacity
hasAlphaSmoothness
? ImageFormat.Jpg
: ImageFormat.Unknown
);
if (pbr.BaseColorTexture != null)
{
ExportTextureTransform(pbr.BaseColorTexture, uMaterial, mainTexProperty, gltf);
}
}
else
{
logger?.Error(LogCode.TextureInvalidType, "main", uMaterial.name);
success = false;
}
}
}
if (uMaterial.HasProperty(MetallicProperty) && !HasMetallicGlossMap(uMaterial))
{
pbr.metallicFactor = uMaterial.GetFloat(MetallicProperty);
}
if (uMaterial.HasProperty(k_Glossiness) || uMaterial.HasProperty(SmoothnessProperty))
{
var smoothnessPropId = uMaterial.HasProperty(SmoothnessProperty) ? SmoothnessProperty : k_Glossiness;
var metallicGlossMap = uMaterial.HasProperty(k_MetallicGlossMap) ? uMaterial.GetTexture(k_MetallicGlossMap) : null;
var smoothness = uMaterial.GetFloat(smoothnessPropId);
pbr.roughnessFactor = (metallicGlossMap != null || hasAlphaSmoothness) && uMaterial.HasProperty(k_GlossMapScale)
? uMaterial.GetFloat(k_GlossMapScale)
: 1f - smoothness;
}
if (uMaterial.HasProperty(k_MetallicGlossMap))
{
var mrTex = uMaterial.GetTexture(k_MetallicGlossMap);
if (mrTex != null)
{
if (mrTex is Texture2D mrTex2d)
{
pbr.metallicRoughnessTexture = pbr.metallicRoughnessTexture ?? new TextureInfo();
metalGlossTexture = mrTex2d;
if (HasMetallicGlossMap(uMaterial))
pbr.metallicFactor = 1.0f;
ExportTextureTransform(pbr.metallicRoughnessTexture, uMaterial, k_MetallicGlossMap, gltf);
}
else
{
logger?.Error(LogCode.TextureInvalidType, "metallic/gloss", uMaterial.name);
success = false;
}
}
}
if (uMaterial.IsKeywordEnabled(k_KeywordSmoothnessTextureAlbedoChannelA))
{
var smoothnessTex = uMaterial.GetTexture(mainTexProperty) as Texture2D;
if (smoothnessTex != null)
{
pbr.metallicRoughnessTexture = pbr.metallicRoughnessTexture ?? new TextureInfo();
smoothnessTexture = smoothnessTex;
ExportTextureTransform(pbr.metallicRoughnessTexture, uMaterial, mainTexProperty, gltf);
}
}
material.pbrMetallicRoughness = pbr;
return success;
}
static bool HasMetallicGlossMap(UnityEngine.Material uMaterial)
{
return uMaterial.IsKeywordEnabled(k_KeywordMetallicGlossMap) // Built-In Standard
#if USING_URP || USING_HDRP
|| uMaterial.IsKeywordEnabled(k_KeywordMetallicSpecGlossMap) // URP Lit
#endif
;
}
static OcclusionTextureInfo ExportOcclusionTextureInfo(
UnityEngine.Texture texture,
IGltfWritable gltf
)
{
var texture2d = texture as Texture2D;
if (texture2d == null)
{
return null;
}
var imageExport = new ImageExport(texture2d);
if (MaterialExport.AddImageExport(gltf, imageExport, out var textureId))
{
return new OcclusionTextureInfo
{
index = textureId
};
}
return null;
}
}
}