Merge pull request #96748 from aaronfranke/gltf-tex-coord

GLTF: Read material texture "texCoord" property on import
This commit is contained in:
Thaddeus Crews
2026-04-13 11:46:12 -05:00
6 changed files with 346 additions and 136 deletions
@@ -133,6 +133,9 @@
How to process the root node during export. The default and recommended value is [constant ROOT_NODE_MODE_SINGLE_ROOT].
[b]Note:[/b] Regardless of how the glTF file is exported, when importing, the root node type and name can be overridden in the scene import settings tab.
</member>
<member name="texture_map_mode" type="int" setter="set_texture_map_mode" getter="get_texture_map_mode" enum="GLTFDocument.TextureMapMode" default="1">
How to handle texture maps during import. The default and recommended value is [constant TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL], which automatically remaps from glTF's flexible texture map system to the more specific texture map slots in Godot's [StandardMaterial3D] class. Alternatively, [constant TEXTURE_MAP_MODE_DO_NOT_REMAP] can be used to preserve the original texture maps from the glTF file, which may be desirable if using the glTF file with custom shaders, but may not display correctly with Godot's built-in materials.
</member>
<member name="visibility_mode" type="int" setter="set_visibility_mode" getter="get_visibility_mode" enum="GLTFDocument.VisibilityMode" default="0">
How to deal with node visibility during export. This setting does nothing if all nodes are visible. The default and recommended value is [constant VISIBILITY_MODE_INCLUDE_REQUIRED], which uses the [code]KHR_node_visibility[/code] extension.
</member>
@@ -147,6 +150,12 @@
<constant name="ROOT_NODE_MODE_MULTI_ROOT" value="2" enum="RootNodeMode">
Treat the Godot scene's root node as the name of the glTF scene, and add all of its children as root nodes of the glTF file. This uses only vanilla glTF features. This avoids an extra root node, but only the name of the Godot scene's root node will be preserved, as it will not be saved as a node.
</constant>
<constant name="TEXTURE_MAP_MODE_DO_NOT_REMAP" value="0" enum="TextureMapMode">
Import the texture maps in the glTF file as they are, without trying to fit them into specific texture slots suitable for Godot's built-in materials. This may be desirable if using the glTF file with custom shaders, but may not display correctly with Godot's built-in materials. This is equivalent to the behavior in Godot 4.6 and earlier.
</constant>
<constant name="TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL" value="1" enum="TextureMapMode">
Import the texture maps in the glTF file remapped to the most suitable texture slots based on Godot's [StandardMaterial3D] class. This is the default behavior.
</constant>
<constant name="VISIBILITY_MODE_INCLUDE_REQUIRED" value="0" enum="VisibilityMode">
If the scene contains any non-visible nodes, include them, mark them as non-visible with [code]KHR_node_visibility[/code], and require that importers respect their non-visibility. Downside: If the importer does not support [code]KHR_node_visibility[/code], the file cannot be imported.
</constant>
@@ -333,6 +333,10 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
int naming_version = p_options["gltf/naming_version"];
gltf->set_naming_version(naming_version);
}
if (p_options.has("gltf/texture_map_mode")) {
int texture_map_mode = p_options["gltf/texture_map_mode"];
gltf->set_texture_map_mode((GLTFDocument::TextureMapMode)texture_map_mode);
}
if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) {
state->set_import_as_skeleton_bones(true);
}
@@ -401,6 +405,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li
ADD_OPTION_BOOL("blender/animation/group_tracks", true);
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.0 or 4.1,Godot 4.2 to 4.4,Godot 4.5 or later"), 2));
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/texture_map_mode", PROPERTY_HINT_ENUM, "Do Not Remap,Remap to StandardMaterial3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFDocument::TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL));
}
void EditorSceneFormatImporterBlend::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
@@ -412,6 +417,11 @@ void EditorSceneFormatImporterBlend::handle_compatibility_options(HashMap<String
}
p_import_params.erase("blender/meshes/colors");
}
if (!p_import_params.has("gltf/texture_map_mode")) {
// If an existing import file is missing the glTF
// texture map mode, we need to use "Do Not Remap".
p_import_params["gltf/naming_version"] = (int64_t)GLTFDocument::TEXTURE_MAP_MODE_DO_NOT_REMAP;
}
}
static bool _test_blender_path(const String &p_path, String *r_err = nullptr) {
@@ -43,26 +43,30 @@ void EditorSceneFormatImporterGLTF::get_extensions(List<String> *r_extensions) c
Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t p_flags,
const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
Ref<GLTFDocument> gltf;
gltf.instantiate();
Ref<GLTFState> state;
state.instantiate();
Ref<GLTFDocument> gltf_doc;
gltf_doc.instantiate();
Ref<GLTFState> gltf_state;
gltf_state.instantiate();
if (p_options.has("gltf/naming_version")) {
int naming_version = p_options["gltf/naming_version"];
gltf->set_naming_version(naming_version);
gltf_doc->set_naming_version(naming_version);
}
if (p_options.has("gltf/embedded_image_handling")) {
int32_t enum_option = p_options["gltf/embedded_image_handling"];
state->set_handle_binary_image_mode((GLTFState::HandleBinaryImageMode)enum_option);
gltf_state->set_handle_binary_image_mode((GLTFState::HandleBinaryImageMode)enum_option);
}
if (p_options.has("gltf/texture_map_mode")) {
int32_t enum_option = p_options["gltf/texture_map_mode"];
gltf_doc->set_texture_map_mode((GLTFDocument::TextureMapMode)enum_option);
}
if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) {
state->set_import_as_skeleton_bones(true);
gltf_state->set_import_as_skeleton_bones(true);
}
if (p_options.has(SNAME("extract_path"))) {
state->set_extract_path(p_options["extract_path"]);
gltf_state->set_extract_path(p_options["extract_path"]);
}
state->set_bake_fps(p_options["animation/fps"]);
Error err = gltf->append_from_file(p_path, state, p_flags);
gltf_state->set_bake_fps(p_options["animation/fps"]);
Error err = gltf_doc->append_from_file(p_path, gltf_state, p_flags);
if (err != OK) {
if (r_err) {
*r_err = err;
@@ -70,12 +74,12 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t
return nullptr;
}
if (p_options.has("animation/import")) {
state->set_create_animations(bool(p_options["animation/import"]));
gltf_state->set_create_animations(bool(p_options["animation/import"]));
}
#ifndef DISABLE_DEPRECATED
bool trimming = p_options.has("animation/trimming") ? (bool)p_options["animation/trimming"] : false;
return gltf->generate_scene(state, state->get_bake_fps(), trimming, false);
return gltf_doc->generate_scene(gltf_state, gltf_state->get_bake_fps(), trimming, false);
#else
return gltf->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false);
#endif
@@ -88,6 +92,15 @@ void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path,
if (p_path.is_empty() || file_extension == "gltf" || file_extension == "glb") {
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.0 or 4.1,Godot 4.2 to 4.4,Godot 4.5 or later"), 2));
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_IMAGE_MODE_EXTRACT_TEXTURES));
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/texture_map_mode", PROPERTY_HINT_ENUM, "Do Not Remap,Remap to StandardMaterial3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFDocument::TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL));
}
}
void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
if (!p_import_params.has("gltf/texture_map_mode")) {
// If an existing import file is missing the glTF
// texture map mode, we need to use "Do Not Remap".
p_import_params["gltf/naming_version"] = (int64_t)GLTFDocument::TEXTURE_MAP_MODE_DO_NOT_REMAP;
}
}
@@ -45,6 +45,7 @@ public:
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) override;
};
+292 -124
View File
@@ -1441,6 +1441,49 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
uint64_t flags = RSE::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
Dictionary mesh_prim = primitives[j];
// Read the material.
Ref<Material> mat;
String mat_name;
String mat_primary_texture_coord = "TEXCOORD_0";
String mat_secondary_texture_coord = "TEXCOORD_1";
if (!p_state->discard_meshes_and_materials) {
if (mesh_prim.has("material")) {
const int material = mesh_prim["material"];
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
// Remap the glTF file's UV texture coordinates to Godot's UV and UV2 as best as possible.
if (mat3d->has_meta("_gltf_primary_texture_coord")) {
const int tex_coord = mat3d->get_meta("_gltf_primary_texture_coord");
mat_primary_texture_coord = "TEXCOORD_" + itos(tex_coord);
if (tex_coord != 0 && !mat3d->has_meta("_gltf_secondary_texture_coord")) {
mat_secondary_texture_coord = "TEXCOORD_0";
}
}
if (mat3d->has_meta("_gltf_secondary_texture_coord")) {
const int tex_coord = mat3d->get_meta("_gltf_secondary_texture_coord");
mat_secondary_texture_coord = "TEXCOORD_" + itos(tex_coord);
}
Ref<BaseMaterial3D> base_material = mat3d;
if (has_vertex_color && base_material.is_valid()) {
base_material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
mat = mat3d;
} else {
Ref<StandardMaterial3D> mat3d;
mat3d.instantiate();
if (has_vertex_color) {
mat3d->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
mat = mat3d;
}
ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
instance_materials.append(mat);
mat_name = mat->get_name();
}
// Read the mesh primitive data into Godot ArrayMesh array data.
Array array;
array.resize(Mesh::ARRAY_MAX);
@@ -1530,11 +1573,13 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
if (a.has("TANGENT")) {
array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_float32s(p_state, a["TANGENT"], indices_vec4_mapping);
}
if (a.has("TEXCOORD_0")) {
array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_0"], indices_mapping);
// Usually mat_primary_texture_coord is "TEXCOORD_0", but in some edge cases it might be different.
if (a.has(mat_primary_texture_coord)) {
array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(p_state, a[mat_primary_texture_coord], indices_mapping);
}
if (a.has("TEXCOORD_1")) {
array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_1"], indices_mapping);
// Usually mat_secondary_texture_coord is "TEXCOORD_1", but in some edge cases it might be different.
if (a.has(mat_secondary_texture_coord)) {
array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(p_state, a[mat_secondary_texture_coord], indices_mapping);
}
for (int custom_i = 0; custom_i < 3; custom_i++) {
Vector<float> cur_custom;
@@ -1912,34 +1957,6 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
morphs.push_back(array_copy);
}
}
Ref<Material> mat;
String mat_name;
if (!p_state->discard_meshes_and_materials) {
if (mesh_prim.has("material")) {
const int material = mesh_prim["material"];
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
Ref<BaseMaterial3D> base_material = mat3d;
if (has_vertex_color && base_material.is_valid()) {
base_material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
mat = mat3d;
} else {
Ref<StandardMaterial3D> mat3d;
mat3d.instantiate();
if (has_vertex_color) {
mat3d->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
mat = mat3d;
}
ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
instance_materials.append(mat);
mat_name = mat->get_name();
}
import_mesh->add_surface(primitive, array, morphs,
Dictionary(), mat, mat_name, flags);
}
@@ -2665,14 +2682,14 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
continue;
}
Dictionary mr;
Dictionary metal_rough_dict;
{
const Color c = base_material->get_albedo().srgb_to_linear();
Array arr = { c.r, c.g, c.b, c.a };
mr["baseColorFactor"] = arr;
metal_rough_dict["baseColorFactor"] = arr;
}
if (_image_format != "None") {
Dictionary bct;
Dictionary base_color_tex_dict;
Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
GLTFTextureIndex gltf_texture_index = -1;
@@ -2681,18 +2698,18 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
gltf_texture_index = _set_texture(p_state, albedo_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
bct["index"] = gltf_texture_index;
base_color_tex_dict["index"] = gltf_texture_index;
Dictionary extensions = _serialize_texture_transform_uv1(material);
if (!extensions.is_empty()) {
bct["extensions"] = extensions;
base_color_tex_dict["extensions"] = extensions;
p_state->use_khr_texture_transform = true;
}
mr["baseColorTexture"] = bct;
metal_rough_dict["baseColorTexture"] = base_color_tex_dict;
}
}
mr["metallicFactor"] = base_material->get_metallic();
mr["roughnessFactor"] = base_material->get_roughness();
metal_rough_dict["metallicFactor"] = base_material->get_metallic();
metal_rough_dict["roughnessFactor"] = base_material->get_roughness();
if (_image_format != "None") {
bool has_roughness = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid();
bool has_ao = base_material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid();
@@ -2814,6 +2831,9 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
if (has_ao) {
Dictionary occt;
occt["index"] = orm_texture_index;
if (base_material->get_flag(BaseMaterial3D::FLAG_AO_ON_UV2)) {
occt["texCoord"] = 1;
}
mat_dict["occlusionTexture"] = occt;
}
if (has_roughness || has_metalness) {
@@ -2824,14 +2844,13 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
mrt["extensions"] = extensions;
p_state->use_khr_texture_transform = true;
}
mr["metallicRoughnessTexture"] = mrt;
metal_rough_dict["metallicRoughnessTexture"] = mrt;
}
}
}
mat_dict["pbrMetallicRoughness"] = mr;
mat_dict["pbrMetallicRoughness"] = metal_rough_dict;
if (base_material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING) && _image_format != "None") {
Dictionary nt;
Ref<ImageTexture> tex;
tex.instantiate();
String path;
@@ -2861,37 +2880,40 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
_set_material_texture_name(tex, path, mat_name, "_normal");
gltf_texture_index = _set_texture(p_state, tex, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
nt["scale"] = base_material->get_normal_scale();
Dictionary normal_tex_dict;
normal_tex_dict["scale"] = base_material->get_normal_scale();
if (gltf_texture_index != -1) {
nt["index"] = gltf_texture_index;
mat_dict["normalTexture"] = nt;
normal_tex_dict["index"] = gltf_texture_index;
mat_dict["normalTexture"] = normal_tex_dict;
}
}
if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) {
const Color c = base_material->get_emission().linear_to_srgb();
Array arr = { c.r, c.g, c.b };
const Color emission_color = base_material->get_emission().linear_to_srgb();
Array arr = { emission_color.r, emission_color.g, emission_color.b };
mat_dict["emissiveFactor"] = arr;
}
if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION) && _image_format != "None") {
Dictionary et;
Ref<Texture2D> emission_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_EMISSION);
GLTFTextureIndex gltf_texture_index = -1;
if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) {
_set_material_texture_name(emission_texture, emission_texture->get_path(), mat_name, "_emission");
gltf_texture_index = _set_texture(p_state, emission_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
et["index"] = gltf_texture_index;
mat_dict["emissiveTexture"] = et;
if (_image_format != "None") {
Ref<Texture2D> emission_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_EMISSION);
GLTFTextureIndex gltf_texture_index = -1;
if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) {
_set_material_texture_name(emission_texture, emission_texture->get_path(), mat_name, "_emission");
gltf_texture_index = _set_texture(p_state, emission_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
Dictionary emissive_tex_dict;
emissive_tex_dict["index"] = gltf_texture_index;
if (base_material->get_flag(BaseMaterial3D::FLAG_EMISSION_ON_UV2)) {
emissive_tex_dict["texCoord"] = 1;
}
mat_dict["emissiveTexture"] = emissive_tex_dict;
}
}
}
const bool ds = base_material->get_cull_mode() == BaseMaterial3D::CULL_DISABLED;
if (ds) {
mat_dict["doubleSided"] = ds;
const bool double_sided = base_material->get_cull_mode() == BaseMaterial3D::CULL_DISABLED;
if (double_sided) {
mat_dict["doubleSided"] = double_sided;
}
if (base_material->get_transparency() == BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR) {
@@ -2961,14 +2983,15 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
}
}
int primary_texture_coord = -1; // Which UV map to use.
if (material_extensions.has("KHR_materials_pbrSpecularGlossiness")) {
WARN_PRINT("Material uses a specular and glossiness workflow. Textures will be converted to roughness and metallic workflow, which may not be 100% accurate.");
Dictionary sgm = material_extensions["KHR_materials_pbrSpecularGlossiness"];
Dictionary spec_gloss_ext_dict = material_extensions["KHR_materials_pbrSpecularGlossiness"];
Ref<GLTFSpecGloss> spec_gloss;
spec_gloss.instantiate();
if (sgm.has("diffuseTexture")) {
const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"];
if (spec_gloss_ext_dict.has("diffuseTexture")) {
const Dictionary &diffuse_texture_dict = spec_gloss_ext_dict["diffuseTexture"];
if (diffuse_texture_dict.has("index")) {
Ref<GLTFTextureSampler> diffuse_sampler = _get_sampler_for_texture(p_state, diffuse_texture_dict["index"]);
if (diffuse_sampler.is_valid()) {
@@ -2981,49 +3004,62 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, diffuse_texture);
}
}
if (diffuse_texture_dict.has("texCoord")) {
primary_texture_coord = diffuse_texture_dict["texCoord"];
} else {
primary_texture_coord = 0;
}
}
if (sgm.has("diffuseFactor")) {
const Array &arr = sgm["diffuseFactor"];
if (spec_gloss_ext_dict.has("diffuseFactor")) {
const Array &arr = spec_gloss_ext_dict["diffuseFactor"];
ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR);
const Color c = Color(arr[0], arr[1], arr[2], arr[3]).linear_to_srgb();
spec_gloss->diffuse_factor = c;
material->set_albedo(spec_gloss->diffuse_factor);
}
if (sgm.has("specularFactor")) {
const Array &arr = sgm["specularFactor"];
if (spec_gloss_ext_dict.has("specularFactor")) {
const Array &arr = spec_gloss_ext_dict["specularFactor"];
ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR);
spec_gloss->specular_factor = Color(arr[0], arr[1], arr[2]);
}
if (sgm.has("glossinessFactor")) {
spec_gloss->gloss_factor = sgm["glossinessFactor"];
if (spec_gloss_ext_dict.has("glossinessFactor")) {
spec_gloss->gloss_factor = spec_gloss_ext_dict["glossinessFactor"];
material->set_roughness(1.0f - CLAMP(spec_gloss->gloss_factor, 0.0f, 1.0f));
}
if (sgm.has("specularGlossinessTexture")) {
const Dictionary &spec_gloss_texture = sgm["specularGlossinessTexture"];
if (spec_gloss_ext_dict.has("specularGlossinessTexture")) {
const Dictionary &spec_gloss_texture = spec_gloss_ext_dict["specularGlossinessTexture"];
if (spec_gloss_texture.has("index")) {
const Ref<Texture2D> orig_texture = _get_texture(p_state, spec_gloss_texture["index"], TEXTURE_TYPE_GENERIC);
if (orig_texture.is_valid()) {
spec_gloss->spec_gloss_img = orig_texture->get_image();
}
}
if (spec_gloss_texture.has("texCoord")) {
const int spec_gloss_tex_coord = spec_gloss_texture["texCoord"];
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 and diffuse textures. Godot does not support this. Using diffuse texture's UV map only and ignoring specular/glossiness texture's UV map.");
}
}
}
spec_gloss_to_rough_metal(spec_gloss, material);
} else if (material_dict.has("pbrMetallicRoughness")) {
const Dictionary &mr = material_dict["pbrMetallicRoughness"];
if (mr.has("baseColorFactor")) {
const Array &arr = mr["baseColorFactor"];
const Dictionary &metal_rough_dict = material_dict["pbrMetallicRoughness"];
if (metal_rough_dict.has("baseColorFactor")) {
const Array &arr = metal_rough_dict["baseColorFactor"];
ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR);
const Color c = Color(arr[0], arr[1], arr[2], arr[3]).linear_to_srgb();
material->set_albedo(c);
}
if (mr.has("baseColorTexture")) {
const Dictionary &bct = mr["baseColorTexture"];
if (bct.has("index")) {
const GLTFTextureIndex base_color_texture_index = bct["index"];
if (metal_rough_dict.has("baseColorTexture")) {
const Dictionary &base_color_tex_dict = metal_rough_dict["baseColorTexture"];
if (base_color_tex_dict.has("index")) {
const GLTFTextureIndex base_color_texture_index = base_color_tex_dict["index"];
material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, _get_texture(p_state, base_color_texture_index, TEXTURE_TYPE_GENERIC));
const Ref<GLTFTextureSampler> bct_sampler = _get_sampler_for_texture(p_state, base_color_texture_index);
if (bct_sampler.is_valid()) {
@@ -3031,59 +3067,90 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, bct_sampler->get_wrap_mode());
}
}
if (!mr.has("baseColorFactor")) {
if (base_color_tex_dict.has("texCoord")) {
primary_texture_coord = base_color_tex_dict["texCoord"];
} else {
primary_texture_coord = 0;
}
if (!metal_rough_dict.has("baseColorFactor")) {
material->set_albedo(Color(1, 1, 1));
}
_set_texture_transform_uv1(bct, material);
_set_texture_transform_uv1(base_color_tex_dict, material);
}
if (mr.has("metallicFactor")) {
material->set_metallic(mr["metallicFactor"]);
if (metal_rough_dict.has("metallicFactor")) {
material->set_metallic(metal_rough_dict["metallicFactor"]);
} else {
material->set_metallic(1.0);
}
if (mr.has("roughnessFactor")) {
material->set_roughness(mr["roughnessFactor"]);
if (metal_rough_dict.has("roughnessFactor")) {
material->set_roughness(metal_rough_dict["roughnessFactor"]);
} else {
material->set_roughness(1.0);
}
if (mr.has("metallicRoughnessTexture")) {
const Dictionary &bct = mr["metallicRoughnessTexture"];
if (bct.has("index")) {
const Ref<Texture2D> t = _get_texture(p_state, bct["index"], TEXTURE_TYPE_GENERIC);
if (metal_rough_dict.has("metallicRoughnessTexture")) {
const Dictionary &metal_rough_tex_dict = metal_rough_dict["metallicRoughnessTexture"];
if (metal_rough_tex_dict.has("index")) {
const Ref<Texture2D> t = _get_texture(p_state, metal_rough_tex_dict["index"], TEXTURE_TYPE_GENERIC);
material->set_texture(BaseMaterial3D::TEXTURE_METALLIC, t);
material->set_metallic_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_BLUE);
material->set_texture(BaseMaterial3D::TEXTURE_ROUGHNESS, t);
material->set_roughness_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_GREEN);
if (!mr.has("metallicFactor")) {
if (!metal_rough_dict.has("metallicFactor")) {
material->set_metallic(1);
}
if (!mr.has("roughnessFactor")) {
if (!metal_rough_dict.has("roughnessFactor")) {
material->set_roughness(1);
}
}
if (metal_rough_tex_dict.has("texCoord")) {
const int metal_rough_tex_coord = metal_rough_tex_dict["texCoord"];
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 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.");
}
}
}
}
if (material_dict.has("normalTexture")) {
const Dictionary &bct = material_dict["normalTexture"];
if (bct.has("index")) {
material->set_texture(BaseMaterial3D::TEXTURE_NORMAL, _get_texture(p_state, bct["index"], TEXTURE_TYPE_NORMAL));
const Dictionary &normal_tex_dict = material_dict["normalTexture"];
if (normal_tex_dict.has("index")) {
material->set_texture(BaseMaterial3D::TEXTURE_NORMAL, _get_texture(p_state, normal_tex_dict["index"], TEXTURE_TYPE_NORMAL));
material->set_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING, true);
}
if (bct.has("scale")) {
material->set_normal_scale(bct["scale"]);
if (normal_tex_dict.has("texCoord")) {
const int normal_tex_coord = normal_tex_dict["texCoord"];
if (primary_texture_coord == -1) {
primary_texture_coord = normal_tex_coord;
} else if (normal_tex_coord != primary_texture_coord) {
WARN_PRINT("glTF: File uses different UV maps for normal and base color textures. Godot does not support this. Using base color texture's UV map only and ignoring normal texture's UV map.");
}
}
if (normal_tex_dict.has("scale")) {
material->set_normal_scale(normal_tex_dict["scale"]);
}
}
int secondary_texture_coord = -1;
if (material_dict.has("occlusionTexture")) {
const Dictionary &bct = material_dict["occlusionTexture"];
if (bct.has("index")) {
material->set_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, _get_texture(p_state, bct["index"], TEXTURE_TYPE_GENERIC));
const Dictionary &occlusion_tex_dict = material_dict["occlusionTexture"];
if (occlusion_tex_dict.has("index")) {
material->set_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, _get_texture(p_state, occlusion_tex_dict["index"], TEXTURE_TYPE_GENERIC));
material->set_ao_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_RED);
material->set_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION, true);
}
if (occlusion_tex_dict.has("texCoord")) {
int occlusion_tex_coord = occlusion_tex_dict["texCoord"];
if (unlikely(primary_texture_coord == -1)) {
primary_texture_coord = occlusion_tex_coord;
} else if (occlusion_tex_coord != primary_texture_coord) {
secondary_texture_coord = occlusion_tex_coord;
material->set_flag(BaseMaterial3D::FLAG_AO_ON_UV2, true);
}
}
}
if (material_dict.has("emissiveFactor")) {
@@ -3096,9 +3163,9 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
}
if (material_dict.has("emissiveTexture")) {
const Dictionary &bct = material_dict["emissiveTexture"];
if (bct.has("index")) {
material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, _get_texture(p_state, bct["index"], TEXTURE_TYPE_GENERIC));
const Dictionary &emissive_tex_dict = material_dict["emissiveTexture"];
if (emissive_tex_dict.has("index")) {
material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, _get_texture(p_state, emissive_tex_dict["index"], TEXTURE_TYPE_GENERIC));
material->set_feature(BaseMaterial3D::FEATURE_EMISSION, true);
material->set_emission_operator(BaseMaterial3D::EMISSION_OP_MULTIPLY);
// glTF spec: emissiveFactor × emissiveTexture. Use WHITE if no factor specified.
@@ -3106,6 +3173,19 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
material->set_emission(Color(1, 1, 1));
}
}
if (emissive_tex_dict.has("texCoord")) {
int emissive_tex_coord = emissive_tex_dict["texCoord"];
if (unlikely(primary_texture_coord == -1)) {
primary_texture_coord = emissive_tex_coord;
} else if (emissive_tex_coord != primary_texture_coord) {
if (emissive_tex_coord == secondary_texture_coord || secondary_texture_coord == -1) {
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, 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.");
}
}
}
}
if (material_dict.has("doubleSided")) {
@@ -3131,6 +3211,14 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
if (material_dict.has("extras")) {
_attach_extras_to_meta(material_dict["extras"], material);
}
if (_texture_map_mode == TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL) {
if (primary_texture_coord != -1) {
material->set_meta("_gltf_primary_texture_coord", primary_texture_coord);
}
if (secondary_texture_coord != -1) {
material->set_meta("_gltf_secondary_texture_coord", secondary_texture_coord);
}
}
p_state->materials.push_back(material);
}
@@ -5121,11 +5209,29 @@ Ref<GLTFObjectModelProperty> GLTFDocument::import_object_model_property(Ref<GLTF
ret->append_path_to_property(mat_path, "normal_scale");
ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
}
} else if (mat_prop == "occlusionTexture") {
if (sub_prop == "strength") {
// This is the closest thing Godot has to an occlusion strength property.
ret->append_path_to_property(mat_path, "ao_light_affect");
ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
} else if (mat_prop == "occlusionTexture" && sub_prop == "strength") {
// This is the closest thing Godot has to an occlusion strength property.
ret->append_path_to_property(mat_path, "ao_light_affect");
ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
} else if (mat_prop == "occlusionTexture" || mat_prop == "emissiveTexture") {
// Occlusion and/or emission textures can use Godot's UV2, so we need to check if KHR_texture_transform animates them.
const Ref<BaseMaterial3D> base_material_3d = pointed_material;
if (base_material_3d.is_valid()) {
if ((mat_prop == "occlusionTexture" && base_material_3d->get_flag(BaseMaterial3D::FLAG_AO_ON_UV2)) || (mat_prop == "emissiveTexture" && base_material_3d->get_flag(BaseMaterial3D::FLAG_EMISSION_ON_UV2))) {
ERR_FAIL_COND_V(split.size() < 5, ret);
const String &tex_ext_dict = split[3];
const String &tex_ext_name = split[4];
const String &tex_ext_prop = split[5];
if (tex_ext_dict == "extensions" && tex_ext_name == "KHR_texture_transform") {
if (tex_ext_prop == "offset") {
ret->append_path_to_property(mat_path, "uv2_offset");
ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
} else if (tex_ext_prop == "scale") {
ret->append_path_to_property(mat_path, "uv2_scale");
ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
}
}
}
}
} else if (mat_prop == "pbrMetallicRoughness") {
if (sub_prop == "baseColorFactor") {
@@ -5235,6 +5341,17 @@ Ref<GLTFObjectModelProperty> GLTFDocument::import_object_model_property(Ref<GLTF
return ret;
}
void GLTFDocument::_append_khr_texture_transform_ext_json_pointer(PackedStringArray &p_split_json_pointer, const String &p_texture_name, const bool p_is_offset) {
p_split_json_pointer.append(p_texture_name);
p_split_json_pointer.append("extensions");
p_split_json_pointer.append("KHR_texture_transform");
if (p_is_offset) {
p_split_json_pointer.append("offset");
} else {
p_split_json_pointer.append("scale");
}
}
Ref<GLTFObjectModelProperty> GLTFDocument::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index) {
Ref<GLTFObjectModelProperty> ret;
const Object *target_object = p_godot_node;
@@ -5294,16 +5411,54 @@ Ref<GLTFObjectModelProperty> GLTFDocument::export_object_model_property(Ref<GLTF
split_json_pointer.append("pbrMetallicRoughness");
split_json_pointer.append("roughnessFactor");
ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
} else if (target_prop == "uv1_offset" || target_prop == "uv1_scale") {
split_json_pointer.append("pbrMetallicRoughness");
split_json_pointer.append("baseColorTexture");
split_json_pointer.append("extensions");
split_json_pointer.append("KHR_texture_transform");
if (target_prop == "uv1_offset") {
split_json_pointer.append("offset");
} else {
split_json_pointer.append("scale");
} else if (target_prop == "uv1_offset" || target_prop == "uv1_scale" || target_prop == "uv2_offset" || target_prop == "uv2_scale") {
Array mat_dicts = p_state->json.get("materials", Array());
ERR_FAIL_INDEX_V(i, mat_dicts.size(), ret);
Dictionary mat_dict = mat_dicts[i];
const bool is_offset = target_prop.ends_with("offset");
const bool is_uv1 = target_prop.begins_with("uv1");
const Ref<BaseMaterial3D> &base_material_3d = p_state->materials[i];
if (base_material_3d.is_valid()) {
const bool is_uv2 = !is_uv1;
// occlusionTexture and emissiveTexture can use Godot's UV2, so we need to check if those are animated.
if (mat_dict.has("occlusionTexture")) {
if (is_uv2 == base_material_3d->get_flag(BaseMaterial3D::FLAG_AO_ON_UV2)) {
PackedStringArray occlusion = split_json_pointer.duplicate();
_append_khr_texture_transform_ext_json_pointer(occlusion, "occlusionTexture", is_offset);
split_json_pointers.append(occlusion);
}
}
if (mat_dict.has("emissiveTexture")) {
if (is_uv2 == base_material_3d->get_flag(BaseMaterial3D::FLAG_EMISSION_ON_UV2)) {
PackedStringArray emissive = split_json_pointer.duplicate();
_append_khr_texture_transform_ext_json_pointer(emissive, "emissiveTexture", is_offset);
split_json_pointers.append(emissive);
}
}
}
if (is_uv1) {
// normalTexture, pbrMetallicRoughness/baseColorTexture, and pbrMetallicRoughness/metallicRoughnessTexture use only UV1.
if (mat_dict.has("normalTexture")) {
PackedStringArray normal = split_json_pointer.duplicate();
_append_khr_texture_transform_ext_json_pointer(normal, "normalTexture", is_offset);
split_json_pointers.append(normal);
}
if (mat_dict.has("pbrMetallicRoughness")) {
Dictionary pbr_metallic_roughness = mat_dict["pbrMetallicRoughness"];
split_json_pointer.append("pbrMetallicRoughness");
if (pbr_metallic_roughness.has("metallicRoughnessTexture")) {
PackedStringArray metal_rough = split_json_pointer.duplicate();
_append_khr_texture_transform_ext_json_pointer(metal_rough, "metallicRoughnessTexture", is_offset);
split_json_pointers.append(metal_rough);
}
if (pbr_metallic_roughness.has("baseColorTexture")) {
PackedStringArray base_color = split_json_pointer.duplicate();
_append_khr_texture_transform_ext_json_pointer(base_color, "baseColorTexture", is_offset);
split_json_pointers.append(base_color);
}
}
}
split_json_pointer.clear();
ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
} else {
split_json_pointer.clear();
@@ -5407,7 +5562,14 @@ Ref<GLTFObjectModelProperty> GLTFDocument::export_object_model_property(Ref<GLTF
// Additional JSON pointers can be added by GLTFDocumentExtension classes.
// We only need this if no mapping has been found yet from GLTFDocument's internal code.
// We pass as many pieces of information as we can to the extension to give it lots of context.
if (split_json_pointer.is_empty()) {
if (!split_json_pointer.is_empty()) {
// GLTFDocument's internal code found a mapping, so set it and return it.
split_json_pointers.append(split_json_pointer);
ret->set_json_pointers(split_json_pointers);
} else if (!split_json_pointers.is_empty()) {
ret->set_json_pointers(split_json_pointers);
} else {
// We don't have a mapping, so we need to ask GLTFDocumentExtension classes if they have a mapping.
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
ret = ext->export_object_model_property(p_state, p_node_path, p_godot_node, p_gltf_node_index, target_object, target_prop_depth);
if (ret.is_valid() && ret->has_json_pointers()) {
@@ -5417,10 +5579,6 @@ Ref<GLTFObjectModelProperty> GLTFDocument::export_object_model_property(Ref<GLTF
break;
}
}
} else {
// GLTFDocument's internal code found a mapping, so set it and return it.
split_json_pointers.append(split_json_pointer);
ret->set_json_pointers(split_json_pointers);
}
return ret;
}
@@ -6709,6 +6867,9 @@ void GLTFDocument::_bind_methods() {
BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT);
BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT);
BIND_ENUM_CONSTANT(TEXTURE_MAP_MODE_DO_NOT_REMAP);
BIND_ENUM_CONSTANT(TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL);
BIND_ENUM_CONSTANT(VISIBILITY_MODE_INCLUDE_REQUIRED);
BIND_ENUM_CONSTANT(VISIBILITY_MODE_INCLUDE_OPTIONAL);
BIND_ENUM_CONSTANT(VISIBILITY_MODE_EXCLUDE);
@@ -6723,6 +6884,8 @@ void GLTFDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_fallback_image_quality"), &GLTFDocument::get_fallback_image_quality);
ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode);
ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode);
ClassDB::bind_method(D_METHOD("set_texture_map_mode", "texture_map_mode"), &GLTFDocument::set_texture_map_mode);
ClassDB::bind_method(D_METHOD("get_texture_map_mode"), &GLTFDocument::get_texture_map_mode);
ClassDB::bind_method(D_METHOD("set_visibility_mode", "visibility_mode"), &GLTFDocument::set_visibility_mode);
ClassDB::bind_method(D_METHOD("get_visibility_mode"), &GLTFDocument::get_visibility_mode);
ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "base_path"),
@@ -6743,6 +6906,7 @@ void GLTFDocument::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "fallback_image_format"), "set_fallback_image_format", "get_fallback_image_format");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fallback_image_quality"), "set_fallback_image_quality", "get_fallback_image_quality");
ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_map_mode"), "set_texture_map_mode", "get_texture_map_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_mode"), "set_visibility_mode", "get_visibility_mode");
ClassDB::bind_static_method("GLTFDocument", D_METHOD("import_object_model_property", "state", "json_pointer"), &GLTFDocument::import_object_model_property);
@@ -7236,6 +7400,10 @@ GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const {
return _root_node_mode;
}
void GLTFDocument::set_texture_map_mode(GLTFDocument::TextureMapMode p_texture_map_mode) {
_texture_map_mode = p_texture_map_mode;
}
void GLTFDocument::set_visibility_mode(VisibilityMode p_visibility_mode) {
_visibility_mode = p_visibility_mode;
}
+9
View File
@@ -56,6 +56,10 @@ public:
ROOT_NODE_MODE_KEEP_ROOT,
ROOT_NODE_MODE_MULTI_ROOT,
};
enum TextureMapMode {
TEXTURE_MAP_MODE_DO_NOT_REMAP,
TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL,
};
enum VisibilityMode {
VISIBILITY_MODE_INCLUDE_REQUIRED,
VISIBILITY_MODE_INCLUDE_OPTIONAL,
@@ -70,6 +74,7 @@ private:
float _fallback_image_quality = 0.25f;
Ref<GLTFDocumentExtension> _image_save_extension;
RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT;
TextureMapMode _texture_map_mode = TextureMapMode::TEXTURE_MAP_MODE_REMAP_TO_STANDARD_MATERIAL;
VisibilityMode _visibility_mode = VisibilityMode::VISIBILITY_MODE_INCLUDE_REQUIRED;
protected:
@@ -102,11 +107,14 @@ public:
float get_fallback_image_quality() const;
void set_root_node_mode(RootNodeMode p_root_node_mode);
RootNodeMode get_root_node_mode() const;
void set_texture_map_mode(TextureMapMode p_texture_map_mode);
TextureMapMode get_texture_map_mode() const { return _texture_map_mode; }
void set_visibility_mode(VisibilityMode p_visibility_mode);
VisibilityMode get_visibility_mode() const;
static String _gen_unique_name_static(HashSet<String> &r_unique_names, const String &p_name);
private:
static void _append_khr_texture_transform_ext_json_pointer(PackedStringArray &p_split_json_pointer, const String &p_texture_name, const bool p_is_offset);
void _build_parent_hierarchy(Ref<GLTFState> p_state);
Error _parse_scenes(Ref<GLTFState> p_state);
Error _parse_nodes(Ref<GLTFState> p_state);
@@ -290,4 +298,5 @@ public:
};
VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode);
VARIANT_ENUM_CAST(GLTFDocument::TextureMapMode);
VARIANT_ENUM_CAST(GLTFDocument::VisibilityMode);