Improve preview colors (#15809)

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
Ian Bassi
2026-09-22 20:50:18 -03:00
committed by GitHub
co-authored by Rodrigo Faselli
parent f83bfa17ff
commit d820303a3f
15 changed files with 512 additions and 209 deletions
+53 -29
View File
@@ -7,18 +7,38 @@
uniform sampler2D color_texture;
uniform sampler2D depth_texture;
uniform sampler2D normal_texture;
uniform vec2 inv_tex_size;
uniform float z_near;
uniform float z_far;
uniform bool is_outline;
// The pass has no normal target to read, so the surface normal is reconstructed from the depth
// buffer. inv_projection_matrix unprojects a pixel back into view space and up_view is world +Z
// expressed in view space, which is what tells a top surface from a wall.
uniform mat4 inv_projection_matrix;
uniform vec3 up_view;
varying vec2 tex_coord;
float linearize_depth(float depth)
// Position of the given pixel in view space. Valid under both an orthographic and a perspective
// camera, unlike the depth linearization it replaces.
vec3 view_pos(vec2 uv)
{
float z = depth * 2.0 - 1.0;
return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near));
vec2 c = clamp(uv, vec2(0.0), vec2(1.0));
float d = texture2D(depth_texture, c).r;
vec4 ndc = vec4(c * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
vec4 view = inv_projection_matrix * ndc;
return view.xyz / view.w;
}
// Surface normal at the given pixel, from the forward differences of the reconstructed view
// position. It rings by a pixel across a depth discontinuity, which is acceptable here: the
// normal only weights the occlusion, nothing is shaded with it.
vec3 view_normal(vec2 uv, vec3 p)
{
vec3 px = view_pos(uv + vec2(inv_tex_size.x, 0.0));
vec3 py = view_pos(uv + vec2(0.0, inv_tex_size.y));
vec3 n = cross(px - p, py - p);
float len = length(n);
return (len > 1e-8) ? n / len : vec3(0.0, 0.0, 1.0);
}
void main()
@@ -28,16 +48,21 @@ void main()
return;
}
vec3 base = texture2D(color_texture, tex_coord).rgb;
float depth_center = linearize_depth(texture2D(depth_texture, tex_coord).r);
// Sample normal at current fragment (range: -1 to 1)
vec3 normal_center = texture2D(normal_texture, tex_coord).rgb * 2.0 - 1.0;
// Nothing was drawn here: occluding the background would only darken the gradient, and its
// reconstructed normal is degenerate anyway.
if (texture2D(depth_texture, tex_coord).r >= 0.9999) {
gl_FragColor = vec4(base, 1.0);
return;
}
vec3 center_pos = view_pos(tex_coord);
float depth_center = -center_pos.z;
vec3 normal_center = view_normal(tex_coord, center_pos);
// Calculate how much the surface faces upward
// up_factor = 1.0 for surfaces pointing straight up (0,0,1)
// up_factor = 0.0 for surfaces pointing down or sideways
float up_factor = max(0.0, normal_center.z); // Assuming Z is up axis
// Alternative: if Y is up, use normal_center.y
// up_factor = 1.0 for surfaces pointing straight up, 0.0 for walls and downward faces
float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0);
// Adaptive sampling radius
float radius = mix(2.0, 4.0, depth_center / z_far);
@@ -52,39 +77,38 @@ void main()
offsets[6] = vec2( 0.0, -1.0);
offsets[7] = vec2( 0.707,-0.707);
// Occlusion is a slope, not a depth difference: how far a neighbour rises out of the
// centre's tangent plane over how far away it is. Unlike a raw difference, that sine is
// free of camera distance and zoom, so a crease reads the same from any view.
const float SLOPE_MIN = 0.08; // ~5 degrees, above the depth-buffer noise of a flat surface
const float SLOPE_MAX = 0.60; // ~37 degrees, a full crease
const float SAMPLE_COUNT = 8.0;
float occlusion = 0.0;
int valid_samples = 0;
for (int i = 0; i < 8; ++i) {
vec2 uv = tex_coord + offsets[i] * inv_tex_size * radius;
uv = clamp(uv, vec2(0.001), vec2(0.999));
float sample_depth = linearize_depth(texture2D(depth_texture, uv).r);
float depth_diff = max(0.0, depth_center - sample_depth);
float threshold = 0.015 * (0.5 + depth_center / z_far);
float contribution = smoothstep(0.001, threshold, depth_diff);
vec3 delta = view_pos(uv) - center_pos;
float dist = length(delta);
float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0;
float contribution = smoothstep(SLOPE_MIN, SLOPE_MAX, rise);
float diagonal_weight = 1.0 - abs(offsets[i].x * offsets[i].y) * 0.5;
occlusion += contribution * diagonal_weight;
valid_samples++;
}
if (valid_samples > 0)
occlusion /= float(valid_samples);
occlusion /= SAMPLE_COUNT;
// flatter/top-like surfaces get less darkening
float ao_intensity = 0.55;
float ambient_occlusion = 1.0 - occlusion * ao_intensity;
// Different min values for top vs bottom surfaces
// Different min values for top vs bottom surfaces. The boost that used to follow lifted a
// top surface back to within 2% of unoccluded once up_factor became a real normal rather
// than a colour, which is where the AO went; the floors alone shape the effect now.
float ao_min = mix(0.45, 0.70, up_factor); // Bottom: 0.45, Top: 0.70
ambient_occlusion = clamp(ambient_occlusion, ao_min, 1.0);
// Boost brightness on top surfaces (optional)
float brightness_boost = 1.0 + up_factor * 0.15; // 15% extra brightness on top
ambient_occlusion = pow(ambient_occlusion, 2.2) * brightness_boost;
ambient_occlusion = clamp(ambient_occlusion, 0.45, 1.05);
gl_FragColor = vec4(base * ambient_occlusion, 1.0);
}
}