From 78692aa08f0db2cffe15f2dbc65998f2d44daf39 Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:44:30 -0300 Subject: [PATCH] Interactive shadows (#14702) Co-authored-by: Ian Bassi <12130714+ianalexis@users.noreply.github.com> Co-authored-by: Ian Bassi --- resources/shaders/110/gouraud.fs | 48 ++- resources/shaders/110/phong.fs | 45 ++- resources/shaders/110/printbed_shadow.fs | 40 +++ resources/shaders/110/printbed_shadow.vs | 16 + resources/shaders/140/gouraud.fs | 48 ++- resources/shaders/140/phong.fs | 47 ++- resources/shaders/140/printbed_shadow.fs | 42 +++ resources/shaders/140/printbed_shadow.vs | 16 + src/slic3r/GUI/GLCanvas3D.cpp | 370 +++++++++++++++-------- src/slic3r/GUI/GLCanvas3D.hpp | 10 +- src/slic3r/GUI/GLShadersManager.cpp | 4 + src/slic3r/GUI/Preferences.cpp | 2 +- 12 files changed, 544 insertions(+), 144 deletions(-) create mode 100644 resources/shaders/110/printbed_shadow.fs create mode 100644 resources/shaders/110/printbed_shadow.vs create mode 100644 resources/shaders/140/printbed_shadow.fs create mode 100644 resources/shaders/140/printbed_shadow.vs diff --git a/resources/shaders/110/gouraud.fs b/resources/shaders/110/gouraud.fs index e602d6067d..960b797c38 100644 --- a/resources/shaders/110/gouraud.fs +++ b/resources/shaders/110/gouraud.fs @@ -50,6 +50,15 @@ uniform PrintVolumeDetection print_volume; uniform float z_far; uniform float z_near; +// Depth-based shadow map (object-on-object and self shadows). shadow_intensity == 0 disables it. +uniform sampler2D shadow_map; +uniform mat4 shadow_light_vp; +uniform float shadow_intensity; +uniform float shadow_map_texel; + +// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs). +const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); + varying vec3 clipping_planes_dots; varying float color_clip_plane_dot; @@ -125,6 +134,35 @@ float DetectSilho(vec2 fragCoord) ); } +// Returns a lighting multiplier in [1 - shadow_intensity, 1]: < 1 where the fragment is +// occluded from the light in the shadow map. 3x3 PCF softens the edges. +float shadow_shade() +{ + if (shadow_intensity <= 0.0) + return 1.0; + + vec4 lp = shadow_light_vp * world_pos; + vec3 proj = lp.xyz / lp.w; + proj = proj * 0.5 + 0.5; + if (proj.z > 1.0) + return 1.0; + + // Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This + // suppresses self-shadow acne without discarding real shadows cast by other objects onto + // back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow). + float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR); + float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0)); + // 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne. + float sum = 0.0; + for (int x = -2; x <= 2; ++x) { + for (int y = -2; y <= 2; ++y) { + float closest = texture2D(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r; + sum += (proj.z - bias > closest) ? 1.0 : 0.0; + } + } + return 1.0 - shadow_intensity * (sum / 25.0); +} + void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -166,9 +204,11 @@ void main() } color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb; + float shade = shadow_shade(); + //BBS: add outline_color if (is_outline) { - color = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a); + color = vec4((vec3(intensity.y) + color.rgb * intensity.x) * shade, color.a); vec2 fragCoord = gl_FragCoord.xy; float s = DetectSilho(fragCoord); // Makes silhouettes thicker. @@ -176,13 +216,13 @@ void main() { s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0))); s = max(s, DetectSilho(fragCoord.xy + vec2(0, i))); - } + } gl_FragColor = vec4(mix(color.rgb, getBackfaceColor(color.rgb), s), color.a); } #ifdef ENABLE_ENVIRONMENT_MAP else if (use_environment_tex) - gl_FragColor = vec4(0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x, color.a); + gl_FragColor = vec4((0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x) * shade, color.a); #endif else - gl_FragColor = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a); + gl_FragColor = vec4((vec3(intensity.y) + color.rgb * intensity.x) * shade, color.a); } \ No newline at end of file diff --git a/resources/shaders/110/phong.fs b/resources/shaders/110/phong.fs index a0c6372ca5..a5c1c5b8ab 100644 --- a/resources/shaders/110/phong.fs +++ b/resources/shaders/110/phong.fs @@ -65,6 +65,12 @@ uniform float z_far; uniform float z_near; uniform bool enable_ssao; +// Depth-based shadow map (object-on-object and self shadows). shadow_intensity == 0 disables it. +uniform sampler2D shadow_map; +uniform mat4 shadow_light_vp; +uniform float shadow_intensity; +uniform float shadow_map_texel; + varying vec3 clipping_planes_dots; varying float color_clip_plane_dot; @@ -167,10 +173,39 @@ vec3 compute_window_reflection(vec3 normal, vec3 view_dir) float intensity = window_light * bars * (0.15 + 0.15 * fresnel) * facing; intensity = clamp(intensity, 0.0, 0.25); - + return vec3(intensity); } +// Returns a lighting multiplier in [1 - shadow_intensity, 1]: < 1 where the fragment is +// occluded from the light in the shadow map. 3x3 PCF softens the edges. +float shadow_shade() +{ + if (shadow_intensity <= 0.0) + return 1.0; + + vec4 lp = shadow_light_vp * world_pos; + vec3 proj = lp.xyz / lp.w; + proj = proj * 0.5 + 0.5; + if (proj.z > 1.0) + return 1.0; + + // Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This + // suppresses self-shadow acne without discarding real shadows cast by other objects onto + // back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow). + float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR); + float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0)); + // 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne. + float sum = 0.0; + for (int x = -2; x <= 2; ++x) { + for (int y = -2; y <= 2; ++y) { + float closest = texture2D(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r; + sum += (proj.z - bias > closest) ? 1.0 : 0.0; + } + } + return 1.0 - shadow_intensity * (sum / 25.0); +} + void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -226,8 +261,10 @@ void main() // SSAO is applied in post-process pass. Keep base lighting unchanged here. + float shade = shadow_shade(); + if (is_outline) { - vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS; + vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade; vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a); vec2 fragCoord = gl_FragCoord.xy; float s = DetectSilho(fragCoord); @@ -240,8 +277,8 @@ void main() } #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); + 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 * shade, 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); + gl_FragColor = vec4(clamp((vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade, vec3(0.0), vec3(1.0)), color.a); } \ No newline at end of file diff --git a/resources/shaders/110/printbed_shadow.fs b/resources/shaders/110/printbed_shadow.fs new file mode 100644 index 0000000000..a4fd6801dd --- /dev/null +++ b/resources/shaders/110/printbed_shadow.fs @@ -0,0 +1,40 @@ +#version 110 + +// Draws the build-plate as a receiver of the same depth shadow map used for object/self shadows, +// so the plate, objects, and self-shadows all come from one unified technique. +uniform sampler2D shadow_map; +uniform mat4 shadow_light_vp; +uniform float shadow_intensity; +uniform float shadow_map_texel; + +varying vec4 world_pos; + +// Fraction of the 5x5 PCF kernel occluded from the light. Matches the object shadow shader. +float shadow_occlusion() +{ + vec4 lp = shadow_light_vp * world_pos; + vec3 proj = lp.xyz / lp.w; + proj = proj * 0.5 + 0.5; + if (proj.z > 1.0) + return 0.0; + + // The plate is a pure receiver (never rendered into the shadow map), so a tiny constant + // bias for numerical safety is enough here. + float bias = 0.0004; + float sum = 0.0; + for (int x = -2; x <= 2; ++x) { + for (int y = -2; y <= 2; ++y) { + float closest = texture2D(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r; + sum += (proj.z - bias > closest) ? 1.0 : 0.0; + } + } + return sum / 25.0; +} + +void main() +{ + float occ = shadow_occlusion(); + if (occ <= 0.0) + discard; + gl_FragColor = vec4(0.0, 0.0, 0.0, shadow_intensity * occ); +} diff --git a/resources/shaders/110/printbed_shadow.vs b/resources/shaders/110/printbed_shadow.vs new file mode 100644 index 0000000000..d004fac7c9 --- /dev/null +++ b/resources/shaders/110/printbed_shadow.vs @@ -0,0 +1,16 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; + +// The plate mask quad is authored directly in world coordinates (z = 0 plane), +// so v_position is already the world position of the fragment. +varying vec4 world_pos; + +void main() +{ + world_pos = vec4(v_position, 1.0); + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/gouraud.fs b/resources/shaders/140/gouraud.fs index bbfb76f7a1..efd7eaff2a 100644 --- a/resources/shaders/140/gouraud.fs +++ b/resources/shaders/140/gouraud.fs @@ -49,6 +49,15 @@ uniform PrintVolumeDetection print_volume; uniform float z_far; uniform float z_near; +// Depth-based shadow map (object-on-object and self shadows). shadow_intensity == 0 disables it. +uniform sampler2D shadow_map; +uniform mat4 shadow_light_vp; +uniform float shadow_intensity; +uniform float shadow_map_texel; + +// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs). +const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); + in vec3 clipping_planes_dots; in float color_clip_plane_dot; @@ -124,6 +133,35 @@ float DetectSilho(vec2 fragCoord) ); } +// Returns a lighting multiplier in [1 - shadow_intensity, 1]: < 1 where the fragment is +// occluded from the light in the shadow map. 3x3 PCF softens the edges. +float shadow_shade() +{ + if (shadow_intensity <= 0.0) + return 1.0; + + vec4 lp = shadow_light_vp * world_pos; + vec3 proj = lp.xyz / lp.w; + proj = proj * 0.5 + 0.5; + if (proj.z > 1.0) + return 1.0; + + // Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This + // suppresses self-shadow acne without discarding real shadows cast by other objects onto + // back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow). + float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR); + float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0)); + // 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne. + float sum = 0.0; + for (int x = -2; x <= 2; ++x) { + for (int y = -2; y <= 2; ++y) { + float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r; + sum += (proj.z - bias > closest) ? 1.0 : 0.0; + } + } + return 1.0 - shadow_intensity * (sum / 25.0); +} + out vec4 out_color; void main() @@ -167,9 +205,11 @@ void main() } color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb; + float shade = shadow_shade(); + //BBS: add outline_color if (is_outline) { - color = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a); + color = vec4((vec3(intensity.y) + color.rgb * intensity.x) * shade, color.a); vec2 fragCoord = gl_FragCoord.xy; float s = DetectSilho(fragCoord); // Makes silhouettes thicker. @@ -177,13 +217,13 @@ void main() { s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0))); s = max(s, DetectSilho(fragCoord.xy + vec2(0, i))); - } + } out_color = vec4(mix(color.rgb, getBackfaceColor(color.rgb), s), color.a); } #ifdef ENABLE_ENVIRONMENT_MAP else if (use_environment_tex) - out_color = vec4(0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x, color.a); + out_color = vec4((0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x) * shade, color.a); #endif else - out_color = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a); + out_color = vec4((vec3(intensity.y) + color.rgb * intensity.x) * shade, color.a); } \ No newline at end of file diff --git a/resources/shaders/140/phong.fs b/resources/shaders/140/phong.fs index d7456663ec..894e3eb863 100644 --- a/resources/shaders/140/phong.fs +++ b/resources/shaders/140/phong.fs @@ -65,6 +65,12 @@ uniform float z_far; uniform float z_near; uniform bool enable_ssao; +// Depth-based shadow map (object-on-object and self shadows). shadow_intensity == 0 disables it. +uniform sampler2D shadow_map; +uniform mat4 shadow_light_vp; +uniform float shadow_intensity; +uniform float shadow_map_texel; + in vec3 clipping_planes_dots; in float color_clip_plane_dot; @@ -170,11 +176,40 @@ vec3 compute_window_reflection(vec3 normal, vec3 view_dir) float intensity = window_light * bars * (0.15 + 0.15 * fresnel) * facing; - intensity = clamp(intensity, 0.0, 0.25); - + intensity = clamp(intensity, 0.0, 0.25); + return vec3(intensity); } +// Returns a lighting multiplier in [1 - shadow_intensity, 1]: < 1 where the fragment is +// occluded from the light in the shadow map. 3x3 PCF softens the edges. +float shadow_shade() +{ + if (shadow_intensity <= 0.0) + return 1.0; + + vec4 lp = shadow_light_vp * world_pos; + vec3 proj = lp.xyz / lp.w; + proj = proj * 0.5 + 0.5; + if (proj.z > 1.0) + return 1.0; + + // Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This + // suppresses self-shadow acne without discarding real shadows cast by other objects onto + // back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow). + float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR); + float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0)); + // 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne. + float sum = 0.0; + for (int x = -2; x <= 2; ++x) { + for (int y = -2; y <= 2; ++y) { + float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r; + sum += (proj.z - bias > closest) ? 1.0 : 0.0; + } + } + return 1.0 - shadow_intensity * (sum / 25.0); +} + void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -230,8 +265,10 @@ void main() // SSAO is applied in post-process pass. Keep base lighting unchanged here. + float shade = shadow_shade(); + if (is_outline) { - vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS; + vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade; vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a); vec2 fragCoord = gl_FragCoord.xy; float s = DetectSilho(fragCoord); @@ -244,8 +281,8 @@ void main() } #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); + 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 * shade, 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); + out_color = vec4(clamp((vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade, vec3(0.0), vec3(1.0)), color.a); } \ No newline at end of file diff --git a/resources/shaders/140/printbed_shadow.fs b/resources/shaders/140/printbed_shadow.fs new file mode 100644 index 0000000000..650d88950e --- /dev/null +++ b/resources/shaders/140/printbed_shadow.fs @@ -0,0 +1,42 @@ +#version 140 + +// Draws the build-plate as a receiver of the same depth shadow map used for object/self shadows, +// so the plate, objects, and self-shadows all come from one unified technique. +uniform sampler2D shadow_map; +uniform mat4 shadow_light_vp; +uniform float shadow_intensity; +uniform float shadow_map_texel; + +in vec4 world_pos; + +out vec4 out_color; + +// Fraction of the 5x5 PCF kernel occluded from the light. Matches the object shadow shader. +float shadow_occlusion() +{ + vec4 lp = shadow_light_vp * world_pos; + vec3 proj = lp.xyz / lp.w; + proj = proj * 0.5 + 0.5; + if (proj.z > 1.0) + return 0.0; + + // The plate is a pure receiver (never rendered into the shadow map), so a tiny constant + // bias for numerical safety is enough here. + float bias = 0.0004; + float sum = 0.0; + for (int x = -2; x <= 2; ++x) { + for (int y = -2; y <= 2; ++y) { + float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r; + sum += (proj.z - bias > closest) ? 1.0 : 0.0; + } + } + return sum / 25.0; +} + +void main() +{ + float occ = shadow_occlusion(); + if (occ <= 0.0) + discard; + out_color = vec4(0.0, 0.0, 0.0, shadow_intensity * occ); +} diff --git a/resources/shaders/140/printbed_shadow.vs b/resources/shaders/140/printbed_shadow.vs new file mode 100644 index 0000000000..297e9e9a12 --- /dev/null +++ b/resources/shaders/140/printbed_shadow.vs @@ -0,0 +1,16 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +in vec3 v_position; + +// The plate mask quad is authored directly in world coordinates (z = 0 plane), +// so v_position is already the world position of the fragment. +out vec4 world_pos; + +void main() +{ + world_pos = vec4(v_position, 1.0); + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 25283a05bd..5b8943d998 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1231,6 +1231,14 @@ GLCanvas3D::~GLCanvas3D() glsafe(::glDeleteTextures(1, &m_ssao_depth_texture_id)); m_ssao_depth_texture_id = 0; } + if (m_shadow_map_texture_id != 0) { + glsafe(::glDeleteTextures(1, &m_shadow_map_texture_id)); + m_shadow_map_texture_id = 0; + } + if (m_shadow_map_fbo != 0) { + glsafe(::glDeleteFramebuffers(1, &m_shadow_map_fbo)); + m_shadow_map_fbo = 0; + } m_plate_shadow_mask.reset(); } m_plate_shadow_mask_key.clear(); @@ -2033,6 +2041,9 @@ void GLCanvas3D::render(bool only_init) // draw scene glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + // Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps + // the Preview / Assemble canvases from sampling a stale map with an outdated light matrix. + m_shadow_map_valid = false; _render_background(); //BBS add partplater rendering logic @@ -2057,7 +2068,8 @@ void GLCanvas3D::render(bool only_init) _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()); + // Depth pass for object-on-object and self shadows; consumed by the gouraud shader below. + _render_shadows(camera.get_view_matrix(), camera.get_projection_matrix()); _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); _render_sla_slices(); _render_selection(); @@ -7858,9 +7870,8 @@ 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) +void GLCanvas3D::_render_shadows(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)) @@ -7874,54 +7885,181 @@ void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, c 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) + if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) { + + // Light direction (same as used in shading and plate shading) + 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 dir_to_light = (view_rot.transpose() * light_dir_eye).normalized(); + + // Bounding box of the printable objects (the shadow casters). + BoundingBoxf3 obj_bb; + for (const GLVolume* volume : m_volumes.volumes) { + if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower) + continue; + obj_bb.merge(volume->transformed_bounding_box()); + } + if (!obj_bb.defined) + return; // no objects to cast shadows + + // Orthographic light-space basis (z points toward the light). + const Vec3d up = (std::abs(dir_to_light.z()) > 0.99) ? Vec3d::UnitY() : Vec3d::UnitZ(); + const Vec3d z_axis = dir_to_light; + const Vec3d x_axis = up.cross(z_axis).normalized(); + const Vec3d y_axis = z_axis.cross(x_axis).normalized(); + + // Fit the frustum to the object AABB *and* the object's shadow projected onto the plate + // (clamped to the plate footprint). This keeps the map tight/high-res for short shadows + // while still covering long shadows at grazing light angles, which previously fell outside + // the map and were clipped. + const Vec3d ray_dir = -dir_to_light; // direction the shadow travels + const BoundingBoxf3 plate_bb = m_bed.build_volume().valid() ? m_bed.build_volume().bounding_volume() : obj_bb; + + Vec3d lmin(DBL_MAX, DBL_MAX, DBL_MAX); + Vec3d lmax(-DBL_MAX, -DBL_MAX, -DBL_MAX); + auto enclose = [&](const Vec3d& p) { + const Vec3d lp(x_axis.dot(p), y_axis.dot(p), z_axis.dot(p)); + lmin = lmin.cwiseMin(lp); + lmax = lmax.cwiseMax(lp); + }; + for (int i = 0; i < 8; ++i) { + const Vec3d corner((i & 1) ? obj_bb.max.x() : obj_bb.min.x(), + (i & 2) ? obj_bb.max.y() : obj_bb.min.y(), + (i & 4) ? obj_bb.max.z() : obj_bb.min.z()); + enclose(corner); + // Where this corner's shadow lands on z = 0, clamped to the plate so a grazing angle + // (t -> infinity) stays bounded. + if (ray_dir.z() < -1e-6) { + const double t = -corner.z() / ray_dir.z(); + Vec3d s = corner + t * ray_dir; + s.x() = std::min(std::max(s.x(), plate_bb.min.x()), plate_bb.max.x()); + s.y() = std::min(std::max(s.y(), plate_bb.min.y()), plate_bb.max.y()); + s.z() = 0.0; + enclose(s); + } + } + + // Light "camera" placed just past the nearest enclosed point, looking toward the scene. + const double range = lmax.z() - lmin.z(); + const double margin = std::max(1.0, 0.05 * range); + const double cx = 0.5 * (lmin.x() + lmax.x()); + const double cy = 0.5 * (lmin.y() + lmax.y()); + const Vec3d eye = x_axis * cx + y_axis * cy + z_axis * (lmax.z() + margin); + + Matrix4d light_view = Matrix4d::Identity(); + light_view.block<1, 3>(0, 0) = x_axis.transpose(); + light_view.block<1, 3>(1, 0) = y_axis.transpose(); + light_view.block<1, 3>(2, 0) = z_axis.transpose(); + light_view(0, 3) = -x_axis.dot(eye); + light_view(1, 3) = -y_axis.dot(eye); + light_view(2, 3) = -z_axis.dot(eye); + + // Ortho fit to the light-space extent (symmetric in X/Y around cx,cy; +2% edge padding). + const double halfx = std::max(0.5 * (lmax.x() - lmin.x()), 1.0) * 1.02; + const double halfy = std::max(0.5 * (lmax.y() - lmin.y()), 1.0) * 1.02; + const double near_z = margin * 0.5; + const double far_z = range + margin * 1.5; + Matrix4d light_proj = Matrix4d::Identity(); + light_proj(0, 0) = 1.0 / halfx; + light_proj(1, 1) = 1.0 / halfy; + light_proj(2, 2) = -2.0 / (far_z - near_z); + light_proj(2, 3) = -(far_z + near_z) / (far_z - near_z); + + m_shadow_light_vp = Transform3d(light_proj * light_view); + + // Create / resize the depth texture and FBO + const unsigned int size = 2048; + if (m_shadow_map_texture_id == 0) { + glsafe(::glGenTextures(1, &m_shadow_map_texture_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_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_BORDER)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)); + const float border[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + glsafe(::glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border)); + m_shadow_map_size = 0; + } + if (m_shadow_map_size != size) { + glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, size, size, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr)); + m_shadow_map_size = size; + } + if (m_shadow_map_fbo == 0) + glsafe(::glGenFramebuffers(1, &m_shadow_map_fbo)); + + // Save OpenGL state that we will modify + GLint prev_viewport[4] = { 0, 0, 0, 0 }; + glsafe(::glGetIntegerv(GL_VIEWPORT, prev_viewport)); + GLint prev_fbo = 0; + glsafe(::glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo)); + GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE }; + glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask)); + const GLboolean prev_cull = ::glIsEnabled(GL_CULL_FACE); + 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)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_map_fbo)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_map_texture_id, 0)); + glsafe(::glDrawBuffer(GL_NONE)); + glsafe(::glReadBuffer(GL_NONE)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prev_fbo))); + m_shadow_map_valid = false; + } else { + glsafe(::glViewport(0, 0, size, size)); + glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_TRUE)); + glsafe(::glDepthFunc(GL_LESS)); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); + glsafe(::glPolygonOffset(4.0f, 4.0f)); + glsafe(::glDisable(GL_CULL_FACE)); + + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d(light_proj)); + for (GLVolume* volume : m_volumes.volumes) { + if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower) + continue; + const Transform3d view_model = Transform3d(light_view) * volume->world_matrix(); + shader->set_uniform("view_model_matrix", view_model); + volume->model.render(shader); + } + shader->stop_using(); + + // Restore state + glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); + glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3])); + if (prev_cull) + glsafe(::glEnable(GL_CULL_FACE)); + else + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glDepthFunc(prev_depth_func)); + glsafe(::glDepthMask(prev_depth_mask)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prev_fbo))); + glsafe(::glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3])); + + m_shadow_map_valid = true; + } + } else { + m_shadow_map_valid = false; + } + + // ---------------------------------------------------------------------------------- + // Unified plate shadow: draw the build-plate footprint and darken it wherever the same + // depth shadow map (built above) says the light is occluded. This replaces the old planar + // stencil projection so plate, object and self shadows all come from one technique. + // ---------------------------------------------------------------------------------- + if (!m_shadow_map_valid) 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 + GLShaderProgram* plate_shader = wxGetApp().get_shader("printbed_shadow"); + if (plate_shader == nullptr) + return; - // 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") @@ -7977,87 +8115,49 @@ void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, c } if (m_plate_shadow_mask.is_initialized()) { - shader->set_uniform("view_model_matrix", view_matrix); - m_plate_shadow_mask.render(shader); + // Blend the shadow over the already-drawn plate. Depth test keeps it behind anything + // already in front; depth writes are off, and a small negative polygon offset lifts it + // just above the bed to avoid z-fighting. + 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(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glDepthFunc(GL_LEQUAL)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); + glsafe(::glPolygonOffset(-1.0f, -1.0f)); + + glsafe(::glActiveTexture(GL_TEXTURE4)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id)); + glsafe(::glActiveTexture(GL_TEXTURE0)); + + plate_shader->start_using(); + plate_shader->set_uniform("view_model_matrix", view_matrix); + plate_shader->set_uniform("projection_matrix", projection_matrix); + plate_shader->set_uniform("shadow_map", 4); + plate_shader->set_uniform("shadow_light_vp", m_shadow_light_vp); + plate_shader->set_uniform("shadow_intensity", 0.35f); + plate_shader->set_uniform("shadow_map_texel", 1.0f / static_cast(m_shadow_map_size)); + m_plate_shadow_mask.render(plate_shader); + plate_shader->stop_using(); + + glsafe(::glActiveTexture(GL_TEXTURE4)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glActiveTexture(GL_TEXTURE0)); + + glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDepthFunc(prev_depth_func)); + glsafe(::glDepthMask(prev_depth_mask)); } } - - // ============================================================ - // 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 @@ -8142,6 +8242,20 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with const bool phong_ssao = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO); shader->set_uniform("enable_ssao", phong_ssao); + // Object-on-object and self shadows: sample the depth map built in _render_shadow_map_pass(). + // shadow_intensity == 0 disables the effect entirely (unchanged behavior when off / unsupported). + if (m_shadow_map_valid && m_shadow_map_texture_id != 0) { + glsafe(::glActiveTexture(GL_TEXTURE4)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id)); + glsafe(::glActiveTexture(GL_TEXTURE0)); + shader->set_uniform("shadow_map", 4); + shader->set_uniform("shadow_light_vp", m_shadow_light_vp); + shader->set_uniform("shadow_map_texel", 1.0f / static_cast(m_shadow_map_size)); + shader->set_uniform("shadow_intensity", 0.35f); + } + else + shader->set_uniform("shadow_intensity", 0.0f); + const Size& cvn_size = get_canvas_size(); { const Camera& camera = wxGetApp().plater()->get_camera(); @@ -8233,6 +8347,12 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with shader->set_uniform("show_wireframe", false); }*/ + if (m_shadow_map_valid && m_shadow_map_texture_id != 0) { + glsafe(::glActiveTexture(GL_TEXTURE4)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glActiveTexture(GL_TEXTURE0)); + } + shader->stop_using(); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a3209dcc1c..4c2402c546 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -732,6 +732,12 @@ public: std::array m_ssao_texture_size{ { 0, 0 } }; GLModel m_plate_shadow_mask; std::string m_plate_shadow_mask_key; + // Depth-based shadow map used to cast object shadows onto other objects and themselves. + unsigned int m_shadow_map_fbo{ 0 }; + unsigned int m_shadow_map_texture_id{ 0 }; + unsigned int m_shadow_map_size{ 0 }; + Transform3d m_shadow_light_vp{ Transform3d::Identity() }; + bool m_shadow_map_valid{ false }; public: explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed); ~GLCanvas3D(); @@ -1251,7 +1257,9 @@ private: 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); + // Build the light-space depth shadow map (consumed by gouraud/phong for object & self shadows) + // and cast it onto the build plate. Realistic view only. + void _render_shadows(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 d368aaecd2..2b8a0edf0a 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -68,6 +68,10 @@ std::pair GLShadersManager::init() valid &= append_shader("thumbnail", { prefix + "thumbnail.vs", prefix + "thumbnail.fs"}); // used to render printbed valid &= append_shader("printbed", { prefix + "printbed.vs", prefix + "printbed.fs" }); +#if !SLIC3R_OPENGL_ES + // used to cast the object shadow map onto the build plate (realistic view) + valid &= append_shader("printbed_shadow", { prefix + "printbed_shadow.vs", prefix + "printbed_shadow.fs" }); +#endif // !SLIC3R_OPENGL_ES valid &= append_shader("hotbed", {prefix + "hotbed.vs", prefix + "hotbed.fs"}); // used to render options in gcode preview if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 20b45d7731..e1501e9c4d 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1827,7 +1827,7 @@ void PreferencesDialog::create_items() auto item_realistic_shadows = create_item_checkbox( _L("Shadows"), - _L("Renders cast shadows on the plate in realistic view."), + _L("Renders cast shadows on the plate, other objects, and each object onto itself in realistic view."), SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS ); g_sizer->Add(item_realistic_shadows);