Good enough parallax POM – Light tones go down – Version 2
The cheap algorithm makes it run at the minimum or zero speed when closer to the horizon, and at the maximum speed when looking down.
Light tones are ideal for patching mechanisms on walls.
If you use the option that lowers light tones, you’d first have to do the “linear invert”.
Limiting from the “Levels” in GIMP gives me more room to maneuver in the shader.
The height map has several fetch. Using a single shader for the BC4 heightmap and at a lower resolution means it uses less bandwidth.
Delete the light lines and hard shadows if they are not used, and enable the normal map by default.
Test Godot 4.5.
Shader code
shader_type spatial;//Walt_GD
render_mode cull_back, diffuse_burley;
// --- Separate textures ---
uniform sampler2D texture_RGB : source_color, repeat_enable; //2k texture compressed in Comprenator AMD: KTX2 BC7 Quality 0.49 or Gimp DDS BC1
uniform sampler2D texture_Normal : hint_normal, repeat_enable;//2k texture compressed in Comprenator AMD: KTX2 BC5 Quality 0.49 or Gimp DDS BC5
uniform sampler2D texture_height_r : hint_default_black, repeat_enable; // 512 texture compressed in Comprenator AMD: KTX2 BC4 Quality 0.49 or Gimp DDS BC4. Black would be the surface, light tones go down.
uniform float parallax_height_scale : hint_range(0.0, 1.5,0.005) = 0.05;
uniform int min_steps = 4;
uniform int max_steps = 8;
uniform int binary_search_steps = 3; // Optional refinement, PC/Console: 4–8, Mobile/Low VRAM: 2
uniform float normal_depth : hint_range(0.0, 1.0, 0.05) = 0.25;
uniform float tiles : hint_range(1.0, 22.0,0.25) = 16.0;
uniform float light_pom : hint_range(0.0, 0.55, 0.05) = 0.5; // more is less
uniform float shadow_pom : hint_range(0.0, 1.5, 0.05) = 1.0;
void vertex() {
}
vec2 parallax_mapping(vec3 view_dir_tangent, vec2 tex_coords) {
float steps_f = mix(float(min_steps), float(max_steps), abs(view_dir_tangent.z));
int steps = max(1, int(round(steps_f)));
float layer_depth = 1.0 / float(steps);
vec2 step_uv = view_dir_tangent.xy * parallax_height_scale * layer_depth;
vec2 current_uv = tex_coords;
vec2 prev_uv = tex_coords;
float current_depth = 0.0;
float prev_depth = 0.0;
bool crossed = false;
// Marching --- Explicit Branching ---
for (int i = 0; i <= steps; i++) {
float h = texture(texture_height_r, current_uv).r; // channel R only
if (current_depth > h) {
crossed = true;
break;
}
prev_uv = current_uv;
prev_depth = current_depth;
current_uv += step_uv; // Light or dark tones
current_depth += layer_depth;
}
if (!crossed) return current_uv;
// Binary Search opcional
vec2 a_uv = prev_uv; float a_depth = prev_depth;
vec2 b_uv = current_uv; float b_depth = current_depth;
for (int k = 0; k < binary_search_steps; k++) {
vec2 mid_uv = 0.5 * (a_uv + b_uv);
float mid_depth = 0.5 * (a_depth + b_depth);
float h_mid = texture(texture_height_r, mid_uv).r;
if (mid_depth > h_mid) {
b_uv = mid_uv; b_depth = mid_depth;
} else {
a_uv = mid_uv; a_depth = mid_depth;
}
}
return 0.5 * (a_uv + b_uv);
}
void fragment() {
// TBN with T-flip and tangent step with transpose
mat3 tbn_matrix = mat3(-TANGENT, BINORMAL, NORMAL);
vec3 view_dir_tangent = normalize(transpose(tbn_matrix) * VIEW);
// UV POM
vec2 corrected_uvs = parallax_mapping(view_dir_tangent, UV * tiles);
// ALBEDO
mediump vec3 rgb_pack = texture(texture_RGB, corrected_uvs).rgb;
ALBEDO = rgb_pack;
// NORMAL_MAP
mediump vec2 xy_pack = texture(texture_Normal, corrected_uvs).xy;
//NORMAL_MAP = vec3(xy_pack.xy, 1.0);// Enable if not in use: Hard Light and Shadows
mediump vec2 LSpom = (step (vec2(light_pom),xy_pack.xy) - shadow_pom);// Optional Hard Light and Shadows
NORMAL_MAP = vec3 ( min(NORMAL_MAP.xy + LSpom,0.0),1.0); // Optional Hard Light and Shadows
NORMAL_MAP_DEPTH = normal_depth;
ROUGHNESS = 1.0;
SPECULAR = 0.5;
}




