diff --git a/resources/shaders/110/phong.fs b/resources/shaders/110/phong.fs new file mode 100644 index 0000000000..f2d1c40590 --- /dev/null +++ b/resources/shaders/110/phong.fs @@ -0,0 +1,247 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const vec3 LightRed = vec3(0.78, 0.0, 0.0); +const vec3 LightBlue = vec3(0.73, 1.0, 1.0); +const float EPSILON = 0.0001; + +#define INTENSITY_CORRECTION 0.6 +#define PHONG_BRIGHTNESS 1.0 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 128.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +#define LIGHT_FRONT_SPECULAR (0.28 * INTENSITY_CORRECTION) +#define LIGHT_FRONT_SHININESS 64.0 + +#define INTENSITY_AMBIENT 0.22 +#define WINDOW_REFLECTION_INTENSITY 0.55 + +struct PrintVolumeDetection +{ + // 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid + int type; + // type = 0 (rectangle): + // x = min.x, y = min.y, z = max.x, w = max.y + // type = 1 (circle): + // x = center.x, y = center.y, z = radius + vec4 xy_data; + // x = min z, y = max z + vec2 z_data; +}; + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform vec4 uniform_color; +uniform bool use_color_clip_plane; +uniform vec4 uniform_color_clip_plane_1; +uniform vec4 uniform_color_clip_plane_2; +uniform SlopeDetection slope; + +//BBS: add outline_color +uniform bool is_outline; +uniform sampler2D depth_tex; +uniform vec2 screen_size; + +#ifdef ENABLE_ENVIRONMENT_MAP + uniform sampler2D environment_tex; + uniform bool use_environment_tex; +#endif // ENABLE_ENVIRONMENT_MAP + +uniform PrintVolumeDetection print_volume; + +uniform float z_far; +uniform float z_near; +uniform bool enable_ssao; + +varying vec3 clipping_planes_dots; +varying float color_clip_plane_dot; + +varying vec4 world_pos; +varying float world_normal_z; +varying vec3 eye_normal; +varying vec3 eye_position; + +vec3 getBackfaceColor(vec3 fill) { + float brightness = 0.2126 * fill.r + 0.7152 * fill.g + 0.0722 * fill.b; + return (brightness > 0.75) ? vec3(0.11, 0.165, 0.208) : vec3(0.988, 0.988, 0.988); +} + +// Silhouette edge detection & rendering algorithm by leoneruggiero +// https://www.shadertoy.com/view/DslXz2 +#define INFLATE 1 + +float GetTolerance(float d, float k) +{ + float A = -(z_far+z_near)/(z_far-z_near); + float B = -2.0*z_far*z_near /(z_far-z_near); + + d = d*2.0-1.0; + + return -k*(d+A)*(d+A)/B; +} + +float DetectSilho(vec2 fragCoord, vec2 dir) +{ + float x0 = abs(texture2D(depth_tex, (fragCoord + dir*-2.0) / screen_size).r); + float x1 = abs(texture2D(depth_tex, (fragCoord + dir*-1.0) / screen_size).r); + float x2 = abs(texture2D(depth_tex, (fragCoord + dir* 0.0) / screen_size).r); + float x3 = abs(texture2D(depth_tex, (fragCoord + dir* 1.0) / screen_size).r); + + float d0 = (x1-x0); + float d1 = (x2-x3); + + float r0 = x1 + d0 - x2; + float r1 = x2 + d1 - x1; + + float tol = GetTolerance(x2, 0.04); + + return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0)); +} + +float DetectSilho(vec2 fragCoord) +{ + return max( + DetectSilho(fragCoord, vec2(1,0)), + DetectSilho(fragCoord, vec2(0,1)) + ); +} + +float compute_ssao_factor(vec3 normal, vec3 view_dir, vec3 eye_pos) +{ + vec3 normal_dx = dFdx(normal); + vec3 normal_dy = dFdy(normal); + float normal_variation = clamp(length(normal_dx) + length(normal_dy), 0.0, 1.0); + + float depth_gradient = clamp(length(vec2(dFdx(eye_pos.z), dFdy(eye_pos.z))) * 0.8, 0.0, 1.0); + + float cavity = clamp(normal_variation * 0.70 + depth_gradient * 0.60, 0.0, 1.0); + float cavity_mask = smoothstep(0.25, 0.75, cavity); + float ao_strength = pow(cavity, 1.15) * cavity_mask; + return clamp(1.0 - ao_strength * 0.90, 0.25, 1.0); +} + +float soft_circle(vec2 p, vec2 center, float radius, float blur) +{ + float dist = distance(p, center); + return 1.0 - smoothstep(radius - blur, radius, dist); +} + +vec3 compute_window_reflection(vec3 normal, vec3 view_dir) +{ + const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); + + vec3 light_dir = normalize(LIGHT_TOP_DIR); + vec3 reflect_light = normalize(reflect(-light_dir, normal)); + + // UV coordinates for the reflection + vec2 uv = (reflect_light.xy / (1.0 + max(reflect_light.z, 0.3))) * 2.2; + + vec2 grad = fwidth(uv) * 0.8; + float blur = 0.12 + grad.x * 1.5; + + // === CIRCULAR WINDOW (porthole style) === + // Single round window, no bars + vec2 window_center = vec2(0.0, 0.0); + float window_radius = 0.5; // Radius of the circular window + + float window_light = soft_circle(uv, window_center, window_radius, blur); + + // No bars - just pure circular glass + float bars = 1.0; + + // Fresnel effect for edge glow + float fresnel = pow(1.0 - max(dot(normal, view_dir), 0.0), 1.0); + float facing = smoothstep(-0.4, 0.6, reflect_light.z); + + float intensity = window_light * bars * (0.25 + 0.25 * fresnel) * facing; + intensity = clamp(intensity, 0.0, 0.45); + + return vec3(intensity); +} + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + vec4 color; + if (use_color_clip_plane) { + color.rgb = (color_clip_plane_dot < 0.0) ? uniform_color_clip_plane_1.rgb : uniform_color_clip_plane_2.rgb; + color.a = uniform_color.a; + } + else + color = uniform_color; + + if (slope.actived) { + if(world_pos.z<0.1 && world_pos.z>-0.1) + { + color.rgb = LightBlue; + color.a = 0.8; + } + else if( world_normal_z < slope.normal_z - EPSILON) + { + color.rgb = color.rgb * 0.5 + LightRed * 0.5; + color.a = 0.8; + } + } + + vec3 pv_check_min = ZERO; + vec3 pv_check_max = ZERO; + if (print_volume.type == 0) { + pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x); + pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y); + } + else if (print_volume.type == 1) { + float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy); + pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x); + pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y); + } + color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb; + + vec3 normal = normalize(eye_normal); + vec3 view_dir = normalize(-eye_position); + + float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0); + float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE; + vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir); + float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS); + + float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + diffuse += NdotL_front * LIGHT_FRONT_DIFFUSE; + vec3 half_front = normalize(LIGHT_FRONT_DIR + view_dir); + specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, half_front), 0.0), LIGHT_FRONT_SHININESS); + vec3 window_reflection = compute_window_reflection(normal, view_dir); + + // SSAO is applied in post-process pass. Keep base lighting unchanged here. + + if (is_outline) { + vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS; + vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a); + vec2 fragCoord = gl_FragCoord.xy; + float s = DetectSilho(fragCoord); + for(int i=1;i<=INFLATE; i++) + { + s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0))); + s = max(s, DetectSilho(fragCoord.xy + vec2(0, i))); + } + gl_FragColor = vec4(mix(shaded_color.rgb, getBackfaceColor(shaded_color.rgb), s), shaded_color.a); + } +#ifdef ENABLE_ENVIRONMENT_MAP + else if (use_environment_tex) + gl_FragColor = vec4(clamp((0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + window_reflection + 0.8 * color.rgb * diffuse) * PHONG_BRIGHTNESS, vec3(0.0), vec3(1.0)), color.a); +#endif + else + gl_FragColor = vec4(clamp((vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS, vec3(0.0), vec3(1.0)), color.a); +} \ No newline at end of file diff --git a/resources/shaders/110/phong.vs b/resources/shaders/110/phong.vs new file mode 100644 index 0000000000..b9e90e7cc2 --- /dev/null +++ b/resources/shaders/110/phong.vs @@ -0,0 +1,54 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; +uniform SlopeDetection slope; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; +// Color clip plane - general orientation. Used by the cut gizmo. +uniform vec4 color_clip_plane; + +attribute vec3 v_position; +attribute vec3 v_normal; + +varying vec3 clipping_planes_dots; +varying float color_clip_plane_dot; + +varying vec4 world_pos; +varying float world_normal_z; +varying vec3 eye_normal; +varying vec3 eye_position; + +void main() +{ + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(view_normal_matrix * v_normal); + + vec4 position = view_model_matrix * vec4(v_position, 1.0); + eye_position = position.xyz; + + // Point in homogenous coordinates. + world_pos = volume_world_matrix * vec4(v_position, 1.0); + + // z component of normal vector in world coordinate used for slope shading + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0; + + gl_Position = projection_matrix * position; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + color_clip_plane_dot = dot(world_pos, color_clip_plane); +} diff --git a/resources/shaders/110/ssao.fs b/resources/shaders/110/ssao.fs new file mode 100644 index 0000000000..43b52a3f29 --- /dev/null +++ b/resources/shaders/110/ssao.fs @@ -0,0 +1,85 @@ +#version 110 + +/** + * SSAO Shader - GLSL 110 version with highlight protection + * Preserves brightness on upward-facing surfaces (top areas) + */ + +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; + +varying vec2 tex_coord; + +float linearize_depth(float depth) +{ + float z = depth * 2.0 - 1.0; + return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near)); +} + +void main() +{ + 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; + + // 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 + + // Adaptive sampling radius + float radius = mix(2.0, 4.0, depth_center / z_far); + + vec2 offsets[8]; + offsets[0] = vec2( 1.0, 0.0); + offsets[1] = vec2( 0.707, 0.707); + offsets[2] = vec2( 0.0, 1.0); + offsets[3] = vec2(-0.707, 0.707); + offsets[4] = vec2(-1.0, 0.0); + offsets[5] = vec2(-0.707,-0.707); + offsets[6] = vec2( 0.0, -1.0); + offsets[7] = vec2( 0.707,-0.707); + + 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); + + 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); + + // 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 + 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); +} \ No newline at end of file diff --git a/resources/shaders/110/ssao.vs b/resources/shaders/110/ssao.vs new file mode 100644 index 0000000000..f066b5dc5f --- /dev/null +++ b/resources/shaders/110/ssao.vs @@ -0,0 +1,15 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; +attribute vec2 v_tex_coord; + +varying vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/phong.fs b/resources/shaders/140/phong.fs new file mode 100644 index 0000000000..a8cc501b49 --- /dev/null +++ b/resources/shaders/140/phong.fs @@ -0,0 +1,250 @@ +#version 140 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const vec3 LightRed = vec3(0.78, 0.0, 0.0); +const vec3 LightBlue = vec3(0.73, 1.0, 1.0); +const float EPSILON = 0.0001; + +#define INTENSITY_CORRECTION 0.6 +#define PHONG_BRIGHTNESS 1.0 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 128.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +#define LIGHT_FRONT_SPECULAR (0.28 * INTENSITY_CORRECTION) +#define LIGHT_FRONT_SHININESS 64.0 + +#define INTENSITY_AMBIENT 0.22 +#define WINDOW_REFLECTION_INTENSITY 0.55 + +struct PrintVolumeDetection +{ + // 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid + int type; + // type = 0 (rectangle): + // x = min.x, y = min.y, z = max.x, w = max.y + // type = 1 (circle): + // x = center.x, y = center.y, z = radius + vec4 xy_data; + // x = min z, y = max z + vec2 z_data; +}; + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform vec4 uniform_color; +uniform bool use_color_clip_plane; +uniform vec4 uniform_color_clip_plane_1; +uniform vec4 uniform_color_clip_plane_2; +uniform SlopeDetection slope; + +//BBS: add outline_color +uniform bool is_outline; +uniform sampler2D depth_tex; +uniform vec2 screen_size; + +#ifdef ENABLE_ENVIRONMENT_MAP + uniform sampler2D environment_tex; + uniform bool use_environment_tex; +#endif // ENABLE_ENVIRONMENT_MAP + +uniform PrintVolumeDetection print_volume; + +uniform float z_far; +uniform float z_near; +uniform bool enable_ssao; + +in vec3 clipping_planes_dots; +in float color_clip_plane_dot; + +in vec4 world_pos; +in float world_normal_z; +in vec3 eye_normal; +in vec3 eye_position; + +out vec4 out_color; + +vec3 getBackfaceColor(vec3 fill) { + float brightness = 0.2126 * fill.r + 0.7152 * fill.g + 0.0722 * fill.b; + return (brightness > 0.75) ? vec3(0.11, 0.165, 0.208) : vec3(0.988, 0.988, 0.988); +} + +// Silhouette edge detection & rendering algorithm by leoneruggiero +// https://www.shadertoy.com/view/DslXz2 +#define INFLATE 1 + +float GetTolerance(float d, float k) +{ + float A=- (z_far+z_near)/(z_far-z_near); + float B=-2.0*z_far*z_near /(z_far-z_near); + + d = d*2.0-1.0; + + return -k*(d+A)*(d+A)/B; +} + +float DetectSilho(vec2 fragCoord, vec2 dir) +{ + float x0 = abs(texture(depth_tex, (fragCoord + dir*-2.0) / screen_size).r); + float x1 = abs(texture(depth_tex, (fragCoord + dir*-1.0) / screen_size).r); + float x2 = abs(texture(depth_tex, (fragCoord + dir* 0.0) / screen_size).r); + float x3 = abs(texture(depth_tex, (fragCoord + dir* 1.0) / screen_size).r); + + float d0 = (x1-x0); + float d1 = (x2-x3); + + float r0 = x1 + d0 - x2; + float r1 = x2 + d1 - x1; + + float tol = GetTolerance(x2, 0.04); + + return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0)); + +} + +float DetectSilho(vec2 fragCoord) +{ + return max( + DetectSilho(fragCoord, vec2(1,0)), + DetectSilho(fragCoord, vec2(0,1)) + ); +} + +float compute_ssao_factor(vec3 normal, vec3 view_dir, vec3 eye_pos) +{ + vec3 normal_dx = dFdx(normal); + vec3 normal_dy = dFdy(normal); + float normal_variation = clamp(length(normal_dx) + length(normal_dy), 0.0, 1.0); + + float depth_gradient = clamp(length(vec2(dFdx(eye_pos.z), dFdy(eye_pos.z))) * 0.8, 0.0, 1.0); + + float cavity = clamp(normal_variation * 0.70 + depth_gradient * 0.60, 0.0, 1.0); + float cavity_mask = smoothstep(0.25, 0.75, cavity); + float ao_strength = pow(cavity, 1.15) * cavity_mask; + return clamp(1.0 - ao_strength * 0.90, 0.25, 1.0); +} + +float soft_circle(vec2 p, vec2 center, float radius, float blur) +{ + float dist = distance(p, center); + return 1.0 - smoothstep(radius - blur, radius, dist); +} + +vec3 compute_window_reflection(vec3 normal, vec3 view_dir) +{ + const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); + + vec3 light_dir = normalize(LIGHT_TOP_DIR); + vec3 reflect_light = normalize(reflect(-light_dir, normal)); + + // UV coordinates for the reflection + vec2 uv = (reflect_light.xy / (1.0 + max(reflect_light.z, 0.3))) * 2.2; + + vec2 grad = fwidth(uv) * 0.8; + float blur = 0.12 + grad.x * 1.5; + + // === CIRCULAR WINDOW (porthole style) === + // Single round window, no bars + vec2 window_center = vec2(0.0, 0.0); + float window_radius = 0.5; // Radius of the circular window + + float window_light = soft_circle(uv, window_center, window_radius, blur); + + // No bars - just pure circular glass + float bars = 1.0; + + // Fresnel effect for edge glow + float fresnel = pow(1.0 - max(dot(normal, view_dir), 0.0), 1.0); + float facing = smoothstep(-0.4, 0.6, reflect_light.z); + + float intensity = window_light * bars * (0.25 + 0.25 * fresnel) * facing; + intensity = clamp(intensity, 0.0, 0.45); + + return vec3(intensity); +} + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + vec4 color; + if (use_color_clip_plane) { + color.rgb = (color_clip_plane_dot < 0.0) ? uniform_color_clip_plane_1.rgb : uniform_color_clip_plane_2.rgb; + color.a = uniform_color.a; + } + else + color = uniform_color; + + if (slope.actived) { + if(world_pos.z<0.1&&world_pos.z>-0.1) + { + color.rgb = LightBlue; + color.a = 0.8; + } + else if( world_normal_z < slope.normal_z - EPSILON) + { + color.rgb = color.rgb * 0.5 + LightRed * 0.5; + color.a = 0.8; + } + } + + vec3 pv_check_min = ZERO; + vec3 pv_check_max = ZERO; + if (print_volume.type == 0) { + pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x); + pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y); + } + else if (print_volume.type == 1) { + float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy); + pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x); + pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y); + } + color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb; + + vec3 normal = normalize(eye_normal); + vec3 view_dir = normalize(-eye_position); + + float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0); + float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE; + vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir); + float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS); + + float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + diffuse += NdotL_front * LIGHT_FRONT_DIFFUSE; + vec3 half_front = normalize(LIGHT_FRONT_DIR + view_dir); + specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, half_front), 0.0), LIGHT_FRONT_SHININESS); + vec3 window_reflection = compute_window_reflection(normal, view_dir); + + // SSAO is applied in post-process pass. Keep base lighting unchanged here. + + if (is_outline) { + vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS; + vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a); + vec2 fragCoord = gl_FragCoord.xy; + float s = DetectSilho(fragCoord); + for(int i=1;i<=INFLATE; i++) + { + s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0))); + s = max(s, DetectSilho(fragCoord.xy + vec2(0, i))); + } + out_color = vec4(mix(shaded_color.rgb, getBackfaceColor(shaded_color.rgb), s), shaded_color.a); + } +#ifdef ENABLE_ENVIRONMENT_MAP + else if (use_environment_tex) + out_color = vec4(clamp((0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + window_reflection + 0.8 * color.rgb * diffuse) * PHONG_BRIGHTNESS, vec3(0.0), vec3(1.0)), color.a); +#endif + else + out_color = vec4(clamp((vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS, vec3(0.0), vec3(1.0)), color.a); +} \ No newline at end of file diff --git a/resources/shaders/140/phong.vs b/resources/shaders/140/phong.vs new file mode 100644 index 0000000000..2ccbaf16ee --- /dev/null +++ b/resources/shaders/140/phong.vs @@ -0,0 +1,54 @@ +#version 140 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; +uniform SlopeDetection slope; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; +// Color clip plane - general orientation. Used by the cut gizmo. +uniform vec4 color_clip_plane; + +in vec3 v_position; +in vec3 v_normal; + +out vec3 clipping_planes_dots; +out float color_clip_plane_dot; + +out vec4 world_pos; +out float world_normal_z; +out vec3 eye_normal; +out vec3 eye_position; + +void main() +{ + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(view_normal_matrix * v_normal); + + vec4 position = view_model_matrix * vec4(v_position, 1.0); + eye_position = position.xyz; + + // Point in homogenous coordinates. + world_pos = volume_world_matrix * vec4(v_position, 1.0); + + // z component of normal vector in world coordinate used for slope shading + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0; + + gl_Position = projection_matrix * position; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + color_clip_plane_dot = dot(world_pos, color_clip_plane); +} diff --git a/resources/shaders/140/ssao.fs b/resources/shaders/140/ssao.fs new file mode 100644 index 0000000000..86ea6a54bf --- /dev/null +++ b/resources/shaders/140/ssao.fs @@ -0,0 +1,103 @@ +#version 140 + +/** + * SSAO Shader - GLSL 140 version with sharp depth threshold + * 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 float z_far; + +in vec2 tex_coord; +out vec4 frag_color; + +float linearize_depth(float depth) +{ + float z = depth * 2.0 - 1.0; + return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near)); +} + +void main() +{ + 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); + + // Calculate upward-facing factor (Z-up coordinate system) + float up_factor = clamp(normal_center.z * 1.5, 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[]( + 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; + + for (int i = 0; i < 12; i++) { + 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 + + float contribution = 0.0; + if (depth_diff > threshold_min) { + // Abrupt mapping with power curve + contribution = (depth_diff - threshold_min) / (threshold_max - threshold_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; + } + + vec3 color = texture(color_texture, tex_coord).rgb; + frag_color = vec4(color * occlusion, 1.0); +} \ No newline at end of file diff --git a/resources/shaders/140/ssao.vs b/resources/shaders/140/ssao.vs new file mode 100644 index 0000000000..5ca1517db0 --- /dev/null +++ b/resources/shaders/140/ssao.vs @@ -0,0 +1,15 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +in vec3 v_position; +in vec2 v_tex_coord; + +out vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index f8e6d87f6d..e9f73d2c1e 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -264,6 +264,21 @@ void AppConfig::set_defaults() if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty()) set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false); + if (get(SETTING_OPENGL_REALISTIC_MODE).empty()) + set_bool(SETTING_OPENGL_REALISTIC_MODE, false); + + if (get(SETTING_OPENGL_REALISTIC_PHONG).empty()) + set_bool(SETTING_OPENGL_REALISTIC_PHONG, true); + + if (get(SETTING_OPENGL_SHADING_MODEL).empty()) + set(SETTING_OPENGL_SHADING_MODEL, "gouraud"); + + if (get(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS).empty()) + set_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS, false); + + if (get(SETTING_OPENGL_PHONG_SSAO).empty()) + set_bool(SETTING_OPENGL_PHONG_SSAO, false); + if (get("export_sources_full_pathnames").empty()) set_bool("export_sources_full_pathnames", false); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 76366784b0..686843f548 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -34,6 +34,11 @@ using namespace nlohmann; #define SETTING_OPENGL_FXAA_ENABLED "opengl_fxaa_enabled" #define SETTING_OPENGL_FPS_CAP "opengl_fps_cap" #define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay" +#define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode" +#define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong" +#define SETTING_OPENGL_SHADING_MODEL "opengl_shading_model" +#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows" +#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao" #if defined(_WIN32) || defined(_WIN64) #define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09" diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 011231a8f8..896f38e97e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1218,10 +1218,22 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) GLCanvas3D::~GLCanvas3D() { - if (m_fxaa_texture_id != 0 && _set_current()) { - glsafe(::glDeleteTextures(1, &m_fxaa_texture_id)); - m_fxaa_texture_id = 0; + if (_set_current()) { + if (m_fxaa_texture_id != 0) { + glsafe(::glDeleteTextures(1, &m_fxaa_texture_id)); + m_fxaa_texture_id = 0; + } + if (m_ssao_color_texture_id != 0) { + glsafe(::glDeleteTextures(1, &m_ssao_color_texture_id)); + m_ssao_color_texture_id = 0; + } + if (m_ssao_depth_texture_id != 0) { + glsafe(::glDeleteTextures(1, &m_ssao_depth_texture_id)); + m_ssao_depth_texture_id = 0; + } + m_plate_shadow_mask.reset(); } + m_plate_shadow_mask_key.clear(); reset_volumes(); @@ -2039,14 +2051,16 @@ void GLCanvas3D::render(bool only_init) /* view3D render*/ int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1; if (m_canvas_type == ECanvasType::CanvasView3D) { - //BBS: add outline logic - _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); - _render_sla_slices(); - _render_selection(); if (!no_partplate) _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes); if (!no_partplate) //BBS: add outline logic _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid); + + //BBS: add outline logic + _render_cast_shadows_on_plate(camera.get_view_matrix(), camera.get_projection_matrix()); + _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); + _render_sla_slices(); + _render_selection(); _render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running()); } /* preview render */ @@ -2103,6 +2117,9 @@ void GLCanvas3D::render(bool only_init) if (m_picking_enabled && m_rectangle_selection.is_dragging()) m_rectangle_selection.render(*this); + if (_is_ssao_enabled()) + _render_ssao_pass(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height())); + if (_is_fxaa_enabled()) _render_fxaa_pass(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height())); @@ -7502,6 +7519,14 @@ bool GLCanvas3D::_is_fxaa_enabled() const return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_FXAA_ENABLED); } +bool GLCanvas3D::_is_ssao_enabled() const +{ + if (wxGetApp().app_config == nullptr) + return false; + return wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE) && + wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO); +} + int GLCanvas3D::_get_effective_fps_cap() const { if (wxGetApp().app_config == nullptr) @@ -7596,6 +7621,159 @@ void GLCanvas3D::_render_fxaa_pass(unsigned int width, unsigned int height) glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); } +void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height) +{ + if (width == 0 || height == 0) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("ssao"); + if (shader == nullptr) + return; + + if (m_ssao_color_texture_id == 0) { + glsafe(::glGenTextures(1, &m_ssao_color_texture_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_color_texture_id)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + } + if (m_ssao_depth_texture_id == 0) { + glsafe(::glGenTextures(1, &m_ssao_depth_texture_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_depth_texture_id)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + } + + if (m_ssao_texture_size[0] != width || m_ssao_texture_size[1] != height) { + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_color_texture_id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_depth_texture_id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr)); + m_ssao_texture_size = { { width, height } }; + } + + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_color_texture_id)); + glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_depth_texture_id)); + glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height)); + + const Camera& camera = wxGetApp().plater()->get_camera(); + + GLint prev_stencil_mask = 0xFF; + glsafe(::glGetIntegerv(GL_STENCIL_WRITEMASK, &prev_stencil_mask)); + GLboolean prev_stencil_test = GL_FALSE; + glsafe(::glGetBooleanv(GL_STENCIL_TEST, &prev_stencil_test)); + GLboolean prev_depth_mask = GL_TRUE; + glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask)); + GLint prev_depth_func = GL_LESS; + glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func)); + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_BLEND)); + + // Build stencil mask for bed/plate and apply SSAO only outside this mask. + glsafe(::glEnable(GL_STENCIL_TEST)); + glsafe(::glStencilMask(0xFF)); + glsafe(::glClearStencil(0)); + glsafe(::glClear(GL_STENCIL_BUFFER_BIT)); + glsafe(::glStencilFunc(GL_ALWAYS, 1, 0xFF)); + glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)); + // Mark only visible plate pixels (do not exclude objects in front of plate). + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glDepthFunc(GL_LEQUAL)); + + GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE }; + glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask)); + glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); + + if (const BuildVolume& build_volume = m_bed.build_volume(); build_volume.valid()) { + GLShaderProgram* flat = wxGetApp().get_shader("flat"); + if (flat != nullptr) { + flat->start_using(); + flat->set_uniform("projection_matrix", camera.get_projection_matrix()); + + GLModel plate_mask; + GLModel::Geometry mask; + mask.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + + if (build_volume.type() == BuildVolume_Type::Rectangle) { + const BoundingBox3Base bb = build_volume.bounding_volume(); + mask.reserve_vertices(4); + mask.reserve_indices(6); + mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.min.y(), 0.0f)); + mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.min.y(), 0.0f)); + mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.max.y(), 0.0f)); + mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.max.y(), 0.0f)); + mask.add_triangle(0, 1, 2); + mask.add_triangle(0, 2, 3); + } else if (build_volume.type() == BuildVolume_Type::Circle) { + const Vec2f c = Vec2f(unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y())); + const float r = unscaled(build_volume.circle().radius); + const int segments = 64; + mask.reserve_vertices(segments + 1); + mask.reserve_indices(segments * 3); + mask.add_vertex(Vec3f(c.x(), c.y(), 0.0f)); + for (int i = 0; i < segments; ++i) { + const float a = (2.0f * float(PI) * float(i)) / float(segments); + mask.add_vertex(Vec3f(c.x() + r * std::cos(a), c.y() + r * std::sin(a), 0.0f)); + } + for (int i = 0; i < segments; ++i) { + const unsigned int i1 = 1 + i; + const unsigned int i2 = 1 + ((i + 1) % segments); + mask.add_triangle(0, i1, i2); + } + } + + if (mask.vertices_count() > 0 && mask.indices_count() > 0) { + plate_mask.init_from(std::move(mask)); + flat->set_uniform("view_model_matrix", camera.get_view_matrix()); + plate_mask.render(flat); + } + flat->stop_using(); + } + } + + glsafe(::glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glStencilMask(0x00)); + glsafe(::glStencilFunc(GL_NOTEQUAL, 1, 0xFF)); + glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)); + + shader->start_using(); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("color_texture", 0); + shader->set_uniform("depth_texture", 1); + shader->set_uniform("inv_tex_size", Vec2f(1.0f / static_cast(width), 1.0f / static_cast(height))); + shader->set_uniform("z_near", camera.get_near_z()); + shader->set_uniform("z_far", camera.get_far_z()); + + glsafe(::glActiveTexture(GL_TEXTURE0)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_color_texture_id)); + glsafe(::glActiveTexture(GL_TEXTURE1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_depth_texture_id)); + m_background.render(); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glActiveTexture(GL_TEXTURE0)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + shader->stop_using(); + + if (!prev_stencil_test) + glsafe(::glDisable(GL_STENCIL_TEST)); + glsafe(::glStencilMask(prev_stencil_mask)); + glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3])); + + glsafe(::glDepthMask(prev_depth_mask)); + glsafe(::glDepthFunc(prev_depth_func)); + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); +} + void GLCanvas3D::_render_background() { bool use_error_color = false; @@ -7686,6 +7864,206 @@ void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transfo wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid); } +void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, const Transform3d& projection_matrix) +{ + // Check if shadow rendering is enabled in configuration + if (wxGetApp().app_config == nullptr) + return; + if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE)) + return; + if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS)) + return; + if (m_volumes.empty()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + // Fixed light direction (pointing downward at an angle) + // Drive shadow direction from current view angle: define light in eye-space, + // then transform it to world-space with inverse view rotation. + const Vec3d light_dir_eye = Vec3d(-0.4574957, 0.4574957, 0.7624929).normalized(); + const Matrix3d view_rot = view_matrix.matrix().block<3, 3>(0, 0); + const Vec3d light_dir_to_light = (view_rot.transpose() * light_dir_eye).normalized(); + const Vec3d ray_dir = -light_dir_to_light; // Direction of shadow projection + + if (std::abs(ray_dir.z()) < 1e-6) + return; + + // Shadow projection matrix - flattens geometry onto Z=0 plane along light direction + Matrix4d shadow_proj = Matrix4d::Identity(); + shadow_proj(0, 2) = -ray_dir.x() / ray_dir.z(); + shadow_proj(1, 2) = -ray_dir.y() / ray_dir.z(); + shadow_proj(2, 0) = 0.0; + shadow_proj(2, 1) = 0.0; + shadow_proj(2, 2) = 0.0; + shadow_proj(2, 3) = 0.01; // Bias to prevent shadow acne + + // Save OpenGL state + GLint prev_depth_func = GL_LESS; + glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func)); + GLboolean prev_depth_mask = GL_TRUE; + glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask)); + GLint prev_stencil_mask = 0xFF; + glsafe(::glGetIntegerv(GL_STENCIL_WRITEMASK, &prev_stencil_mask)); + GLboolean prev_stencil_test = GL_FALSE; + glsafe(::glGetBooleanv(GL_STENCIL_TEST, &prev_stencil_test)); + + // ============================================================ + // PASS 0: Create stencil mask for the build plate (value = 1) + // ============================================================ + glsafe(::glEnable(GL_STENCIL_TEST)); + glsafe(::glStencilMask(0xFF)); + glsafe(::glClearStencil(0)); + glsafe(::glClear(GL_STENCIL_BUFFER_BIT)); + + glsafe(::glStencilFunc(GL_ALWAYS, 1, 0xFF)); + glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)); + + glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); + glsafe(::glDisable(GL_DEPTH_TEST)); + + shader->start_using(); + shader->set_uniform("projection_matrix", projection_matrix); + + // Draw the build plate (cached model to avoid per-frame uploads) + if (const BuildVolume& build_volume = m_bed.build_volume(); build_volume.valid()) { + const std::string mask_key = build_volume.type() == BuildVolume_Type::Rectangle + ? (boost::format("rect|%1$.5f|%2$.5f|%3$.5f|%4$.5f") + % build_volume.bounding_volume().min.x() + % build_volume.bounding_volume().min.y() + % build_volume.bounding_volume().max.x() + % build_volume.bounding_volume().max.y()).str() + : (build_volume.type() == BuildVolume_Type::Circle + ? (boost::format("circle|%1$.5f|%2$.5f|%3$.5f") + % unscaled(build_volume.circle().center.x()) + % unscaled(build_volume.circle().center.y()) + % unscaled(build_volume.circle().radius)).str() + : std::string("invalid")); + + if (mask_key != m_plate_shadow_mask_key) { + m_plate_shadow_mask.reset(); + m_plate_shadow_mask_key = mask_key; + + GLModel::Geometry mask; + mask.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + + if (build_volume.type() == BuildVolume_Type::Rectangle) { + const BoundingBox3Base bb = build_volume.bounding_volume(); + mask.reserve_vertices(4); + mask.reserve_indices(6); + mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.min.y(), 0.0f)); + mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.min.y(), 0.0f)); + mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.max.y(), 0.0f)); + mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.max.y(), 0.0f)); + mask.add_triangle(0, 1, 2); + mask.add_triangle(0, 2, 3); + } + else if (build_volume.type() == BuildVolume_Type::Circle) { + const Vec2f c = Vec2f(unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y())); + const float r = unscaled(build_volume.circle().radius); + const int segments = 64; + mask.reserve_vertices(segments + 1); + mask.reserve_indices(segments * 3); + mask.add_vertex(Vec3f(c.x(), c.y(), 0.0f)); + for (int i = 0; i < segments; ++i) { + const float a = (2.0f * float(PI) * float(i)) / float(segments); + mask.add_vertex(Vec3f(c.x() + r * std::cos(a), c.y() + r * std::sin(a), 0.0f)); + } + for (int i = 0; i < segments; ++i) { + const unsigned int i1 = 1 + i; + const unsigned int i2 = 1 + ((i + 1) % segments); + mask.add_triangle(0, i1, i2); + } + } + + if (mask.vertices_count() > 0 && mask.indices_count() > 0) + m_plate_shadow_mask.init_from(std::move(mask)); + } + + if (m_plate_shadow_mask.is_initialized()) { + shader->set_uniform("view_model_matrix", view_matrix); + m_plate_shadow_mask.render(shader); + } + } + + // ============================================================ + // PASS 1: Project object shadows onto plate (increment stencil to 2) + // ============================================================ + // Only render where plate exists (stencil == 1), then increment to 2 + glsafe(::glStencilFunc(GL_EQUAL, 1, 0xFF)); + glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_INCR)); + + glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthFunc(GL_ALWAYS)); // Shadows don't need depth testing + glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); + glsafe(::glPolygonOffset(-2.0f, -2.0f)); + glsafe(::glDisable(GL_CULL_FACE)); + + // Render projected shadow geometry + for (GLVolume* volume : m_volumes.volumes) { + if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower) + continue; + + // CRITICAL FIX: Apply shadow projection in object's local space, then to world, then to view + // This ensures shadows are cast from the object's actual position + Matrix4d world_matrix = volume->world_matrix().matrix(); + + // Project the shadow - this flattens the geometry onto Z=0 in WORLD space + Matrix4d shadow_world_matrix = shadow_proj * world_matrix; + + // Transform to view space for rendering + Matrix4d view_shadow_matrix = view_matrix.matrix() * shadow_world_matrix; + + shader->set_uniform("view_model_matrix", view_shadow_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + + volume->model.render(shader); + } + + // ============================================================ + // PASS 2: Draw shadow color where stencil == 2 + // ============================================================ + glsafe(::glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); + glsafe(::glStencilFunc(GL_EQUAL, 2, 0xFF)); + glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)); + glsafe(::glStencilMask(0x00)); + + glsafe(::glDepthFunc(GL_ALWAYS)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + // Draw shadow fill + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + + const ColorRGBA shadow_fill_color(0.0f, 0.0f, 0.0f, 0.4f); // Darker shadow for visibility + const ColorRGBA prev_bg_color = m_background.get_geometry().color; + m_background.set_color(shadow_fill_color); + shader->set_uniform("uniform_color", shadow_fill_color); + m_background.render(shader); + m_background.set_color(prev_bg_color); + shader->set_uniform("uniform_color", prev_bg_color); + + shader->stop_using(); + + // ============================================================ + // RESTORE STATE + // ============================================================ + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(prev_depth_mask)); + glsafe(::glDepthFunc(prev_depth_func)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); + glsafe(::glDisable(GL_BLEND)); + + if (!prev_stencil_test) + glsafe(::glDisable(GL_STENCIL_TEST)); + glsafe(::glStencilMask(prev_stencil_mask)); +} + void GLCanvas3D::_render_plane() const { ;//TODO render assemble plane @@ -7756,12 +8134,20 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with else m_volumes.set_show_sinking_contours(!m_gizmos.is_hiding_instances()); - GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); + const bool realistic_mode = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE); + const bool realistic_phong = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_PHONG); + const std::string shader_name = (realistic_mode && realistic_phong) ? "phong" : "gouraud"; + GLShaderProgram* shader = wxGetApp().get_shader(shader_name); + if (shader == nullptr && shader_name != "gouraud") + shader = wxGetApp().get_shader("gouraud"); ECanvasType canvas_type = this->m_canvas_type; bool partly_inside_enable = canvas_type == ECanvasType::CanvasAssembleView ? false : true; if (shader != nullptr) { shader->start_using(); + const bool phong_ssao = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO); + shader->set_uniform("enable_ssao", phong_ssao); + const Size& cvn_size = get_canvas_size(); { const Camera& camera = wxGetApp().plater()->get_camera(); @@ -8836,6 +9222,15 @@ void GLCanvas3D::_render_canvas_toolbar() [this]{wxGetApp().toggle_show_outline();} ); + create_menu_item( _utf8(L("Realistic View")), + true, + cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE), + [this, &cfg]{ + cfg->set_bool(SETTING_OPENGL_REALISTIC_MODE, !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE)); + cfg->save(); + } + ); + ImGui::Separator(); create_menu_item( _utf8(L("Perspective")), diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6c3e4573e1..72e7f1f8e2 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -727,6 +727,11 @@ public: GLModel m_background; unsigned int m_fxaa_texture_id{ 0 }; std::array m_fxaa_texture_size{ 0, 0 }; + unsigned int m_ssao_color_texture_id{ 0 }; + unsigned int m_ssao_depth_texture_id{ 0 }; + std::array m_ssao_texture_size{ { 0, 0 } }; + GLModel m_plate_shadow_mask; + std::string m_plate_shadow_mask_key; public: explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed); ~GLCanvas3D(); @@ -1238,12 +1243,15 @@ private: void _picking_pass(); void _rectangular_selection_picking_pass(); bool _is_fxaa_enabled() const; + bool _is_ssao_enabled() const; int _get_effective_fps_cap() const; bool _is_fps_overlay_enabled() const; void _render_fps_overlay(int fps) const; void _render_fxaa_pass(unsigned int width, unsigned int height); + void _render_ssao_pass(unsigned int width, unsigned int height); void _render_background(); void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes); + void _render_cast_shadows_on_plate(const Transform3d& view_matrix, const Transform3d& projection_matrix); //BBS: add part plate related logic void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true); //BBS: add outline drawing logic diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 620fc2e773..d368aaecd2 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -50,6 +50,8 @@ std::pair GLShadersManager::init() valid &= append_shader("flat_texture", { prefix + "flat_texture.vs", prefix + "flat_texture.fs" }); // used to apply post-processing antialiasing in screen space valid &= append_shader("fxaa", { prefix + "fxaa.vs", prefix + "fxaa.fs" }); + // used to apply screen-space ambient occlusion in post process + valid &= append_shader("ssao", { prefix + "ssao.vs", prefix + "ssao.fs" }); // used to render 3D scene background valid &= append_shader("background", { prefix + "background.vs", prefix + "background.fs" }); #if SLIC3R_OPENGL_ES @@ -76,6 +78,12 @@ std::pair GLShadersManager::init() valid &= append_shader("gouraud", { prefix + "gouraud.vs", prefix + "gouraud.fs" } #if ENABLE_ENVIRONMENT_MAP , { "ENABLE_ENVIRONMENT_MAP"sv } +#endif // ENABLE_ENVIRONMENT_MAP + ); + // used to render objects in 3d editor with phong shading + valid &= append_shader("phong", { prefix + "phong.vs", prefix + "phong.fs" } +#if ENABLE_ENVIRONMENT_MAP + , { "ENABLE_ENVIRONMENT_MAP"sv } #endif // ENABLE_ENVIRONMENT_MAP ); // used to render variable layers heights in 3d editor diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 0ca62d849a..94c76d896b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -1386,8 +1386,13 @@ bool GLGizmosManager::activate_gizmo(EType type) UndoRedo::SnapshotType::LeavingGizmoWithAction); } - if (type == Undefined) { + if (type == Undefined) { // it is deactivation of gizmo + if (m_restore_realistic_view_after_paint && wxGetApp().app_config != nullptr) { + wxGetApp().app_config->set_bool(SETTING_OPENGL_REALISTIC_MODE, true); + wxGetApp().app_config->save(); + m_restore_realistic_view_after_paint = false; + } m_current = Undefined; return true; } @@ -1396,6 +1401,16 @@ bool GLGizmosManager::activate_gizmo(EType type) GLGizmoBase& new_gizmo = *m_gizmos[type]; if (!new_gizmo.is_activable()) return false; + if (type == Seam || type == FdmSupports || type == FuzzySkin) { + if (wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE)) { + m_restore_realistic_view_after_paint = true; + wxGetApp().app_config->set_bool(SETTING_OPENGL_REALISTIC_MODE, false); + wxGetApp().app_config->save(); + } + } else { + m_restore_realistic_view_after_paint = false; + } + if (!m_serializing && new_gizmo.wants_enter_leave_snapshots()) Plater::TakeSnapshot snapshot(wxGetApp().plater(), new_gizmo.get_gizmo_entering_text(), diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 5897c2a512..01814521aa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -150,6 +150,7 @@ private: static std::map icon_list; bool m_is_dark = false; + bool m_restore_realistic_view_after_paint = false; /// /// Process mouse event on gizmo toolbar diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 31b3ba95a7..1e6b1f6558 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1554,6 +1554,30 @@ void PreferencesDialog::create_items() g_sizer = f_sizers.back(); g_sizer->AddGrowableCol(0, 1); + //// GRAPHICS > Realistic view + g_sizer->Add(create_item_title(_L("Realistic View")), 1, wxEXPAND); + + auto item_realistic_phong = create_item_checkbox( + _L("Phong shading"), + _L("Uses Phong shading inside realistic view.") + , SETTING_OPENGL_REALISTIC_PHONG + ); + g_sizer->Add(item_realistic_phong); + + auto item_realistic_ssao = create_item_checkbox( + _L("SSAO ambient occlusion"), + _L("Applies SSAO in realistic view."), + SETTING_OPENGL_PHONG_SSAO + ); + g_sizer->Add(item_realistic_ssao); + + auto item_realistic_shadows = create_item_checkbox( + _L("Shadows"), + _L("Renders cast shadows on the plate in realistic view."), + SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS + ); + g_sizer->Add(item_realistic_shadows); + //// GRAPHICS > Anti-aliasing g_sizer->Add(create_item_title(_L("Anti-aliasing")), 1, wxEXPAND);