mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 10:21:00 +00:00
Improve preview colors (#15809)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
co-authored by
Rodrigo Faselli
parent
f83bfa17ff
commit
d820303a3f
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,45 @@
|
||||
#version 140
|
||||
|
||||
/**
|
||||
* SSAO Shader - GLSL 140 version with sharp depth threshold
|
||||
* SSAO Shader - GLSL 140 version with a slope-based occlusion test
|
||||
* Only darkens valleys/concave areas, ignores smooth variations
|
||||
*/
|
||||
|
||||
uniform sampler2D color_texture;
|
||||
uniform sampler2D depth_texture;
|
||||
uniform sampler2D normal_texture;
|
||||
uniform float z_near;
|
||||
uniform vec2 inv_tex_size;
|
||||
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;
|
||||
|
||||
in vec2 tex_coord;
|
||||
out vec4 frag_color;
|
||||
|
||||
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(ivec2 pixel)
|
||||
{
|
||||
float z = depth * 2.0 - 1.0;
|
||||
return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near));
|
||||
ivec2 p = clamp(pixel, ivec2(0), textureSize(depth_texture, 0) - 1);
|
||||
float d = texelFetch(depth_texture, p, 0).r;
|
||||
vec4 ndc = vec4((vec2(p) + 0.5) * inv_tex_size * 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(ivec2 pixel, vec3 p)
|
||||
{
|
||||
vec3 px = view_pos(pixel + ivec2(1, 0));
|
||||
vec3 py = view_pos(pixel + ivec2(0, 1));
|
||||
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,81 +49,72 @@ void main()
|
||||
return;
|
||||
}
|
||||
ivec2 pixel = ivec2(gl_FragCoord.xy);
|
||||
float center_depth = linearize_depth(texelFetch(depth_texture, pixel, 0).r);
|
||||
|
||||
// Sample normal buffer (stored as RGB in 0-1 range, convert to -1 to 1)
|
||||
vec3 normal_center = texelFetch(normal_texture, pixel, 0).rgb * 2.0 - 1.0;
|
||||
normal_center = normalize(normal_center);
|
||||
|
||||
vec3 color = texture(color_texture, tex_coord).rgb;
|
||||
|
||||
// Nothing was drawn here: occluding the background would only darken the gradient, and its
|
||||
// reconstructed normal is degenerate anyway.
|
||||
if (texelFetch(depth_texture, pixel, 0).r >= 0.9999) {
|
||||
frag_color = vec4(color, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 center_pos = view_pos(pixel);
|
||||
float center_depth = -center_pos.z;
|
||||
vec3 normal_center = view_normal(pixel, center_pos);
|
||||
|
||||
// Calculate upward-facing factor (Z-up coordinate system)
|
||||
float up_factor = clamp(normal_center.z * 1.5, 0.0, 1.0);
|
||||
|
||||
float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0);
|
||||
|
||||
// Adaptive radius in pixel space
|
||||
int radius = int(mix(2.0, 4.0, center_depth / z_far));
|
||||
|
||||
// Optimized sampling pattern
|
||||
const ivec2 offsets[12] = ivec2[](
|
||||
const int SAMPLE_COUNT = 12;
|
||||
const ivec2 offsets[SAMPLE_COUNT] = ivec2[](
|
||||
ivec2(1, 0), ivec2(-1, 0), ivec2(0, 1), ivec2(0, -1),
|
||||
ivec2(1, 1), ivec2(-1, 1), ivec2(1, -1), ivec2(-1, -1),
|
||||
ivec2(2, 0), ivec2(-2, 0), ivec2(0, 2), ivec2(0, -2)
|
||||
);
|
||||
|
||||
float occlusion = 0.0;
|
||||
int valid_samples = 0;
|
||||
// Occlusion is a slope, not a depth difference: the sine of the angle a neighbour subtends
|
||||
// above the centre's tangent plane. A raw difference depends on camera distance and zoom,
|
||||
// so no fixed thresholds suit both a 0.2 mm layer step and a 5 mm overhang.
|
||||
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
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
float occlusion = 0.0;
|
||||
|
||||
for (int i = 0; i < SAMPLE_COUNT; i++) {
|
||||
// No edge rejection: view_pos clamps, giving a near-zero delta and no occlusion.
|
||||
// Rejecting one side only would bias the denominator against the other.
|
||||
ivec2 sample_pixel = pixel + offsets[i] * radius;
|
||||
|
||||
if (sample_pixel.x < 0 || sample_pixel.y < 0)
|
||||
continue;
|
||||
|
||||
float sample_depth = linearize_depth(texelFetch(depth_texture, sample_pixel, 0).r);
|
||||
|
||||
// Sample normal at neighbor
|
||||
vec3 normal_sample = texelFetch(normal_texture, sample_pixel, 0).rgb * 2.0 - 1.0;
|
||||
|
||||
// Depth difference (positive if neighbor is closer to camera)
|
||||
float depth_diff = center_depth - sample_depth;
|
||||
|
||||
// Sharp depth threshold ===
|
||||
// Minimum depth difference to consider occlusion (ignores small variations)
|
||||
float threshold_min = 0.008; // Higher = only deep valleys get darkened
|
||||
float threshold_max = 0.04; // Transition range for full occlusion
|
||||
vec3 delta = view_pos(sample_pixel) - center_pos;
|
||||
float dist = length(delta);
|
||||
// How far the neighbour rises towards the viewer out of the centre's tangent plane. A
|
||||
// flat surface gives ~0 whatever its orientation, so this also subsumes the separate
|
||||
// planar test the normals were compared for.
|
||||
float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0;
|
||||
|
||||
float contribution = 0.0;
|
||||
if (depth_diff > threshold_min) {
|
||||
if (rise > SLOPE_MIN) {
|
||||
// Abrupt mapping with power curve
|
||||
contribution = (depth_diff - threshold_min) / (threshold_max - threshold_min);
|
||||
contribution = (rise - SLOPE_MIN) / (SLOPE_MAX - SLOPE_MIN);
|
||||
contribution = clamp(contribution, 0.0, 1.0);
|
||||
contribution = pow(contribution, 2.0); // Steeper curve for sharper transition
|
||||
}
|
||||
|
||||
// Reduce occlusion on planar surfaces (similar normals)
|
||||
float normal_similarity = dot(normal_center, normal_sample);
|
||||
float planar_factor = smoothstep(0.75, 0.95, normal_similarity);
|
||||
contribution *= (1.0 - planar_factor * 0.6);
|
||||
|
||||
occlusion += contribution;
|
||||
valid_samples++;
|
||||
}
|
||||
|
||||
if (valid_samples > 0) {
|
||||
// Calculate ambient occlusion factor with higher base intensity
|
||||
float ao_factor = 1.0 - (occlusion / float(valid_samples)) * 0.6;
|
||||
|
||||
// Keep bright areas clean (higher minimum for upward-facing surfaces)
|
||||
float ao_min = mix(0.55, 0.85, up_factor);
|
||||
ao_factor = clamp(ao_factor, ao_min, 1.0);
|
||||
|
||||
// Slight brightness boost for upward-facing surfaces
|
||||
float brightness_boost = 1.0 + up_factor * 0.15;
|
||||
ao_factor = ao_factor * brightness_boost;
|
||||
|
||||
occlusion = ao_factor;
|
||||
} else {
|
||||
occlusion = 1.0;
|
||||
}
|
||||
// Calculate ambient occlusion factor with higher base intensity
|
||||
float ao_factor = 1.0 - (occlusion / float(SAMPLE_COUNT)) * 0.6;
|
||||
|
||||
// Keep bright areas clean (higher minimum for upward-facing surfaces). The old 0.85 floor
|
||||
// and 1.15 boost were set when up_factor came from the colour buffer and read ~0; with a
|
||||
// real normal they capped a top surface at 2% darkening, which hid the AO entirely.
|
||||
float ao_min = mix(0.45, 0.70, up_factor);
|
||||
occlusion = clamp(ao_factor, ao_min, 1.0);
|
||||
|
||||
vec3 color = texture(color_texture, tex_coord).rgb;
|
||||
frag_color = vec4(color * occlusion, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user