Parcourir la source

Add stencil support for spatial materials

Apples il y a 2 ans
Parent
commit
d674c9e289
29 fichiers modifiés avec 1319 ajouts et 125 suppressions
  1. 62 0
      doc/classes/BaseMaterial3D.xml
  2. 4 0
      drivers/gles3/rasterizer_gles3.cpp
  3. 1 0
      drivers/gles3/rasterizer_gles3.h
  4. 82 10
      drivers/gles3/rasterizer_scene_gles3.cpp
  5. 56 0
      drivers/gles3/rasterizer_scene_gles3.h
  6. 25 0
      drivers/gles3/storage/material_storage.cpp
  7. 22 0
      drivers/gles3/storage/material_storage.h
  8. 22 20
      drivers/gles3/storage/render_scene_buffers_gles3.cpp
  9. 38 10
      drivers/gles3/storage/texture_storage.cpp
  10. 3 0
      drivers/gles3/storage/texture_storage.h
  11. 46 16
      editor/plugins/text_shader_editor.cpp
  12. 1 0
      editor/plugins/visual_shader_editor_plugin.cpp
  13. 286 1
      scene/resources/material.cpp
  14. 71 0
      scene/resources/material.h
  15. 132 0
      scene/resources/visual_shader.cpp
  16. 5 0
      scene/resources/visual_shader.h
  17. 21 1
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
  18. 2 0
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
  19. 67 0
      servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
  20. 22 0
      servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
  21. 11 0
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
  22. 66 0
      servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
  23. 22 0
      servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
  24. 18 0
      servers/rendering/shader_compiler.cpp
  25. 2 0
      servers/rendering/shader_compiler.h
  26. 214 66
      servers/rendering/shader_language.cpp
  27. 8 1
      servers/rendering/shader_language.h
  28. 8 0
      servers/rendering/shader_types.cpp
  29. 2 0
      servers/rendering/shader_types.h

+ 62 - 0
doc/classes/BaseMaterial3D.xml

@@ -378,6 +378,24 @@
 			The method for rendering the specular blob.
 			[b]Note:[/b] [member specular_mode] only applies to the specular blob. It does not affect specular reflections from the sky, screen-space reflections, [VoxelGI], SDFGI or [ReflectionProbe]s. To disable reflections from these sources as well, set [member metallic_specular] to [code]0.0[/code] instead.
 		</member>
+		<member name="stencil_color" type="Color" setter="set_stencil_effect_color" getter="get_stencil_effect_color" default="Color(0, 0, 0, 1)" experimental="May be affected by future rendering pipeline changes.">
+			The primary color of the stencil effect.
+		</member>
+		<member name="stencil_compare" type="int" setter="set_stencil_compare" getter="get_stencil_compare" enum="BaseMaterial3D.StencilCompare" default="0" experimental="May be affected by future rendering pipeline changes.">
+			The comparison operator to use for stencil masking operations. See [enum StencilCompare].
+		</member>
+		<member name="stencil_flags" type="int" setter="set_stencil_flags" getter="get_stencil_flags" default="0" experimental="May be affected by future rendering pipeline changes.">
+			The flags dictating how the stencil operation behaves. See [enum StencilFlags].
+		</member>
+		<member name="stencil_mode" type="int" setter="set_stencil_mode" getter="get_stencil_mode" enum="BaseMaterial3D.StencilMode" default="0" experimental="May be affected by future rendering pipeline changes.">
+			The stencil effect mode. See [enum StencilMode].
+		</member>
+		<member name="stencil_outline_thickness" type="float" setter="set_stencil_effect_outline_thickness" getter="get_stencil_effect_outline_thickness" default="0.01" experimental="May be affected by future rendering pipeline changes.">
+			The outline thickness for [constant STENCIL_MODE_OUTLINE].
+		</member>
+		<member name="stencil_reference" type="int" setter="set_stencil_reference" getter="get_stencil_reference" default="1" experimental="May be affected by future rendering pipeline changes.">
+			The stencil reference value (0-255). Typically a power of 2.
+		</member>
 		<member name="subsurf_scatter_enabled" type="bool" setter="set_feature" getter="get_feature" default="false">
 			If [code]true[/code], subsurface scattering is enabled. Emulates light that penetrates an object's surface, is scattered, and then emerges. Subsurface scattering quality is controlled by [member ProjectSettings.rendering/environment/subsurface_scattering/subsurface_scattering_quality].
 		</member>
@@ -828,5 +846,49 @@
 		<constant name="DISTANCE_FADE_OBJECT_DITHER" value="3" enum="DistanceFadeMode">
 			Smoothly fades the object out based on the object's distance from the camera using a dithering approach. Dithering discards pixels based on a set pattern to smoothly fade without enabling transparency. On certain hardware, this can be faster than [constant DISTANCE_FADE_PIXEL_ALPHA] and [constant DISTANCE_FADE_PIXEL_DITHER].
 		</constant>
+		<constant name="STENCIL_MODE_DISABLED" value="0" enum="StencilMode">
+			Disables stencil operations.
+		</constant>
+		<constant name="STENCIL_MODE_OUTLINE" value="1" enum="StencilMode">
+			Stencil preset which applies an outline to the object.
+			[b]Note:[/b] Requires a [member Material.next_pass] material which will be automatically applied. Any manual changes made to [member Material.next_pass] will be lost when the stencil properties are modified or the scene is reloaded. To safely apply a [member Material.next_pass] material on a material that uses stencil presets, use [member GeometryInstance3D.material_overlay] instead.
+		</constant>
+		<constant name="STENCIL_MODE_XRAY" value="2" enum="StencilMode">
+			Stencil preset which shows a silhouette of the object behind walls.
+			[b]Note:[/b] Requires a [member Material.next_pass] material which will be automatically applied. Any manual changes made to [member Material.next_pass] will be lost when the stencil properties are modified or the scene is reloaded. To safely apply a [member Material.next_pass] material on a material that uses stencil presets, use [member GeometryInstance3D.material_overlay] instead.
+		</constant>
+		<constant name="STENCIL_MODE_CUSTOM" value="3" enum="StencilMode">
+			Enables stencil operations without a preset.
+		</constant>
+		<constant name="STENCIL_FLAG_READ" value="1" enum="StencilFlags">
+			The material will only be rendered where it passes a stencil comparison with existing stencil buffer values. See [enum StencilCompare].
+		</constant>
+		<constant name="STENCIL_FLAG_WRITE" value="2" enum="StencilFlags">
+			The material will write the reference value to the stencil buffer where it passes the depth test.
+		</constant>
+		<constant name="STENCIL_FLAG_WRITE_DEPTH_FAIL" value="4" enum="StencilFlags">
+			The material will write the reference value to the stencil buffer where it fails the depth test.
+		</constant>
+		<constant name="STENCIL_COMPARE_ALWAYS" value="0" enum="StencilCompare">
+			Always passes the stencil test.
+		</constant>
+		<constant name="STENCIL_COMPARE_LESS" value="1" enum="StencilCompare">
+			Passes the stencil test when the reference value is less than the existing stencil value.
+		</constant>
+		<constant name="STENCIL_COMPARE_EQUAL" value="2" enum="StencilCompare">
+			Passes the stencil test when the reference value is equal to the existing stencil value.
+		</constant>
+		<constant name="STENCIL_COMPARE_LESS_OR_EQUAL" value="3" enum="StencilCompare">
+			Passes the stencil test when the reference value is less than or equal to the existing stencil value.
+		</constant>
+		<constant name="STENCIL_COMPARE_GREATER" value="4" enum="StencilCompare">
+			Passes the stencil test when the reference value is greater than the existing stencil value.
+		</constant>
+		<constant name="STENCIL_COMPARE_NOT_EQUAL" value="5" enum="StencilCompare">
+			Passes the stencil test when the reference value is not equal to the existing stencil value.
+		</constant>
+		<constant name="STENCIL_COMPARE_GREATER_OR_EQUAL" value="6" enum="StencilCompare">
+			Passes the stencil test when the reference value is greater than or equal to the existing stencil value.
+		</constant>
 	</constants>
 </class>

+ 4 - 0
drivers/gles3/rasterizer_gles3.cpp

@@ -138,6 +138,10 @@ void RasterizerGLES3::clear_depth(float p_depth) {
 #endif // GLES_API_ENABLED
 }
 
+void RasterizerGLES3::clear_stencil(int32_t p_stencil) {
+	glClearStencil(p_stencil);
+}
+
 #ifdef CAN_DEBUG
 static void GLAPIENTRY _gl_debug_print(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const GLvoid *userParam) {
 	// These are ultimately annoying, so removing for now.

+ 1 - 0
drivers/gles3/rasterizer_gles3.h

@@ -116,6 +116,7 @@ public:
 
 	static bool is_gles_over_gl() { return gles_over_gl; }
 	static void clear_depth(float p_depth);
+	static void clear_stencil(int32_t p_stencil);
 
 	static void make_current(bool p_gles_over_gl) {
 		gles_over_gl = p_gles_over_gl;

+ 82 - 10
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -224,6 +224,10 @@ void RasterizerSceneGLES3::_geometry_instance_add_surface_with_material(Geometry
 		flags |= GeometryInstanceSurface::FLAG_USES_DOUBLE_SIDED_SHADOWS;
 	}
 
+	if (p_material->shader_data->stencil_enabled) {
+		flags |= GeometryInstanceSurface::FLAG_USES_STENCIL;
+	}
+
 	if (has_alpha || has_read_screen_alpha || p_material->shader_data->depth_draw == GLES3::SceneShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test != GLES3::SceneShaderData::DEPTH_TEST_ENABLED) {
 		//material is only meant for alpha pass
 		flags |= GeometryInstanceSurface::FLAG_PASS_ALPHA;
@@ -237,6 +241,17 @@ void RasterizerSceneGLES3::_geometry_instance_add_surface_with_material(Geometry
 		flags |= GeometryInstanceSurface::FLAG_PASS_SHADOW;
 	}
 
+	if (p_material->shader_data->stencil_enabled) {
+		if (p_material->shader_data->stencil_flags & GLES3::SceneShaderData::STENCIL_FLAG_READ) {
+			// Stencil materials which read from the stencil buffer must be in the alpha pass.
+			// This is critical to preserve compatibility once we'll have the compositor.
+			if (!(flags & GeometryInstanceSurface::FLAG_PASS_ALPHA)) {
+				String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")";
+				ERR_PRINT_ED(vformat("Attempting to use a shader %s that reads stencil but is not in the alpha queue. Ensure the material uses alpha blending or has depth_draw disabled or depth_test disabled.", shader_path));
+			}
+		}
+	}
+
 	GLES3::SceneMaterialData *material_shadow = nullptr;
 	void *surface_shadow = nullptr;
 	if (!p_material->shader_data->uses_particle_trails && !p_material->shader_data->writes_modelview_or_projection && !p_material->shader_data->uses_vertex && !p_material->shader_data->uses_discard && !p_material->shader_data->uses_depth_prepass_alpha && !p_material->shader_data->uses_alpha_clip && !p_material->shader_data->uses_world_coordinates && !p_material->shader_data->wireframe) {
@@ -1234,6 +1249,7 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const
 		scene_state.used_screen_texture = false;
 		scene_state.used_normal_texture = false;
 		scene_state.used_depth_texture = false;
+		scene_state.used_opaque_stencil = false;
 	}
 
 	Plane near_plane;
@@ -1427,6 +1443,9 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const
 				if (surf->flags & GeometryInstanceSurface::FLAG_USES_DEPTH_TEXTURE) {
 					scene_state.used_depth_texture = true;
 				}
+				if ((surf->flags & GeometryInstanceSurface::FLAG_USES_STENCIL) && !force_alpha && (surf->flags & (GeometryInstanceSurface::FLAG_PASS_DEPTH | GeometryInstanceSurface::FLAG_PASS_OPAQUE))) {
+					scene_state.used_opaque_stencil = true;
+				}
 
 			} else if (p_pass_mode == PASS_MODE_SHADOW) {
 				if (surf->flags & GeometryInstanceSurface::FLAG_PASS_SHADOW) {
@@ -2490,6 +2509,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 	// Do depth prepass if it's explicitly enabled
 	bool use_depth_prepass = config->use_depth_prepass;
 
+	// Forcibly enable depth prepass if opaque stencil writes are used.
+	use_depth_prepass = use_depth_prepass || scene_state.used_opaque_stencil;
+
 	// Don't do depth prepass we are rendering overdraw
 	use_depth_prepass = use_depth_prepass && get_debug_draw_mode() != RS::VIEWPORT_DEBUG_DRAW_OVERDRAW;
 
@@ -2506,10 +2528,13 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 		scene_state.enable_gl_blend(false);
 		scene_state.set_gl_depth_func(GL_GEQUAL);
 		scene_state.enable_gl_scissor_test(false);
+		scene_state.enable_gl_stencil_test(false);
+		scene_state.set_gl_stencil_write_mask(255);
 
 		glColorMask(0, 0, 0, 0);
 		RasterizerGLES3::clear_depth(0.0);
-		glClear(GL_DEPTH_BUFFER_BIT);
+		RasterizerGLES3::clear_stencil(0);
+		glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 		// Some desktop GL implementations fall apart when using Multiview with GL_NONE.
 		GLuint db = p_camera_data->view_count > 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE;
 		glDrawBuffers(1, &db);
@@ -2549,9 +2574,12 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 		glDrawBuffers(1, &db);
 	}
 
+	scene_state.enable_gl_stencil_test(false);
+
 	if (!fb_cleared) {
 		RasterizerGLES3::clear_depth(0.0);
-		glClear(GL_DEPTH_BUFFER_BIT);
+		RasterizerGLES3::clear_stencil(0);
+		glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 	}
 
 	// Need to clear framebuffer unless:
@@ -2631,6 +2659,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 	_render_list_template<PASS_MODE_COLOR>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_OPAQUE].elements.size());
 
 	scene_state.enable_gl_depth_draw(false);
+	scene_state.enable_gl_stencil_test(false);
 
 	if (draw_sky || draw_sky_fog_only) {
 		RENDER_TIMESTAMP("Render Sky");
@@ -2689,7 +2718,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 			if (scene_state.used_depth_texture) {
 				glBlitFramebuffer(0, 0, size.x, size.y,
 						0, 0, size.x, size.y,
-						GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+						GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
 				glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 7);
 				glBindTexture(GL_TEXTURE_2D, backbuffer_depth);
 			}
@@ -2707,6 +2736,8 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 
 	_render_list_template<PASS_MODE_COLOR_TRANSPARENT>(&render_list_params_alpha, &render_data, 0, render_list[RENDER_LIST_ALPHA].elements.size(), true);
 
+	scene_state.enable_gl_stencil_test(false);
+
 	if (!flip_y) {
 		// Restore the default winding order.
 		glFrontFace(GL_CCW);
@@ -2836,7 +2867,7 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend
 			// Copy depth buffer
 			glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_int);
 			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_rt);
-			glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+			glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
 		}
 
 		glBindFramebuffer(GL_FRAMEBUFFER, fbo_rt);
@@ -2865,10 +2896,10 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend
 
 			for (uint32_t v = 0; v < view_count; v++) {
 				glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, read_color, 0, v);
-				glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, read_depth, 0, v);
+				glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, read_depth, 0, v);
 				glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, write_color, 0, v);
-				glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, write_depth, 0, v);
-				glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, internal_size.x, internal_size.y, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+				glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, write_depth, 0, v);
+				glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, internal_size.x, internal_size.y, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
 			}
 		}
 
@@ -2910,10 +2941,10 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend
 			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]);
 
 			for (uint32_t v = 0; v < view_count; v++) {
-				glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, read_depth, 0, v);
-				glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, write_depth, 0, v);
+				glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, read_depth, 0, v);
+				glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, write_depth, 0, v);
 
-				glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+				glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
 			}
 		}
 
@@ -3052,6 +3083,47 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 				}
 			}
 
+			// Stencil.
+			if (shader->stencil_enabled) {
+				static const GLenum stencil_compare_table[GLES3::SceneShaderData::STENCIL_COMPARE_MAX] = {
+					GL_LESS,
+					GL_EQUAL,
+					GL_LEQUAL,
+					GL_GREATER,
+					GL_NOTEQUAL,
+					GL_GEQUAL,
+					GL_ALWAYS,
+				};
+
+				GLenum stencil_compare = stencil_compare_table[shader->stencil_compare];
+				GLuint stencil_compare_mask = 0;
+				GLuint stencil_write_mask = 0;
+				GLenum stencil_op_dpfail = GL_KEEP;
+				GLenum stencil_op_dppass = GL_KEEP;
+
+				if (shader->stencil_flags & GLES3::SceneShaderData::STENCIL_FLAG_READ) {
+					stencil_compare_mask = 255;
+				}
+
+				if (shader->stencil_flags & GLES3::SceneShaderData::STENCIL_FLAG_WRITE) {
+					stencil_op_dppass = GL_REPLACE;
+					stencil_write_mask = 255;
+				}
+
+				if (shader->stencil_flags & GLES3::SceneShaderData::STENCIL_FLAG_WRITE_DEPTH_FAIL) {
+					stencil_op_dpfail = GL_REPLACE;
+					stencil_write_mask = 255;
+				}
+
+				scene_state.enable_gl_stencil_test(true);
+				scene_state.set_gl_stencil_func(stencil_compare, shader->stencil_reference, stencil_compare_mask);
+				scene_state.set_gl_stencil_write_mask(stencil_write_mask);
+				scene_state.set_gl_stencil_op(GL_KEEP, stencil_op_dpfail, stencil_op_dppass);
+			} else {
+				scene_state.enable_gl_stencil_test(false);
+				scene_state.set_gl_stencil_write_mask(255);
+			}
+
 			if constexpr (p_pass_mode == PASS_MODE_COLOR || p_pass_mode == PASS_MODE_COLOR_TRANSPARENT) {
 				if (!uses_additive_lighting && pass == 1) {
 					// Don't render additive passes if not using additive lighting.

+ 56 - 0
drivers/gles3/rasterizer_scene_gles3.h

@@ -247,6 +247,7 @@ private:
 			FLAG_USES_DEPTH_TEXTURE = 4096,
 			FLAG_USES_NORMAL_TEXTURE = 8192,
 			FLAG_USES_DOUBLE_SIDED_SHADOWS = 16384,
+			FLAG_USES_STENCIL = 32768,
 		};
 
 		union {
@@ -488,6 +489,15 @@ private:
 
 			glDepthFunc(GL_GEQUAL);
 			current_depth_function = GL_GEQUAL;
+
+			glDisable(GL_STENCIL_TEST);
+			current_stencil_test_enabled = false;
+			glStencilMask(255);
+			current_stencil_write_mask = 255;
+			glStencilFunc(GL_ALWAYS, 0, 255);
+			current_stencil_compare = GL_ALWAYS;
+			current_stencil_reference = 0;
+			current_stencil_compare_mask = 255;
 		}
 
 		void set_gl_cull_mode(RS::CullMode p_mode) {
@@ -552,10 +562,56 @@ private:
 			}
 		}
 
+		void enable_gl_stencil_test(bool p_enabled) {
+			if (current_stencil_test_enabled != p_enabled) {
+				if (p_enabled) {
+					glEnable(GL_STENCIL_TEST);
+				} else {
+					glDisable(GL_STENCIL_TEST);
+				}
+				current_stencil_test_enabled = p_enabled;
+			}
+		}
+
+		void set_gl_stencil_func(GLenum p_compare, GLint p_reference, GLenum p_compare_mask) {
+			if (current_stencil_compare != p_compare || current_stencil_reference != p_reference || current_stencil_compare_mask != p_compare_mask) {
+				glStencilFunc(p_compare, p_reference, p_compare_mask);
+				current_stencil_compare = p_compare;
+				current_stencil_reference = p_reference;
+				current_stencil_compare_mask = p_compare_mask;
+			}
+		}
+
+		void set_gl_stencil_write_mask(GLuint p_mask) {
+			if (current_stencil_write_mask != p_mask) {
+				glStencilMask(p_mask);
+				current_stencil_write_mask = p_mask;
+			}
+		}
+
+		void set_gl_stencil_op(GLenum p_op_fail, GLenum p_op_dpfail, GLenum p_op_dppass) {
+			if (current_stencil_op_fail != p_op_fail || current_stencil_op_dpfail != p_op_dpfail || current_stencil_op_dppass != p_op_dppass) {
+				glStencilOp(p_op_fail, p_op_dpfail, p_op_dppass);
+				current_stencil_op_fail = p_op_fail;
+				current_stencil_op_dpfail = p_op_dpfail;
+				current_stencil_op_dppass = p_op_dppass;
+			}
+		}
+
+		GLenum current_stencil_compare = GL_ALWAYS;
+		GLuint current_stencil_compare_mask = 255;
+		GLuint current_stencil_write_mask = 255;
+		GLint current_stencil_reference = 0;
+		GLenum current_stencil_op_fail = GL_KEEP;
+		GLenum current_stencil_op_dpfail = GL_KEEP;
+		GLenum current_stencil_op_dppass = GL_KEEP;
+		bool current_stencil_test_enabled = false;
+
 		bool texscreen_copied = false;
 		bool used_screen_texture = false;
 		bool used_normal_texture = false;
 		bool used_depth_texture = false;
+		bool used_opaque_stencil = false;
 
 		LightData *omni_lights = nullptr;
 		LightData *spot_lights = nullptr;

+ 25 - 0
drivers/gles3/storage/material_storage.cpp

@@ -2947,6 +2947,12 @@ void SceneShaderData::set_code(const String &p_code) {
 	int cull_modei = RS::CULL_MODE_BACK;
 	int depth_drawi = DEPTH_DRAW_OPAQUE;
 
+	int stencil_readi = 0;
+	int stencil_writei = 0;
+	int stencil_write_depth_faili = 0;
+	int stencil_comparei = STENCIL_COMPARE_ALWAYS;
+	int stencil_referencei = -1;
+
 	ShaderCompiler::IdentifierActions actions;
 	actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX;
 	actions.entry_point_stages["fragment"] = ShaderCompiler::STAGE_FRAGMENT;
@@ -3016,6 +3022,20 @@ void SceneShaderData::set_code(const String &p_code) {
 	actions.usage_flag_pointers["BONE_INDICES"] = &uses_bones;
 	actions.usage_flag_pointers["BONE_WEIGHTS"] = &uses_weights;
 
+	actions.stencil_mode_values["read"] = Pair<int *, int>(&stencil_readi, STENCIL_FLAG_READ);
+	actions.stencil_mode_values["write"] = Pair<int *, int>(&stencil_writei, STENCIL_FLAG_WRITE);
+	actions.stencil_mode_values["write_depth_fail"] = Pair<int *, int>(&stencil_write_depth_faili, STENCIL_FLAG_WRITE_DEPTH_FAIL);
+
+	actions.stencil_mode_values["compare_less"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_LESS);
+	actions.stencil_mode_values["compare_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_EQUAL);
+	actions.stencil_mode_values["compare_less_or_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_LESS_OR_EQUAL);
+	actions.stencil_mode_values["compare_greater"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_GREATER);
+	actions.stencil_mode_values["compare_not_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_NOT_EQUAL);
+	actions.stencil_mode_values["compare_greater_or_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_GREATER_OR_EQUAL);
+	actions.stencil_mode_values["compare_always"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_ALWAYS);
+
+	actions.stencil_reference = &stencil_referencei;
+
 	actions.uniforms = &uniforms;
 
 	Error err = MaterialStorage::get_singleton()->shaders.compiler_scene.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code);
@@ -3056,6 +3076,11 @@ void SceneShaderData::set_code(const String &p_code) {
 	uses_vertex_time = gen_code.uses_vertex_time;
 	uses_fragment_time = gen_code.uses_fragment_time;
 
+	stencil_enabled = stencil_referencei != -1;
+	stencil_flags = stencil_readi | stencil_writei | stencil_write_depth_faili;
+	stencil_compare = StencilCompare(stencil_comparei);
+	stencil_reference = stencil_referencei;
+
 #ifdef DEBUG_ENABLED
 	if (uses_particle_trails) {
 		WARN_PRINT_ONCE_ED("Particle trails are only available when using the Forward+ or Mobile renderers.");

+ 22 - 0
drivers/gles3/storage/material_storage.h

@@ -261,6 +261,23 @@ struct SceneShaderData : public ShaderData {
 		DEPTH_TEST_ENABLED_INVERTED,
 	};
 
+	enum StencilCompare {
+		STENCIL_COMPARE_LESS,
+		STENCIL_COMPARE_EQUAL,
+		STENCIL_COMPARE_LESS_OR_EQUAL,
+		STENCIL_COMPARE_GREATER,
+		STENCIL_COMPARE_NOT_EQUAL,
+		STENCIL_COMPARE_GREATER_OR_EQUAL,
+		STENCIL_COMPARE_ALWAYS,
+		STENCIL_COMPARE_MAX // not an actual operator, just the amount of operators
+	};
+
+	enum StencilFlags {
+		STENCIL_FLAG_READ = 1,
+		STENCIL_FLAG_WRITE = 2,
+		STENCIL_FLAG_WRITE_DEPTH_FAIL = 4,
+	};
+
 	enum AlphaAntiAliasing {
 		ALPHA_ANTIALIASING_OFF,
 		ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE,
@@ -286,6 +303,11 @@ struct SceneShaderData : public ShaderData {
 	DepthTest depth_test;
 	RS::CullMode cull_mode;
 
+	StencilCompare stencil_compare;
+	uint32_t stencil_flags;
+	int32_t stencil_reference;
+	bool stencil_enabled;
+
 	bool uses_point_size;
 	bool uses_alpha;
 	bool uses_alpha_clip;

+ 22 - 20
drivers/gles3/storage/render_scene_buffers_gles3.cpp

@@ -63,14 +63,14 @@ void RenderSceneBuffersGLES3::_rt_attach_textures(GLuint p_color, GLuint p_depth
 		if (p_samples > 1) {
 #if defined(ANDROID_ENABLED) || defined(WEB_ENABLED)
 			glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, p_samples, 0, p_view_count);
-			glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, p_samples, 0, p_view_count);
+			glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, p_depth, 0, p_samples, 0, p_view_count);
 #else
 			ERR_PRINT_ONCE("Multiview MSAA isn't supported on this platform.");
 #endif
 		} else {
 #ifndef IOS_ENABLED
 			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, 0, p_view_count);
-			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, 0, p_view_count);
+			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, p_depth, 0, 0, p_view_count);
 #else
 			ERR_PRINT_ONCE("Multiview isn't supported on this platform.");
 #endif
@@ -79,13 +79,13 @@ void RenderSceneBuffersGLES3::_rt_attach_textures(GLuint p_color, GLuint p_depth
 		if (p_samples > 1) {
 #ifdef ANDROID_ENABLED
 			glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_color, 0, p_samples);
-			glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0, p_samples);
+			glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0, p_samples);
 #else
 			ERR_PRINT_ONCE("MSAA via EXT_multisampled_render_to_texture isn't supported on this platform.");
 #endif
 		} else {
 			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_color, 0);
-			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0);
 		}
 	}
 }
@@ -196,7 +196,8 @@ void RenderSceneBuffersGLES3::_check_render_buffers() {
 	ERR_FAIL_COND(view_count == 0);
 
 	bool use_internal_buffer = scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF || apply_color_adjustments_in_post;
-	uint32_t depth_format_size = 3;
+	GLenum depth_format = GL_DEPTH24_STENCIL8;
+	uint32_t depth_format_size = 4;
 	bool use_multiview = view_count > 1;
 
 	if ((!use_internal_buffer || internal3d.color != 0) && (msaa3d.mode == RS::VIEWPORT_MSAA_DISABLED || msaa3d.color != 0)) {
@@ -230,9 +231,9 @@ void RenderSceneBuffersGLES3::_check_render_buffers() {
 		glBindTexture(texture_target, internal3d.depth);
 
 		if (use_multiview) {
-			glTexImage3D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+			glTexImage3D(texture_target, 0, depth_format, internal_size.x, internal_size.y, view_count, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
 		} else {
-			glTexImage2D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+			glTexImage2D(texture_target, 0, depth_format, internal_size.x, internal_size.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
 		}
 
 		glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -250,13 +251,13 @@ void RenderSceneBuffersGLES3::_check_render_buffers() {
 #ifndef IOS_ENABLED
 		if (use_multiview) {
 			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, internal3d.color, 0, 0, view_count);
-			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, internal3d.depth, 0, 0, view_count);
+			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, internal3d.depth, 0, 0, view_count);
 		} else {
 #else
 		{
 #endif
 			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target, internal3d.color, 0);
-			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_target, internal3d.depth, 0);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, texture_target, internal3d.depth, 0);
 		}
 
 		GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -299,15 +300,15 @@ void RenderSceneBuffersGLES3::_check_render_buffers() {
 			glGenRenderbuffers(1, &msaa3d.depth);
 			glBindRenderbuffer(GL_RENDERBUFFER, msaa3d.depth);
 
-			glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y);
-			GLES3::Utilities::get_singleton()->render_buffer_allocated_data(msaa3d.depth, internal_size.x * internal_size.y * view_count * 3 * msaa3d.samples, "MSAA 3D depth render buffer");
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa3d.samples, depth_format, internal_size.x, internal_size.y);
+			GLES3::Utilities::get_singleton()->render_buffer_allocated_data(msaa3d.depth, internal_size.x * internal_size.y * view_count * depth_format_size * msaa3d.samples, "MSAA 3D depth render buffer");
 
 			// Create our MSAA 3D FBO.
 			glGenFramebuffers(1, &msaa3d.fbo);
 			glBindFramebuffer(GL_FRAMEBUFFER, msaa3d.fbo);
 
 			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaa3d.color);
-			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msaa3d.depth);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msaa3d.depth);
 
 			GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
 			if (status != GL_FRAMEBUFFER_COMPLETE) {
@@ -341,9 +342,9 @@ void RenderSceneBuffersGLES3::_check_render_buffers() {
 			glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.depth);
 
 #ifdef ANDROID_ENABLED
-			glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, GL_TRUE);
+			glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, depth_format, internal_size.x, internal_size.y, view_count, GL_TRUE);
 #else
-			glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, GL_TRUE);
+			glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, depth_format, internal_size.x, internal_size.y, view_count, GL_TRUE);
 #endif
 
 			GLES3::Utilities::get_singleton()->texture_allocated_data(msaa3d.depth, internal_size.x * internal_size.y * view_count * depth_format_size * msaa3d.samples, "MSAA 3D depth texture");
@@ -353,7 +354,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() {
 			glBindFramebuffer(GL_FRAMEBUFFER, msaa3d.fbo);
 
 			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, msaa3d.color, 0, 0, view_count);
-			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, msaa3d.depth, 0, 0, view_count);
+			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, msaa3d.depth, 0, 0, view_count);
 
 			GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
 			if (status != GL_FRAMEBUFFER_COMPLETE) {
@@ -474,7 +475,8 @@ void RenderSceneBuffersGLES3::check_backbuffer(bool p_need_color, bool p_need_de
 
 	bool use_multiview = view_count > 1 && GLES3::Config::get_singleton()->multiview_supported;
 	GLenum texture_target = use_multiview ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
-	uint32_t depth_format_size = 3;
+	GLenum depth_format = GL_DEPTH24_STENCIL8;
+	uint32_t depth_format_size = 4;
 
 	if (backbuffer3d.color == 0 && p_need_color) {
 		glGenTextures(1, &backbuffer3d.color);
@@ -509,9 +511,9 @@ void RenderSceneBuffersGLES3::check_backbuffer(bool p_need_color, bool p_need_de
 		glBindTexture(texture_target, backbuffer3d.depth);
 
 		if (use_multiview) {
-			glTexImage3D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+			glTexImage3D(texture_target, 0, depth_format, internal_size.x, internal_size.y, view_count, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT, nullptr);
 		} else {
-			glTexImage2D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+			glTexImage2D(texture_target, 0, depth_format, internal_size.x, internal_size.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT, nullptr);
 		}
 
 		glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -523,12 +525,12 @@ void RenderSceneBuffersGLES3::check_backbuffer(bool p_need_color, bool p_need_de
 
 #ifndef IOS_ENABLED
 		if (use_multiview) {
-			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, backbuffer3d.depth, 0, 0, view_count);
+			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, backbuffer3d.depth, 0, 0, view_count);
 		} else {
 #else
 		{
 #endif
-			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_target, backbuffer3d.depth, 0);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, texture_target, backbuffer3d.depth, 0);
 		}
 	}
 

+ 38 - 10
drivers/gles3/storage/texture_storage.cpp

@@ -2183,14 +2183,15 @@ void TextureStorage::_update_render_target(RenderTarget *rt) {
 			ERR_FAIL_NULL(texture);
 
 			rt->depth = texture->tex_id;
+			rt->depth_has_stencil = rt->overridden.depth_has_stencil;
 		} else {
 			glGenTextures(1, &rt->depth);
 			glBindTexture(texture_target, rt->depth);
 
 			if (use_multiview) {
-				glTexImage3D(texture_target, 0, GL_DEPTH_COMPONENT24, rt->size.x, rt->size.y, rt->view_count, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+				glTexImage3D(texture_target, 0, GL_DEPTH24_STENCIL8, rt->size.x, rt->size.y, rt->view_count, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
 			} else {
-				glTexImage2D(texture_target, 0, GL_DEPTH_COMPONENT24, rt->size.x, rt->size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+				glTexImage2D(texture_target, 0, GL_DEPTH24_STENCIL8, rt->size.x, rt->size.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
 			}
 
 			glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -2198,16 +2199,19 @@ void TextureStorage::_update_render_target(RenderTarget *rt) {
 			glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 			glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
-			GLES3::Utilities::get_singleton()->texture_allocated_data(rt->depth, rt->size.x * rt->size.y * rt->view_count * 3, "Render target depth texture");
+			rt->depth_has_stencil = true;
+
+			GLES3::Utilities::get_singleton()->texture_allocated_data(rt->depth, rt->size.x * rt->size.y * rt->view_count * 4, "Render target depth texture");
 		}
+
 #ifndef IOS_ENABLED
 		if (use_multiview) {
-			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count);
+			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, rt->depth_has_stencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count);
 		} else {
 #else
 		{
 #endif
-			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_target, rt->depth, 0);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, rt->depth_has_stencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT, texture_target, rt->depth, 0);
 		}
 
 		GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -2354,12 +2358,33 @@ void GLES3::TextureStorage::check_backbuffer(RenderTarget *rt, const bool uses_s
 	if (rt->backbuffer_depth == 0 && uses_depth_texture) {
 		glGenTextures(1, &rt->backbuffer_depth);
 		glBindTexture(texture_target, rt->backbuffer_depth);
+
+		GLint internal_format;
+		GLenum format;
+		GLenum type;
+		GLenum attachment;
+		int element_size;
+
+		if (rt->depth_has_stencil) {
+			internal_format = GL_DEPTH24_STENCIL8;
+			format = GL_DEPTH_STENCIL;
+			type = GL_UNSIGNED_INT_24_8;
+			attachment = GL_DEPTH_STENCIL_ATTACHMENT;
+			element_size = 4;
+		} else {
+			internal_format = GL_DEPTH_COMPONENT24;
+			format = GL_DEPTH_COMPONENT;
+			type = GL_UNSIGNED_INT;
+			attachment = GL_DEPTH_ATTACHMENT;
+			element_size = 3;
+		}
+
 		if (use_multiview) {
-			glTexImage3D(texture_target, 0, GL_DEPTH_COMPONENT24, rt->size.x, rt->size.y, rt->view_count, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+			glTexImage3D(texture_target, 0, internal_format, rt->size.x, rt->size.y, rt->view_count, 0, format, type, nullptr);
 		} else {
-			glTexImage2D(texture_target, 0, GL_DEPTH_COMPONENT24, rt->size.x, rt->size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+			glTexImage2D(texture_target, 0, internal_format, rt->size.x, rt->size.y, 0, format, type, nullptr);
 		}
-		GLES3::Utilities::get_singleton()->texture_allocated_data(rt->backbuffer_depth, rt->size.x * rt->size.y * rt->view_count * 3, "Render target backbuffer depth texture");
+		GLES3::Utilities::get_singleton()->texture_allocated_data(rt->backbuffer_depth, rt->size.x * rt->size.y * rt->view_count * element_size, "Render target backbuffer depth texture");
 
 		glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 		glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
@@ -2367,12 +2392,12 @@ void GLES3::TextureStorage::check_backbuffer(RenderTarget *rt, const bool uses_s
 		glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 #ifndef IOS_ENABLED
 		if (use_multiview) {
-			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->backbuffer_depth, 0, 0, rt->view_count);
+			glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, attachment, rt->backbuffer_depth, 0, 0, rt->view_count);
 		} else {
 #else
 		{
 #endif
-			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->backbuffer_depth, 0);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, rt->backbuffer_depth, 0);
 		}
 	}
 }
@@ -2546,6 +2571,7 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color
 
 	rt->overridden.color = p_color_texture;
 	rt->overridden.depth = p_depth_texture;
+	rt->overridden.depth_has_stencil = p_depth_texture.is_null();
 	rt->overridden.is_overridden = true;
 
 	uint32_t hash_key = hash_murmur3_one_64(p_color_texture.get_id());
@@ -2557,6 +2583,7 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color
 		rt->fbo = cache->get().fbo;
 		rt->color = cache->get().color;
 		rt->depth = cache->get().depth;
+		rt->depth_has_stencil = cache->get().depth_has_stencil;
 		rt->size = cache->get().size;
 		rt->texture = p_color_texture;
 		return;
@@ -2568,6 +2595,7 @@ void TextureStorage::render_target_set_override(RID p_render_target, RID p_color
 	new_entry.fbo = rt->fbo;
 	new_entry.color = rt->color;
 	new_entry.depth = rt->depth;
+	new_entry.depth_has_stencil = rt->depth_has_stencil;
 	new_entry.size = rt->size;
 	// Keep track of any textures we had to allocate because they weren't overridden.
 	if (p_color_texture.is_null()) {

+ 3 - 0
drivers/gles3/storage/texture_storage.h

@@ -347,6 +347,7 @@ struct RenderTarget {
 	GLuint backbuffer_fbo = 0;
 	GLuint backbuffer = 0;
 	GLuint backbuffer_depth = 0;
+	bool depth_has_stencil = true;
 
 	bool hdr = false; // For Compatibility this effects both 2D and 3D rendering!
 	GLuint color_internal_format = GL_RGBA8;
@@ -375,6 +376,7 @@ struct RenderTarget {
 
 	struct RTOverridden {
 		bool is_overridden = false;
+		bool depth_has_stencil = false;
 		RID color;
 		RID depth;
 		RID velocity;
@@ -385,6 +387,7 @@ struct RenderTarget {
 			GLuint depth;
 			Size2i size;
 			Vector<GLuint> allocated_textures;
+			bool depth_has_stencil;
 		};
 		RBMap<uint32_t, FBOCacheEntry> fbo_cache;
 	} overridden;

+ 46 - 16
editor/plugins/text_shader_editor.cpp

@@ -269,17 +269,31 @@ void ShaderTextEditor::_load_theme_settings() {
 				}
 			}
 
-			const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+			{
+				const Vector<ShaderLanguage::ModeInfo> &render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+
+				for (const ShaderLanguage::ModeInfo &mode_info : render_modes) {
+					if (!mode_info.options.is_empty()) {
+						for (const StringName &option : mode_info.options) {
+							built_ins.push_back(String(mode_info.name) + "_" + String(option));
+						}
+					} else {
+						built_ins.push_back(String(mode_info.name));
+					}
+				}
+			}
 
-			for (int j = 0; j < modes.size(); j++) {
-				const ShaderLanguage::ModeInfo &mode_info = modes[j];
+			{
+				const Vector<ShaderLanguage::ModeInfo> &stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(i));
 
-				if (!mode_info.options.is_empty()) {
-					for (int k = 0; k < mode_info.options.size(); k++) {
-						built_ins.push_back(String(mode_info.name) + "_" + String(mode_info.options[k]));
+				for (const ShaderLanguage::ModeInfo &mode_info : stencil_modes) {
+					if (!mode_info.options.is_empty()) {
+						for (const StringName &option : mode_info.options) {
+							built_ins.push_back(String(mode_info.name) + "_" + String(option));
+						}
+					} else {
+						built_ins.push_back(String(mode_info.name));
 					}
-				} else {
-					built_ins.push_back(String(mode_info.name));
 				}
 			}
 		}
@@ -290,17 +304,31 @@ void ShaderTextEditor::_load_theme_settings() {
 			}
 		}
 
-		const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
+		{
+			const Vector<ShaderLanguage::ModeInfo> &shader_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
 
-		for (int i = 0; i < modes.size(); i++) {
-			const ShaderLanguage::ModeInfo &mode_info = modes[i];
+			for (const ShaderLanguage::ModeInfo &mode_info : shader_modes) {
+				if (!mode_info.options.is_empty()) {
+					for (const StringName &option : mode_info.options) {
+						built_ins.push_back(String(mode_info.name) + "_" + String(option));
+					}
+				} else {
+					built_ins.push_back(String(mode_info.name));
+				}
+			}
+		}
 
-			if (!mode_info.options.is_empty()) {
-				for (int j = 0; j < mode_info.options.size(); j++) {
-					built_ins.push_back(String(mode_info.name) + "_" + String(mode_info.options[j]));
+		{
+			const Vector<ShaderLanguage::ModeInfo> &stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader->get_mode()));
+
+			for (const ShaderLanguage::ModeInfo &mode_info : stencil_modes) {
+				if (!mode_info.options.is_empty()) {
+					for (const StringName &option : mode_info.options) {
+						built_ins.push_back(String(mode_info.name) + "_" + String(option));
+					}
+				} else {
+					built_ins.push_back(String(mode_info.name));
 				}
-			} else {
-				built_ins.push_back(String(mode_info.name));
 			}
 		}
 	}
@@ -437,6 +465,7 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLa
 	_check_shader_mode();
 	comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()));
 	comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
+	comp_info.stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader->get_mode()));
 	comp_info.shader_types = ShaderTypes::get_singleton()->get_types();
 
 	sl.complete(code, comp_info, r_options, calltip);
@@ -541,6 +570,7 @@ void ShaderTextEditor::_validate_script() {
 			Shader::Mode mode = shader->get_mode();
 			comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode));
 			comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode));
+			comp_info.stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(mode));
 			comp_info.shader_types = ShaderTypes::get_singleton()->get_types();
 		}
 

+ 1 - 0
editor/plugins/visual_shader_editor_plugin.cpp

@@ -6297,6 +6297,7 @@ void VisualShaderEditor::_update_preview() {
 	ShaderLanguage::ShaderCompileInfo info;
 	info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(visual_shader->get_mode()));
 	info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(visual_shader->get_mode()));
+	info.stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(visual_shader->get_mode()));
 	info.shader_types = ShaderTypes::get_singleton()->get_types();
 	info.global_shader_uniform_type_func = _visual_shader_editor_get_global_shader_uniform_type;
 

+ 286 - 1
scene/resources/material.cpp

@@ -912,6 +912,56 @@ void BaseMaterial3D::_update_shader() {
 
 	code += ";\n";
 
+	if (stencil_mode != STENCIL_MODE_DISABLED && stencil_flags != 0) {
+		code += "stencil_mode ";
+
+		if (stencil_flags & STENCIL_FLAG_READ) {
+			code += "read";
+		}
+
+		if (stencil_flags & STENCIL_FLAG_WRITE) {
+			if (stencil_flags & STENCIL_FLAG_READ) {
+				code += ", ";
+			}
+			code += "write";
+		}
+
+		if (stencil_flags & STENCIL_FLAG_WRITE_DEPTH_FAIL) {
+			if (stencil_flags & (STENCIL_FLAG_READ | STENCIL_FLAG_WRITE)) {
+				code += ", ";
+			}
+			code += "write_depth_fail";
+		}
+
+		switch (stencil_compare) {
+			case STENCIL_COMPARE_ALWAYS:
+				code += ", compare_always";
+				break;
+			case STENCIL_COMPARE_LESS:
+				code += ", compare_less";
+				break;
+			case STENCIL_COMPARE_EQUAL:
+				code += ", compare_equal";
+				break;
+			case STENCIL_COMPARE_LESS_OR_EQUAL:
+				code += ", compare_less_or_equal";
+				break;
+			case STENCIL_COMPARE_GREATER:
+				code += ", compare_greater";
+				break;
+			case STENCIL_COMPARE_NOT_EQUAL:
+				code += ", compare_not_equal";
+				break;
+			case STENCIL_COMPARE_GREATER_OR_EQUAL:
+				code += ", compare_greater_or_equal";
+				break;
+			case STENCIL_COMPARE_MAX:
+				break;
+		}
+
+		code += vformat(", %s;\n", stencil_reference);
+	}
+
 	// Generate list of uniforms.
 	code += vformat(R"(
 uniform vec4 albedo : source_color;
@@ -2591,7 +2641,23 @@ void BaseMaterial3D::_validate_property(PropertyInfo &p_property) const {
 	}
 
 	if (p_property.name == "depth_test" && flags[FLAG_DISABLE_DEPTH_TEST]) {
-		p_property.usage = PROPERTY_USAGE_NONE;
+		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+	}
+
+	if (p_property.name == "stencil_reference" && stencil_mode == STENCIL_MODE_DISABLED) {
+		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+	}
+
+	if ((p_property.name == "stencil_flags" || p_property.name == "stencil_compare") && stencil_mode != STENCIL_MODE_CUSTOM) {
+		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+	}
+
+	if (p_property.name == "stencil_color" && stencil_mode != STENCIL_MODE_OUTLINE && stencil_mode != STENCIL_MODE_XRAY) {
+		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+	}
+
+	if (p_property.name == "stencil_outline_thickness" && stencil_mode != STENCIL_MODE_OUTLINE) {
+		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
 	}
 
 	if (flags[FLAG_SUBSURFACE_MODE_SKIN] && (p_property.name == "subsurf_scatter_transmittance_color" || p_property.name == "subsurf_scatter_transmittance_texture")) {
@@ -3066,6 +3132,179 @@ RID BaseMaterial3D::get_rid() const {
 	return _get_material();
 }
 
+void BaseMaterial3D::_prepare_stencil_effect() {
+	const Ref<Material> current_next_pass = get_next_pass();
+
+	if (stencil_mode == STENCIL_MODE_DISABLED || stencil_mode == STENCIL_MODE_CUSTOM) {
+		if (current_next_pass.is_valid() && current_next_pass->has_meta("_stencil_owned")) {
+			set_next_pass(current_next_pass->get_next_pass());
+		}
+		return;
+	}
+
+	Ref<BaseMaterial3D> stencil_next_pass;
+
+	if (current_next_pass.is_null() || !current_next_pass->has_meta("_stencil_owned")) {
+		stencil_next_pass = Ref<BaseMaterial3D>(memnew(StandardMaterial3D));
+		stencil_next_pass->set_meta("_stencil_owned", true);
+		stencil_next_pass->set_next_pass(current_next_pass);
+		set_next_pass(stencil_next_pass);
+	} else {
+		stencil_next_pass = current_next_pass;
+	}
+
+	switch (stencil_mode) {
+		case STENCIL_MODE_DISABLED:
+			break;
+		case STENCIL_MODE_OUTLINE:
+			set_stencil_flags(STENCIL_FLAG_WRITE);
+			set_stencil_compare(STENCIL_COMPARE_ALWAYS);
+			stencil_next_pass->set_render_priority(-1);
+			stencil_next_pass->set_shading_mode(SHADING_MODE_UNSHADED);
+			stencil_next_pass->set_transparency(TRANSPARENCY_ALPHA);
+			stencil_next_pass->set_flag(FLAG_DISABLE_DEPTH_TEST, false);
+			stencil_next_pass->set_grow_enabled(true);
+			stencil_next_pass->set_grow(stencil_effect_outline_thickness);
+			stencil_next_pass->set_albedo(stencil_effect_color);
+			stencil_next_pass->set_stencil_mode(STENCIL_MODE_CUSTOM);
+			stencil_next_pass->set_stencil_flags(STENCIL_FLAG_READ | STENCIL_FLAG_WRITE);
+			stencil_next_pass->set_stencil_compare(STENCIL_COMPARE_NOT_EQUAL);
+			stencil_next_pass->set_stencil_reference(stencil_reference);
+			break;
+		case STENCIL_MODE_XRAY:
+			set_stencil_flags(STENCIL_FLAG_WRITE);
+			set_stencil_compare(STENCIL_COMPARE_ALWAYS);
+			stencil_next_pass->set_render_priority(-1);
+			stencil_next_pass->set_shading_mode(SHADING_MODE_UNSHADED);
+			stencil_next_pass->set_transparency(TRANSPARENCY_ALPHA);
+			stencil_next_pass->set_flag(FLAG_DISABLE_DEPTH_TEST, true);
+			stencil_next_pass->set_grow_enabled(false);
+			stencil_next_pass->set_grow(0);
+			stencil_next_pass->set_albedo(stencil_effect_color);
+			stencil_next_pass->set_stencil_mode(STENCIL_MODE_CUSTOM);
+			stencil_next_pass->set_stencil_flags(STENCIL_FLAG_READ | STENCIL_FLAG_WRITE);
+			stencil_next_pass->set_stencil_compare(STENCIL_COMPARE_NOT_EQUAL);
+			stencil_next_pass->set_stencil_reference(stencil_reference);
+			break;
+		case STENCIL_MODE_CUSTOM:
+			break;
+		case STENCIL_MODE_MAX:
+			break;
+	}
+}
+
+Ref<BaseMaterial3D> BaseMaterial3D::_get_stencil_next_pass() const {
+	const Ref<Material> current_next_pass = get_next_pass();
+	Ref<BaseMaterial3D> stencil_next_pass;
+
+	if (current_next_pass.is_valid() && current_next_pass->has_meta("_stencil_owned")) {
+		stencil_next_pass = current_next_pass;
+	}
+
+	return stencil_next_pass;
+}
+
+void BaseMaterial3D::set_stencil_mode(StencilMode p_stencil_mode) {
+	if (stencil_mode == p_stencil_mode) {
+		return;
+	}
+
+	stencil_mode = p_stencil_mode;
+	_prepare_stencil_effect();
+	_queue_shader_change();
+	notify_property_list_changed();
+}
+
+BaseMaterial3D::StencilMode BaseMaterial3D::get_stencil_mode() const {
+	return stencil_mode;
+}
+
+void BaseMaterial3D::set_stencil_flags(int p_stencil_flags) {
+	if (stencil_flags == p_stencil_flags) {
+		return;
+	}
+
+	if ((p_stencil_flags & STENCIL_FLAG_READ) && (stencil_flags & (STENCIL_FLAG_WRITE | STENCIL_FLAG_WRITE_DEPTH_FAIL))) {
+		p_stencil_flags = p_stencil_flags & STENCIL_FLAG_READ;
+	}
+
+	if ((p_stencil_flags & (STENCIL_FLAG_WRITE | STENCIL_FLAG_WRITE_DEPTH_FAIL)) && (stencil_flags & STENCIL_FLAG_READ)) {
+		p_stencil_flags = p_stencil_flags & (STENCIL_FLAG_WRITE | STENCIL_FLAG_WRITE_DEPTH_FAIL);
+	}
+
+	stencil_flags = p_stencil_flags;
+	_queue_shader_change();
+}
+
+int BaseMaterial3D::get_stencil_flags() const {
+	return stencil_flags;
+}
+
+void BaseMaterial3D::set_stencil_compare(BaseMaterial3D::StencilCompare p_op) {
+	if (stencil_compare == p_op) {
+		return;
+	}
+
+	stencil_compare = p_op;
+	_queue_shader_change();
+}
+
+BaseMaterial3D::StencilCompare BaseMaterial3D::get_stencil_compare() const {
+	return stencil_compare;
+}
+
+void BaseMaterial3D::set_stencil_reference(int p_reference) {
+	if (stencil_reference == p_reference) {
+		return;
+	}
+
+	stencil_reference = p_reference;
+	_queue_shader_change();
+
+	Ref<BaseMaterial3D> stencil_next_pass = _get_stencil_next_pass();
+	if (stencil_next_pass.is_valid()) {
+		stencil_next_pass->set_stencil_reference(p_reference);
+	}
+}
+
+int BaseMaterial3D::get_stencil_reference() const {
+	return stencil_reference;
+}
+
+void BaseMaterial3D::set_stencil_effect_color(const Color &p_color) {
+	if (stencil_effect_color == p_color) {
+		return;
+	}
+
+	stencil_effect_color = p_color;
+
+	Ref<BaseMaterial3D> stencil_next_pass = _get_stencil_next_pass();
+	if (stencil_next_pass.is_valid()) {
+		stencil_next_pass->set_albedo(p_color);
+	}
+}
+
+Color BaseMaterial3D::get_stencil_effect_color() const {
+	return stencil_effect_color;
+}
+
+void BaseMaterial3D::set_stencil_effect_outline_thickness(float p_outline_thickness) {
+	if (stencil_effect_outline_thickness == p_outline_thickness) {
+		return;
+	}
+
+	stencil_effect_outline_thickness = p_outline_thickness;
+
+	Ref<BaseMaterial3D> stencil_next_pass = _get_stencil_next_pass();
+	if (stencil_next_pass.is_valid()) {
+		stencil_next_pass->set_grow(p_outline_thickness);
+	}
+}
+
+float BaseMaterial3D::get_stencil_effect_outline_thickness() const {
+	return stencil_effect_outline_thickness;
+}
+
 RID BaseMaterial3D::get_shader_rid() const {
 	const_cast<BaseMaterial3D *>(this)->_update_shader();
 	return shader_rid;
@@ -3291,6 +3530,24 @@ void BaseMaterial3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_fov_override", "scale"), &BaseMaterial3D::set_fov_override);
 	ClassDB::bind_method(D_METHOD("get_fov_override"), &BaseMaterial3D::get_fov_override);
 
+	ClassDB::bind_method(D_METHOD("set_stencil_mode", "stencil_mode"), &BaseMaterial3D::set_stencil_mode);
+	ClassDB::bind_method(D_METHOD("get_stencil_mode"), &BaseMaterial3D::get_stencil_mode);
+
+	ClassDB::bind_method(D_METHOD("set_stencil_flags", "stencil_flags"), &BaseMaterial3D::set_stencil_flags);
+	ClassDB::bind_method(D_METHOD("get_stencil_flags"), &BaseMaterial3D::get_stencil_flags);
+
+	ClassDB::bind_method(D_METHOD("set_stencil_compare", "stencil_compare"), &BaseMaterial3D::set_stencil_compare);
+	ClassDB::bind_method(D_METHOD("get_stencil_compare"), &BaseMaterial3D::get_stencil_compare);
+
+	ClassDB::bind_method(D_METHOD("set_stencil_reference", "stencil_reference"), &BaseMaterial3D::set_stencil_reference);
+	ClassDB::bind_method(D_METHOD("get_stencil_reference"), &BaseMaterial3D::get_stencil_reference);
+
+	ClassDB::bind_method(D_METHOD("set_stencil_effect_color", "stencil_color"), &BaseMaterial3D::set_stencil_effect_color);
+	ClassDB::bind_method(D_METHOD("get_stencil_effect_color"), &BaseMaterial3D::get_stencil_effect_color);
+
+	ClassDB::bind_method(D_METHOD("set_stencil_effect_outline_thickness", "stencil_outline_thickness"), &BaseMaterial3D::set_stencil_effect_outline_thickness);
+	ClassDB::bind_method(D_METHOD("get_stencil_effect_outline_thickness"), &BaseMaterial3D::get_stencil_effect_outline_thickness);
+
 	ADD_GROUP("Transparency", "");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "transparency", PROPERTY_HINT_ENUM, "Disabled,Alpha,Alpha Scissor,Alpha Hash,Depth Pre-Pass"), "set_transparency", "get_transparency");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_scissor_threshold", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_alpha_scissor_threshold", "get_alpha_scissor_threshold");
@@ -3478,6 +3735,15 @@ void BaseMaterial3D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_min_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:m"), "set_distance_fade_min_distance", "get_distance_fade_min_distance");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:m"), "set_distance_fade_max_distance", "get_distance_fade_max_distance");
 
+	ADD_GROUP("Stencil", "stencil_");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "stencil_mode", PROPERTY_HINT_ENUM, "Disabled,Outline,X-Ray,Custom"), "set_stencil_mode", "get_stencil_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "stencil_flags", PROPERTY_HINT_FLAGS, "Read,Write,Write Depth Fail"), "set_stencil_flags", "get_stencil_flags");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "stencil_compare", PROPERTY_HINT_ENUM, "Always,Less,Equal,Less Or Equal,Greater,Not Equal,Greater Or Equal"), "set_stencil_compare", "get_stencil_compare");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "stencil_reference", PROPERTY_HINT_RANGE, "0,255,1"), "set_stencil_reference", "get_stencil_reference");
+
+	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "stencil_color", PROPERTY_HINT_NONE), "set_stencil_effect_color", "get_stencil_effect_color");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stencil_outline_thickness", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_stencil_effect_outline_thickness", "get_stencil_effect_outline_thickness");
+
 	BIND_ENUM_CONSTANT(TEXTURE_ALBEDO);
 	BIND_ENUM_CONSTANT(TEXTURE_METALLIC);
 	BIND_ENUM_CONSTANT(TEXTURE_ROUGHNESS);
@@ -3612,6 +3878,23 @@ void BaseMaterial3D::_bind_methods() {
 	BIND_ENUM_CONSTANT(DISTANCE_FADE_PIXEL_ALPHA);
 	BIND_ENUM_CONSTANT(DISTANCE_FADE_PIXEL_DITHER);
 	BIND_ENUM_CONSTANT(DISTANCE_FADE_OBJECT_DITHER);
+
+	BIND_ENUM_CONSTANT(STENCIL_MODE_DISABLED);
+	BIND_ENUM_CONSTANT(STENCIL_MODE_OUTLINE);
+	BIND_ENUM_CONSTANT(STENCIL_MODE_XRAY);
+	BIND_ENUM_CONSTANT(STENCIL_MODE_CUSTOM);
+
+	BIND_ENUM_CONSTANT(STENCIL_FLAG_READ);
+	BIND_ENUM_CONSTANT(STENCIL_FLAG_WRITE);
+	BIND_ENUM_CONSTANT(STENCIL_FLAG_WRITE_DEPTH_FAIL);
+
+	BIND_ENUM_CONSTANT(STENCIL_COMPARE_ALWAYS);
+	BIND_ENUM_CONSTANT(STENCIL_COMPARE_LESS);
+	BIND_ENUM_CONSTANT(STENCIL_COMPARE_EQUAL);
+	BIND_ENUM_CONSTANT(STENCIL_COMPARE_LESS_OR_EQUAL);
+	BIND_ENUM_CONSTANT(STENCIL_COMPARE_GREATER);
+	BIND_ENUM_CONSTANT(STENCIL_COMPARE_NOT_EQUAL);
+	BIND_ENUM_CONSTANT(STENCIL_COMPARE_GREATER_OR_EQUAL);
 }
 
 BaseMaterial3D::BaseMaterial3D(bool p_orm) :
@@ -3680,6 +3963,8 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) :
 	set_z_clip_scale(1.0);
 	set_fov_override(75.0);
 
+	set_stencil_mode(STENCIL_MODE_DISABLED);
+
 	flags[FLAG_ALBEDO_TEXTURE_MSDF] = false;
 	flags[FLAG_USE_TEXTURE_REPEAT] = true;
 

+ 71 - 0
scene/resources/material.h

@@ -326,6 +326,33 @@ public:
 		DISTANCE_FADE_MAX
 	};
 
+	enum StencilMode {
+		STENCIL_MODE_DISABLED,
+		STENCIL_MODE_OUTLINE,
+		STENCIL_MODE_XRAY,
+		STENCIL_MODE_CUSTOM,
+		STENCIL_MODE_MAX // Not an actual mode, just the amount of modes.
+	};
+
+	enum StencilFlags {
+		STENCIL_FLAG_READ = 1,
+		STENCIL_FLAG_WRITE = 2,
+		STENCIL_FLAG_WRITE_DEPTH_FAIL = 4,
+
+		STENCIL_FLAG_NUM_BITS = 3 // Not an actual mode, just the amount of bits.
+	};
+
+	enum StencilCompare {
+		STENCIL_COMPARE_ALWAYS,
+		STENCIL_COMPARE_LESS,
+		STENCIL_COMPARE_EQUAL,
+		STENCIL_COMPARE_LESS_OR_EQUAL,
+		STENCIL_COMPARE_GREATER,
+		STENCIL_COMPARE_NOT_EQUAL,
+		STENCIL_COMPARE_GREATER_OR_EQUAL,
+		STENCIL_COMPARE_MAX // Not an actual operator, just the amount of operators.
+	};
+
 private:
 	struct MaterialKey {
 		// enum values
@@ -345,6 +372,13 @@ private:
 		uint64_t roughness_channel : get_num_bits(TEXTURE_CHANNEL_MAX - 1);
 		uint64_t emission_op : get_num_bits(EMISSION_OP_MAX - 1);
 		uint64_t distance_fade : get_num_bits(DISTANCE_FADE_MAX - 1);
+
+		// stencil
+		uint64_t stencil_mode : get_num_bits(STENCIL_MODE_MAX - 1);
+		uint64_t stencil_flags : STENCIL_FLAG_NUM_BITS;
+		uint64_t stencil_compare : get_num_bits(STENCIL_COMPARE_MAX - 1);
+		uint64_t stencil_reference : 8;
+
 		// booleans
 		uint64_t invalid_key : 1;
 		uint64_t deep_parallax : 1;
@@ -406,6 +440,11 @@ private:
 		mk.alpha_antialiasing_mode = alpha_antialiasing_mode;
 		mk.orm = orm;
 
+		mk.stencil_mode = stencil_mode;
+		mk.stencil_flags = stencil_flags;
+		mk.stencil_compare = stencil_compare;
+		mk.stencil_reference = stencil_reference;
+
 		for (int i = 0; i < FEATURE_MAX; i++) {
 			if (features[i]) {
 				mk.feature_mask |= ((uint64_t)1 << i);
@@ -579,10 +618,21 @@ private:
 	float z_clip_scale = 1.0;
 	float fov_override = 75.0;
 
+	StencilMode stencil_mode = STENCIL_MODE_DISABLED;
+	int stencil_flags = 0;
+	StencilCompare stencil_compare = STENCIL_COMPARE_ALWAYS;
+	int stencil_reference = 1;
+
+	Color stencil_effect_color;
+	float stencil_effect_outline_thickness = 0.01f;
+
 	bool features[FEATURE_MAX] = {};
 
 	Ref<Texture2D> textures[TEXTURE_MAX];
 
+	void _prepare_stencil_effect();
+	Ref<BaseMaterial3D> _get_stencil_next_pass() const;
+
 	static HashMap<uint64_t, Ref<StandardMaterial3D>> materials_for_2d; //used by Sprite3D, Label3D and other stuff
 
 protected:
@@ -790,6 +840,24 @@ public:
 	void set_emission_operator(EmissionOperator p_op);
 	EmissionOperator get_emission_operator() const;
 
+	void set_stencil_mode(StencilMode p_stencil_mode);
+	StencilMode get_stencil_mode() const;
+
+	void set_stencil_flags(int p_stencil_flags);
+	int get_stencil_flags() const;
+
+	void set_stencil_compare(StencilCompare p_op);
+	StencilCompare get_stencil_compare() const;
+
+	void set_stencil_reference(int p_reference);
+	int get_stencil_reference() const;
+
+	void set_stencil_effect_color(const Color &p_color);
+	Color get_stencil_effect_color() const;
+
+	void set_stencil_effect_outline_thickness(float p_outline_thickness);
+	float get_stencil_effect_outline_thickness() const;
+
 	void set_metallic_texture_channel(TextureChannel p_channel);
 	TextureChannel get_metallic_texture_channel() const;
 	void set_roughness_texture_channel(TextureChannel p_channel);
@@ -837,6 +905,9 @@ VARIANT_ENUM_CAST(BaseMaterial3D::BillboardMode)
 VARIANT_ENUM_CAST(BaseMaterial3D::TextureChannel)
 VARIANT_ENUM_CAST(BaseMaterial3D::EmissionOperator)
 VARIANT_ENUM_CAST(BaseMaterial3D::DistanceFadeMode)
+VARIANT_ENUM_CAST(BaseMaterial3D::StencilMode)
+VARIANT_ENUM_CAST(BaseMaterial3D::StencilFlags)
+VARIANT_ENUM_CAST(BaseMaterial3D::StencilCompare)
 
 class StandardMaterial3D : public BaseMaterial3D {
 	GDCLASS(StandardMaterial3D, BaseMaterial3D)

+ 132 - 0
scene/resources/visual_shader.cpp

@@ -1715,6 +1715,41 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) {
 		}
 		_queue_update();
 		return true;
+	} else if (prop_name == "stencil/enabled") {
+		stencil_enabled = bool(p_value);
+		_queue_update();
+		notify_property_list_changed();
+		return true;
+	} else if (prop_name == "stencil/reference") {
+		stencil_reference = int(p_value);
+		_queue_update();
+		return true;
+	} else if (prop_name.begins_with("stencil_flags/")) {
+		StringName flag = prop_name.get_slicec('/', 1);
+		bool enable = p_value;
+		if (enable) {
+			stencil_flags.insert(flag);
+			if (flag == "read") {
+				stencil_flags.erase("write");
+				stencil_flags.erase("write_depth_fail");
+			} else if (flag == "write" || flag == "write_depth_fail") {
+				stencil_flags.erase("read");
+			}
+		} else {
+			stencil_flags.erase(flag);
+		}
+		_queue_update();
+		return true;
+	} else if (prop_name.begins_with("stencil_modes/")) {
+		String mode_name = prop_name.get_slicec('/', 1);
+		int value = p_value;
+		if (value == 0) {
+			stencil_modes.erase(mode_name); // It's default anyway, so don't store it.
+		} else {
+			stencil_modes[mode_name] = value;
+		}
+		_queue_update();
+		return true;
 	} else if (prop_name.begins_with("varyings/")) {
 		String var_name = prop_name.get_slicec('/', 1);
 		Varying value = Varying();
@@ -1798,6 +1833,24 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const {
 			r_ret = 0;
 		}
 		return true;
+	} else if (prop_name == "stencil/enabled") {
+		r_ret = stencil_enabled;
+		return true;
+	} else if (prop_name == "stencil/reference") {
+		r_ret = stencil_reference;
+		return true;
+	} else if (prop_name.begins_with("stencil_flags/")) {
+		StringName flag = prop_name.get_slicec('/', 1);
+		r_ret = stencil_flags.has(flag);
+		return true;
+	} else if (prop_name.begins_with("stencil_modes/")) {
+		String mode_name = prop_name.get_slicec('/', 1);
+		if (stencil_modes.has(mode_name)) {
+			r_ret = stencil_modes[mode_name];
+		} else {
+			r_ret = 0;
+		}
+		return true;
 	} else if (prop_name.begins_with("varyings/")) {
 		String var_name = prop_name.get_slicec('/', 1);
 		if (varyings.has(var_name)) {
@@ -1934,6 +1987,45 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
 		p_list->push_back(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("flags"), E)));
 	}
 
+	const Vector<ShaderLanguage::ModeInfo> &smodes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader_mode));
+
+	if (smodes.size() > 0) {
+		p_list->push_back(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("stencil"), PNAME("enabled"))));
+
+		uint32_t stencil_prop_usage = stencil_enabled ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE;
+
+		p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%s", PNAME("stencil"), PNAME("reference")), PROPERTY_HINT_RANGE, "0,255,1", stencil_prop_usage));
+
+		HashMap<String, String> stencil_enums;
+		HashSet<String> stencil_toggles;
+
+		for (const ShaderLanguage::ModeInfo &info : smodes) {
+			if (!info.options.is_empty()) {
+				const String begin = String(info.name);
+
+				for (int j = 0; j < info.options.size(); j++) {
+					const String option = String(info.options[j]).capitalize();
+
+					if (!stencil_enums.has(begin)) {
+						stencil_enums[begin] = option;
+					} else {
+						stencil_enums[begin] += "," + option;
+					}
+				}
+			} else {
+				stencil_toggles.insert(String(info.name));
+			}
+		}
+
+		for (const KeyValue<String, String> &E : stencil_enums) {
+			p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%s", PNAME("stencil_modes"), E.key), PROPERTY_HINT_ENUM, E.value, stencil_prop_usage));
+		}
+
+		for (const String &E : stencil_toggles) {
+			p_list->push_back(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("stencil_flags"), E), PROPERTY_HINT_NONE, "", stencil_prop_usage));
+		}
+	}
+
 	for (const KeyValue<String, Varying> &E : varyings) {
 		p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", "varyings", E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
 	}
@@ -2621,6 +2713,46 @@ void VisualShader::_update_shader() const {
 		global_code += "render_mode " + render_mode + ";\n\n";
 	}
 
+	const Vector<ShaderLanguage::ModeInfo> &smodes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader_mode));
+
+	if (stencil_enabled && smodes.size() > 0 && (stencil_flags.has("read") || stencil_flags.has("write") || stencil_flags.has("write_depth_fail"))) {
+		String stencil_mode;
+
+		Vector<String> flag_names;
+
+		// Add enum modes first.
+		for (const ShaderLanguage::ModeInfo &info : smodes) {
+			const String temp = String(info.name);
+
+			if (!info.options.is_empty()) {
+				if (stencil_modes.has(temp) && stencil_modes[temp] < info.options.size()) {
+					if (!stencil_mode.is_empty()) {
+						stencil_mode += ", ";
+					}
+					stencil_mode += temp + "_" + info.options[stencil_modes[temp]];
+				}
+			} else if (stencil_flags.has(temp)) {
+				flag_names.push_back(temp);
+			}
+		}
+
+		// Add flags afterward.
+		for (const String &flag_name : flag_names) {
+			if (!stencil_mode.is_empty()) {
+				stencil_mode += ", ";
+			}
+			stencil_mode += flag_name;
+		}
+
+		// Add reference value.
+		if (!stencil_mode.is_empty()) {
+			stencil_mode += ", ";
+		}
+		stencil_mode += itos(stencil_reference);
+
+		global_code += "stencil_mode " + stencil_mode + ";\n\n";
+	}
+
 	static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "start", "process", "collide", "start_custom", "process_custom", "sky", "fog" };
 
 	String global_expressions;

+ 5 - 0
scene/resources/visual_shader.h

@@ -138,6 +138,11 @@ private:
 	HashMap<String, int> modes;
 	HashSet<StringName> flags;
 
+	bool stencil_enabled = false;
+	HashMap<String, int> stencil_modes;
+	HashSet<StringName> stencil_flags;
+	int stencil_reference = 1;
+
 	HashMap<String, Varying> varyings;
 #ifdef TOOLS_ENABLED
 	HashMap<String, Variant> preview_params;

+ 21 - 1
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp

@@ -881,6 +881,7 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
 		scene_state.used_normal_texture = false;
 		scene_state.used_depth_texture = false;
 		scene_state.used_lightmap = false;
+		scene_state.used_opaque_stencil = false;
 	}
 	uint32_t lightmap_captures_used = 0;
 
@@ -1126,6 +1127,9 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
 				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DEPTH_TEXTURE) {
 					scene_state.used_depth_texture = true;
 				}
+				if ((surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_STENCIL) && !force_alpha && (surf->flags & (GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH | GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE))) {
+					scene_state.used_opaque_stencil = true;
+				}
 			} else if (p_pass_mode == PASS_MODE_SHADOW || p_pass_mode == PASS_MODE_SHADOW_DP) {
 				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW) {
 					rl->add_element(surf);
@@ -2041,7 +2045,8 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 
 	bool debug_voxelgis = get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_VOXEL_GI_ALBEDO || get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_VOXEL_GI_LIGHTING || get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_VOXEL_GI_EMISSION;
 	bool debug_sdfgi_probes = get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_SDFGI_PROBES;
-	bool depth_pre_pass = bool(GLOBAL_GET_CACHED(bool, "rendering/driver/depth_prepass/enable")) && depth_framebuffer.is_valid();
+	bool force_depth_pre_pass = scene_state.used_opaque_stencil;
+	bool depth_pre_pass = (force_depth_pre_pass || bool(GLOBAL_GET_CACHED(bool, "rendering/driver/depth_prepass/enable"))) && depth_framebuffer.is_valid();
 
 	SceneShaderForwardClustered::ShaderSpecialization base_specialization = scene_shader.default_specialization;
 	base_specialization.use_depth_fog = p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH;
@@ -3984,6 +3989,10 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
 		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_DOUBLE_SIDED_SHADOWS;
 	}
 
+	if (p_material->shader_data->stencil_enabled) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_STENCIL;
+	}
+
 	if (p_material->shader_data->uses_alpha_pass()) {
 		flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA;
 		if (p_material->shader_data->uses_depth_in_alpha_pass()) {
@@ -4004,6 +4013,17 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
 		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_MOTION_VECTOR;
 	}
 
+	if (p_material->shader_data->stencil_enabled) {
+		if (p_material->shader_data->stencil_flags & SceneShaderForwardClustered::ShaderData::STENCIL_FLAG_READ) {
+			// Stencil materials which read from the stencil buffer must be in the alpha pass.
+			// This is critical to preserve compatibility once we'll have the compositor.
+			if (!(flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA)) {
+				String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")";
+				ERR_PRINT_ED(vformat("Attempting to use a shader %s that reads stencil but is not in the alpha queue. Ensure the material uses alpha blending or has depth_draw disabled or depth_test disabled.", shader_path));
+			}
+		}
+	}
+
 	SceneShaderForwardClustered::MaterialData *material_shadow = nullptr;
 	void *surface_shadow = nullptr;
 	if (p_material->shader_data->uses_shared_shadow_material()) {

+ 2 - 0
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h

@@ -408,6 +408,7 @@ private:
 		bool used_depth_texture = false;
 		bool used_sss = false;
 		bool used_lightmap = false;
+		bool used_opaque_stencil = false;
 
 		struct ShadowPass {
 			uint32_t element_from;
@@ -485,6 +486,7 @@ private:
 			FLAG_USES_DOUBLE_SIDED_SHADOWS = 32768,
 			FLAG_USES_PARTICLE_TRAILS = 65536,
 			FLAG_USES_MOTION_VECTOR = 131072,
+			FLAG_USES_STENCIL = 262144,
 		};
 
 		union {

+ 67 - 0
servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp

@@ -84,6 +84,12 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
 
 	int depth_drawi = DEPTH_DRAW_OPAQUE;
 
+	int stencil_readi = 0;
+	int stencil_writei = 0;
+	int stencil_write_depth_faili = 0;
+	int stencil_comparei = STENCIL_COMPARE_ALWAYS;
+	int stencil_referencei = -1;
+
 	ShaderCompiler::IdentifierActions actions;
 	actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX;
 	actions.entry_point_stages["fragment"] = ShaderCompiler::STAGE_FRAGMENT;
@@ -145,6 +151,20 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
 	actions.write_flag_pointers["POSITION"] = &uses_position;
 	actions.write_flag_pointers["Z_CLIP_SCALE"] = &uses_z_clip_scale;
 
+	actions.stencil_mode_values["read"] = Pair<int *, int>(&stencil_readi, STENCIL_FLAG_READ);
+	actions.stencil_mode_values["write"] = Pair<int *, int>(&stencil_writei, STENCIL_FLAG_WRITE);
+	actions.stencil_mode_values["write_depth_fail"] = Pair<int *, int>(&stencil_write_depth_faili, STENCIL_FLAG_WRITE_DEPTH_FAIL);
+
+	actions.stencil_mode_values["compare_less"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_LESS);
+	actions.stencil_mode_values["compare_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_EQUAL);
+	actions.stencil_mode_values["compare_less_or_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_LESS_OR_EQUAL);
+	actions.stencil_mode_values["compare_greater"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_GREATER);
+	actions.stencil_mode_values["compare_not_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_NOT_EQUAL);
+	actions.stencil_mode_values["compare_greater_or_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_GREATER_OR_EQUAL);
+	actions.stencil_mode_values["compare_always"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_ALWAYS);
+
+	actions.stencil_reference = &stencil_referencei;
+
 	actions.uniforms = &uniforms;
 
 	Error err = OK;
@@ -185,6 +205,11 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
 	uses_tangent |= uses_normal_map;
 	uses_tangent |= uses_bent_normal_map;
 
+	stencil_enabled = stencil_referencei != -1;
+	stencil_flags = stencil_readi | stencil_writei | stencil_write_depth_faili;
+	stencil_compare = StencilCompare(stencil_comparei);
+	stencil_reference = stencil_referencei;
+
 #if 0
 	print_line("**compiling shader:");
 	print_line("**defines:\n");
@@ -333,6 +358,48 @@ void SceneShaderForwardClustered::ShaderData::_create_pipeline(PipelineKey p_pip
 			depth_stencil_state.depth_compare_operator = RD::COMPARE_OP_LESS;
 		}
 	}
+
+	depth_stencil_state.enable_stencil = stencil_enabled;
+	if (stencil_enabled) {
+		static const RD::CompareOperator stencil_compare_rd_table[STENCIL_COMPARE_MAX] = {
+			RD::COMPARE_OP_LESS,
+			RD::COMPARE_OP_EQUAL,
+			RD::COMPARE_OP_LESS_OR_EQUAL,
+			RD::COMPARE_OP_GREATER,
+			RD::COMPARE_OP_NOT_EQUAL,
+			RD::COMPARE_OP_GREATER_OR_EQUAL,
+			RD::COMPARE_OP_ALWAYS,
+		};
+
+		uint32_t stencil_mask = 255;
+
+		RD::PipelineDepthStencilState::StencilOperationState op;
+		op.fail = RD::STENCIL_OP_KEEP;
+		op.pass = RD::STENCIL_OP_KEEP;
+		op.depth_fail = RD::STENCIL_OP_KEEP;
+		op.compare = stencil_compare_rd_table[stencil_compare];
+		op.compare_mask = 0;
+		op.write_mask = 0;
+		op.reference = stencil_reference;
+
+		if (stencil_flags & STENCIL_FLAG_READ) {
+			op.compare_mask = stencil_mask;
+		}
+
+		if (stencil_flags & STENCIL_FLAG_WRITE) {
+			op.pass = RD::STENCIL_OP_REPLACE;
+			op.write_mask = stencil_mask;
+		}
+
+		if (stencil_flags & STENCIL_FLAG_WRITE_DEPTH_FAIL) {
+			op.depth_fail = RD::STENCIL_OP_REPLACE;
+			op.write_mask = stencil_mask;
+		}
+
+		depth_stencil_state.front_op = op;
+		depth_stencil_state.back_op = op;
+	}
+
 	bool depth_pre_pass_enabled = bool(GLOBAL_GET_CACHED(bool, "rendering/driver/depth_prepass/enable"));
 
 	RD::RenderPrimitive primitive_rd_table[RS::PRIMITIVE_MAX] = {

+ 22 - 0
servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h

@@ -168,6 +168,23 @@ public:
 			ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE
 		};
 
+		enum StencilFlags {
+			STENCIL_FLAG_READ = 1,
+			STENCIL_FLAG_WRITE = 2,
+			STENCIL_FLAG_WRITE_DEPTH_FAIL = 4,
+		};
+
+		enum StencilCompare {
+			STENCIL_COMPARE_LESS,
+			STENCIL_COMPARE_EQUAL,
+			STENCIL_COMPARE_LESS_OR_EQUAL,
+			STENCIL_COMPARE_GREATER,
+			STENCIL_COMPARE_NOT_EQUAL,
+			STENCIL_COMPARE_GREATER_OR_EQUAL,
+			STENCIL_COMPARE_ALWAYS,
+			STENCIL_COMPARE_MAX // Not an actual operator, just the amount of operators.
+		};
+
 		struct PipelineKey {
 			RD::VertexFormatID vertex_format_id;
 			RD::FramebufferFormatID framebuffer_format_id;
@@ -250,6 +267,11 @@ public:
 		bool uses_z_clip_scale = false;
 		RS::CullMode cull_mode = RS::CULL_MODE_DISABLED;
 
+		bool stencil_enabled = false;
+		uint32_t stencil_flags = 0;
+		StencilCompare stencil_compare = STENCIL_COMPARE_LESS;
+		uint32_t stencil_reference = 0;
+
 		uint64_t last_pass = 0;
 		uint32_t index = 0;
 

+ 11 - 0
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp

@@ -2675,6 +2675,17 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryI
 		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_PARTICLE_TRAILS;
 	}
 
+	if (p_material->shader_data->stencil_enabled) {
+		if (p_material->shader_data->stencil_flags & SceneShaderForwardMobile::ShaderData::STENCIL_FLAG_READ) {
+			// Stencil materials which read from the stencil buffer must be in the alpha pass.
+			// This is critical to preserve compatibility once we'll have the compositor.
+			if (!(flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA)) {
+				String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")";
+				ERR_PRINT_ED(vformat("Attempting to use a shader %s that reads stencil but is not in the alpha queue. Ensure the material uses alpha blending or has depth_draw disabled or depth_test disabled.", shader_path));
+			}
+		}
+	}
+
 	SceneShaderForwardMobile::MaterialData *material_shadow = nullptr;
 	void *surface_shadow = nullptr;
 	if (p_material->shader_data->uses_shared_shadow_material()) {

+ 66 - 0
servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp

@@ -84,6 +84,12 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
 
 	int depth_drawi = DEPTH_DRAW_OPAQUE;
 
+	int stencil_readi = 0;
+	int stencil_writei = 0;
+	int stencil_write_depth_faili = 0;
+	int stencil_comparei = STENCIL_COMPARE_ALWAYS;
+	int stencil_referencei = -1;
+
 	ShaderCompiler::IdentifierActions actions;
 	actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX;
 	actions.entry_point_stages["fragment"] = ShaderCompiler::STAGE_FRAGMENT;
@@ -143,6 +149,20 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
 	actions.write_flag_pointers["PROJECTION_MATRIX"] = &writes_modelview_or_projection;
 	actions.write_flag_pointers["VERTEX"] = &uses_vertex;
 
+	actions.stencil_mode_values["read"] = Pair<int *, int>(&stencil_readi, STENCIL_FLAG_READ);
+	actions.stencil_mode_values["write"] = Pair<int *, int>(&stencil_writei, STENCIL_FLAG_WRITE);
+	actions.stencil_mode_values["write_depth_fail"] = Pair<int *, int>(&stencil_write_depth_faili, STENCIL_FLAG_WRITE_DEPTH_FAIL);
+
+	actions.stencil_mode_values["compare_less"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_LESS);
+	actions.stencil_mode_values["compare_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_EQUAL);
+	actions.stencil_mode_values["compare_less_or_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_LESS_OR_EQUAL);
+	actions.stencil_mode_values["compare_greater"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_GREATER);
+	actions.stencil_mode_values["compare_not_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_NOT_EQUAL);
+	actions.stencil_mode_values["compare_greater_or_equal"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_GREATER_OR_EQUAL);
+	actions.stencil_mode_values["compare_always"] = Pair<int *, int>(&stencil_comparei, STENCIL_COMPARE_ALWAYS);
+
+	actions.stencil_reference = &stencil_referencei;
+
 	actions.uniforms = &uniforms;
 
 	MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
@@ -179,6 +199,11 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
 	uses_tangent |= uses_normal_map;
 	uses_tangent |= uses_bent_normal_map;
 
+	stencil_enabled = stencil_referencei != -1;
+	stencil_flags = stencil_readi | stencil_writei | stencil_write_depth_faili;
+	stencil_compare = StencilCompare(stencil_comparei);
+	stencil_reference = stencil_referencei;
+
 #ifdef DEBUG_ENABLED
 	if (uses_sss) {
 		WARN_PRINT_ONCE_ED("Subsurface scattering is only available when using the Forward+ renderer.");
@@ -300,6 +325,47 @@ void SceneShaderForwardMobile::ShaderData::_create_pipeline(PipelineKey p_pipeli
 		RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
 	};
 
+	depth_stencil_state.enable_stencil = stencil_enabled;
+	if (stencil_enabled) {
+		static const RD::CompareOperator stencil_compare_rd_table[STENCIL_COMPARE_MAX] = {
+			RD::COMPARE_OP_LESS,
+			RD::COMPARE_OP_EQUAL,
+			RD::COMPARE_OP_LESS_OR_EQUAL,
+			RD::COMPARE_OP_GREATER,
+			RD::COMPARE_OP_NOT_EQUAL,
+			RD::COMPARE_OP_GREATER_OR_EQUAL,
+			RD::COMPARE_OP_ALWAYS,
+		};
+
+		uint32_t stencil_mask = 255;
+
+		RD::PipelineDepthStencilState::StencilOperationState op;
+		op.fail = RD::STENCIL_OP_KEEP;
+		op.pass = RD::STENCIL_OP_KEEP;
+		op.depth_fail = RD::STENCIL_OP_KEEP;
+		op.compare = stencil_compare_rd_table[stencil_compare];
+		op.compare_mask = 0;
+		op.write_mask = 0;
+		op.reference = stencil_reference;
+
+		if (stencil_flags & STENCIL_FLAG_READ) {
+			op.compare_mask = stencil_mask;
+		}
+
+		if (stencil_flags & STENCIL_FLAG_WRITE) {
+			op.pass = RD::STENCIL_OP_REPLACE;
+			op.write_mask = stencil_mask;
+		}
+
+		if (stencil_flags & STENCIL_FLAG_WRITE_DEPTH_FAIL) {
+			op.depth_fail = RD::STENCIL_OP_REPLACE;
+			op.write_mask = stencil_mask;
+		}
+
+		depth_stencil_state.front_op = op;
+		depth_stencil_state.back_op = op;
+	}
+
 	RD::RenderPrimitive primitive_rd = uses_point_size ? RD::RENDER_PRIMITIVE_POINTS : primitive_rd_table[p_pipeline_key.primitive_type];
 
 	RD::PipelineRasterizationState raster_state;

+ 22 - 0
servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h

@@ -166,6 +166,23 @@ public:
 			ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE
 		};
 
+		enum StencilFlags {
+			STENCIL_FLAG_READ = 1,
+			STENCIL_FLAG_WRITE = 2,
+			STENCIL_FLAG_WRITE_DEPTH_FAIL = 4,
+		};
+
+		enum StencilCompare {
+			STENCIL_COMPARE_LESS,
+			STENCIL_COMPARE_EQUAL,
+			STENCIL_COMPARE_LESS_OR_EQUAL,
+			STENCIL_COMPARE_GREATER,
+			STENCIL_COMPARE_NOT_EQUAL,
+			STENCIL_COMPARE_GREATER_OR_EQUAL,
+			STENCIL_COMPARE_ALWAYS,
+			STENCIL_COMPARE_MAX // Not an actual operator, just the amount of operators.
+		};
+
 		struct PipelineKey {
 			RD::VertexFormatID vertex_format_id;
 			RD::FramebufferFormatID framebuffer_format_id;
@@ -246,6 +263,11 @@ public:
 		bool writes_modelview_or_projection = false;
 		bool uses_world_coordinates = false;
 
+		bool stencil_enabled = false;
+		uint32_t stencil_flags = 0;
+		StencilCompare stencil_compare = STENCIL_COMPARE_LESS;
+		uint32_t stencil_reference = 0;
+
 		uint64_t last_pass = 0;
 		uint32_t index = 0;
 

+ 18 - 0
servers/rendering/shader_compiler.cpp

@@ -453,6 +453,8 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
 		case SL::Node::NODE_TYPE_SHADER: {
 			SL::ShaderNode *pnode = (SL::ShaderNode *)p_node;
 
+			// Render modes.
+
 			for (int i = 0; i < pnode->render_modes.size(); i++) {
 				if (p_default_actions.render_mode_defines.has(pnode->render_modes[i]) && !used_rmode_defines.has(pnode->render_modes[i])) {
 					r_gen_code.defines.push_back(p_default_actions.render_mode_defines[pnode->render_modes[i]]);
@@ -469,6 +471,21 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
 				}
 			}
 
+			// Stencil modes.
+
+			for (int i = 0; i < pnode->stencil_modes.size(); i++) {
+				if (p_actions.stencil_mode_values.has(pnode->stencil_modes[i])) {
+					Pair<int *, int> &p = p_actions.stencil_mode_values[pnode->stencil_modes[i]];
+					*p.first = p.second;
+				}
+			}
+
+			// Stencil reference value.
+
+			if (p_actions.stencil_reference && pnode->stencil_reference != -1) {
+				*p_actions.stencil_reference = pnode->stencil_reference;
+			}
+
 			// structs
 
 			for (int i = 0; i < pnode->vstructs.size(); i++) {
@@ -1463,6 +1480,7 @@ Error ShaderCompiler::compile(RS::ShaderMode p_mode, const String &p_code, Ident
 	SL::ShaderCompileInfo info;
 	info.functions = ShaderTypes::get_singleton()->get_functions(p_mode);
 	info.render_modes = ShaderTypes::get_singleton()->get_modes(p_mode);
+	info.stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(p_mode);
 	info.shader_types = ShaderTypes::get_singleton()->get_types();
 	info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
 	info.base_varying_index = actions.base_varying_index;

+ 2 - 0
servers/rendering/shader_compiler.h

@@ -50,6 +50,8 @@ public:
 		HashMap<StringName, bool *> render_mode_flags;
 		HashMap<StringName, bool *> usage_flag_pointers;
 		HashMap<StringName, bool *> write_flag_pointers;
+		HashMap<StringName, Pair<int *, int>> stencil_mode_values;
+		int *stencil_reference = nullptr;
 
 		HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> *uniforms = nullptr;
 	};

+ 214 - 66
servers/rendering/shader_language.cpp

@@ -339,6 +339,7 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
 	{ TK_STRUCT, "struct", CF_GLOBAL_SPACE, {}, {} },
 	{ TK_SHADER_TYPE, "shader_type", CF_SHADER_TYPE, {}, {} },
 	{ TK_RENDER_MODE, "render_mode", CF_GLOBAL_SPACE, {}, {} },
+	{ TK_STENCIL_MODE, "stencil_mode", CF_GLOBAL_SPACE, {}, {} },
 
 	// uniform qualifiers
 
@@ -4128,7 +4129,7 @@ bool ShaderLanguage::is_token_operator_assign(TokenType p_type) {
 }
 
 bool ShaderLanguage::is_token_hint(TokenType p_type) {
-	return int(p_type) > int(TK_RENDER_MODE) && int(p_type) < int(TK_SHADER_TYPE);
+	return int(p_type) > int(TK_STENCIL_MODE) && int(p_type) < int(TK_SHADER_TYPE);
 }
 
 bool ShaderLanguage::convert_constant(ConstantNode *p_constant, DataType p_to_type, Scalar *p_value) {
@@ -9110,7 +9111,7 @@ bool ShaderLanguage::_parse_numeric_constant_expression(const FunctionInfo &p_fu
 	return true;
 }
 
-Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const HashSet<String> &p_shader_types) {
+Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const Vector<ModeInfo> &p_stencil_modes, const HashSet<String> &p_shader_types) {
 	Token tk;
 	TkPos prev_pos;
 	Token next;
@@ -9173,7 +9174,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
 
 	const FunctionInfo &constants = p_functions.has("constants") ? p_functions["constants"] : FunctionInfo();
 
-	HashMap<String, String> defined_modes;
+	HashMap<String, String> defined_render_modes;
+	HashMap<String, String> defined_stencil_modes;
 
 	while (tk.type != TK_EOF) {
 		switch (tk.type) {
@@ -9182,83 +9184,64 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
 				keyword_completion_context = CF_UNSPECIFIED;
 #endif // DEBUG_ENABLED
 				while (true) {
-					StringName mode;
-					_get_completable_identifier(nullptr, COMPLETION_RENDER_MODE, mode);
-
-					if (mode == StringName()) {
-						_set_error(RTR("Expected an identifier for render mode."));
-						return ERR_PARSE_ERROR;
+					Error error = _parse_shader_mode(false, p_render_modes, defined_render_modes);
+					if (error != OK) {
+						return error;
 					}
 
-					const String smode = String(mode);
+					tk = _get_token();
 
-					if (shader->render_modes.has(mode)) {
-						_set_error(vformat(RTR("Duplicated render mode: '%s'."), smode));
+					if (tk.type == TK_COMMA) {
+						// All good, do nothing.
+					} else if (tk.type == TK_SEMICOLON) {
+						break; // Done.
+					} else {
+						_set_error(vformat(RTR("Unexpected token: '%s'."), get_token_text(tk)));
 						return ERR_PARSE_ERROR;
 					}
+				}
+#ifdef DEBUG_ENABLED
+				keyword_completion_context = CF_GLOBAL_SPACE;
+#endif // DEBUG_ENABLED
+			} break;
+			case TK_STENCIL_MODE: {
+#ifdef DEBUG_ENABLED
+				keyword_completion_context = CF_UNSPECIFIED;
+#endif // DEBUG_ENABLED
+				while (true) {
+					TkPos pos = _get_tkpos();
+					tk = _get_token();
 
-					bool found = false;
-
-					if (is_shader_inc) {
-						for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
-							const Vector<ModeInfo> modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+					if (tk.is_integer_constant()) {
+						const int reference_value = tk.constant;
 
-							for (int j = 0; j < modes.size(); j++) {
-								const ModeInfo &info = modes[j];
-								const String name = String(info.name);
+						if (shader->stencil_reference != -1) {
+							_set_error(vformat(RTR("Duplicated stencil mode reference value: '%s'."), reference_value));
+							return ERR_PARSE_ERROR;
+						}
 
-								if (smode.begins_with(name)) {
-									if (!info.options.is_empty()) {
-										if (info.options.has(smode.substr(name.length() + 1))) {
-											found = true;
+						if (reference_value < 0) {
+							_set_error(vformat(RTR("Stencil mode reference value cannot be negative: '%s'."), reference_value));
+							return ERR_PARSE_ERROR;
+						}
 
-											if (defined_modes.has(name)) {
-												_set_error(vformat(RTR("Redefinition of render mode: '%s'. The '%s' mode has already been set to '%s'."), smode, name, defined_modes[name]));
-												return ERR_PARSE_ERROR;
-											}
-											defined_modes.insert(name, smode);
-											break;
-										}
-									} else {
-										found = true;
-										break;
-									}
-								}
-							}
+						if (reference_value > 255) {
+							_set_error(vformat(RTR("Stencil mode reference value cannot be greater than 255: '%s'."), reference_value));
+							return ERR_PARSE_ERROR;
 						}
-					} else {
-						for (int i = 0; i < p_render_modes.size(); i++) {
-							const ModeInfo &info = p_render_modes[i];
-							const String name = String(info.name);
 
-							if (smode.begins_with(name)) {
-								if (!info.options.is_empty()) {
-									if (info.options.has(smode.substr(name.length() + 1))) {
-										found = true;
+						shader->stencil_reference = reference_value;
+					} else {
+						_set_tkpos(pos);
 
-										if (defined_modes.has(name)) {
-											_set_error(vformat(RTR("Redefinition of render mode: '%s'. The '%s' mode has already been set to '%s'."), smode, name, defined_modes[name]));
-											return ERR_PARSE_ERROR;
-										}
-										defined_modes.insert(name, smode);
-										break;
-									}
-								} else {
-									found = true;
-									break;
-								}
-							}
+						Error error = _parse_shader_mode(true, p_stencil_modes, defined_stencil_modes);
+						if (error != OK) {
+							return error;
 						}
 					}
 
-					if (!found) {
-						_set_error(vformat(RTR("Invalid render mode: '%s'."), smode));
-						return ERR_PARSE_ERROR;
-					}
-
-					shader->render_modes.push_back(mode);
-
 					tk = _get_token();
+
 					if (tk.type == TK_COMMA) {
 						//all good, do nothing
 					} else if (tk.type == TK_SEMICOLON) {
@@ -11076,6 +11059,110 @@ Error ShaderLanguage::_find_last_flow_op_in_block(BlockNode *p_block, FlowOperat
 	return FAILED;
 }
 
+Error ShaderLanguage::_parse_shader_mode(bool p_is_stencil, const Vector<ModeInfo> &p_modes, HashMap<String, String> &r_defined_modes) {
+	StringName mode;
+	_get_completable_identifier(nullptr, p_is_stencil ? COMPLETION_STENCIL_MODE : COMPLETION_RENDER_MODE, mode);
+
+	if (mode == StringName()) {
+		if (p_is_stencil) {
+			_set_error(RTR("Expected an identifier for stencil mode."));
+		} else {
+			_set_error(RTR("Expected an identifier for render mode."));
+		}
+		return ERR_PARSE_ERROR;
+	}
+
+	const String smode = String(mode);
+
+	Vector<StringName> &current_modes = p_is_stencil ? shader->stencil_modes : shader->render_modes;
+
+	if (current_modes.has(mode)) {
+		if (p_is_stencil) {
+			_set_error(vformat(RTR("Duplicated stencil mode: '%s'."), smode));
+		} else {
+			_set_error(vformat(RTR("Duplicated render mode: '%s'."), smode));
+		}
+		return ERR_PARSE_ERROR;
+	}
+
+	bool found = false;
+
+	if (is_shader_inc) {
+		for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
+			const Vector<ModeInfo> modes = p_is_stencil ? ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(i)) : ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+
+			for (const ModeInfo &info : modes) {
+				const String name = String(info.name);
+
+				if (smode.begins_with(name)) {
+					if (!info.options.is_empty()) {
+						if (info.options.has(smode.substr(name.length() + 1))) {
+							found = true;
+
+							if (r_defined_modes.has(name)) {
+								if (p_is_stencil) {
+									_set_error(vformat(RTR("Redefinition of stencil mode: '%s'. The '%s' mode has already been set to '%s'."), smode, name, r_defined_modes[name]));
+								} else {
+									_set_error(vformat(RTR("Redefinition of render mode: '%s'. The '%s' mode has already been set to '%s'."), smode, name, r_defined_modes[name]));
+								}
+								return ERR_PARSE_ERROR;
+							}
+							r_defined_modes.insert(name, smode);
+							break;
+						}
+					} else {
+						found = true;
+						break;
+					}
+				}
+			}
+		}
+	} else {
+		for (const ModeInfo &info : p_modes) {
+			const String name = String(info.name);
+
+			if (smode.begins_with(name)) {
+				if (!info.options.is_empty()) {
+					if (info.options.has(smode.substr(name.length() + 1))) {
+						found = true;
+
+						if (r_defined_modes.has(name)) {
+							if (p_is_stencil) {
+								_set_error(vformat(RTR("Redefinition of stencil mode: '%s'. The '%s' mode has already been set to '%s'."), smode, name, r_defined_modes[name]));
+							} else {
+								_set_error(vformat(RTR("Redefinition of render mode: '%s'. The '%s' mode has already been set to '%s'."), smode, name, r_defined_modes[name]));
+							}
+							return ERR_PARSE_ERROR;
+						}
+						r_defined_modes.insert(name, smode);
+						break;
+					}
+				} else {
+					found = true;
+					break;
+				}
+			}
+		}
+	}
+
+	if (!found) {
+		if (p_is_stencil) {
+			_set_error(vformat(RTR("Invalid stencil mode: '%s'."), smode));
+		} else {
+			_set_error(vformat(RTR("Invalid render mode: '%s'."), smode));
+		}
+		return ERR_PARSE_ERROR;
+	}
+
+	if (p_is_stencil) {
+		shader->stencil_modes.push_back(mode);
+	} else {
+		shader->render_modes.push_back(mode);
+	}
+
+	return OK;
+}
+
 // skips over whitespace and /* */ and // comments
 static int _get_first_ident_pos(const String &p_code) {
 	int idx = 0;
@@ -11226,7 +11313,7 @@ Error ShaderLanguage::compile(const String &p_code, const ShaderCompileInfo &p_i
 	nodes = nullptr;
 
 	shader = alloc_node<ShaderNode>();
-	Error err = _parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types);
+	Error err = _parse_shader(p_info.functions, p_info.render_modes, p_info.stencil_modes, p_info.shader_types);
 
 #ifdef DEBUG_ENABLED
 	if (check_warnings) {
@@ -11251,7 +11338,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
 	global_shader_uniform_get_type_func = p_info.global_shader_uniform_type_func;
 
 	shader = alloc_node<ShaderNode>();
-	_parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types);
+	_parse_shader(p_info.functions, p_info.render_modes, p_info.stencil_modes, p_info.shader_types);
 
 #ifdef DEBUG_ENABLED
 	// Adds context keywords.
@@ -11349,6 +11436,67 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
 
 			return OK;
 		} break;
+		case COMPLETION_STENCIL_MODE: {
+			if (is_shader_inc) {
+				for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
+					const Vector<ModeInfo> modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(i));
+
+					for (const ModeInfo &info : modes) {
+						if (!info.options.is_empty()) {
+							bool found = false;
+
+							for (const StringName &option : info.options) {
+								if (shader->stencil_modes.has(String(info.name) + "_" + String(option))) {
+									found = true;
+								}
+							}
+
+							if (!found) {
+								for (const StringName &option : info.options) {
+									ScriptLanguage::CodeCompletionOption completion_option(String(info.name) + "_" + String(option), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+									r_options->push_back(completion_option);
+								}
+							}
+						} else {
+							const String name = String(info.name);
+
+							if (!shader->stencil_modes.has(name)) {
+								ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+								r_options->push_back(option);
+							}
+						}
+					}
+				}
+			} else {
+				for (const ModeInfo &info : p_info.stencil_modes) {
+					if (!info.options.is_empty()) {
+						bool found = false;
+
+						for (const StringName &option : info.options) {
+							if (shader->stencil_modes.has(String(info.name) + "_" + String(option))) {
+								found = true;
+							}
+						}
+
+						if (!found) {
+							for (const StringName &option : info.options) {
+								ScriptLanguage::CodeCompletionOption completion_option(String(info.name) + "_" + String(option), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+								r_options->push_back(completion_option);
+							}
+						}
+					} else {
+						const String name = String(info.name);
+
+						if (!shader->stencil_modes.has(name)) {
+							ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+							r_options->push_back(option);
+						}
+					}
+				}
+			}
+
+			return OK;
+		} break;
 		case COMPLETION_STRUCT: {
 			if (shader->structs.has(completion_struct)) {
 				StructNode *node = shader->structs[completion_struct].shader_struct;

+ 8 - 1
servers/rendering/shader_language.h

@@ -164,6 +164,7 @@ public:
 		TK_ARG_OUT,
 		TK_ARG_INOUT,
 		TK_RENDER_MODE,
+		TK_STENCIL_MODE,
 		TK_HINT_DEFAULT_WHITE_TEXTURE,
 		TK_HINT_DEFAULT_BLACK_TEXTURE,
 		TK_HINT_DEFAULT_TRANSPARENT_TEXTURE,
@@ -723,6 +724,8 @@ public:
 		HashMap<StringName, Struct> structs;
 		HashMap<StringName, Function> functions;
 		Vector<StringName> render_modes;
+		Vector<StringName> stencil_modes;
+		int stencil_reference = -1;
 
 		Vector<Function> vfunctions;
 		Vector<Constant> vconstants;
@@ -799,6 +802,7 @@ public:
 		COMPLETION_NONE,
 		COMPLETION_SHADER_TYPE,
 		COMPLETION_RENDER_MODE,
+		COMPLETION_STENCIL_MODE,
 		COMPLETION_MAIN_FUNCTION,
 		COMPLETION_IDENTIFIER,
 		COMPLETION_FUNCTION_CALL,
@@ -1222,11 +1226,13 @@ private:
 	String _get_qualifier_str(ArgumentQualifier p_qualifier) const;
 
 	bool _parse_numeric_constant_expression(const FunctionInfo &p_function_info, float &r_constant);
-	Error _parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const HashSet<String> &p_shader_types);
+	Error _parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const Vector<ModeInfo> &p_stencil_modes, const HashSet<String> &p_shader_types);
 
 	Error _find_last_flow_op_in_block(BlockNode *p_block, FlowOperation p_op);
 	Error _find_last_flow_op_in_op(ControlFlowNode *p_flow, FlowOperation p_op);
 
+	Error _parse_shader_mode(bool p_is_stencil, const Vector<ModeInfo> &p_modes, HashMap<String, String> &r_defined_modes);
+
 public:
 #ifdef DEBUG_ENABLED
 	List<ShaderWarning>::Element *get_warnings_ptr();
@@ -1248,6 +1254,7 @@ public:
 	struct ShaderCompileInfo {
 		HashMap<StringName, FunctionInfo> functions;
 		Vector<ModeInfo> render_modes;
+		Vector<ModeInfo> stencil_modes;
 		VaryingFunctionNames varying_function_names;
 		HashSet<String> shader_types;
 		GlobalShaderUniformGetTypeFunc global_shader_uniform_type_func = nullptr;

+ 8 - 0
servers/rendering/shader_types.cpp

@@ -38,6 +38,10 @@ const Vector<ShaderLanguage::ModeInfo> &ShaderTypes::get_modes(RS::ShaderMode p_
 	return shader_modes[p_mode].modes;
 }
 
+const Vector<ShaderLanguage::ModeInfo> &ShaderTypes::get_stencil_modes(RS::ShaderMode p_mode) const {
+	return shader_modes[p_mode].stencil_modes;
+}
+
 const HashSet<String> &ShaderTypes::get_types() const {
 	return shader_types;
 }
@@ -246,6 +250,10 @@ ShaderTypes::ShaderTypes() {
 		shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("debug_shadow_splits") });
 		shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("fog_disabled") });
 		shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("specular_occlusion_disabled") });
+		shader_modes[RS::SHADER_SPATIAL].stencil_modes.push_back({ PNAME("read") });
+		shader_modes[RS::SHADER_SPATIAL].stencil_modes.push_back({ PNAME("write") });
+		shader_modes[RS::SHADER_SPATIAL].stencil_modes.push_back({ PNAME("write_depth_fail") });
+		shader_modes[RS::SHADER_SPATIAL].stencil_modes.push_back({ PNAME("compare"), { "always", "less", "equal", "less_or_equal", "greater", "not_equal", "greater_or_equal" } });
 	}
 
 	/************ CANVAS ITEM **************************/

+ 2 - 0
servers/rendering/shader_types.h

@@ -37,6 +37,7 @@ class ShaderTypes {
 	struct Type {
 		HashMap<StringName, ShaderLanguage::FunctionInfo> functions;
 		Vector<ShaderLanguage::ModeInfo> modes;
+		Vector<ShaderLanguage::ModeInfo> stencil_modes;
 	};
 
 	HashMap<RS::ShaderMode, Type> shader_modes;
@@ -51,6 +52,7 @@ public:
 
 	const HashMap<StringName, ShaderLanguage::FunctionInfo> &get_functions(RS::ShaderMode p_mode) const;
 	const Vector<ShaderLanguage::ModeInfo> &get_modes(RS::ShaderMode p_mode) const;
+	const Vector<ShaderLanguage::ModeInfo> &get_stencil_modes(RS::ShaderMode p_mode) const;
 	const HashSet<String> &get_types() const;
 	const List<String> &get_types_list() const;