diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml index 20cea106cdbc..08f969fd8067 100644 --- a/doc/classes/BaseMaterial3D.xml +++ b/doc/classes/BaseMaterial3D.xml @@ -244,6 +244,10 @@ Grows object vertices in the direction of their normals. Only effective if [member grow] is [code]true[/code]. + + If [code]true[/code], makes the material receive shadows at the correct height according to the deep parallax effect. This means the silhouette from the received shadows will match the material's shape. This also allows the material to receive self-shadowing from lights, which can be visible at grazing angles if the shadow is sharp enough (this works best with [OmniLight3D] and [SpotLight3D]). This internally makes the deep parallax effect write to [code]LIGHT_VERTEX[/code] in the shader. Enabling this has a small performance impact. + [b]Note:[/b] Enabling this can result in self-shadowing artifacts if the lights have a shadow bias that is too low, or if the material has a [member heightmap_scale] that is too high. Enabling [member heightmap_write_depth] at the same time can alleviate those self-shadowing artifacts, with the associated performance cost of that feature. + If [code]true[/code], uses parallax occlusion mapping to represent depth in the material instead of simple offset mapping (see [member heightmap_enabled]). This results in a more convincing depth effect, but is much more expensive on the GPU. Only enable this on materials where it makes a significant visual difference. @@ -278,6 +282,15 @@ For best results, the texture should be normalized (with [member heightmap_scale] reduced to compensate). In [url=https://gimp.org]GIMP[/url], this can be done using [b]Colors > Auto > Equalize[/b]. If the texture only uses a small part of its available range, the parallax effect may look strange, especially when the camera moves. [b]Note:[/b] To reduce memory usage and improve loading times, you may be able to use a lower-resolution heightmap texture as most heightmaps are only comprised of low-frequency data. + + If [code]true[/code], makes the material able to change its silhouette according to the deep parallax effect. shadows at the correct height according to the deep parallax effect. This means the silhouette from the received shadows will match the material's shape. This internally makes the deep parallax effect write to [code]LIGHT_VERTEX[/code] in the shader. See also [member heightmap_write_depth]. + [b]Note:[/b] Enabling this can result in artifacts depending on the mesh's UV layout. This effect may not look correct on all meshes as a result. + [b]Note:[/b] Enabling this prevents the depth prepass from working, which has a moderate performance impact. + + + If [code]true[/code], makes the material's silhouette able to correctly interact with other meshes in the scene according to the deep parallax effect. This allows the material to better blend in with other materials in the scene, regardless of whether the other material has deep parallax enabled. This internally makes the deep parallax effect write to [code]DEPTH[/code] in the shader. See also [member heightmap_trim_edges]. + [b]Note:[/b] Enabling this enables depth writing and prevents the depth prepass from working, which has a significant performance impact. + A high value makes the material appear more like a metal. Non-metals use their albedo as the diffuse color and add diffuse to the specular reflection. With non-metals, the reflection appears on top of the albedo color. Metals use their albedo as a multiplier to the specular reflection and set the diffuse color to black resulting in a tinted reflection. Materials work better when fully metal or fully non-metal, values between [code]0[/code] and [code]1[/code] should only be used for blending between metal and non-metal sections. To alter the amount of reflection use [member roughness]. diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 8141491d3e0d..44c77def8e74 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -1507,6 +1507,18 @@ vec4 triplanar_texture(sampler2D p_sampler, vec3 p_weights, vec3 p_triplanar_pos )"; } + if (features[FEATURE_HEIGHT_MAPPING]) { + code += R"( +vec4 height_texture(sampler2D p_sampler, vec2 uv) { +)"; + if (flags[FLAG_INVERT_HEIGHTMAP]) { + code += " return texture(p_sampler, uv);\n"; + } else { + code += " return 1.0 - texture(p_sampler, uv);\n"; + } + code += "}\n"; + } + // Generate fragment shader. code += R"( void fragment() {)"; @@ -1554,51 +1566,105 @@ void fragment() {)"; // Multiply the heightmap scale by 0.01 to improve heightmap scale usability. code += R"( // Height Deep Parallax: Enabled - float num_layers = mix(float(heightmap_max_layers), float(heightmap_min_layers), abs(dot(vec3(0.0, 0.0, 1.0), view_dir))); - float layer_depth = 1.0 / num_layers; - float current_layer_depth = 0.0; - vec2 p = view_dir.xy * heightmap_scale * 0.01; - vec2 delta = p / num_layers; + + vec3 view_direction = normalize(VIEW); + mat3 tangent_basis = mat3( + TANGENT, + -BINORMAL, + NORMAL); + + vec3 tangent_view_dir = transpose(tangent_basis) * view_direction; + vec2 ofs = base_uv; -)"; - if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += " float depth = texture(texture_heightmap, ofs).r;\n"; - } else { - code += " float depth = 1.0 - texture(texture_heightmap, ofs).r;\n"; + vec2 final_tex_coords; + float offset; + { + const float HEIGHT_SCALE_FACTOR = 0.01; + + float num_layers = mix(float(heightmap_max_layers), float(heightmap_min_layers), abs(dot(vec3(0.0, 0.0, 1.0), tangent_view_dir))); + // Calculate the size of each layer. + float layer_depth = 1.0 / num_layers; + // Depth of current layer. + float current_layer_depth = 0.0; + // The amount to shift the texture coordinates per layer (from vector P). + vec2 P = tangent_view_dir.xy / tangent_view_dir.z * heightmap_scale * HEIGHT_SCALE_FACTOR; + vec2 delta_tex_coords = P / num_layers * 0.5; + + // Get initial values. + vec2 current_tex_coords = ofs; + float current_depth_map_value = height_texture(texture_heightmap, current_tex_coords).r; + + while (current_layer_depth < current_depth_map_value) { + // Shift texture coordinates along direction of P. + current_tex_coords -= delta_tex_coords; + // Get depthmap value at current texture coordinates. + current_depth_map_value = height_texture(texture_heightmap, current_tex_coords).r; + // Get depth of next layer. + current_layer_depth += layer_depth; } - code += R"( - float current_depth = 0.0; - while (current_depth < depth) { - ofs -= delta; + + // Get texture coordinates before collision (reverse operations). + vec2 prev_tex_coords = current_tex_coords + delta_tex_coords; + + // Get depth after and before collision for linear interpolation. + float after_depth = current_depth_map_value - current_layer_depth; + float before_depth = height_texture(texture_heightmap, prev_tex_coords).r - current_layer_depth + layer_depth; + + // Interpolate texture coordinates to make low layer counts look smoother. + float weight = after_depth / (after_depth - before_depth); + + offset = current_layer_depth / abs(dot(vec3(0.0, 0.0, 1.0), tangent_view_dir)) * heightmap_scale * HEIGHT_SCALE_FACTOR; + ofs = prev_tex_coords * weight + current_tex_coords * (1.0 - weight); + } )"; - if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += " depth = texture(texture_heightmap, ofs).r;\n"; - } else { - code += " depth = 1.0 - texture(texture_heightmap, ofs).r;\n"; + if (heightmap_parallax_trim_edges) { + code += R"( + + // Height Deep Parallax Trim Edges: Enabled + // Discard if outside UV bounds to make depth visible on the mesh's edges. + // We undo the UV1 scale/offset here so that texture repeat can work. + vec2 ofs_unscaled = ofs / uv1_scale.xy - uv1_offset.xy; + if (ofs_unscaled.x > 1.0 || ofs_unscaled.y > 1.0 || ofs_unscaled.x < 0.0 || ofs_unscaled.y < 0.0) { + discard; + })"; } + code += R"( - current_depth += layer_depth; - } + vec4 view_position = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, FRAGCOORD.z, 1.0); + view_position.xyz /= view_position.w; + view_position.xyz -= offset * view_direction; - vec2 prev_ofs = ofs + delta; - float after_depth = depth - current_depth; + vec4 ndc_position = PROJECTION_MATRIX * vec4(view_position.xyz, 1.0); + ndc_position.xyz /= ndc_position.w; +)"; + + if (heightmap_parallax_correct_shadow_receive) { + code += R"( + // Height Deep Parallax Correct Shadow Receive: Enabled + LIGHT_VERTEX -= offset * view_direction; )"; - if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += " float before_depth = texture(texture_heightmap, prev_ofs).r - current_depth + layer_depth;\n"; - } else { - code += " float before_depth = (1.0 - texture(texture_heightmap, prev_ofs).r) - current_depth + layer_depth;\n"; } - code += R"( - float weight = after_depth / (after_depth - before_depth); - ofs = mix(ofs, prev_ofs, weight); + + if (heightmap_parallax_write_depth) { + code += R"( + // Height Deep Parallax Write Depth: Enabled +)"; + if (!heightmap_parallax_trim_edges) { + code += R"( + if (false) { + // Calling `discard` anywhere prevents the depth prepass from running, + // as it breaks writing to depth. + discard; + } )"; + } - } else { - if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += " float depth = texture(texture_heightmap, base_uv).r;\n"; - } else { - code += " float depth = 1.0 - texture(texture_heightmap, base_uv).r;\n"; + code += R"( + DEPTH = ndc_position.z; +)"; } + } else { + code += " float depth = height_texture(texture_heightmap, base_uv).r;\n"; // Use offset limiting to improve the appearance of non-deep parallax. // This reduces the impression of depth, but avoids visible warping in the distance. // Multiply the heightmap scale by 0.01 to improve heightmap scale usability. @@ -2660,7 +2726,12 @@ void BaseMaterial3D::_validate_property(PropertyInfo &p_property) const { p_property.usage = PROPERTY_USAGE_NONE; } - if ((p_property.name == "heightmap_min_layers" || p_property.name == "heightmap_max_layers") && !deep_parallax) { + if ((p_property.name == "heightmap_min_layers" || + p_property.name == "heightmap_max_layers" || + p_property.name == "heightmap_correct_shadow_receive" || + p_property.name == "heightmap_write_depth" || + p_property.name == "heightmap_trim_edges") && + !deep_parallax) { p_property.usage = PROPERTY_USAGE_NONE; } @@ -2906,6 +2977,45 @@ bool BaseMaterial3D::get_heightmap_deep_parallax_flip_binormal() const { return heightmap_parallax_flip_binormal; } +void BaseMaterial3D::set_heightmap_deep_parallax_correct_shadow_receive(bool p_enable) { + if (heightmap_parallax_correct_shadow_receive == p_enable) { + return; + } + + heightmap_parallax_correct_shadow_receive = p_enable; + _queue_shader_change(); +} + +bool BaseMaterial3D::is_heightmap_deep_parallax_correcting_shadow_receive() const { + return heightmap_parallax_correct_shadow_receive; +} + +void BaseMaterial3D::set_heightmap_deep_parallax_write_depth(bool p_enable) { + if (heightmap_parallax_write_depth == p_enable) { + return; + } + + heightmap_parallax_write_depth = p_enable; + _queue_shader_change(); +} + +bool BaseMaterial3D::is_heightmap_deep_parallax_writing_depth() const { + return heightmap_parallax_write_depth; +} + +void BaseMaterial3D::set_heightmap_deep_parallax_trim_edges(bool p_enable) { + if (heightmap_parallax_trim_edges == p_enable) { + return; + } + + heightmap_parallax_trim_edges = p_enable; + _queue_shader_change(); +} + +bool BaseMaterial3D::is_heightmap_deep_parallax_trimming_edges() const { + return heightmap_parallax_trim_edges; +} + void BaseMaterial3D::set_grow_enabled(bool p_enable) { grow_enabled = p_enable; _queue_shader_change(); @@ -3513,6 +3623,15 @@ void BaseMaterial3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_heightmap_deep_parallax_flip_binormal", "flip"), &BaseMaterial3D::set_heightmap_deep_parallax_flip_binormal); ClassDB::bind_method(D_METHOD("get_heightmap_deep_parallax_flip_binormal"), &BaseMaterial3D::get_heightmap_deep_parallax_flip_binormal); + ClassDB::bind_method(D_METHOD("set_heightmap_deep_parallax_correct_shadow_receive", "enable"), &BaseMaterial3D::set_heightmap_deep_parallax_correct_shadow_receive); + ClassDB::bind_method(D_METHOD("is_heightmap_deep_parallax_correcting_shadow_receive"), &BaseMaterial3D::is_heightmap_deep_parallax_correcting_shadow_receive); + + ClassDB::bind_method(D_METHOD("set_heightmap_deep_parallax_write_depth", "enable"), &BaseMaterial3D::set_heightmap_deep_parallax_write_depth); + ClassDB::bind_method(D_METHOD("is_heightmap_deep_parallax_writing_depth"), &BaseMaterial3D::is_heightmap_deep_parallax_writing_depth); + + ClassDB::bind_method(D_METHOD("set_heightmap_deep_parallax_trim_edges", "enable"), &BaseMaterial3D::set_heightmap_deep_parallax_trim_edges); + ClassDB::bind_method(D_METHOD("is_heightmap_deep_parallax_trimming_edges"), &BaseMaterial3D::is_heightmap_deep_parallax_trimming_edges); + ClassDB::bind_method(D_METHOD("set_grow", "amount"), &BaseMaterial3D::set_grow); ClassDB::bind_method(D_METHOD("get_grow"), &BaseMaterial3D::get_grow); @@ -3683,6 +3802,9 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "heightmap_max_layers", PROPERTY_HINT_RANGE, "1,64,1"), "set_heightmap_deep_parallax_max_layers", "get_heightmap_deep_parallax_max_layers"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "heightmap_flip_tangent"), "set_heightmap_deep_parallax_flip_tangent", "get_heightmap_deep_parallax_flip_tangent"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "heightmap_flip_binormal"), "set_heightmap_deep_parallax_flip_binormal", "get_heightmap_deep_parallax_flip_binormal"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "heightmap_correct_shadow_receive"), "set_heightmap_deep_parallax_correct_shadow_receive", "is_heightmap_deep_parallax_correcting_shadow_receive"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "heightmap_write_depth"), "set_heightmap_deep_parallax_write_depth", "is_heightmap_deep_parallax_writing_depth"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "heightmap_trim_edges"), "set_heightmap_deep_parallax_trim_edges", "is_heightmap_deep_parallax_trimming_edges"); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "heightmap_texture", PROPERTY_HINT_RESOURCE_TYPE, Texture2D::get_class_static()), "set_texture", "get_texture", TEXTURE_HEIGHTMAP); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "heightmap_flip_texture"), "set_flag", "get_flag", FLAG_INVERT_HEIGHTMAP); @@ -3999,6 +4121,9 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : set_heightmap_deep_parallax_min_layers(8); set_heightmap_deep_parallax_max_layers(32); set_heightmap_deep_parallax_flip_tangent(false); //also sets binormal + set_heightmap_deep_parallax_correct_shadow_receive(true); + set_heightmap_deep_parallax_write_depth(false); + set_heightmap_deep_parallax_trim_edges(false); set_z_clip_scale(1.0); set_fov_override(75.0); diff --git a/scene/resources/material.h b/scene/resources/material.h index 2397824f6f1c..c97e802f62c8 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -383,6 +383,9 @@ class BaseMaterial3D : public Material { // booleans uint64_t invalid_key : 1; uint64_t deep_parallax : 1; + uint64_t heightmap_parallax_correct_shadow_receive : 1; + uint64_t heightmap_parallax_write_depth : 1; + uint64_t heightmap_parallax_trim_edges : 1; uint64_t grow : 1; uint64_t proximity_fade : 1; uint64_t orm : 1; @@ -434,6 +437,9 @@ class BaseMaterial3D : public Material { mk.specular_mode = specular_mode; mk.billboard_mode = billboard_mode; mk.deep_parallax = deep_parallax; + mk.heightmap_parallax_correct_shadow_receive = heightmap_parallax_correct_shadow_receive; + mk.heightmap_parallax_write_depth = heightmap_parallax_write_depth; + mk.heightmap_parallax_trim_edges = heightmap_parallax_trim_edges; mk.grow = grow_enabled; mk.proximity_fade = proximity_fade_enabled; mk.distance_fade = distance_fade; @@ -586,6 +592,9 @@ class BaseMaterial3D : public Material { int deep_parallax_max_layers = 0; bool heightmap_parallax_flip_tangent = false; bool heightmap_parallax_flip_binormal = false; + bool heightmap_parallax_correct_shadow_receive = true; + bool heightmap_parallax_write_depth = false; + bool heightmap_parallax_trim_edges = false; bool proximity_fade_enabled = false; float proximity_fade_distance = 0.0f; @@ -702,6 +711,15 @@ class BaseMaterial3D : public Material { void set_heightmap_deep_parallax_flip_binormal(bool p_flip); bool get_heightmap_deep_parallax_flip_binormal() const; + void set_heightmap_deep_parallax_correct_shadow_receive(bool p_enable); + bool is_heightmap_deep_parallax_correcting_shadow_receive() const; + + void set_heightmap_deep_parallax_write_depth(bool p_enable); + bool is_heightmap_deep_parallax_writing_depth() const; + + void set_heightmap_deep_parallax_trim_edges(bool p_enable); + bool is_heightmap_deep_parallax_trimming_edges() const; + void set_subsurface_scattering_strength(float p_subsurface_scattering_strength); float get_subsurface_scattering_strength() const;