diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml
index 1fdbe5ce4c1b..4d6516aeadcb 100644
--- a/doc/classes/BaseMaterial3D.xml
+++ b/doc/classes/BaseMaterial3D.xml
@@ -150,6 +150,13 @@
Determines when depth rendering takes place. See [enum DepthDrawMode]. See also [member transparency].
+
+ If [code]true[/code], offsets the per-pixel depth by the amount specified in [member depth_offset_amount]. This can be used to force the material to render in front of (or behind) other meshes that are at the exact same position, which can prevent Z-fighting artifacts. This can be useful for mesh-based decals, or when rendering meshes as overlays to other meshes.
+ [b]Note:[/b] Enabling depth offset forces the material to go through the transparent pipeline, which has a performance cost and can result in transparency sorting issues.
+
+
+ The depth offset to apply when [member depth_offset] is [code]true[/code]. Negative values will push the pixels towards the camera, while positive values will push the pixels away from the camera. Typical values are between [code]-0.01[/code] and [code]0.01[/code]. If you still notice Z-fighting after adjusting the depth offset, try using a lower value (if negative) or a higher value (if positive).
+
Texture that specifies the color of the detail overlay. [member detail_albedo]'s alpha channel is used as a mask, even when the material is opaque. To use a dedicated texture as a mask, see [member detail_mask].
[b]Note:[/b] [member detail_albedo] is [i]not[/i] modulated by [member albedo_color].
diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
index b92abbcf79c0..fb3b0ddaddf8 100644
--- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
@@ -78,6 +78,11 @@ void CollisionShape3DGizmoPlugin::create_collision_material(const String &p_name
material->set_albedo(color);
material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+
+ // Prevent collision shape triangles that overlap with geometry from Z-fighting.
+ material->set_depth_offset_enabled(true);
+ material->set_depth_offset(-0.005);
+
material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1);
material->set_cull_mode(StandardMaterial3D::CULL_BACK);
material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
diff --git a/scene/resources/3d/shape_3d.cpp b/scene/resources/3d/shape_3d.cpp
index a1ee7b85dd53..c459ba44440e 100644
--- a/scene/resources/3d/shape_3d.cpp
+++ b/scene/resources/3d/shape_3d.cpp
@@ -147,6 +147,11 @@ Ref Shape3D::get_debug_collision_material() {
material->set_albedo(Color(1.0, 1.0, 1.0));
material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+
+ // Prevent collision shape triangles that overlap with geometry from Z-fighting.
+ material->set_depth_offset_enabled(true);
+ material->set_depth_offset(-0.005);
+
material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1);
material->set_cull_mode(StandardMaterial3D::CULL_BACK);
material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 77ac0569ff35..9a909024ec25 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -620,6 +620,7 @@ void BaseMaterial3D::init_shaders() {
shader_names->heightmap_flip = "heightmap_flip";
shader_names->grow = "grow";
+ shader_names->depth_offset = "depth_offset";
shader_names->ao_light_affect = "ao_light_affect";
@@ -894,6 +895,10 @@ uniform sampler2D texture_albedo : source_color, %s;
code += "uniform float grow : hint_range(-16.0, 16.0, 0.001);\n";
}
+ if (depth_offset_enabled) {
+ code += "uniform float depth_offset : hint_range(-10.0, 10.0, 0.001);\n";
+ }
+
if (proximity_fade_enabled) {
code += "uniform float proximity_fade_distance : hint_range(0.0, 4096.0, 0.01);\n";
}
@@ -1404,6 +1409,21 @@ void fragment() {)";
)";
}
+ if (depth_offset_enabled) {
+ code += R"(
+ // Depth Offset: Enabled
+ // Force transparency on the material (required for depth offset).
+ ALPHA = 1.0;
+ vec4 view_position = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2. - 1.0, FRAGCOORD.z, 1.0);
+ vec3 view_direction = normalize(VIEW);
+ view_position.xyz /= view_position.w;
+ view_position.xyz -= depth_offset * view_direction;
+ vec4 ndc_position = PROJECTION_MATRIX * vec4(view_position.xyz, 1.0);
+ ndc_position.xyz /= ndc_position.w;
+ DEPTH = ndc_position.z;
+)";
+ }
+
if (features[FEATURE_HEIGHT_MAPPING] && flags[FLAG_UV1_USE_TRIPLANAR]) {
// Display both resource name and albedo texture name.
// Materials are often built-in to scenes, so displaying the resource name alone may not be meaningful.
@@ -2414,6 +2434,10 @@ void BaseMaterial3D::_validate_property(PropertyInfo &p_property) const {
_validate_feature("refraction", FEATURE_REFRACTION, p_property);
_validate_feature("detail", FEATURE_DETAIL, p_property);
+ if (p_property.name == "depth_offset_amount" && !depth_offset_enabled) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+
if (p_property.name == "emission_intensity" && !GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
p_property.usage = PROPERTY_USAGE_NONE;
}
@@ -2743,6 +2767,25 @@ float BaseMaterial3D::get_alpha_antialiasing_edge() const {
return alpha_antialiasing_edge;
}
+void BaseMaterial3D::set_depth_offset_enabled(bool p_enabled) {
+ depth_offset_enabled = p_enabled;
+ _queue_shader_change();
+ notify_property_list_changed();
+}
+
+bool BaseMaterial3D::is_depth_offset_enabled() const {
+ return depth_offset_enabled;
+}
+
+void BaseMaterial3D::set_depth_offset(float p_offset) {
+ depth_offset = p_offset;
+ _material_set_param(shader_names->depth_offset, p_offset);
+}
+
+float BaseMaterial3D::get_depth_offset() const {
+ return depth_offset;
+}
+
void BaseMaterial3D::set_grow(float p_grow) {
grow = p_grow;
_material_set_param(shader_names->grow, p_grow);
@@ -2964,6 +3007,12 @@ void BaseMaterial3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alpha_antialiasing_edge", "edge"), &BaseMaterial3D::set_alpha_antialiasing_edge);
ClassDB::bind_method(D_METHOD("get_alpha_antialiasing_edge"), &BaseMaterial3D::get_alpha_antialiasing_edge);
+ ClassDB::bind_method(D_METHOD("set_depth_offset_enabled", "enabled"), &BaseMaterial3D::set_depth_offset_enabled);
+ ClassDB::bind_method(D_METHOD("is_depth_offset_enabled"), &BaseMaterial3D::is_depth_offset_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_depth_offset", "offset"), &BaseMaterial3D::set_depth_offset);
+ ClassDB::bind_method(D_METHOD("get_depth_offset"), &BaseMaterial3D::get_depth_offset);
+
ClassDB::bind_method(D_METHOD("set_shading_mode", "shading_mode"), &BaseMaterial3D::set_shading_mode);
ClassDB::bind_method(D_METHOD("get_shading_mode"), &BaseMaterial3D::get_shading_mode);
@@ -3166,6 +3215,8 @@ void BaseMaterial3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mode", PROPERTY_HINT_ENUM, "Back,Front,Disabled"), "set_cull_mode", "get_cull_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "depth_draw_mode", PROPERTY_HINT_ENUM, "Opaque Only,Always,Never"), "set_depth_draw_mode", "get_depth_draw_mode");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_flag", "get_flag", FLAG_DISABLE_DEPTH_TEST);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "depth_offset"), "set_depth_offset_enabled", "is_depth_offset_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_offset_amount", PROPERTY_HINT_RANGE, "-10,10,0.001,or_less,or_greater"), "set_depth_offset", "get_depth_offset");
ADD_GROUP("Shading", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "shading_mode", PROPERTY_HINT_ENUM, "Unshaded,Per-Pixel,Per-Vertex"), "set_shading_mode", "get_shading_mode");
diff --git a/scene/resources/material.h b/scene/resources/material.h
index 36d325bd33f8..5331cdd811d0 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -339,6 +339,7 @@ class BaseMaterial3D : public Material {
uint64_t invalid_key : 1;
uint64_t deep_parallax : 1;
uint64_t grow : 1;
+ uint64_t depth_offset : 1;
uint64_t proximity_fade : 1;
uint64_t orm : 1;
@@ -389,6 +390,7 @@ class BaseMaterial3D : public Material {
mk.billboard_mode = billboard_mode;
mk.deep_parallax = deep_parallax;
mk.grow = grow_enabled;
+ mk.depth_offset = depth_offset_enabled;
mk.proximity_fade = proximity_fade_enabled;
mk.distance_fade = distance_fade;
mk.emission_op = emission_op;
@@ -444,6 +446,7 @@ class BaseMaterial3D : public Material {
StringName uv1_blend_sharpness;
StringName uv2_blend_sharpness;
StringName grow;
+ StringName depth_offset;
StringName proximity_fade_distance;
StringName msdf_pixel_range;
StringName msdf_outline_size;
@@ -508,6 +511,8 @@ class BaseMaterial3D : public Material {
float alpha_scissor_threshold = 0.0f;
float alpha_hash_scale = 0.0f;
float alpha_antialiasing_edge = 0.0f;
+ bool depth_offset_enabled = false;
+ float depth_offset = 0.0f;
bool grow_enabled = false;
float ao_light_affect = 0.0f;
float grow = 0.0f;
@@ -667,6 +672,12 @@ class BaseMaterial3D : public Material {
void set_alpha_antialiasing_edge(float p_edge);
float get_alpha_antialiasing_edge() const;
+ void set_depth_offset_enabled(bool p_enabled);
+ bool is_depth_offset_enabled() const;
+
+ void set_depth_offset(float p_offset);
+ float get_depth_offset() const;
+
void set_shading_mode(ShadingMode p_shading_mode);
ShadingMode get_shading_mode() const;