Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/classes/BaseMaterial3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@
<member name="grow_amount" type="float" setter="set_grow" getter="get_grow" default="0.0">
Grows object vertices in the direction of their normals. Only effective if [member grow] is [code]true[/code].
</member>
<member name="heightmap_correct_shadow_receive" type="bool" setter="set_heightmap_deep_parallax_correct_shadow_receive" getter="is_heightmap_deep_parallax_correcting_shadow_receive">
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.
</member>
<member name="heightmap_deep_parallax" type="bool" setter="set_heightmap_deep_parallax" getter="is_heightmap_deep_parallax_enabled" default="false">
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.
</member>
Expand Down Expand Up @@ -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 &gt; Auto &gt; 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.
</member>
<member name="heightmap_trim_edges" type="bool" setter="set_heightmap_deep_parallax_trim_edges" getter="is_heightmap_deep_parallax_trimming_edges">
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.
</member>
<member name="heightmap_write_depth" type="bool" setter="set_heightmap_deep_parallax_write_depth" getter="is_heightmap_deep_parallax_writing_depth">
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.
</member>
<member name="metallic" type="float" setter="set_metallic" getter="get_metallic" default="0.0">
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].
</member>
Expand Down
195 changes: 160 additions & 35 deletions scene/resources/material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {)";
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions scene/resources/material.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down