Browse Source

Merge pull request #59481 from LunaticInAHat/gltf_texture_filter_fix_4

Respect texture filtering when importing GLTF
Clay John 2 years ago
parent
commit
4b52c6caef

+ 1 - 0
modules/gltf/config.py

@@ -26,6 +26,7 @@ def get_doc_classes():
         "GLTFSpecGloss",
         "GLTFState",
         "GLTFTexture",
+        "GLTFTextureSampler",
     ]
 
 

+ 13 - 0
modules/gltf/doc_classes/GLTFState.xml

@@ -93,6 +93,12 @@
 			<description>
 			</description>
 		</method>
+		<method name="get_texture_samplers">
+			<return type="GLTFTextureSampler[]" />
+			<description>
+				Retrieves the array of texture samplers that are used by the textures contained in the GLTF.
+			</description>
+		</method>
 		<method name="get_textures">
 			<return type="GLTFTexture[]" />
 			<description>
@@ -180,6 +186,13 @@
 			<description>
 			</description>
 		</method>
+		<method name="set_texture_samplers">
+			<return type="void" />
+			<param index="0" name="texture_samplers" type="GLTFTextureSampler[]" />
+			<description>
+				Sets the array of texture samplers that are used by the textures contained in the GLTF.
+			</description>
+		</method>
 		<method name="set_textures">
 			<return type="void" />
 			<param index="0" name="textures" type="GLTFTexture[]" />

+ 3 - 0
modules/gltf/doc_classes/GLTFTexture.xml

@@ -7,6 +7,9 @@
 	<tutorials>
 	</tutorials>
 	<members>
+		<member name="sampler" type="int" setter="set_sampler" getter="get_sampler" default="-1">
+			ID of the texture sampler to use when sampling the image. If -1, then the default texture sampler is used (linear filtering, and repeat wrapping in both axes).
+		</member>
 		<member name="src_image" type="int" setter="set_src_image" getter="get_src_image" default="0">
 		</member>
 	</members>

+ 25 - 0
modules/gltf/doc_classes/GLTFTextureSampler.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFTextureSampler" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+	<brief_description>
+		Represents a GLTF texture sampler
+	</brief_description>
+	<description>
+		Represents a texture sampler as defined by the base GLTF spec. Texture samplers in GLTF specify how to sample data from the texture's base image, when rendering the texture on an object.
+	</description>
+	<tutorials>
+	</tutorials>
+	<members>
+		<member name="mag_filter" type="int" setter="set_mag_filter" getter="get_mag_filter" default="9729">
+			Texture's magnification filter, used when texture appears larger on screen than the source image.
+		</member>
+		<member name="min_filter" type="int" setter="set_min_filter" getter="get_min_filter" default="9987">
+			Texture's minification filter, used when the texture appears smaller on screen than the source image.
+		</member>
+		<member name="wrap_s" type="int" setter="set_wrap_s" getter="get_wrap_s" default="10497">
+			Wrapping mode to use for S-axis (horizontal) texture coordinates.
+		</member>
+		<member name="wrap_t" type="int" setter="set_wrap_t" getter="get_wrap_t" default="10497">
+			Wrapping mode to use for T-axis (vertical) texture coordinates.
+		</member>
+	</members>
+</class>

+ 2 - 0
modules/gltf/gltf_defines.h

@@ -58,6 +58,7 @@ class GLTFSkin;
 class GLTFSpecGloss;
 class GLTFState;
 class GLTFTexture;
+class GLTFTextureSampler;
 
 // GLTF index aliases.
 using GLTFAccessorIndex = int;
@@ -73,6 +74,7 @@ using GLTFNodeIndex = int;
 using GLTFSkeletonIndex = int;
 using GLTFSkinIndex = int;
 using GLTFTextureIndex = int;
+using GLTFTextureSamplerIndex = int;
 
 enum GLTFType {
 	TYPE_SCALAR,

+ 133 - 5
modules/gltf/gltf_document.cpp

@@ -145,6 +145,12 @@ Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) {
 		return Error::FAILED;
 	}
 
+	/* STEP SERIALIZE TEXTURE SAMPLERS */
+	err = _serialize_texture_samplers(state);
+	if (err != OK) {
+		return Error::FAILED;
+	}
+
 	/* STEP SERIALIZE ANIMATIONS */
 	err = _serialize_animations(state);
 	if (err != OK) {
@@ -3219,6 +3225,11 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> state) {
 		Ref<GLTFTexture> t = state->textures[i];
 		ERR_CONTINUE(t->get_src_image() == -1);
 		d["source"] = t->get_src_image();
+
+		GLTFTextureSamplerIndex sampler_index = t->get_sampler();
+		if (sampler_index != -1) {
+			d["sampler"] = sampler_index;
+		}
 		textures.push_back(d);
 	}
 	state->json["textures"] = textures;
@@ -3240,13 +3251,18 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> state) {
 		Ref<GLTFTexture> t;
 		t.instantiate();
 		t->set_src_image(d["source"]);
+		if (d.has("sampler")) {
+			t->set_sampler(d["sampler"]);
+		} else {
+			t->set_sampler(-1);
+		}
 		state->textures.push_back(t);
 	}
 
 	return OK;
 }
 
-GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture) {
+GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) {
 	ERR_FAIL_COND_V(p_texture.is_null(), -1);
 	Ref<GLTFTexture> gltf_texture;
 	gltf_texture.instantiate();
@@ -3254,6 +3270,7 @@ GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D>
 	GLTFImageIndex gltf_src_image_i = state->images.size();
 	state->images.push_back(p_texture);
 	gltf_texture->set_src_image(gltf_src_image_i);
+	gltf_texture->set_sampler(_set_sampler_for_mode(state, p_filter_mode, p_repeats));
 	GLTFTextureIndex gltf_texture_i = state->textures.size();
 	state->textures.push_back(gltf_texture);
 	return gltf_texture_i;
@@ -3268,6 +3285,102 @@ Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> state, const GLTFTextur
 	return state->images[image];
 }
 
+GLTFTextureSamplerIndex GLTFDocument::_set_sampler_for_mode(Ref<GLTFState> state, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) {
+	for (int i = 0; i < state->texture_samplers.size(); ++i) {
+		if (state->texture_samplers[i]->get_filter_mode() == p_filter_mode) {
+			return i;
+		}
+	}
+
+	GLTFTextureSamplerIndex gltf_sampler_i = state->texture_samplers.size();
+	Ref<GLTFTextureSampler> gltf_sampler;
+	gltf_sampler.instantiate();
+	gltf_sampler->set_filter_mode(p_filter_mode);
+	gltf_sampler->set_wrap_mode(p_repeats);
+	state->texture_samplers.push_back(gltf_sampler);
+	return gltf_sampler_i;
+}
+
+Ref<GLTFTextureSampler> GLTFDocument::_get_sampler_for_texture(Ref<GLTFState> state, const GLTFTextureIndex p_texture) {
+	ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref<Texture2D>());
+	const GLTFTextureSamplerIndex sampler = state->textures[p_texture]->get_sampler();
+
+	if (sampler == -1) {
+		return state->default_texture_sampler;
+	} else {
+		ERR_FAIL_INDEX_V(sampler, state->texture_samplers.size(), Ref<GLTFTextureSampler>());
+
+		return state->texture_samplers[sampler];
+	}
+}
+
+Error GLTFDocument::_serialize_texture_samplers(Ref<GLTFState> state) {
+	if (!state->texture_samplers.size()) {
+		return OK;
+	}
+
+	Array samplers;
+	for (int32_t i = 0; i < state->texture_samplers.size(); ++i) {
+		Dictionary d;
+		Ref<GLTFTextureSampler> s = state->texture_samplers[i];
+		d["magFilter"] = s->get_mag_filter();
+		d["minFilter"] = s->get_min_filter();
+		d["wrapS"] = s->get_wrap_s();
+		d["wrapT"] = s->get_wrap_t();
+		samplers.push_back(d);
+	}
+	state->json["samplers"] = samplers;
+
+	return OK;
+}
+
+Error GLTFDocument::_parse_texture_samplers(Ref<GLTFState> state) {
+	state->default_texture_sampler.instantiate();
+	state->default_texture_sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR);
+	state->default_texture_sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR);
+	state->default_texture_sampler->set_wrap_s(GLTFTextureSampler::WrapMode::REPEAT);
+	state->default_texture_sampler->set_wrap_t(GLTFTextureSampler::WrapMode::REPEAT);
+
+	if (!state->json.has("samplers")) {
+		return OK;
+	}
+
+	const Array &samplers = state->json["samplers"];
+	for (int i = 0; i < samplers.size(); ++i) {
+		const Dictionary &d = samplers[i];
+
+		Ref<GLTFTextureSampler> sampler;
+		sampler.instantiate();
+
+		if (d.has("minFilter")) {
+			sampler->set_min_filter(d["minFilter"]);
+		} else {
+			sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR);
+		}
+		if (d.has("magFilter")) {
+			sampler->set_mag_filter(d["magFilter"]);
+		} else {
+			sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR);
+		}
+
+		if (d.has("wrapS")) {
+			sampler->set_wrap_s(d["wrapS"]);
+		} else {
+			sampler->set_wrap_s(GLTFTextureSampler::WrapMode::DEFAULT);
+		}
+
+		if (d.has("wrapT")) {
+			sampler->set_wrap_t(d["wrapT"]);
+		} else {
+			sampler->set_wrap_t(GLTFTextureSampler::WrapMode::DEFAULT);
+		}
+
+		state->texture_samplers.push_back(sampler);
+	}
+
+	return OK;
+}
+
 Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
 	Array materials;
 	for (int32_t i = 0; i < state->materials.size(); i++) {
@@ -3299,7 +3412,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
 
 				if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
 					albedo_texture->set_name(material->get_name() + "_albedo");
-					gltf_texture_index = _set_texture(state, albedo_texture);
+					gltf_texture_index = _set_texture(state, albedo_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
 				}
 				if (gltf_texture_index != -1) {
 					bct["index"] = gltf_texture_index;
@@ -3429,7 +3542,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
 				GLTFTextureIndex orm_texture_index = -1;
 				if (has_ao || has_roughness || has_metalness) {
 					orm_texture->set_name(material->get_name() + "_orm");
-					orm_texture_index = _set_texture(state, orm_texture);
+					orm_texture_index = _set_texture(state, orm_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
 				}
 				if (has_ao) {
 					Dictionary occt;
@@ -3484,7 +3597,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
 			GLTFTextureIndex gltf_texture_index = -1;
 			if (tex.is_valid() && tex->get_image().is_valid()) {
 				tex->set_name(material->get_name() + "_normal");
-				gltf_texture_index = _set_texture(state, tex);
+				gltf_texture_index = _set_texture(state, tex, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
 			}
 			nt["scale"] = material->get_normal_scale();
 			if (gltf_texture_index != -1) {
@@ -3507,7 +3620,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
 			GLTFTextureIndex gltf_texture_index = -1;
 			if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) {
 				emission_texture->set_name(material->get_name() + "_emission");
-				gltf_texture_index = _set_texture(state, emission_texture);
+				gltf_texture_index = _set_texture(state, emission_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
 			}
 
 			if (gltf_texture_index != -1) {
@@ -3566,6 +3679,11 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) {
 			if (sgm.has("diffuseTexture")) {
 				const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"];
 				if (diffuse_texture_dict.has("index")) {
+					Ref<GLTFTextureSampler> diffuse_sampler = _get_sampler_for_texture(state, diffuse_texture_dict["index"]);
+					if (diffuse_sampler.is_valid()) {
+						material->set_texture_filter(diffuse_sampler->get_filter_mode());
+						material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, diffuse_sampler->get_wrap_mode());
+					}
 					Ref<Texture2D> diffuse_texture = _get_texture(state, diffuse_texture_dict["index"]);
 					if (diffuse_texture.is_valid()) {
 						spec_gloss->diffuse_img = diffuse_texture->get_image();
@@ -3614,6 +3732,9 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) {
 			if (mr.has("baseColorTexture")) {
 				const Dictionary &bct = mr["baseColorTexture"];
 				if (bct.has("index")) {
+					Ref<GLTFTextureSampler> bct_sampler = _get_sampler_for_texture(state, bct["index"]);
+					material->set_texture_filter(bct_sampler->get_filter_mode());
+					material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, bct_sampler->get_wrap_mode());
 					material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, _get_texture(state, bct["index"]));
 				}
 				if (!mr.has("baseColorFactor")) {
@@ -6477,8 +6598,10 @@ Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess>
 		err = ext->import_preflight(state);
 		ERR_FAIL_COND_V(err != OK, err);
 	}
+
 	err = _parse_gltf_state(state, p_path, p_bake_fps);
 	ERR_FAIL_COND_V(err != OK, err);
+
 	return OK;
 }
 
@@ -6834,6 +6957,11 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_sear
 
 		ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
 
+		/* PARSE TEXTURE SAMPLERS */
+		err = _parse_texture_samplers(state);
+
+		ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
+
 		/* PARSE TEXTURES */
 		err = _parse_textures(state);
 

+ 8 - 1
modules/gltf/gltf_document.h

@@ -95,9 +95,14 @@ private:
 	String _gen_unique_bone_name(Ref<GLTFState> state,
 			const GLTFSkeletonIndex skel_i,
 			const String &p_name);
-	GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture);
+	GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture,
+			StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats);
 	Ref<Texture2D> _get_texture(Ref<GLTFState> state,
 			const GLTFTextureIndex p_texture);
+	GLTFTextureSamplerIndex _set_sampler_for_mode(Ref<GLTFState> state,
+			StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats);
+	Ref<GLTFTextureSampler> _get_sampler_for_texture(Ref<GLTFState> state,
+			const GLTFTextureIndex p_texture);
 	Error _parse_json(const String &p_path, Ref<GLTFState> state);
 	Error _parse_glb(Ref<FileAccess> f, Ref<GLTFState> state);
 	void _compute_node_heights(Ref<GLTFState> state);
@@ -145,10 +150,12 @@ private:
 			const bool p_for_vertex);
 	Error _parse_meshes(Ref<GLTFState> state);
 	Error _serialize_textures(Ref<GLTFState> state);
+	Error _serialize_texture_samplers(Ref<GLTFState> state);
 	Error _serialize_images(Ref<GLTFState> state, const String &p_path);
 	Error _serialize_lights(Ref<GLTFState> state);
 	Error _parse_images(Ref<GLTFState> state, const String &p_base_path);
 	Error _parse_textures(Ref<GLTFState> state);
+	Error _parse_texture_samplers(Ref<GLTFState> state);
 	Error _parse_materials(Ref<GLTFState> state);
 	void _set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> material);
 	void spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss,

+ 11 - 0
modules/gltf/gltf_state.cpp

@@ -64,6 +64,8 @@ void GLTFState::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_root_nodes", "root_nodes"), &GLTFState::set_root_nodes);
 	ClassDB::bind_method(D_METHOD("get_textures"), &GLTFState::get_textures);
 	ClassDB::bind_method(D_METHOD("set_textures", "textures"), &GLTFState::set_textures);
+	ClassDB::bind_method(D_METHOD("get_texture_samplers"), &GLTFState::get_texture_samplers);
+	ClassDB::bind_method(D_METHOD("set_texture_samplers", "texture_samplers"), &GLTFState::set_texture_samplers);
 	ClassDB::bind_method(D_METHOD("get_images"), &GLTFState::get_images);
 	ClassDB::bind_method(D_METHOD("set_images", "images"), &GLTFState::set_images);
 	ClassDB::bind_method(D_METHOD("get_skins"), &GLTFState::get_skins);
@@ -101,6 +103,7 @@ void GLTFState::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_path"), "set_base_path", "get_base_path"); // String
 	ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "root_nodes"), "set_root_nodes", "get_root_nodes"); // Vector<int>
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector<Ref<GLTFTexture>>
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "texture_samplers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_texture_samplers", "get_texture_samplers"); //Vector<Ref<GLTFTextureSampler>>
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "images", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_images", "get_images"); // Vector<Ref<Texture>
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skins", "get_skins"); // Vector<Ref<GLTFSkin>>
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "cameras", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_cameras", "get_cameras"); // Vector<Ref<GLTFCamera>>
@@ -236,6 +239,14 @@ void GLTFState::set_textures(TypedArray<GLTFTexture> p_textures) {
 	GLTFTemplateConvert::set_from_array(textures, p_textures);
 }
 
+TypedArray<GLTFTextureSampler> GLTFState::get_texture_samplers() {
+	return GLTFTemplateConvert::to_array(texture_samplers);
+}
+
+void GLTFState::set_texture_samplers(TypedArray<GLTFTextureSampler> p_texture_samplers) {
+	GLTFTemplateConvert::set_from_array(texture_samplers, p_texture_samplers);
+}
+
 TypedArray<Texture2D> GLTFState::get_images() {
 	return GLTFTemplateConvert::to_array(images);
 }

+ 6 - 0
modules/gltf/gltf_state.h

@@ -42,6 +42,7 @@
 #include "structures/gltf_skeleton.h"
 #include "structures/gltf_skin.h"
 #include "structures/gltf_texture.h"
+#include "structures/gltf_texture_sampler.h"
 
 #include "core/templates/rb_map.h"
 #include "scene/animation/animation_player.h"
@@ -77,6 +78,8 @@ class GLTFState : public Resource {
 	String scene_name;
 	Vector<int> root_nodes;
 	Vector<Ref<GLTFTexture>> textures;
+	Vector<Ref<GLTFTextureSampler>> texture_samplers;
+	Ref<GLTFTextureSampler> default_texture_sampler;
 	Vector<Ref<Texture2D>> images;
 	Vector<String> extensions_used;
 	Vector<String> extensions_required;
@@ -149,6 +152,9 @@ public:
 	TypedArray<GLTFTexture> get_textures();
 	void set_textures(TypedArray<GLTFTexture> p_textures);
 
+	TypedArray<GLTFTextureSampler> get_texture_samplers();
+	void set_texture_samplers(TypedArray<GLTFTextureSampler> p_texture_samplers);
+
 	TypedArray<Texture2D> get_images();
 	void set_images(TypedArray<Texture2D> p_images);
 

+ 2 - 0
modules/gltf/register_types.cpp

@@ -47,6 +47,7 @@
 #include "structures/gltf_skeleton.h"
 #include "structures/gltf_skin.h"
 #include "structures/gltf_texture.h"
+#include "structures/gltf_texture_sampler.h"
 
 #ifdef TOOLS_ENABLED
 #include "core/config/project_settings.h"
@@ -126,6 +127,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
 		GDREGISTER_CLASS(GLTFSpecGloss);
 		GDREGISTER_CLASS(GLTFState);
 		GDREGISTER_CLASS(GLTFTexture);
+		GDREGISTER_CLASS(GLTFTextureSampler);
 	}
 
 #ifdef TOOLS_ENABLED

+ 11 - 0
modules/gltf/structures/gltf_texture.cpp

@@ -33,8 +33,11 @@
 void GLTFTexture::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_src_image"), &GLTFTexture::get_src_image);
 	ClassDB::bind_method(D_METHOD("set_src_image", "src_image"), &GLTFTexture::set_src_image);
+	ClassDB::bind_method(D_METHOD("get_sampler"), &GLTFTexture::get_sampler);
+	ClassDB::bind_method(D_METHOD("set_sampler", "sampler"), &GLTFTexture::set_sampler);
 
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "src_image"), "set_src_image", "get_src_image"); // int
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "sampler"), "set_sampler", "get_sampler"); // int
 }
 
 GLTFImageIndex GLTFTexture::get_src_image() const {
@@ -44,3 +47,11 @@ GLTFImageIndex GLTFTexture::get_src_image() const {
 void GLTFTexture::set_src_image(GLTFImageIndex val) {
 	src_image = val;
 }
+
+GLTFTextureSamplerIndex GLTFTexture::get_sampler() const {
+	return sampler;
+}
+
+void GLTFTexture::set_sampler(GLTFTextureSamplerIndex val) {
+	sampler = val;
+}

+ 3 - 0
modules/gltf/structures/gltf_texture.h

@@ -39,6 +39,7 @@ class GLTFTexture : public Resource {
 
 private:
 	GLTFImageIndex src_image = 0;
+	GLTFTextureSamplerIndex sampler = -1;
 
 protected:
 	static void _bind_methods();
@@ -46,6 +47,8 @@ protected:
 public:
 	GLTFImageIndex get_src_image() const;
 	void set_src_image(GLTFImageIndex val);
+	GLTFTextureSamplerIndex get_sampler() const;
+	void set_sampler(GLTFTextureSamplerIndex val);
 };
 
 #endif // GLTF_TEXTURE_H

+ 47 - 0
modules/gltf/structures/gltf_texture_sampler.cpp

@@ -0,0 +1,47 @@
+/*************************************************************************/
+/*  gltf_texture_sampler.cpp                                             */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "gltf_texture_sampler.h"
+
+void GLTFTextureSampler::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("get_mag_filter"), &GLTFTextureSampler::get_mag_filter);
+	ClassDB::bind_method(D_METHOD("set_mag_filter", "filter_mode"), &GLTFTextureSampler::set_mag_filter);
+	ClassDB::bind_method(D_METHOD("get_min_filter"), &GLTFTextureSampler::get_min_filter);
+	ClassDB::bind_method(D_METHOD("set_min_filter", "filter_mode"), &GLTFTextureSampler::set_min_filter);
+	ClassDB::bind_method(D_METHOD("get_wrap_s"), &GLTFTextureSampler::get_wrap_s);
+	ClassDB::bind_method(D_METHOD("set_wrap_s", "wrap_mode"), &GLTFTextureSampler::set_wrap_s);
+	ClassDB::bind_method(D_METHOD("get_wrap_t"), &GLTFTextureSampler::get_wrap_t);
+	ClassDB::bind_method(D_METHOD("set_wrap_t", "wrap_mode"), &GLTFTextureSampler::set_wrap_t);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "mag_filter"), "set_mag_filter", "get_mag_filter");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "min_filter"), "set_min_filter", "get_min_filter");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_s"), "set_wrap_s", "get_wrap_s");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_t"), "set_wrap_t", "get_wrap_t");
+}

+ 163 - 0
modules/gltf/structures/gltf_texture_sampler.h

@@ -0,0 +1,163 @@
+/*************************************************************************/
+/*  gltf_texture_sampler.h                                               */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GLTF_TEXTURE_SAMPLER_H
+#define GLTF_TEXTURE_SAMPLER_H
+
+#include "core/io/resource.h"
+#include "scene/resources/material.h"
+
+class GLTFTextureSampler : public Resource {
+	GDCLASS(GLTFTextureSampler, Resource);
+
+public:
+	enum FilterMode {
+		NEAREST = 9728,
+		LINEAR = 9729,
+		NEAREST_MIPMAP_NEAREST = 9984,
+		LINEAR_MIPMAP_NEAREST = 9985,
+		NEAREST_MIPMAP_LINEAR = 9986,
+		LINEAR_MIPMAP_LINEAR = 9987
+	};
+
+	enum WrapMode {
+		CLAMP_TO_EDGE = 33071,
+		MIRRORED_REPEAT = 33648,
+		REPEAT = 10497,
+		DEFAULT = REPEAT
+	};
+
+	int get_mag_filter() const {
+		return mag_filter;
+	}
+
+	void set_mag_filter(const int filter_mode) {
+		mag_filter = (FilterMode)filter_mode;
+	}
+
+	int get_min_filter() const {
+		return min_filter;
+	}
+
+	void set_min_filter(const int filter_mode) {
+		min_filter = (FilterMode)filter_mode;
+	}
+
+	int get_wrap_s() const {
+		return wrap_s;
+	}
+
+	void set_wrap_s(const int wrap_mode) {
+		wrap_s = (WrapMode)wrap_mode;
+	}
+
+	int get_wrap_t() const {
+		return wrap_t;
+	}
+
+	void set_wrap_t(const int wrap_mode) {
+		wrap_s = (WrapMode)wrap_mode;
+	}
+
+	StandardMaterial3D::TextureFilter get_filter_mode() const {
+		using TextureFilter = StandardMaterial3D::TextureFilter;
+
+		switch (min_filter) {
+			case NEAREST:
+				return TextureFilter::TEXTURE_FILTER_NEAREST;
+			case LINEAR:
+				return TextureFilter::TEXTURE_FILTER_LINEAR;
+			case NEAREST_MIPMAP_NEAREST:
+			case NEAREST_MIPMAP_LINEAR:
+				return TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;
+			case LINEAR_MIPMAP_NEAREST:
+			case LINEAR_MIPMAP_LINEAR:
+			default:
+				return TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS;
+		}
+	}
+
+	void set_filter_mode(StandardMaterial3D::TextureFilter mode) {
+		using TextureFilter = StandardMaterial3D::TextureFilter;
+
+		switch (mode) {
+			case TextureFilter::TEXTURE_FILTER_NEAREST:
+				min_filter = FilterMode::NEAREST;
+				mag_filter = FilterMode::NEAREST;
+				break;
+			case TextureFilter::TEXTURE_FILTER_LINEAR:
+				min_filter = FilterMode::LINEAR;
+				mag_filter = FilterMode::LINEAR;
+				break;
+			case TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS:
+			case TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC:
+				min_filter = FilterMode::NEAREST_MIPMAP_LINEAR;
+				mag_filter = FilterMode::NEAREST;
+				break;
+			case TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS:
+			case TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC:
+			default:
+				min_filter = FilterMode::LINEAR_MIPMAP_LINEAR;
+				mag_filter = FilterMode::LINEAR;
+				break;
+		}
+	}
+
+	bool get_wrap_mode() const {
+		// BaseMaterial3D presents wrapping as a boolean property. Either the texture is repeated
+		// in both dimensions, non-mirrored, or it isn't repeated at all. This will cause oddities
+		// when people import models having other wrapping mode combinations.
+		return (wrap_s == WrapMode::REPEAT) && (wrap_t == WrapMode::REPEAT);
+	}
+
+	void set_wrap_mode(bool mat_repeats) {
+		if (mat_repeats) {
+			wrap_s = WrapMode::REPEAT;
+			wrap_t = WrapMode::REPEAT;
+		} else {
+			wrap_s = WrapMode::CLAMP_TO_EDGE;
+			wrap_t = WrapMode::CLAMP_TO_EDGE;
+		}
+	}
+
+protected:
+	static void _bind_methods();
+
+private:
+	FilterMode mag_filter = FilterMode::LINEAR;
+	FilterMode min_filter = FilterMode::LINEAR_MIPMAP_LINEAR;
+	WrapMode wrap_s = WrapMode::REPEAT;
+	WrapMode wrap_t = WrapMode::REPEAT;
+};
+
+VARIANT_ENUM_CAST(GLTFTextureSampler::FilterMode);
+VARIANT_ENUM_CAST(GLTFTextureSampler::WrapMode);
+
+#endif // GLTF_TEXTURE_SAMPLER_H