diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml
index 2a7383682fe4..71a6498a4219 100644
--- a/doc/classes/BaseMaterial3D.xml
+++ b/doc/classes/BaseMaterial3D.xml
@@ -363,6 +363,24 @@
The method for rendering the specular blob. See [enum SpecularMode].
[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.
+
+ The primary color of the stencil effect.
+
+
+ The comparison operator to use for stencil masking operations. See [enum StencilCompare].
+
+
+ The flags dictating how the stencil operation behaves. See [enum StencilFlags].
+
+
+ The stencil effect mode. See [enum StencilMode].
+
+
+ The outline thickness for STENCIL_MODE_OUTLINE.
+
+
+ The stencil reference value (0-255). Typically a power of 2.
+
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].
@@ -805,5 +823,47 @@
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].
+
+ Disables stencil operations.
+
+
+ Stencil preset which applies an outline to the object. Requires a next_pass.
+
+
+ Stencil preset which shows a silhouette of the object behind walls. Requires a next_pass.
+
+
+ Enables stencil operations without a preset.
+
+
+ The material will only be rendered where it passes a stencil comparison with existing stencil buffer values. See [enum StencilCompare].
+
+
+ The material will write the reference value to the stencil buffer where it passes the depth test.
+
+
+ The material will write the reference value to the stencil buffer where it fails the depth test.
+
+
+ Passes the stencil test when the reference value is less than the existing stencil value.
+
+
+ Passes the stencil test when the reference value is equal to the existing stencil value.
+
+
+ Passes the stencil test when the reference value is less than or equal to the existing stencil value.
+
+
+ Passes the stencil test when the reference value is greater than the existing stencil value.
+
+
+ Passes the stencil test when the reference value is not equal to the existing stencil value.
+
+
+ Passes the stencil test when the reference value is greater than or equal to the existing stencil value.
+
+
+ Always passes the stencil test.
+
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index 276c57533f72..82e2d2c7d717 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -268,17 +268,35 @@ void ShaderTextEditor::_load_theme_settings() {
}
}
- const Vector &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+ {
+ const Vector &render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+
+ for (int j = 0; j < render_modes.size(); j++) {
+ const ShaderLanguage::ModeInfo &mode_info = render_modes[j];
+
+ 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]));
+ }
+ } 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 &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 (int j = 0; j < stencil_modes.size(); j++) {
+ const ShaderLanguage::ModeInfo &mode_info = stencil_modes[j];
+
+ 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]));
+ }
+ } else {
+ built_ins.push_back(String(mode_info.name));
}
- } else {
- built_ins.push_back(String(mode_info.name));
}
}
}
@@ -289,17 +307,35 @@ void ShaderTextEditor::_load_theme_settings() {
}
}
- const Vector &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
+ {
+ const Vector &shader_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()));
+
+ for (int i = 0; i < shader_modes.size(); i++) {
+ const ShaderLanguage::ModeInfo &mode_info = shader_modes[i];
+
+ 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]));
+ }
+ } else {
+ built_ins.push_back(String(mode_info.name));
+ }
+ }
+ }
+
+ {
+ const Vector &stencil_modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader->get_mode()));
- for (int i = 0; i < modes.size(); i++) {
- const ShaderLanguage::ModeInfo &mode_info = modes[i];
+ for (int i = 0; i < stencil_modes.size(); i++) {
+ const ShaderLanguage::ModeInfo &mode_info = stencil_modes[i];
- 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]));
+ 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]));
+ }
+ } else {
+ built_ins.push_back(String(mode_info.name));
}
- } else {
- built_ins.push_back(String(mode_info.name));
}
}
}
@@ -436,6 +472,7 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, Listget_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);
@@ -540,6 +577,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();
}
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 3215f85293a8..62f57fa31921 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -5942,6 +5942,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;
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index ea121003b69b..85e365a01985 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -845,6 +845,50 @@ void BaseMaterial3D::_update_shader() {
code += ";\n";
+ if (stencil_mode != STENCIL_MODE_DISABLED) {
+ code += "stencil_mode ";
+
+ if (stencil_flags & STENCIL_FLAG_READ) {
+ code += "read,";
+ }
+
+ if (stencil_flags & STENCIL_FLAG_WRITE) {
+ code += "write,";
+ }
+
+ if (stencil_flags & STENCIL_FLAG_WRITE_DEPTH_FAIL) {
+ code += "write_depth_fail,";
+ }
+
+ switch (stencil_compare) {
+ 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_ALWAYS:
+ code += "compare_always,";
+ break;
+ case STENCIL_COMPARE_MAX:
+ break;
+ }
+
+ code += vformat("%s;\n", stencil_reference);
+ }
+
// Generate list of uniforms.
code += vformat(R"(
uniform vec4 albedo : source_color;
@@ -2433,6 +2477,22 @@ void BaseMaterial3D::_validate_property(PropertyInfo &p_property) const {
p_property.usage = PROPERTY_USAGE_NONE;
}
+ if (p_property.name == "stencil_reference" && stencil_mode == STENCIL_MODE_DISABLED) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+
+ if ((p_property.name == "stencil_flags" || p_property.name == "stencil_compare") && stencil_mode != STENCIL_MODE_CUSTOM) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+
+ if (p_property.name == "stencil_color" && stencil_mode != STENCIL_MODE_OUTLINE && stencil_mode != STENCIL_MODE_XRAY) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+
+ if (p_property.name == "stencil_outline_thickness" && stencil_mode != STENCIL_MODE_OUTLINE) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+
if (flags[FLAG_SUBSURFACE_MODE_SKIN] && (p_property.name == "subsurf_scatter_transmittance_color" || p_property.name == "subsurf_scatter_transmittance_texture")) {
p_property.usage = PROPERTY_USAGE_NONE;
}
@@ -2877,6 +2937,171 @@ BaseMaterial3D::EmissionOperator BaseMaterial3D::get_emission_operator() const {
return emission_op;
}
+void BaseMaterial3D::_prepare_stencil_effect() {
+ const Ref 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 stencil_next_pass;
+
+ if (!current_next_pass.is_valid() || !current_next_pass->has_meta("_stencil_owned")) {
+ stencil_next_pass = Ref(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::_get_stencil_next_pass() const {
+ const Ref current_next_pass = get_next_pass();
+ Ref 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;
+ }
+
+ 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 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 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 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 {
MutexLock lock(material_mutex);
if (element.in_list()) {
@@ -3100,6 +3325,24 @@ void BaseMaterial3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_distance_fade_min_distance", "distance"), &BaseMaterial3D::set_distance_fade_min_distance);
ClassDB::bind_method(D_METHOD("get_distance_fade_min_distance"), &BaseMaterial3D::get_distance_fade_min_distance);
+ 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");
@@ -3275,6 +3518,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,Xray,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, "Less,Equal,Less Or Equal,Greater,Not Equal,Greater Or Equal,Always"), "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);
@@ -3410,6 +3662,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_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);
+ BIND_ENUM_CONSTANT(STENCIL_COMPARE_ALWAYS);
}
BaseMaterial3D::BaseMaterial3D(bool p_orm) :
@@ -3475,6 +3744,8 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) :
set_heightmap_deep_parallax_max_layers(32);
set_heightmap_deep_parallax_flip_tangent(false); //also sets binormal
+ set_stencil_mode(STENCIL_MODE_DISABLED);
+
flags[FLAG_ALBEDO_TEXTURE_MSDF] = false;
flags[FLAG_USE_TEXTURE_REPEAT] = true;
diff --git a/scene/resources/material.h b/scene/resources/material.h
index 77288734691f..7682a334e67f 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -322,6 +322,33 @@ class BaseMaterial3D : public Material {
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_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.
+ };
+
private:
struct MaterialKey {
// enum values
@@ -341,6 +368,13 @@ class BaseMaterial3D : public Material {
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;
@@ -401,6 +435,11 @@ class BaseMaterial3D : public Material {
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);
@@ -565,12 +604,23 @@ class BaseMaterial3D : public Material {
AlphaAntiAliasing alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
+ StencilMode stencil_mode = STENCIL_MODE_DISABLED;
+ int stencil_flags = STENCIL_FLAG_READ;
+ StencilCompare stencil_compare = STENCIL_COMPARE_EQUAL;
+ int stencil_reference = 1;
+
+ Color stencil_effect_color;
+ float stencil_effect_outline_thickness = 0.01f;
+
bool features[FEATURE_MAX] = {};
Ref textures[TEXTURE_MAX];
_FORCE_INLINE_ void _validate_feature(const String &text, Feature feature, PropertyInfo &property) const;
+ void _prepare_stencil_effect();
+ Ref _get_stencil_next_pass() const;
+
static HashMap> materials_for_2d; //used by Sprite3D, Label3D and other stuff
protected:
@@ -778,6 +828,24 @@ class BaseMaterial3D : public Material {
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);
@@ -819,6 +887,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)
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 4bedcb18200f..bb5fc605214d 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -1686,6 +1686,35 @@ 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);
+ } 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();
@@ -1760,6 +1789,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)) {
@@ -1861,6 +1908,47 @@ void VisualShader::_get_property_list(List *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("flags"), E)));
}
+ const Vector &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 stencil_enums;
+ HashSet stencil_toggles;
+
+ for (int i = 0; i < smodes.size(); i++) {
+ const ShaderLanguage::ModeInfo &info = smodes[i];
+
+ 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 &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 &E : varyings) {
p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("varyings"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
@@ -2523,6 +2611,47 @@ void VisualShader::_update_shader() const {
global_code += "render_mode " + render_mode + ";\n\n";
}
+ const Vector &smodes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(shader_mode));
+
+ if (smodes.size() > 0 && stencil_enabled) {
+ String stencil_mode;
+
+ Vector flag_names;
+
+ // Add enum modes first.
+ for (int i = 0; i < smodes.size(); i++) {
+ const ShaderLanguage::ModeInfo &info = smodes[i];
+ 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 (int i = 0; i < flag_names.size(); i++) {
+ if (!stencil_mode.is_empty()) {
+ stencil_mode += ", ";
+ }
+ stencil_mode += flag_names[i];
+ }
+
+ // 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;
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index 9cd8f86d0fe3..85fe439d766c 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -141,6 +141,11 @@ class VisualShader : public Shader {
HashMap modes;
HashSet flags;
+ bool stencil_enabled = false;
+ HashMap stencil_modes;
+ HashSet stencil_flags;
+ int stencil_reference = 1;
+
HashMap varyings;
List varyings_list;
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index f97ed3d21564..2f681d129531 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -827,6 +827,7 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
if (p_render_list == RENDER_LIST_OPAQUE) {
// Opaque fills motion and alpha lists.
render_list[RENDER_LIST_MOTION].clear();
+ render_list[RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS].clear(); // Opaque fills opaque_no_depth_prepass too.
render_list[RENDER_LIST_ALPHA].clear();
}
}
@@ -1031,7 +1032,12 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
}
if (!force_alpha && (surf->flags & (GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH | GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE))) {
- rl->add_element(surf);
+ if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_NO_DEPTH_PREPASS) {
+ render_list[RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS].add_element(surf);
+ scene_state.used_opaque_no_depth_prepass = true;
+ } else {
+ rl->add_element(surf);
+ }
}
if (force_alpha || (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA)) {
@@ -1740,11 +1746,13 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
_fill_render_list(RENDER_LIST_OPAQUE, p_render_data, PASS_MODE_COLOR, using_sdfgi, using_sdfgi || using_voxelgi, using_motion_pass);
render_list[RENDER_LIST_OPAQUE].sort_by_key();
render_list[RENDER_LIST_MOTION].sort_by_key();
+ render_list[RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS].sort_by_key();
render_list[RENDER_LIST_ALPHA].sort_by_reverse_depth_and_priority();
int *render_info = p_render_data->render_info ? p_render_data->render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_VISIBLE] : (int *)nullptr;
_fill_instance_data(RENDER_LIST_OPAQUE, render_info);
_fill_instance_data(RENDER_LIST_MOTION, render_info);
+ _fill_instance_data(RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS, render_info);
_fill_instance_data(RENDER_LIST_ALPHA);
RD::get_singleton()->draw_command_end_label();
@@ -2020,8 +2028,17 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
uint32_t opaque_color_pass_flags = using_motion_pass ? (color_pass_flags & ~COLOR_PASS_FLAG_MOTION_VECTORS) : color_pass_flags;
RID opaque_framebuffer = using_motion_pass ? rb_data->get_color_pass_fb(opaque_color_pass_flags) : color_framebuffer;
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
- _render_list_with_draw_list(&render_list_params, opaque_framebuffer, load_color ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pre_pass ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, c, 0.0, 0);
+
+ {
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ _render_list_with_draw_list(&render_list_params, opaque_framebuffer, load_color ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, scene_state.used_opaque_no_depth_prepass ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_STORE, depth_pre_pass ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, scene_state.used_opaque_no_depth_prepass ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_STORE, c, 0.0, 0);
+ }
+
+ if (scene_state.used_opaque_no_depth_prepass) {
+ rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS, p_render_data, radiance_texture, samplers, true);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS].elements.ptr(), render_list[RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS].element_info.ptr(), render_list[RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ _render_list_with_draw_list(&render_list_params, opaque_framebuffer, RD::INITIAL_ACTION_CONTINUE, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CONTINUE, RD::FINAL_ACTION_STORE, c, 0.0, 0);
+ }
}
RD::get_singleton()->draw_command_end_label();
@@ -3786,6 +3803,13 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_MOTION_VECTOR;
}
+ if (p_material->shader_data->stencil_enabled) {
+ // Stencil materials which read from the stencil buffer must be sorted after all other depth-related operations.
+ if (p_material->shader_data->stencil_flags & SceneShaderForwardClustered::ShaderData::STENCIL_FLAG_READ) {
+ flags |= GeometryInstanceSurfaceDataCache::FLAG_NO_DEPTH_PREPASS;
+ }
+ }
+
SceneShaderForwardClustered::MaterialData *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_position && !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_alpha_antialiasing && p_material->shader_data->cull_mode == SceneShaderForwardClustered::ShaderData::CULL_BACK && !p_material->shader_data->uses_point_size && !p_material->shader_data->uses_world_coordinates) {
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index 0aa4a0667ec0..dc37dd553f28 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -86,6 +86,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
enum RenderListType {
RENDER_LIST_OPAQUE, //used for opaque objects
RENDER_LIST_MOTION, //used for opaque objects with motion
+ RENDER_LIST_OPAQUE_NO_DEPTH_PREPASS, // Used for opaque objects which cannot use depth prepass.
RENDER_LIST_ALPHA, //used for transparent objects
RENDER_LIST_SECONDARY, //used for shadows and other objects
RENDER_LIST_MAX
@@ -339,6 +340,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
bool used_depth_texture = false;
bool used_sss = false;
bool used_lightmap = false;
+ bool used_opaque_no_depth_prepass = false;
struct ShadowPass {
uint32_t element_from;
@@ -408,6 +410,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
FLAG_USES_DOUBLE_SIDED_SHADOWS = 32768,
FLAG_USES_PARTICLE_TRAILS = 65536,
FLAG_USES_MOTION_VECTOR = 131072,
+ FLAG_NO_DEPTH_PREPASS = 262144,
};
union {
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index c72795262b2d..fca3fe4519c0 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -82,6 +82,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;
@@ -149,6 +155,20 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
actions.write_flag_pointers["VERTEX"] = &uses_vertex;
actions.write_flag_pointers["POSITION"] = &uses_position;
+ actions.stencil_mode_values["read"] = Pair(&stencil_readi, STENCIL_FLAG_READ);
+ actions.stencil_mode_values["write"] = Pair(&stencil_writei, STENCIL_FLAG_WRITE);
+ actions.stencil_mode_values["write_depth_fail"] = Pair(&stencil_write_depth_faili, STENCIL_FLAG_WRITE_DEPTH_FAIL);
+
+ actions.stencil_mode_values["compare_less"] = Pair(&stencil_comparei, STENCIL_COMPARE_LESS);
+ actions.stencil_mode_values["compare_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_EQUAL);
+ actions.stencil_mode_values["compare_less_or_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_LESS_OR_EQUAL);
+ actions.stencil_mode_values["compare_greater"] = Pair(&stencil_comparei, STENCIL_COMPARE_GREATER);
+ actions.stencil_mode_values["compare_not_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_NOT_EQUAL);
+ actions.stencil_mode_values["compare_greater_or_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_GREATER_OR_EQUAL);
+ actions.stencil_mode_values["compare_always"] = Pair(&stencil_comparei, STENCIL_COMPARE_ALWAYS);
+
+ actions.stencil_reference = &stencil_referencei;
+
actions.uniforms = &uniforms;
SceneShaderForwardClustered *shader_singleton = (SceneShaderForwardClustered *)SceneShaderForwardClustered::singleton;
@@ -172,6 +192,11 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
uses_normal |= uses_normal_map;
uses_tangent |= uses_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");
@@ -298,7 +323,48 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
depth_stencil_state.depth_compare_operator = depth_function_rd_table[depth_function];
}
- bool depth_pre_pass_enabled = bool(GLOBAL_GET("rendering/driver/depth_prepass/enable"));
+ depth_stencil_state.enable_stencil = stencil_enabled;
+ if (stencil_enabled) {
+ 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 = !stencil_enabled && bool(GLOBAL_GET("rendering/driver/depth_prepass/enable"));
for (int i = 0; i < CULL_VARIANT_MAX; i++) {
RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
index 1af184a2d7d4..ebe7af666c5f 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
@@ -153,6 +153,23 @@ class SceneShaderForwardClustered {
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.
+ };
+
bool valid = false;
RID version;
uint64_t vertex_input_mask = 0;
@@ -198,6 +215,11 @@ class SceneShaderForwardClustered {
bool uses_screen_texture_mipmaps = false;
Cull cull_mode = CULL_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;
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index fc28fd4ef0f6..7d73b11f243d 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -83,6 +83,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;
@@ -149,6 +155,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(&stencil_readi, STENCIL_FLAG_READ);
+ actions.stencil_mode_values["write"] = Pair(&stencil_writei, STENCIL_FLAG_WRITE);
+ actions.stencil_mode_values["write_depth_fail"] = Pair(&stencil_write_depth_faili, STENCIL_FLAG_WRITE_DEPTH_FAIL);
+
+ actions.stencil_mode_values["compare_less"] = Pair(&stencil_comparei, STENCIL_COMPARE_LESS);
+ actions.stencil_mode_values["compare_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_EQUAL);
+ actions.stencil_mode_values["compare_less_or_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_LESS_OR_EQUAL);
+ actions.stencil_mode_values["compare_greater"] = Pair(&stencil_comparei, STENCIL_COMPARE_GREATER);
+ actions.stencil_mode_values["compare_not_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_NOT_EQUAL);
+ actions.stencil_mode_values["compare_greater_or_equal"] = Pair(&stencil_comparei, STENCIL_COMPARE_GREATER_OR_EQUAL);
+ actions.stencil_mode_values["compare_always"] = Pair(&stencil_comparei, STENCIL_COMPARE_ALWAYS);
+
+ actions.stencil_reference = &stencil_referencei;
+
actions.uniforms = &uniforms;
SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
@@ -172,6 +192,11 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
uses_normal |= uses_normal_map;
uses_tangent |= uses_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("Sub-surface scattering is only available when using the Forward+ rendering backend.");
@@ -309,6 +334,47 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
depth_stencil_state.depth_compare_operator = depth_function_rd_table[depth_function];
}
+ depth_stencil_state.enable_stencil = stencil_enabled;
+ if (stencil_enabled) {
+ 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;
+ }
+
for (int i = 0; i < CULL_VARIANT_MAX; i++) {
RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
{ RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_FRONT, RD::POLYGON_CULL_BACK },
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
index e6ff7e547e90..d9972a8585ab 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
@@ -108,6 +108,23 @@ class SceneShaderForwardMobile {
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.
+ };
+
bool valid = false;
RID version;
uint64_t vertex_input_mask = 0;
@@ -150,6 +167,11 @@ class SceneShaderForwardMobile {
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;
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index a4ee33ecc0a2..991f019b51be 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/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 &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++) {
@@ -1459,6 +1476,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;
diff --git a/servers/rendering/shader_compiler.h b/servers/rendering/shader_compiler.h
index 66106d7eb734..39eea01b55b0 100644
--- a/servers/rendering/shader_compiler.h
+++ b/servers/rendering/shader_compiler.h
@@ -51,6 +51,8 @@ class ShaderCompiler {
HashMap render_mode_flags;
HashMap usage_flag_pointers;
HashMap write_flag_pointers;
+ HashMap> stencil_mode_values;
+ int *stencil_reference;
HashMap *uniforms = nullptr;
};
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index e35fde406f7d..72254a139698 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -334,6 +334,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
@@ -3697,7 +3698,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, ConstantNode::Value *p_value) {
@@ -8139,7 +8140,7 @@ Error ShaderLanguage::_validate_precision(DataType p_type, DataPrecision p_preci
return OK;
}
-Error ShaderLanguage::_parse_shader(const HashMap &p_functions, const Vector &p_render_modes, const HashSet &p_shader_types) {
+Error ShaderLanguage::_parse_shader(const HashMap &p_functions, const Vector &p_render_modes, const Vector &p_stencil_modes, const HashSet &p_shader_types) {
Token tk;
TkPos prev_pos;
Token next;
@@ -8208,7 +8209,8 @@ Error ShaderLanguage::_parse_shader(const HashMap &p_f
const FunctionInfo &constants = p_functions.has("constants") ? p_functions["constants"] : FunctionInfo();
- HashMap defined_modes;
+ HashMap defined_render_modes;
+ HashMap defined_stencil_modes;
while (tk.type != TK_EOF) {
switch (tk.type) {
@@ -8217,83 +8219,64 @@ Error ShaderLanguage::_parse_shader(const HashMap &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 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) {
@@ -10007,6 +9990,112 @@ 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 &p_modes, HashMap &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 ¤t_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 modes = p_is_stencil ? ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(i)) : ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i));
+
+ for (int j = 0; j < modes.size(); j++) {
+ const ModeInfo &info = modes[j];
+ 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 (int i = 0; i < p_modes.size(); i++) {
+ const ModeInfo &info = p_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;
+
+ 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;
@@ -10156,7 +10245,7 @@ Error ShaderLanguage::compile(const String &p_code, const ShaderCompileInfo &p_i
nodes = nullptr;
shader = alloc_node();
- 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) {
@@ -10181,7 +10270,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();
- _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.
@@ -10282,6 +10371,71 @@ 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 modes = ShaderTypes::get_singleton()->get_stencil_modes(RenderingServer::ShaderMode(i));
+
+ for (int j = 0; j < modes.size(); j++) {
+ const ModeInfo &info = modes[j];
+
+ if (!info.options.is_empty()) {
+ bool found = false;
+
+ for (int k = 0; k < info.options.size(); k++) {
+ if (shader->stencil_modes.has(String(info.name) + "_" + String(info.options[k]))) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ for (int k = 0; k < info.options.size(); k++) {
+ ScriptLanguage::CodeCompletionOption option(String(info.name) + "_" + String(info.options[k]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ r_options->push_back(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 (int i = 0; i < p_info.stencil_modes.size(); i++) {
+ const ModeInfo &info = p_info.stencil_modes[i];
+
+ if (!info.options.is_empty()) {
+ bool found = false;
+
+ for (int j = 0; j < info.options.size(); j++) {
+ if (shader->stencil_modes.has(String(info.name) + "_" + String(info.options[j]))) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ for (int j = 0; j < info.options.size(); j++) {
+ ScriptLanguage::CodeCompletionOption option(String(info.name) + "_" + String(info.options[j]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ r_options->push_back(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;
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index d8d7cc197bfc..696ee66db4a9 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -162,6 +162,7 @@ class ShaderLanguage {
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,
@@ -713,6 +714,8 @@ class ShaderLanguage {
HashMap structs;
HashMap functions;
Vector render_modes;
+ Vector stencil_modes;
+ int stencil_reference = -1;
Vector vfunctions;
Vector vconstants;
@@ -745,6 +748,7 @@ class ShaderLanguage {
COMPLETION_NONE,
COMPLETION_SHADER_TYPE,
COMPLETION_RENDER_MODE,
+ COMPLETION_STENCIL_MODE,
COMPLETION_MAIN_FUNCTION,
COMPLETION_IDENTIFIER,
COMPLETION_FUNCTION_CALL,
@@ -1135,11 +1139,13 @@ class ShaderLanguage {
String _get_shader_type_list(const HashSet &p_shader_types) const;
String _get_qualifier_str(ArgumentQualifier p_qualifier) const;
- Error _parse_shader(const HashMap &p_functions, const Vector &p_render_modes, const HashSet &p_shader_types);
+ Error _parse_shader(const HashMap &p_functions, const Vector &p_render_modes, const Vector &p_stencil_modes, const HashSet &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 &p_modes, HashMap &r_defined_modes);
+
public:
#ifdef DEBUG_ENABLED
List::Element *get_warnings_ptr();
@@ -1161,6 +1167,7 @@ class ShaderLanguage {
struct ShaderCompileInfo {
HashMap functions;
Vector render_modes;
+ Vector stencil_modes;
VaryingFunctionNames varying_function_names = VaryingFunctionNames();
HashSet shader_types;
GlobalShaderUniformGetTypeFunc global_shader_uniform_type_func = nullptr;
diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp
index a3f92b8c5303..6bfd05e0f5fa 100644
--- a/servers/rendering/shader_types.cpp
+++ b/servers/rendering/shader_types.cpp
@@ -39,6 +39,10 @@ const Vector &ShaderTypes::get_modes(RS::ShaderMode p_
return shader_modes[p_mode].modes;
}
+const Vector &ShaderTypes::get_stencil_modes(RS::ShaderMode p_mode) const {
+ return shader_modes[p_mode].stencil_modes;
+}
+
const HashSet &ShaderTypes::get_types() const {
return shader_types;
}
@@ -232,6 +236,10 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("alpha_to_coverage_and_one") });
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].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"), { "less", "equal", "less_or_equal", "greater", "not_equal", "greater_or_equal", "always" } });
}
/************ CANVAS ITEM **************************/
diff --git a/servers/rendering/shader_types.h b/servers/rendering/shader_types.h
index 5a7423b6618d..f250329e5a29 100644
--- a/servers/rendering/shader_types.h
+++ b/servers/rendering/shader_types.h
@@ -39,6 +39,7 @@ class ShaderTypes {
struct Type {
HashMap functions;
Vector modes;
+ Vector stencil_modes;
};
HashMap shader_modes;
@@ -53,6 +54,7 @@ class ShaderTypes {
const HashMap &get_functions(RS::ShaderMode p_mode) const;
const Vector &get_modes(RS::ShaderMode p_mode) const;
+ const Vector &get_stencil_modes(RS::ShaderMode p_mode) const;
const HashSet &get_types() const;
const List &get_types_list() const;