You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity GLTF导出器材质/纹理嵌入异常问题排查

GLTF导出材质全白问题排查与解决

问题描述

测试GLTF导出器包时,将简单门窗对象拖入场景导出后,模型未显示对应材质,呈现全白状态。之前遇到过FBX导出器类似问题,通过重映射材质解决,想了解GLTF是否适用该方法或有其他解决方案。

导出前后对比

  • 导出前:导出前场景
  • 导出后:导出后模型

导出器核心代码片段

public GLTFSceneExporter(Transform[] rootTransforms, ExportOptions options)
{
    _exportOptions = options;

    var metalGlossChannelSwapShader = Resources.Load("MetalGlossChannelSwap", typeof(Shader)) as Shader;
    _metalGlossChannelSwapMaterial = new Material(metalGlossChannelSwapShader);

    var normalChannelShader = Resources.Load("NormalChannel", typeof(Shader)) as Shader;
    _normalChannelMaterial = new Material(normalChannelShader);

    _rootTransforms = rootTransforms;
    _root = new GLTFRoot
    {
        Accessors = new List<Accessor>(),
        Asset = new Asset
        {
            Version = "2.0"
        },
        Buffers = new List<GLTFBuffer>(),
        BufferViews = new List<BufferView>(),
        Cameras = new List<GLTFCamera>(),
        Images = new List<GLTFImage>(),
        Materials = new List<GLTFMaterial>(),
        Meshes = new List<GLTFMesh>(),
        Nodes = new List<Node>(),
        Samplers = new List<Sampler>(),
        Scenes = new List<GLTFScene>(),
        Textures = new List<GLTFTexture>()
    };

    _imageInfos = new List<ImageInfo>();
    _materials = new List<Material>();
    _textures = new List<Texture>();

    _buffer = new GLTFBuffer();
    _bufferId = new BufferId
    {
        Id = _root.Buffers.Count,
        Root = _root
    };
    _root.Buffers.Add(_buffer);
}

private void ExportNormalTexture(Texture2D texture, string outputPath)
{
    var destRenderTexture = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);

    Graphics.Blit(texture, destRenderTexture, _normalChannelMaterial);

    var exportTexture = new Texture2D(texture.width, texture.height);
    exportTexture.ReadPixels(new Rect(0, 0, destRenderTexture.width, destRenderTexture.height), 0, 0);
    exportTexture.Apply();

    var finalFilenamePath = ConstructImageFilenamePath(texture, outputPath);
    File.WriteAllBytes(finalFilenamePath, exportTexture.EncodeToPNG());

    RenderTexture.ReleaseTemporary(destRenderTexture);
    if (Application.isEditor)
    {
        GameObject.DestroyImmediate(exportTexture);
    }
    else
    {
        GameObject.Destroy(exportTexture);
    }
}

private void ExportTexture(Texture2D texture, string outputPath)
{
    var destRenderTexture = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);

    Graphics.Blit(texture, destRenderTexture);

    var exportTexture = new Texture2D(texture.width, texture.height);
    exportTexture.ReadPixels(new Rect(0, 0, destRenderTexture.width, destRenderTexture.height), 0, 0);
    exportTexture.Apply();

    var finalFilenamePath = ConstructImageFilenamePath(texture, outputPath);
    File.WriteAllBytes(finalFilenamePath, exportTexture.EncodeToPNG());

    RenderTexture.ReleaseTemporary(destRenderTexture);
    if (Application.isEditor)
    {
        GameObject.DestroyImmediate(exportTexture);
    }
    else
    {
        GameObject.Destroy(exportTexture);
    }
}

private MaterialId ExportMaterial(Material materialObj)
{
    MaterialId id = GetMaterialId(_root, materialObj);

    //if material has already been exported, return the existing id
    if (id != null)
    {
        return id;
    }

    //else create a new GLTF material
    var material = new GLTFMaterial();

    if (ExportNames)
    {
        material.Name = materialObj.name;
    }

    //if the material has the property of cutoff, 
    if (materialObj.HasProperty("_Cutoff"))
    {
        //extract the cutoff value from the material and apply it to the GLTF material
        material.AlphaCutoff = materialObj.GetFloat("_Cutoff");
    }

    switch (materialObj.GetTag("RenderType", false, ""))
    {
        case "TransparentCutout":
            //if the tag is transparentcutout, the GLTF material alphamode is set to AlphaMode.mask, which will have a binary transparency affect
            material.AlphaMode = AlphaMode.MASK;
            material.Name = material.Name + "_C";
            break;
        case "Transparent":
            //if the tag is transparent, the GLTF material alphamode is set to AlphaMode.blend, which will have smooth transparency (allows objects to be see thru)
            material.AlphaMode = AlphaMode.BLEND;
            material.Name = material.Name + "_T";
            break;
        default:
            //if no tags match, default is opaque, which means nothing can be see thru
            material.AlphaMode = AlphaMode.OPAQUE;
            break;
    }

    material.DoubleSided = materialObj.HasProperty("_Cull") &&
        materialObj.GetInt("_Cull") == (float)CullMode.Off;
        
    if (materialObj.HasProperty("_EmissionColor"))
    {
        material.EmissiveFactor = materialObj.GetColor("_EmissionColor").ToNumericsColorRaw();
    }

    if (materialObj.HasProperty("_EmissionMap"))
    {
        var emissionTex = materialObj.GetTexture("_EmissionMap");

        if (emissionTex != null)
        {
            if(emissionTex is Texture2D)
            {
                material.EmissiveTexture = ExportTextureInfo(emissionTex, TextureMapType.Emission);

                ExportTextureTransform(material.EmissiveTexture, materialObj, "_EmissionMap");
            }
            else
            {
                Debug.LogErrorFormat("Can't export a {0} emissive texture in material {1}", emissionTex.GetType(), materialObj.name);
            }

        }
    }

    if (materialObj.HasProperty("_BumpMap"))
    {
        var normalTex = materialObj.GetTexture("_BumpMap");

        if (normalTex != null)
        {
            if(normalTex is Texture2D)
            {
                material.NormalTexture = ExportNormalTextureInfo(normalTex, TextureMapType.Bump, materialObj);
                ExportTextureTransform(material.NormalTexture, materialObj, "_BumpMap");
            }
            else
            {
                Debug.LogErrorFormat("Can't export a {0} normal texture in material {1}", normalTex.GetType(), materialObj.name);
            }
        }
    }

    if (materialObj.HasProperty("_OcclusionMap"))
    {
        var occTex = materialObj.GetTexture("_OcclusionMap");
        if (occTex != null)
        {
            if(occTex is Texture2D)
            {
                material.OcclusionTexture = ExportOcclusionTextureInfo(occTex, TextureMapType.Occlusion, materialObj);
                ExportTextureTransform(material.OcclusionTexture, materialObj, "_OcclusionMap");
            }
            else
            {
                Debug.LogErrorFormat("Can't export a {0} occlusion texture in material {1}", occTex.GetType(), materialObj.name);
            }
        }
    }

    if (IsPBRMetallicRoughness(materialObj))
    {
        material.PbrMetallicRoughness = ExportPBRMetallicRoughness(materialObj);
    }
    else if (IsCommonConstant(materialObj))
    {
        material.CommonConstant = ExportCommonConstant(materialObj);
    }

    _materials.Add(materialObj);

    id = new MaterialId
    {
        Id = _root.Materials.Count,
        Root = _root
    };
    _root.Materials.Add(material);

    return id;
}

解决方案

1. 修复材质类型覆盖逻辑

从代码看,导出器仅处理PBR金属粗糙度和通用常量两类材质,若你的门窗材质不属于这两类,会生成无基础颜色/纹理的GLTF材质,导致全白。

处理方式:

  • 检查IsPBRMetallicRoughness和IsCommonConstant的判定规则,确保覆盖你的材质类型;
  • 补充Unity标准材质的导出分支,示例代码:
    else if (materialObj.shader.name == "Standard")
    {
        var pbr = new PbrMetallicRoughness();
        // 映射Unity标准材质参数到GLTF PBR结构
        pbr.BaseColorFactor = materialObj.GetColor("_Color").ToNumericsColorRaw();
        if (materialObj.HasProperty("_MainTex"))
        {
            var mainTex = materialObj.GetTexture("_MainTex") as Texture2D;
            if (mainTex != null)
            {
                pbr.BaseColorTexture = ExportTextureInfo(mainTex, TextureMapType.BaseColor);
                ExportTextureTransform(pbr.BaseColorTexture, materialObj, "_MainTex");
            }
        }
        // Unity的Glossiness与GLTF的Roughness反向,需转换
        pbr.MetallicFactor = materialObj.GetFloat("_Metallic");
        pbr.RoughnessFactor = 1 - materialObj.GetFloat("_Glossiness");
        material.PbrMetallicRoughness = pbr;
    }
    

2. 验证纹理导出与引用

  • 确认ConstructImageFilenamePath生成的路径正确,纹理文件与GLTF文件目录匹配;
  • 检查ExportTextureInfo是否正确将纹理关联到GLTF材质的对应属性(如基础颜色纹理)。

3. 材质重映射(兼容FBX解决方案思路)

GLTF同样支持材质重映射,导出前将非兼容材质替换为导出器支持的类型:

// 遍历所有渲染器替换材质
foreach (var renderer in rootTransforms.SelectMany(t => t.GetComponentsInChildren<Renderer>()))
{
    List<Material> newMaterials = new List<Material>();
    foreach (var mat in renderer.sharedMaterials)
    {
        if (!IsPBRMetallicRoughness(mat) && !IsCommonConstant(mat))
        {
            // 创建临时PBR材质并复制原参数
            Material tempMat = new Material(Shader.Find("Standard"));
            tempMat.CopyPropertiesFromMaterial(mat);
            newMaterials.Add(tempMat);
        }
        else
        {
            newMaterials.Add(mat);
        }
    }
    renderer.sharedMaterials = newMaterials.ToArray();
}

4. 调试验证

  • 导出后打开GLTF文件,检查materials节点是否包含pbrMetallicRoughness或commonConstant属性,以及textures引用是否有效;
  • 使用GLTF Viewer加载导出文件,查看控制台报错定位缺失资源或属性。

内容的提问来源于stack exchange,提问作者ferb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 19:17:04