Skip to content

GLTF: Read material texture "texCoord" property on import#96748

Open
aaronfranke wants to merge 1 commit intogodotengine:masterfrom
aaronfranke:gltf-tex-coord
Open

GLTF: Read material texture "texCoord" property on import#96748
aaronfranke wants to merge 1 commit intogodotengine:masterfrom
aaronfranke:gltf-tex-coord

Conversation

@aaronfranke
Copy link
Member

@aaronfranke aaronfranke commented Sep 9, 2024

Fixes #80858 and fixes #93884 and fixes #100172 to the extent which is possible in the glTF code.

Supersedes PR #80522, supersedes PR #106786.

Godot uses UV1 for the base color, normal, and metallic/roughness. It can also have occlusion and emission on either UV1 or UV2. The glTF spec allows any texture to use any UV map, so not everything in glTF is possible in Godot.

In the current master, the glTF import code does not read this property, and expects that every texture is on UV1. We can do better than that. With this PR, the glTF import code will do its best to consider the UV preferences of material textures, allowing for many more configurations such as color+normal+metallic on a different texCoord, or occlusion/emission on a separate texCoord. When the code encounters a configuration not supported by Godot, it will print a warning.

Here is the test file from #93884. Left is current master, right is with this PR.

Screenshot 2024-09-09 at 2 31 49 AM

Here is the test file from #80858.

Screenshot 2024-10-09 at 9 08 48 PM

This PR also handles the case of exporting materials with emission and/or occlusion on UV2, while previously they always exported as if they were on UV1. The code for exporting is much more straightforward than importing, the only changes are in the _serialize_materials function.

While not all glTF files will have their UV maps perfectly imported into Godot, this should be enough to ensure that exporting from Godot and importing back into Godot will preserve any UV2 map usage.

Also, I renamed some local variables for readability (ex: p -> mesh_prim_dict, sgm -> spec_gloss_ext_dict).

I'm labeling this as an enhancement because support for UV2 in glTF is a new feature, but arguably it's a bug because this is a case of Godot not following the glTF specification.

@fire
Copy link
Member

fire commented Sep 9, 2024

I am in favour of the proposal. Did not technical review yet.

Copy link
Member

@fire fire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronfranke what's a prose version of the algorithm for picking the coordinates?

@aaronfranke
Copy link
Member Author

@fire The prose version is that we want to read what's in the file, except that to fit within Godot's material system, we can only read at most two UVs, and the second UV can only be used for certain things. Therefore, we prefer reading the most important UV maps first to provide the best mapping, starting with the base color / albedo texture, and continuing on to less important UV maps.

@fire
Copy link
Member

fire commented Oct 7, 2024

So like a greedy algorithm that has 2 slots and from the inputs we fit the base color / albedo texture to the first slot and then the others.

@lyuma
Copy link
Contributor

lyuma commented Oct 30, 2024

I am not personally in favor of this proposal as is. In short, this creates a few hardcoded rules which impact the mesh depending on a series of conditions, and this logic will never be able to change without breaking compatibility.

Most importantly, I couldn't find the import option to control this. Having an option to control this behavior is an absolute necessity. For example, the change may alter the mesh channels of content already imported into Godot, perhaps using a custom ,tres material.

The thing I am worried about is that this unconditionally changes the order of the UV channels, based on materials which may even be replaced or swapped during the import process.

In my experience, it is common for the UV channels to have semantic meaning, for example TEXCOORD_1 is often used for lightmapping while TEXCOORD_0 is conventionally used for albedo. This flips it around, so that the material attached to a particular mesh determines the UV channels to more closely match with this semantic. While there is some sense in this, it means that the mesh data will be drastically affected by the presence of various settings on the materials.

For example, if the glTF is exported without materials, or perhaps if the user changes some of their material nodes in Blender before export, the order of mesh channels in Godot will be unexpectedly flipped, and the cause will be difficult to diagnose, for example depending on the presence of albedo or emission in blender nodes which may not even be used in Godot.

Still, I might be swayed to be okay with this as an import option.

@aaronfranke
Copy link
Member Author

aaronfranke commented Oct 30, 2024

@lyuma Currently, meshes are plainly broken on import if they are using different UV maps from what you mention is the common convention. I'm not inclined to keep adding more and more import options to preserve broken behavior. I prefer to just have the correct behavior be the only option.

If users are expecting UV maps to be preserved as-is, that is already not the case. Godot doesn't support more than 2 UV maps, so any others are discarded. Furthermore, currently UV2 is always unused on import, and can only be used with custom materials, this PR fixes that.

In my opinion, the primary focus should be to ensure that the glTF's intended appearance is correct in Godot, not in keeping texCoord 0 mapped to Godot's UV1. I don't believe it is worth supporting import of a file where the user explicitly wants Godot to use the wrong UV map. To me it just seems like nonsense behavior that there would be an option that says "yes, import me with the wrong visuals" for the sake of always using the first UV map as the used one. It's better to just always use the UV map that the glTF file says is the correct UV map.

As for a user exporting a glTF without materials and getting different behavior - I don't think it's reasonable to expect the same behavior between different files. Actually, in fact, it may be good that this is the case. This way, if users want the behavior you want, where texCoord 0 is imported as UV1, they can just not export materials. If the user is not putting materials in the glTF file, then they need to specify them in Godot anyway if they want materials.

@huwpascoe
Copy link
Contributor

huwpascoe commented Oct 30, 2024

I agree with what this PR is doing, the GLTF should be visually represented in engine as closely as possible, even if that means there are no guarantees of how data is mapped.

The thing I am worried about is that this unconditionally changes the order of the UV channels, based on materials which may even be replaced or swapped during the import process.

Yes, sometimes you do want explicit control over how things are mapped: #97756.
Perhaps rather than a script extension, it could be fully realized as a property group to override default behavior?

@aaronfranke
Copy link
Member Author

Updated to account for PR #94165, supporting animated UV maps on textures on both import and export to the best extent possible with Godot's material system. I tested UV map animation on the red chair and it works, and also tested the animation pointer test file again just in case and that also works.

@fire
Copy link
Member

fire commented Nov 6, 2024

I would ideally want to see something like #96748 (comment), do you think it's possible for us to do in this or another pr?

@aaronfranke
Copy link
Member Author

aaronfranke commented Nov 6, 2024

@fire There is PR #97756 from @huwpascoe which implements the ability to import custom attributes. That way users can use that system to preserve a specific UV map, separate from the material's UV1/UV2 maps.

@huwpascoe
Copy link
Contributor

Perhaps rather than a script extension, it could be fully realized as a property group to override default behavior?

I've determined that the UI approach is just too much of a mess for what is a technical feature. So I'll be moving forward with the original plan to add the method to DocumentExtension.

How do you want to coordinate this @aaronfranke? This PR is older so I'd probably say get this merged first and then I can add the _remap overrides where needed?

Copy link
Contributor

@huwpascoe huwpascoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as expected, both import and export.

image

The warning messages are a very nice addition and explains clearly why an import fails.

I couldn't find any problems with the source. 👍

@lyuma
Copy link
Contributor

lyuma commented Dec 10, 2024

There was some opposition to this change in the rendering meeting, and I just wanted to comment further on this and also what I think is a better path forward.

First, one of the reasons we are encountering so much trouble handling texcoords is due to limitations on the standard material: basically, the standard material only permits certain texture maps on UV and UV2, and there are no other combinations... Therefore, this change is forced to remap texcoord channels to 0 and 1 so that they can be correctly assigned to the material.

I have some questions how common this issue really is, and it sounds more like making compatibility with marketplace assets than supporting a workflow for game art.

With that, I see two diverging usecases for the asset importer (And accordingly perhaps an import setting or some way to tell Godot which user you are):

  1. For game art designed with import to Godot in mind, this content will already be designed to support StandardMaterial and will not want UVs remapped.
  2. For marketplace assets or when full compatibility with glTF is desired, rather than conforming to Godot material conventions.

So given that description, for usecase 2, we have the goal of trying to render a glTF asset as accurately as possible, but we are not necessarily required to use StandardMaterial to do so. In this case, it might interesting to create material nodes with VisualShader that best mimics the visual representation in glTF, similar to how Blender works.

The downside of this is it means it will require more complexity to export the material, but it should be possible given that Blender is also able to export material graphs to glTF.

This PR is more like a baking approach where instead of creating a custom material graph, we are reordering UVs or baking textures to fit on the first two UV channels, which will work for some glTF materials but not in all cases. As I said before, I can be swayed if there is an import option, but this approach still feels a little bit messy: it will improve render compatibility with some glTF files but not 100%.

I think it might be worth considering a baking approach where when multiple texcoords are used, Godot can bake them to a single texcoord channel, which can make it look correct (although maybe a bit lower resolution), and this will also improve performance on mobile, which struggles when multiple UVs are in use at once.

And finally, the approach in godotengine/godot-proposals#10892 could be combined with a way for the user to manually select custom channels or UVs. Though there's still a problem that the StandardMaterial is too limited to use CUSTOM for textures.

@tlobig
Copy link
Contributor

tlobig commented Dec 23, 2024

as for the question whether this is an unusual request: no. I'm currently trying to establish an art pipeline from Blender to Godot for a VR game where I need lightmaps as SDFGI is very broken for VR :-/ - LightmapGI doesn't have the same quality as what I can do with a Blender addon and requires it's own painful workaround.

I'm a bit stuck as with Blender I get 2 UV maps and Godot doesn't load these properly as standard material:
grafik

I approximated what it might look like hand fiddling a visual shader for each of these three objects (disregard overall quality, important thing for me was the shadow of the small cube)
grafik

but this is not suitable for an art pipeline. I will see if I can change the Blender side of things to merge everything into the diffuse texture, but this also means I'm less versatile with only one UV map.

so I was quite pumped to see there is a PR fixing MY issue - lo and behold, it does fix it:
grafik

Therefore I would love to see this PR or a version of it to make it to 4.4

@lyuma
Copy link
Contributor

lyuma commented Dec 24, 2024

@tlobig I'd like to understand more on how the exported file looks and how the blender nodes are set up, to understand better how this change helps.

Since you mention using Blender to create a baked lightmap, is it done through multiplying two diffuse textures? Or is the albedo being baked together with the lighting to a second diffuse texture?

My understanding is with this change, Godot will still only pick a single UV, but it might pick UV2 instead of UV1. Even if it looks better to you, I suspect there is still something it's getting wrong. (Or if not, it should be possible to replicate this on vanilla godot by swapping the order of the UV maps in Blender).

@tlobig
Copy link
Contributor

tlobig commented Dec 24, 2024

@lyuma good instincts. I used the lightmap addon "The Lightmapper" (https://github.com/Naxela/The_Lightmapper), and it generates a second UV map automagically and then sets up a texture node using this uv map as input to mix (muliply) the diffuse color. The addon is supposed to be helping setting things up for game engines, so not just for Blender. Yet the standard material is too limited and we would need more involved import options to deal with the problem.

Simply baking (everything to diffuse/albedo) in Blender works, though the result is not as nice as it could be.
That's the TL;DR

Here is a minimal scene, I added the baked result and what the lightmapper generates (combined light (direct+indirect) + AO)
minimal_blender_scene.zip
grafik

red cube's color is lost in conversion, bottom cube has no material before the lightmapper does it's thing and the textured cube looses the texture after import to Godot (this PR)

I might be missing a few of the finer points.

if (primary_texture_coord == -1) {
primary_texture_coord = spec_gloss_tex_coord;
} else if (spec_gloss_tex_coord != primary_texture_coord) {
WARN_PRINT("glTF: File uses different UV maps for specular/glossiness texture and diffuse texture. Godot does not support this. Using diffuse texture's UV map only and ignoring specular/glossiness texture's UV map.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WARN_PRINT("glTF: File uses different UV maps for specular/glossiness texture and diffuse texture. Godot does not support this. Using diffuse texture's UV map only and ignoring specular/glossiness texture's UV map.");
WARN_PRINT("glTF: File uses different UV maps for specular/glossiness and diffuse textures. Godot does not support this. Using diffuse texture's UV map only and ignoring specular/glossiness texture's UV map.");

if (primary_texture_coord == -1) {
primary_texture_coord = metal_rough_tex_coord;
} else if (metal_rough_tex_coord != primary_texture_coord) {
WARN_PRINT("glTF: File uses different UV maps for metallic/roughness texture and base color texture. Godot does not support this. Using base color texture's UV map only and ignoring metallic/roughness texture's UV map.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WARN_PRINT("glTF: File uses different UV maps for metallic/roughness texture and base color texture. Godot does not support this. Using base color texture's UV map only and ignoring metallic/roughness texture's UV map.");
WARN_PRINT("glTF: File uses different UV maps for metallic/roughness and base color textures. Godot does not support this. Using base color texture's UV map only and ignoring metallic/roughness texture's UV map.");

secondary_texture_coord = emissive_tex_coord;
material->set_flag(BaseMaterial3D::FLAG_EMISSION_ON_UV2, true);
} else {
WARN_PRINT("glTF: File uses different UV maps for emission texture, occlusion texture, and primary texture (baseColor/normal/etc). Godot does not support this, it only supports up to two UV maps. Using occlusion texture's UV map only and ignoring emission texture's UV map.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WARN_PRINT("glTF: File uses different UV maps for emission texture, occlusion texture, and primary texture (baseColor/normal/etc). Godot does not support this, it only supports up to two UV maps. Using occlusion texture's UV map only and ignoring emission texture's UV map.");
WARN_PRINT("glTF: File uses different UV maps for emission, occlusion, and primary textures (baseColor/normal/etc). Godot does not support this, it only supports up to two UV maps. Using occlusion texture's UV map only and ignoring emission texture's UV map.");

@fire
Copy link
Member

fire commented Nov 18, 2025

As far as I know the disagreement from #96748 is still active.

Copy link
Contributor

@lyuma lyuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I've been negative about this PR, but on the plus side, improving compatibility with GLTF assets by default is good for the godot user experience, for most users who do not need custom shaders.

All of my objections are related to compatibility of materials between imports, and usage for custom shaders or other uses of mesh UVs. So the main limiter in my approving this change is the lack of a setting to turn this off.

Therefore, we proposed the following in the animation/asset metting:

Make the function to determine the primary_texture_coord a standalone function to isolate some of the complex logic and make the rest of the material remapping easier to read.

add an import enum setting shader_uv_mapping ("Preserve UV Mapping" / "StandardMaterial3D")
New meshes can use StandardMaterial3D. Existing files should be "Preserve UV Mapping" to avoid breaking compatibility.

(Eventually, our visual shader node idea could eventually be added as a third option (like "VisualShader") )

Users who wish to use custom shaders can now switch the setting back to Preserve UV Mapping.

I just have another comment: There's a usability issue that it will be difficult to change this setting and extract materials at the same time. Perhaps the Extract Materials dialog can also have this setting, but I would like to address this usability issue before merging this to make sure the workflow of assigning a custom material / shader is smooth.
One way to create such a workflow is to solve #107351 so we need to discuss this part further to see how that change interacts.

@aaronfranke aaronfranke requested a review from a team as a code owner February 21, 2026 21:27
@aaronfranke
Copy link
Member Author

aaronfranke commented Feb 21, 2026

As requested in today's @godotengine/asset-pipeline meeting, I made this feature optional:

Screenshot 2026-02-21 at 1 24 53 PM

Existing files will use "Do Not Remap" by default, while new files will use "Remap to StandardMaterial3D".

The reason this is an enum instead of a boolean is to allow for future options to be added for more ways to handle this, such as @lyuma's idea of using a custom shader instead of StandardMaterial3D.

@aaronfranke aaronfranke requested a review from a team as a code owner March 5, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

UV Map corruption on GLB import UV Map Node Ignored in GLTF/GLB Import GLTF mesh primitive texCoord property ignored

6 participants