Skip to content

Commit 71920b1

Browse files
committed
Merge pull request #104784 from aaronfranke/gltf-fallback-image
GLTF export: Allow using a PNG or JPEG fallback image
2 parents d1d4d5f + 03b7934 commit 71920b1

File tree

4 files changed

+173
-78
lines changed

4 files changed

+173
-78
lines changed

modules/gltf/doc_classes/GLTFDocument.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,16 @@
115115
</method>
116116
</methods>
117117
<members>
118+
<member name="fallback_image_format" type="String" setter="set_fallback_image_format" getter="get_fallback_image_format" default="&quot;None&quot;">
119+
The user-friendly name of the fallback image format. This is used when exporting the glTF file, including writing to a file and writing to a byte array.
120+
This property may only be one of "None", "PNG", or "JPEG", and is only used when the [member image_format] is not one of "None", "PNG", or "JPEG". If having multiple extension image formats is desired, that can be done using a [GLTFDocumentExtension] class - this property only covers the use case of providing a base glTF fallback image when using a custom image format.
121+
</member>
122+
<member name="fallback_image_quality" type="float" setter="set_fallback_image_quality" getter="get_fallback_image_quality" default="0.25">
123+
The quality of the fallback image, if any. For PNG files, this downscales the image on both dimensions by this factor. For JPEG files, this is the lossy quality of the image. A low value is recommended, since including multiple high quality images in a glTF file defeats the file size gains of using a more efficient image format.
124+
</member>
118125
<member name="image_format" type="String" setter="set_image_format" getter="get_image_format" default="&quot;PNG&quot;">
119126
The user-friendly name of the export image format. This is used when exporting the glTF file, including writing to a file and writing to a byte array.
120-
By default, Godot allows the following options: "None", "PNG", "JPEG", "Lossless WebP", and "Lossy WebP". Support for more image formats can be added in [GLTFDocumentExtension] classes.
127+
By default, Godot allows the following options: "None", "PNG", "JPEG", "Lossless WebP", and "Lossy WebP". Support for more image formats can be added in [GLTFDocumentExtension] classes. A single extension class can provide multiple options for the specific format to use, or even an option that uses multiple formats at once.
121128
</member>
122129
<member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75">
123130
If [member image_format] is a lossy image format, this determines the lossy quality of the image. On a range of [code]0.0[/code] to [code]1.0[/code], where [code]0.0[/code] is the lowest quality and [code]1.0[/code] is the highest quality. A lossy quality of [code]1.0[/code] is not the same as lossless.

modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ bool EditorSceneExporterGLTFSettings::_set(const StringName &p_name, const Varia
4646
_document->set_lossy_quality(p_value);
4747
return true;
4848
}
49+
if (p_name == StringName("fallback_image_format")) {
50+
_document->set_fallback_image_format(p_value);
51+
emit_signal(CoreStringName(property_list_changed));
52+
return true;
53+
}
54+
if (p_name == StringName("fallback_image_quality")) {
55+
_document->set_fallback_image_quality(p_value);
56+
return true;
57+
}
4958
if (p_name == StringName("root_node_mode")) {
5059
_document->set_root_node_mode((GLTFDocument::RootNodeMode)(int64_t)p_value);
5160
return true;
@@ -66,6 +75,14 @@ bool EditorSceneExporterGLTFSettings::_get(const StringName &p_name, Variant &r_
6675
r_ret = _document->get_lossy_quality();
6776
return true;
6877
}
78+
if (p_name == StringName("fallback_image_format")) {
79+
r_ret = _document->get_fallback_image_format();
80+
return true;
81+
}
82+
if (p_name == StringName("fallback_image_quality")) {
83+
r_ret = _document->get_fallback_image_quality();
84+
return true;
85+
}
6986
if (p_name == StringName("root_node_mode")) {
7087
r_ret = _document->get_root_node_mode();
7188
return true;
@@ -76,10 +93,21 @@ bool EditorSceneExporterGLTFSettings::_get(const StringName &p_name, Variant &r_
7693
void EditorSceneExporterGLTFSettings::_get_property_list(List<PropertyInfo> *p_list) const {
7794
for (PropertyInfo prop : _property_list) {
7895
if (prop.name == "lossy_quality") {
79-
String image_format = get("image_format");
80-
bool is_image_format_lossy = image_format == "JPEG" || image_format.containsn("Lossy");
96+
const String image_format = get("image_format");
97+
const bool is_image_format_lossy = image_format == "JPEG" || image_format.containsn("Lossy");
8198
prop.usage = is_image_format_lossy ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
8299
}
100+
if (prop.name == "fallback_image_format") {
101+
const String image_format = get("image_format");
102+
const bool is_image_format_extension = image_format != "None" && image_format != "PNG" && image_format != "JPEG";
103+
prop.usage = is_image_format_extension ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
104+
}
105+
if (prop.name == "fallback_image_quality") {
106+
const String image_format = get("image_format");
107+
const bool is_image_format_extension = image_format != "None" && image_format != "PNG" && image_format != "JPEG";
108+
const String fallback_format = get("fallback_image_format");
109+
prop.usage = (is_image_format_extension && fallback_format != "None") ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
110+
}
83111
p_list->push_back(prop);
84112
}
85113
}
@@ -117,7 +145,7 @@ String get_friendly_config_prefix(Ref<GLTFDocumentExtension> p_extension) {
117145
return config_prefix;
118146
}
119147
const String class_name = p_extension->get_class_name();
120-
config_prefix = class_name.trim_prefix("GLTFDocumentExtension").capitalize();
148+
config_prefix = class_name.trim_prefix("GLTFDocumentExtension").trim_suffix("GLTFDocumentExtension").capitalize();
121149
if (!config_prefix.is_empty()) {
122150
return config_prefix;
123151
}
@@ -166,6 +194,10 @@ void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p
166194
_property_list.push_back(image_format_prop);
167195
PropertyInfo lossy_quality_prop = PropertyInfo(Variant::FLOAT, "lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01");
168196
_property_list.push_back(lossy_quality_prop);
197+
PropertyInfo fallback_image_format_prop = PropertyInfo(Variant::STRING, "fallback_image_format", PROPERTY_HINT_ENUM, "None,PNG,JPEG");
198+
_property_list.push_back(fallback_image_format_prop);
199+
PropertyInfo fallback_image_quality_prop = PropertyInfo(Variant::FLOAT, "fallback_image_quality", PROPERTY_HINT_RANGE, "0,1,0.01");
200+
_property_list.push_back(fallback_image_quality_prop);
169201
PropertyInfo root_node_mode_prop = PropertyInfo(Variant::INT, "root_node_mode", PROPERTY_HINT_ENUM, "Single Root,Keep Root,Multi Root");
170202
_property_list.push_back(root_node_mode_prop);
171203
}

modules/gltf/gltf_document.cpp

Lines changed: 123 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3814,6 +3814,22 @@ float GLTFDocument::get_lossy_quality() const {
38143814
return _lossy_quality;
38153815
}
38163816

3817+
void GLTFDocument::set_fallback_image_format(const String &p_fallback_image_format) {
3818+
_fallback_image_format = p_fallback_image_format;
3819+
}
3820+
3821+
String GLTFDocument::get_fallback_image_format() const {
3822+
return _fallback_image_format;
3823+
}
3824+
3825+
void GLTFDocument::set_fallback_image_quality(float p_fallback_image_quality) {
3826+
_fallback_image_quality = p_fallback_image_quality;
3827+
}
3828+
3829+
float GLTFDocument::get_fallback_image_quality() const {
3830+
return _fallback_image_quality;
3831+
}
3832+
38173833
Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
38183834
Array images;
38193835
// Check if any extension wants to be the image saver.
@@ -3829,83 +3845,21 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
38293845
// Serialize every image in the state's images array.
38303846
for (int i = 0; i < p_state->images.size(); i++) {
38313847
Dictionary image_dict;
3832-
3833-
ERR_CONTINUE(p_state->images[i].is_null());
3834-
3835-
Ref<Image> image = p_state->images[i]->get_image();
3836-
ERR_CONTINUE(image.is_null());
3837-
if (image->is_compressed()) {
3838-
image->decompress();
3839-
ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "glTF: Image was compressed, but could not be decompressed.");
3840-
}
3841-
3842-
if (p_state->filename.to_lower().ends_with("gltf")) {
3843-
String img_name = p_state->images[i]->get_name();
3844-
if (img_name.is_empty()) {
3845-
img_name = itos(i).pad_zeros(3);
3846-
}
3847-
img_name = _gen_unique_name(p_state, img_name);
3848-
String relative_texture_dir = "textures";
3849-
String full_texture_dir = p_state->base_path.path_join(relative_texture_dir);
3850-
Ref<DirAccess> da = DirAccess::open(p_state->base_path);
3851-
ERR_FAIL_COND_V(da.is_null(), FAILED);
3852-
3853-
if (!da->dir_exists(full_texture_dir)) {
3854-
da->make_dir(full_texture_dir);
3855-
}
3856-
if (_image_save_extension.is_valid()) {
3857-
img_name = img_name + _image_save_extension->get_image_file_extension();
3858-
Error err = _image_save_extension->save_image_at_path(p_state, image, full_texture_dir.path_join(img_name), _image_format, _lossy_quality);
3859-
ERR_FAIL_COND_V_MSG(err != OK, err, "glTF: Failed to save image in '" + _image_format + "' format as a separate file.");
3860-
} else if (_image_format == "PNG") {
3861-
img_name = img_name + ".png";
3862-
image->save_png(full_texture_dir.path_join(img_name));
3863-
} else if (_image_format == "JPEG") {
3864-
img_name = img_name + ".jpg";
3865-
image->save_jpg(full_texture_dir.path_join(img_name), _lossy_quality);
3866-
} else {
3867-
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'.");
3868-
}
3869-
image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode();
3848+
if (p_state->images[i].is_null()) {
3849+
ERR_PRINT("glTF export: Image Texture2D is null.");
38703850
} else {
3871-
GLTFBufferViewIndex bvi;
3872-
3873-
Ref<GLTFBufferView> bv;
3874-
bv.instantiate();
3875-
3876-
const GLTFBufferIndex bi = 0;
3877-
bv->buffer = bi;
3878-
bv->byte_offset = p_state->buffers[bi].size();
3879-
ERR_FAIL_INDEX_V(bi, p_state->buffers.size(), ERR_PARAMETER_RANGE_ERROR);
3880-
3881-
Vector<uint8_t> buffer;
3882-
Ref<ImageTexture> img_tex = image;
3883-
if (img_tex.is_valid()) {
3884-
image = img_tex->get_image();
3885-
}
3886-
// Save in various image formats. Note that if the format is "None",
3887-
// the state's images will be empty, so this code will not be reached.
3888-
if (_image_save_extension.is_valid()) {
3889-
buffer = _image_save_extension->serialize_image_to_bytes(p_state, image, image_dict, _image_format, _lossy_quality);
3890-
} else if (_image_format == "PNG") {
3891-
buffer = image->save_png_to_buffer();
3892-
image_dict["mimeType"] = "image/png";
3893-
} else if (_image_format == "JPEG") {
3894-
buffer = image->save_jpg_to_buffer(_lossy_quality);
3895-
image_dict["mimeType"] = "image/jpeg";
3851+
Ref<Image> image = p_state->images[i]->get_image();
3852+
if (image.is_null()) {
3853+
ERR_PRINT("glTF export: Image's image is null.");
38963854
} else {
3897-
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'.");
3855+
String image_name = p_state->images[i]->get_name();
3856+
if (image_name.is_empty()) {
3857+
image_name = itos(i).pad_zeros(3);
3858+
}
3859+
image_name = _gen_unique_name(p_state, image_name);
3860+
image->set_name(image_name);
3861+
image_dict = _serialize_image(p_state, image, _image_format, _lossy_quality, _image_save_extension);
38983862
}
3899-
ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "glTF: Failed to save image in '" + _image_format + "' format.");
3900-
3901-
bv->byte_length = buffer.size();
3902-
p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length);
3903-
memcpy(&p_state->buffers.write[bi].write[bv->byte_offset], buffer.ptr(), buffer.size());
3904-
ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > p_state->buffers[bi].size(), ERR_FILE_CORRUPT);
3905-
3906-
p_state->buffer_views.push_back(bv);
3907-
bvi = p_state->buffer_views.size() - 1;
3908-
image_dict["bufferView"] = bvi;
39093863
}
39103864
images.push_back(image_dict);
39113865
}
@@ -3920,6 +3874,80 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
39203874
return OK;
39213875
}
39223876

3877+
Dictionary GLTFDocument::_serialize_image(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_image_format, float p_lossy_quality, Ref<GLTFDocumentExtension> p_image_save_extension) {
3878+
Dictionary image_dict;
3879+
if (p_image->is_compressed()) {
3880+
p_image->decompress();
3881+
ERR_FAIL_COND_V_MSG(p_image->is_compressed(), image_dict, "glTF: Image was compressed, but could not be decompressed.");
3882+
}
3883+
3884+
if (p_state->filename.to_lower().ends_with("gltf")) {
3885+
String relative_texture_dir = "textures";
3886+
String full_texture_dir = p_state->base_path.path_join(relative_texture_dir);
3887+
Ref<DirAccess> da = DirAccess::open(p_state->base_path);
3888+
ERR_FAIL_COND_V(da.is_null(), image_dict);
3889+
3890+
if (!da->dir_exists(full_texture_dir)) {
3891+
da->make_dir(full_texture_dir);
3892+
}
3893+
String image_file_name = p_image->get_name();
3894+
if (p_image_save_extension.is_valid()) {
3895+
image_file_name = image_file_name + p_image_save_extension->get_image_file_extension();
3896+
Error err = p_image_save_extension->save_image_at_path(p_state, p_image, full_texture_dir.path_join(image_file_name), p_image_format, p_lossy_quality);
3897+
ERR_FAIL_COND_V_MSG(err != OK, image_dict, "glTF: Failed to save image in '" + p_image_format + "' format as a separate file, error " + itos(err) + ".");
3898+
} else if (p_image_format == "PNG") {
3899+
image_file_name = image_file_name + ".png";
3900+
p_image->save_png(full_texture_dir.path_join(image_file_name));
3901+
} else if (p_image_format == "JPEG") {
3902+
image_file_name = image_file_name + ".jpg";
3903+
p_image->save_jpg(full_texture_dir.path_join(image_file_name), p_lossy_quality);
3904+
} else {
3905+
ERR_FAIL_V_MSG(image_dict, "glTF: Unknown image format '" + p_image_format + "'.");
3906+
}
3907+
image_dict["uri"] = relative_texture_dir.path_join(image_file_name).uri_encode();
3908+
} else {
3909+
GLTFBufferViewIndex bvi;
3910+
3911+
Ref<GLTFBufferView> bv;
3912+
bv.instantiate();
3913+
3914+
const GLTFBufferIndex bi = 0;
3915+
bv->buffer = bi;
3916+
ERR_FAIL_INDEX_V(bi, p_state->buffers.size(), image_dict);
3917+
bv->byte_offset = p_state->buffers[bi].size();
3918+
3919+
Vector<uint8_t> buffer;
3920+
Ref<ImageTexture> img_tex = p_image;
3921+
if (img_tex.is_valid()) {
3922+
p_image = img_tex->get_image();
3923+
}
3924+
// Save in various image formats. Note that if the format is "None",
3925+
// the state's images will be empty, so this code will not be reached.
3926+
if (_image_save_extension.is_valid()) {
3927+
buffer = _image_save_extension->serialize_image_to_bytes(p_state, p_image, image_dict, p_image_format, p_lossy_quality);
3928+
} else if (p_image_format == "PNG") {
3929+
buffer = p_image->save_png_to_buffer();
3930+
image_dict["mimeType"] = "image/png";
3931+
} else if (p_image_format == "JPEG") {
3932+
buffer = p_image->save_jpg_to_buffer(p_lossy_quality);
3933+
image_dict["mimeType"] = "image/jpeg";
3934+
} else {
3935+
ERR_FAIL_V_MSG(image_dict, "glTF: Unknown image format '" + p_image_format + "'.");
3936+
}
3937+
ERR_FAIL_COND_V_MSG(buffer.is_empty(), image_dict, "glTF: Failed to save image in '" + p_image_format + "' format.");
3938+
3939+
bv->byte_length = buffer.size();
3940+
p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length);
3941+
memcpy(&p_state->buffers.write[bi].write[bv->byte_offset], buffer.ptr(), buffer.size());
3942+
ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > p_state->buffers[bi].size(), image_dict);
3943+
3944+
p_state->buffer_views.push_back(bv);
3945+
bvi = p_state->buffer_views.size() - 1;
3946+
image_dict["bufferView"] = bvi;
3947+
}
3948+
return image_dict;
3949+
}
3950+
39233951
Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension) {
39243952
Ref<Image> r_image;
39253953
r_image.instantiate();
@@ -4209,6 +4237,21 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) {
42094237
if (_image_save_extension.is_valid()) {
42104238
Error err = _image_save_extension->serialize_texture_json(p_state, texture_dict, gltf_texture, _image_format);
42114239
ERR_FAIL_COND_V(err != OK, err);
4240+
// If a fallback image format was specified, serialize another image for it.
4241+
// Note: This must only be done after serializing other images to keep the indices of those consistent.
4242+
if (_fallback_image_format != "None" && p_state->json.has("images")) {
4243+
Array json_images = p_state->json["images"];
4244+
texture_dict["source"] = json_images.size();
4245+
Ref<Image> image = p_state->source_images[gltf_texture->get_src_image()];
4246+
String fallback_name = _gen_unique_name(p_state, image->get_name() + "_fallback");
4247+
image = image->duplicate();
4248+
image->set_name(fallback_name);
4249+
ERR_CONTINUE(image.is_null());
4250+
if (_fallback_image_format == "PNG") {
4251+
image->resize(image->get_width() * _fallback_image_quality, image->get_height() * _fallback_image_quality);
4252+
}
4253+
json_images.push_back(_serialize_image(p_state, image, _fallback_image_format, _fallback_image_quality, nullptr));
4254+
}
42124255
} else {
42134256
ERR_CONTINUE(gltf_texture->get_src_image() == -1);
42144257
texture_dict["source"] = gltf_texture->get_src_image();
@@ -8169,6 +8212,10 @@ void GLTFDocument::_bind_methods() {
81698212
ClassDB::bind_method(D_METHOD("get_image_format"), &GLTFDocument::get_image_format);
81708213
ClassDB::bind_method(D_METHOD("set_lossy_quality", "lossy_quality"), &GLTFDocument::set_lossy_quality);
81718214
ClassDB::bind_method(D_METHOD("get_lossy_quality"), &GLTFDocument::get_lossy_quality);
8215+
ClassDB::bind_method(D_METHOD("set_fallback_image_format", "fallback_image_format"), &GLTFDocument::set_fallback_image_format);
8216+
ClassDB::bind_method(D_METHOD("get_fallback_image_format"), &GLTFDocument::get_fallback_image_format);
8217+
ClassDB::bind_method(D_METHOD("set_fallback_image_quality", "fallback_image_quality"), &GLTFDocument::set_fallback_image_quality);
8218+
ClassDB::bind_method(D_METHOD("get_fallback_image_quality"), &GLTFDocument::get_fallback_image_quality);
81728219
ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode);
81738220
ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode);
81748221
ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "base_path"),
@@ -8186,6 +8233,8 @@ void GLTFDocument::_bind_methods() {
81868233

81878234
ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_format"), "set_image_format", "get_image_format");
81888235
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality");
8236+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "fallback_image_format"), "set_fallback_image_format", "get_fallback_image_format");
8237+
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fallback_image_quality"), "set_fallback_image_quality", "get_fallback_image_quality");
81898238
ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode");
81908239

81918240
ClassDB::bind_static_method("GLTFDocument", D_METHOD("import_object_model_property", "state", "json_pointer"), &GLTFDocument::import_object_model_property);

modules/gltf/gltf_document.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ class GLTFDocument : public Resource {
6565
int _naming_version = 1;
6666
String _image_format = "PNG";
6767
float _lossy_quality = 0.75f;
68+
String _fallback_image_format = "None";
69+
float _fallback_image_quality = 0.25f;
6870
Ref<GLTFDocumentExtension> _image_save_extension;
6971
RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT;
7072

@@ -92,6 +94,10 @@ class GLTFDocument : public Resource {
9294
String get_image_format() const;
9395
void set_lossy_quality(float p_lossy_quality);
9496
float get_lossy_quality() const;
97+
void set_fallback_image_format(const String &p_fallback_image_format);
98+
String get_fallback_image_format() const;
99+
void set_fallback_image_quality(float p_fallback_image_quality);
100+
float get_fallback_image_quality() const;
95101
void set_root_node_mode(RootNodeMode p_root_node_mode);
96102
RootNodeMode get_root_node_mode() const;
97103
static String _gen_unique_name_static(HashSet<String> &r_unique_names, const String &p_name);
@@ -182,6 +188,7 @@ class GLTFDocument : public Resource {
182188
Error _serialize_textures(Ref<GLTFState> p_state);
183189
Error _serialize_texture_samplers(Ref<GLTFState> p_state);
184190
Error _serialize_images(Ref<GLTFState> p_state);
191+
Dictionary _serialize_image(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_image_format, float p_lossy_quality, Ref<GLTFDocumentExtension> p_image_save_extension);
185192
Error _serialize_lights(Ref<GLTFState> p_state);
186193
Ref<Image> _parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension);
187194
void _parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_resource_uri, const String &p_file_extension, int p_index, Ref<Image> p_image);

0 commit comments

Comments
 (0)