Improve preview colors (#15809)

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
Ian Bassi
2026-09-22 20:50:18 -03:00
committed by GitHub
co-authored by Rodrigo Faselli
parent f83bfa17ff
commit d820303a3f
15 changed files with 512 additions and 209 deletions
+53 -29
View File
@@ -7,18 +7,38 @@
uniform sampler2D color_texture;
uniform sampler2D depth_texture;
uniform sampler2D normal_texture;
uniform vec2 inv_tex_size;
uniform float z_near;
uniform float z_far;
uniform bool is_outline;
// The pass has no normal target to read, so the surface normal is reconstructed from the depth
// buffer. inv_projection_matrix unprojects a pixel back into view space and up_view is world +Z
// expressed in view space, which is what tells a top surface from a wall.
uniform mat4 inv_projection_matrix;
uniform vec3 up_view;
varying vec2 tex_coord;
float linearize_depth(float depth)
// Position of the given pixel in view space. Valid under both an orthographic and a perspective
// camera, unlike the depth linearization it replaces.
vec3 view_pos(vec2 uv)
{
float z = depth * 2.0 - 1.0;
return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near));
vec2 c = clamp(uv, vec2(0.0), vec2(1.0));
float d = texture2D(depth_texture, c).r;
vec4 ndc = vec4(c * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
vec4 view = inv_projection_matrix * ndc;
return view.xyz / view.w;
}
// Surface normal at the given pixel, from the forward differences of the reconstructed view
// position. It rings by a pixel across a depth discontinuity, which is acceptable here: the
// normal only weights the occlusion, nothing is shaded with it.
vec3 view_normal(vec2 uv, vec3 p)
{
vec3 px = view_pos(uv + vec2(inv_tex_size.x, 0.0));
vec3 py = view_pos(uv + vec2(0.0, inv_tex_size.y));
vec3 n = cross(px - p, py - p);
float len = length(n);
return (len > 1e-8) ? n / len : vec3(0.0, 0.0, 1.0);
}
void main()
@@ -28,16 +48,21 @@ void main()
return;
}
vec3 base = texture2D(color_texture, tex_coord).rgb;
float depth_center = linearize_depth(texture2D(depth_texture, tex_coord).r);
// Sample normal at current fragment (range: -1 to 1)
vec3 normal_center = texture2D(normal_texture, tex_coord).rgb * 2.0 - 1.0;
// Nothing was drawn here: occluding the background would only darken the gradient, and its
// reconstructed normal is degenerate anyway.
if (texture2D(depth_texture, tex_coord).r >= 0.9999) {
gl_FragColor = vec4(base, 1.0);
return;
}
vec3 center_pos = view_pos(tex_coord);
float depth_center = -center_pos.z;
vec3 normal_center = view_normal(tex_coord, center_pos);
// Calculate how much the surface faces upward
// up_factor = 1.0 for surfaces pointing straight up (0,0,1)
// up_factor = 0.0 for surfaces pointing down or sideways
float up_factor = max(0.0, normal_center.z); // Assuming Z is up axis
// Alternative: if Y is up, use normal_center.y
// up_factor = 1.0 for surfaces pointing straight up, 0.0 for walls and downward faces
float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0);
// Adaptive sampling radius
float radius = mix(2.0, 4.0, depth_center / z_far);
@@ -52,39 +77,38 @@ void main()
offsets[6] = vec2( 0.0, -1.0);
offsets[7] = vec2( 0.707,-0.707);
// Occlusion is a slope, not a depth difference: how far a neighbour rises out of the
// centre's tangent plane over how far away it is. Unlike a raw difference, that sine is
// free of camera distance and zoom, so a crease reads the same from any view.
const float SLOPE_MIN = 0.08; // ~5 degrees, above the depth-buffer noise of a flat surface
const float SLOPE_MAX = 0.60; // ~37 degrees, a full crease
const float SAMPLE_COUNT = 8.0;
float occlusion = 0.0;
int valid_samples = 0;
for (int i = 0; i < 8; ++i) {
vec2 uv = tex_coord + offsets[i] * inv_tex_size * radius;
uv = clamp(uv, vec2(0.001), vec2(0.999));
float sample_depth = linearize_depth(texture2D(depth_texture, uv).r);
float depth_diff = max(0.0, depth_center - sample_depth);
float threshold = 0.015 * (0.5 + depth_center / z_far);
float contribution = smoothstep(0.001, threshold, depth_diff);
vec3 delta = view_pos(uv) - center_pos;
float dist = length(delta);
float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0;
float contribution = smoothstep(SLOPE_MIN, SLOPE_MAX, rise);
float diagonal_weight = 1.0 - abs(offsets[i].x * offsets[i].y) * 0.5;
occlusion += contribution * diagonal_weight;
valid_samples++;
}
if (valid_samples > 0)
occlusion /= float(valid_samples);
occlusion /= SAMPLE_COUNT;
// flatter/top-like surfaces get less darkening
float ao_intensity = 0.55;
float ambient_occlusion = 1.0 - occlusion * ao_intensity;
// Different min values for top vs bottom surfaces
// Different min values for top vs bottom surfaces. The boost that used to follow lifted a
// top surface back to within 2% of unoccluded once up_factor became a real normal rather
// than a colour, which is where the AO went; the floors alone shape the effect now.
float ao_min = mix(0.45, 0.70, up_factor); // Bottom: 0.45, Top: 0.70
ambient_occlusion = clamp(ambient_occlusion, ao_min, 1.0);
// Boost brightness on top surfaces (optional)
float brightness_boost = 1.0 + up_factor * 0.15; // 15% extra brightness on top
ambient_occlusion = pow(ambient_occlusion, 2.2) * brightness_boost;
ambient_occlusion = clamp(ambient_occlusion, 0.45, 1.05);
gl_FragColor = vec4(base * ambient_occlusion, 1.0);
}
}
+71 -59
View File
@@ -1,24 +1,45 @@
#version 140
/**
* SSAO Shader - GLSL 140 version with sharp depth threshold
* SSAO Shader - GLSL 140 version with a slope-based occlusion test
* Only darkens valleys/concave areas, ignores smooth variations
*/
uniform sampler2D color_texture;
uniform sampler2D depth_texture;
uniform sampler2D normal_texture;
uniform float z_near;
uniform vec2 inv_tex_size;
uniform float z_far;
uniform bool is_outline;
// The pass has no normal target to read, so the surface normal is reconstructed from the depth
// buffer. inv_projection_matrix unprojects a pixel back into view space and up_view is world +Z
// expressed in view space, which is what tells a top surface from a wall.
uniform mat4 inv_projection_matrix;
uniform vec3 up_view;
in vec2 tex_coord;
out vec4 frag_color;
float linearize_depth(float depth)
// Position of the given pixel in view space. Valid under both an orthographic and a perspective
// camera, unlike the depth linearization it replaces.
vec3 view_pos(ivec2 pixel)
{
float z = depth * 2.0 - 1.0;
return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near));
ivec2 p = clamp(pixel, ivec2(0), textureSize(depth_texture, 0) - 1);
float d = texelFetch(depth_texture, p, 0).r;
vec4 ndc = vec4((vec2(p) + 0.5) * inv_tex_size * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
vec4 view = inv_projection_matrix * ndc;
return view.xyz / view.w;
}
// Surface normal at the given pixel, from the forward differences of the reconstructed view
// position. It rings by a pixel across a depth discontinuity, which is acceptable here: the
// normal only weights the occlusion, nothing is shaded with it.
vec3 view_normal(ivec2 pixel, vec3 p)
{
vec3 px = view_pos(pixel + ivec2(1, 0));
vec3 py = view_pos(pixel + ivec2(0, 1));
vec3 n = cross(px - p, py - p);
float len = length(n);
return (len > 1e-8) ? n / len : vec3(0.0, 0.0, 1.0);
}
void main()
@@ -28,81 +49,72 @@ void main()
return;
}
ivec2 pixel = ivec2(gl_FragCoord.xy);
float center_depth = linearize_depth(texelFetch(depth_texture, pixel, 0).r);
// Sample normal buffer (stored as RGB in 0-1 range, convert to -1 to 1)
vec3 normal_center = texelFetch(normal_texture, pixel, 0).rgb * 2.0 - 1.0;
normal_center = normalize(normal_center);
vec3 color = texture(color_texture, tex_coord).rgb;
// Nothing was drawn here: occluding the background would only darken the gradient, and its
// reconstructed normal is degenerate anyway.
if (texelFetch(depth_texture, pixel, 0).r >= 0.9999) {
frag_color = vec4(color, 1.0);
return;
}
vec3 center_pos = view_pos(pixel);
float center_depth = -center_pos.z;
vec3 normal_center = view_normal(pixel, center_pos);
// Calculate upward-facing factor (Z-up coordinate system)
float up_factor = clamp(normal_center.z * 1.5, 0.0, 1.0);
float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0);
// Adaptive radius in pixel space
int radius = int(mix(2.0, 4.0, center_depth / z_far));
// Optimized sampling pattern
const ivec2 offsets[12] = ivec2[](
const int SAMPLE_COUNT = 12;
const ivec2 offsets[SAMPLE_COUNT] = ivec2[](
ivec2(1, 0), ivec2(-1, 0), ivec2(0, 1), ivec2(0, -1),
ivec2(1, 1), ivec2(-1, 1), ivec2(1, -1), ivec2(-1, -1),
ivec2(2, 0), ivec2(-2, 0), ivec2(0, 2), ivec2(0, -2)
);
float occlusion = 0.0;
int valid_samples = 0;
// Occlusion is a slope, not a depth difference: the sine of the angle a neighbour subtends
// above the centre's tangent plane. A raw difference depends on camera distance and zoom,
// so no fixed thresholds suit both a 0.2 mm layer step and a 5 mm overhang.
const float SLOPE_MIN = 0.08; // ~5 degrees, above the depth-buffer noise of a flat surface
const float SLOPE_MAX = 0.60; // ~37 degrees, a full crease
for (int i = 0; i < 12; i++) {
float occlusion = 0.0;
for (int i = 0; i < SAMPLE_COUNT; i++) {
// No edge rejection: view_pos clamps, giving a near-zero delta and no occlusion.
// Rejecting one side only would bias the denominator against the other.
ivec2 sample_pixel = pixel + offsets[i] * radius;
if (sample_pixel.x < 0 || sample_pixel.y < 0)
continue;
float sample_depth = linearize_depth(texelFetch(depth_texture, sample_pixel, 0).r);
// Sample normal at neighbor
vec3 normal_sample = texelFetch(normal_texture, sample_pixel, 0).rgb * 2.0 - 1.0;
// Depth difference (positive if neighbor is closer to camera)
float depth_diff = center_depth - sample_depth;
// Sharp depth threshold ===
// Minimum depth difference to consider occlusion (ignores small variations)
float threshold_min = 0.008; // Higher = only deep valleys get darkened
float threshold_max = 0.04; // Transition range for full occlusion
vec3 delta = view_pos(sample_pixel) - center_pos;
float dist = length(delta);
// How far the neighbour rises towards the viewer out of the centre's tangent plane. A
// flat surface gives ~0 whatever its orientation, so this also subsumes the separate
// planar test the normals were compared for.
float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0;
float contribution = 0.0;
if (depth_diff > threshold_min) {
if (rise > SLOPE_MIN) {
// Abrupt mapping with power curve
contribution = (depth_diff - threshold_min) / (threshold_max - threshold_min);
contribution = (rise - SLOPE_MIN) / (SLOPE_MAX - SLOPE_MIN);
contribution = clamp(contribution, 0.0, 1.0);
contribution = pow(contribution, 2.0); // Steeper curve for sharper transition
}
// Reduce occlusion on planar surfaces (similar normals)
float normal_similarity = dot(normal_center, normal_sample);
float planar_factor = smoothstep(0.75, 0.95, normal_similarity);
contribution *= (1.0 - planar_factor * 0.6);
occlusion += contribution;
valid_samples++;
}
if (valid_samples > 0) {
// Calculate ambient occlusion factor with higher base intensity
float ao_factor = 1.0 - (occlusion / float(valid_samples)) * 0.6;
// Keep bright areas clean (higher minimum for upward-facing surfaces)
float ao_min = mix(0.55, 0.85, up_factor);
ao_factor = clamp(ao_factor, ao_min, 1.0);
// Slight brightness boost for upward-facing surfaces
float brightness_boost = 1.0 + up_factor * 0.15;
ao_factor = ao_factor * brightness_boost;
occlusion = ao_factor;
} else {
occlusion = 1.0;
}
// Calculate ambient occlusion factor with higher base intensity
float ao_factor = 1.0 - (occlusion / float(SAMPLE_COUNT)) * 0.6;
// Keep bright areas clean (higher minimum for upward-facing surfaces). The old 0.85 floor
// and 1.15 boost were set when up_factor came from the colour buffer and read ~0; with a
// real normal they capped a top surface at 2% darkening, which hid the AO entirely.
float ao_min = mix(0.45, 0.70, up_factor);
occlusion = clamp(ao_factor, ao_min, 1.0);
vec3 color = texture(color_texture, tex_coord).rgb;
frag_color = vec4(color * occlusion, 1.0);
}
}
+3
View File
@@ -303,6 +303,9 @@ void AppConfig::set_defaults()
if (get(SETTING_OPENGL_REALISTIC_PHONG).empty())
set_bool(SETTING_OPENGL_REALISTIC_PHONG, true);
if (get(SETTING_OPENGL_REALISTIC_PREVIEW).empty())
set_bool(SETTING_OPENGL_REALISTIC_PREVIEW, false);
if (get(SETTING_OPENGL_SHADING_MODEL).empty())
set(SETTING_OPENGL_SHADING_MODEL, "gouraud");
+1
View File
@@ -42,6 +42,7 @@ using namespace nlohmann;
#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows"
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
#define SETTING_OPENGL_REALISTIC_PREVIEW "opengl_realistic_preview"
#define SETTING_PLUGIN_PAGES_VISIBLE_COUNT "plugin_pages_visible_count"
#define PLUGIN_PAGES_VISIBLE_COUNT_MIN 1
+16
View File
@@ -60,6 +60,22 @@ public:
// using the given camera matrices.
//
void render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
//
// ORCA: realistic view. Render the toolpaths as seen from the light, to fill the caller's
// shadow map. Depth only - the caller masks colour writes and owns the framebuffer.
//
void render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position);
//
// ORCA: realistic view. The shadow map the toolpaths sample, in the given texture unit.
// intensity == 0, the default, turns the lookup off and restores the plain shading.
//
void set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size);
//
// ORCA: tone applied to the shaded toolpaths, to pay back the light the lighting term,
// the shadow and the SSAO pass each take off. 1.0/1.0, the default, is a no-op; the
// caller decides which of the two it varies with the realistic view setting.
//
void set_tone(float exposure, float saturation);
//
// ************************************************************************
+62 -6
View File
@@ -16,7 +16,8 @@ static const char* Segments_Vertex_Shader =
"#define FIX_TWISTING\n"
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
"const float light_top_specular = 0.6 * 0.125;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
"const float light_top_shininess = 20.0;\n"
"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n"
"const float light_front_diffuse = 0.6 * 0.2;\n"
@@ -30,8 +31,19 @@ static const char* Segments_Vertex_Shader =
"uniform samplerBuffer height_width_angle_tex;\n"
"uniform samplerBuffer color_tex;\n"
"uniform usamplerBuffer segment_index_tex;\n"
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
// world_position, so the caster would write a depth the receiver never looks up.
"uniform float bias_scale;\n"
"in int vertex_id;\n"
"out vec3 color;\n"
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
"// ambient and emissive terms in color, which a shadow does not occlude. Their sum is the\n"
"// single lighting term this replaces, so shading is unchanged while shadows are off.\n"
"out vec3 color_direct;\n"
"// ORCA: realistic view - the fragment shader looks the fragment up in the shadow map, which\n"
"// needs its world position and, for the depth bias, its eye space normal.\n"
"out vec3 world_position;\n"
"out vec3 shadow_normal;\n"
"vec3 decode_color(float color) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
@@ -40,11 +52,11 @@ static const char* Segments_Vertex_Shader =
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n"
" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n"
" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n"
" return top_diffuse + front_diffuse + top_specular;\n"
"}\n"
"void main() {\n"
" int id_a = int(texelFetch(segment_index_tex, gl_InstanceID).r);\n"
@@ -135,19 +147,63 @@ static const char* Segments_Vertex_Shader =
" }\n"
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
" eye_position.z += bias;\n"
" eye_position.z += bias * bias_scale;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" color = color_base * (ambient + emission);\n"
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
" world_position = pos;\n"
" shadow_normal = eye_normal;\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
static const char* Segments_Fragment_Shader =
"#version 150\n"
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
"uniform float shadow_map_texel;\n"
// ORCA: the lighting term peaks near 0.9 and every later multiplier - the shadow, then the SSAO
// post pass - only takes more light away, so the print reads dimmer and duller than the legend
// colours. These pay that back. Both are 1.0 for an untouched image; what the caller actually
// passes in each mode is decided in GLCanvas3D::_render_gcode, not here.
"uniform float exposure;\n"
"uniform float saturation;\n"
"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n"
"in vec3 color;\n"
"in vec3 color_direct;\n"
"in vec3 world_position;\n"
"in vec3 shadow_normal;\n"
"out vec4 fragment_color;\n"
"float shadow_shade() {\n"
" if (shadow_intensity <= 0.0)\n"
" return 1.0;\n"
" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n"
" vec3 proj = lp.xyz / lp.w;\n"
" proj = proj * 0.5 + 0.5;\n"
" if (proj.z > 1.0)\n"
" return 1.0;\n"
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n"
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
" float sum = 0.0;\n"
" for (int x = -2; x <= 2; ++x) {\n"
" for (int y = -2; y <= 2; ++y) {\n"
" float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r;\n"
" sum += (proj.z - bias > closest) ? 1.0 : 0.0;\n"
" }\n"
" }\n"
" return 1.0 - shadow_intensity * (sum / 25.0);\n"
"}\n"
"void main() {\n"
" fragment_color = vec4(color, 1.0);\n"
" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n"
" c = mix(vec3(dot(c, LUMA)), c, saturation);\n"
" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n"
"}\n";
static const char* Options_Vertex_Shader =
+61 -5
View File
@@ -17,7 +17,8 @@ static const char* Segments_Vertex_Shader_ES =
"#define FIX_TWISTING\n"
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
"const float light_top_specular = 0.6 * 0.125;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
"const float light_top_shininess = 20.0;\n"
"const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n"
"const float light_front_diffuse = 0.6 * 0.3;\n"
@@ -33,6 +34,14 @@ static const char* Segments_Vertex_Shader_ES =
"uniform usampler2D segment_index_tex;\n"
"in float vertex_id_float;\n"
"out vec3 color;\n"
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
"// ambient and emissive terms in color, which a shadow does not occlude. Their sum is the\n"
"// single lighting term this replaces, so shading is unchanged while shadows are off.\n"
"out vec3 color_direct;\n"
"// ORCA: realistic view - the fragment shader looks the fragment up in the shadow map, which\n"
"// needs its world position and, for the depth bias, its eye space normal.\n"
"out vec3 world_position;\n"
"out vec3 shadow_normal;\n"
"vec3 decode_color(float color) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
@@ -41,11 +50,11 @@ static const char* Segments_Vertex_Shader_ES =
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
" float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n"
" float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n"
" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n"
" return top_diffuse + front_diffuse + top_specular;\n"
"}\n"
"ivec2 tex_coord(sampler2D sampler, int id) {\n"
" ivec2 tex_size = textureSize(sampler, 0);\n"
@@ -143,17 +152,64 @@ static const char* Segments_Vertex_Shader_ES =
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" color = color_base * (ambient + emission);\n"
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
" world_position = pos;\n"
" shadow_normal = eye_normal;\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
static const char* Segments_Fragment_Shader_ES =
"#version 300 es\n"
"precision highp float;\n"
"// ORCA: sampler2D defaults to lowp in an ES fragment shader, far too coarse to compare\n"
"// shadow map depths against.\n"
"precision highp sampler2D;\n"
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
"uniform float shadow_map_texel;\n"
// ORCA: the lighting term peaks near 0.9 and every later multiplier - the shadow, then the SSAO
// post pass - only takes more light away, so the print reads dimmer and duller than the legend
// colours. These pay that back. Both are 1.0 for an untouched image; what the caller actually
// passes in each mode is decided in GLCanvas3D::_render_gcode, not here.
"uniform float exposure;\n"
"uniform float saturation;\n"
"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n"
"in vec3 color;\n"
"in vec3 color_direct;\n"
"in vec3 world_position;\n"
"in vec3 shadow_normal;\n"
"out vec4 fragment_color;\n"
"float shadow_shade() {\n"
" if (shadow_intensity <= 0.0)\n"
" return 1.0;\n"
" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n"
" vec3 proj = lp.xyz / lp.w;\n"
" proj = proj * 0.5 + 0.5;\n"
" if (proj.z > 1.0)\n"
" return 1.0;\n"
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n"
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
" float sum = 0.0;\n"
" for (int x = -2; x <= 2; ++x) {\n"
" for (int y = -2; y <= 2; ++y) {\n"
" float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r;\n"
" sum += (proj.z - bias > closest) ? 1.0 : 0.0;\n"
" }\n"
" }\n"
" return 1.0 - shadow_intensity * (sum / 25.0);\n"
"}\n"
"void main() {\n"
" fragment_color = vec4(color, 1.0);\n"
" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n"
" c = mix(vec3(dot(c, LUMA)), c, saturation);\n"
" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n"
"}\n";
static const char* Options_Vertex_Shader_ES =
+15
View File
@@ -42,6 +42,21 @@ void Viewer::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
m_impl->render(view_matrix, projection_matrix);
}
void Viewer::render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position)
{
m_impl->render_shadow_casters(view_matrix, projection_matrix, light_position);
}
void Viewer::set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size)
{
m_impl->set_shadow_map(texture_unit, light_view_projection, intensity, texel_size);
}
void Viewer::set_tone(float exposure, float saturation)
{
m_impl->set_tone(exposure, saturation);
}
EViewType Viewer::get_view_type() const
{
return m_impl->get_view_type();
+47 -1
View File
@@ -763,6 +763,14 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
// ORCA: realistic view
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
m_uni_segments_shadow_intensity_id = glGetUniformLocation(m_segments_shader_id, "shadow_intensity");
m_uni_segments_shadow_map_texel_id = glGetUniformLocation(m_segments_shader_id, "shadow_map_texel");
m_uni_segments_exposure_id = glGetUniformLocation(m_segments_shader_id, "exposure");
m_uni_segments_saturation_id = glGetUniformLocation(m_segments_shader_id, "saturation");
m_uni_segments_bias_scale_id = glGetUniformLocation(m_segments_shader_id, "bias_scale");
glcheck();
assert(m_uni_segments_view_matrix_id != -1 &&
m_uni_segments_projection_matrix_id != -1 &&
@@ -1321,7 +1329,7 @@ void ViewerImpl::update_colors()
m_settings.update_colors = false;
}
void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
void ViewerImpl::apply_pending_updates()
{
if (m_settings.update_view_full_range)
update_view_full_range();
@@ -1331,6 +1339,11 @@ void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matr
if (m_settings.update_colors)
update_colors();
}
void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
{
apply_pending_updates();
const Mat4x4 inv_view_matrix = inverse(view_matrix);
const Vec3 camera_position = { inv_view_matrix[12], inv_view_matrix[13], inv_view_matrix[14] };
@@ -1345,6 +1358,30 @@ void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matr
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
}
void ViewerImpl::render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position)
{
apply_pending_updates();
// Only the extrusions and travels cast: the option markers are indicators, not material.
m_rendering_shadow_casters = true;
render_segments(view_matrix, projection_matrix, light_position);
m_rendering_shadow_casters = false;
}
void ViewerImpl::set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size)
{
m_shadow_map_texture_unit = texture_unit;
m_shadow_light_vp = light_view_projection;
m_shadow_intensity = intensity;
m_shadow_map_texel = texel_size;
}
void ViewerImpl::set_tone(float exposure, float saturation)
{
m_exposure = exposure;
m_saturation = saturation;
}
void ViewerImpl::set_view_type(EViewType type)
{
m_settings.view_type = type;
@@ -1994,6 +2031,15 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off.
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data()));
glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_rendering_shadow_casters ? 0.0f : m_shadow_intensity));
glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel));
glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure));
glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation));
glsafe(glUniform1f(m_uni_segments_bias_scale_id, m_rendering_shadow_casters ? 0.0f : 1.0f));
glsafe(glDisable(GL_CULL_FACE));
+46
View File
@@ -71,6 +71,24 @@ public:
// Render the toolpaths
//
void render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
//
// ORCA: realistic view. Render the toolpaths as seen from the light, to fill the caller's
// shadow map. Only depth matters here, so the caller masks colour writes; light_position
// takes the place of the camera when the segment boxes are expanded, which gives their
// silhouette as the light sees it.
//
void render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position);
//
// ORCA: realistic view. The shadow map the toolpaths sample, in the given texture unit.
// intensity == 0, the default, turns the lookup off and restores the plain shading.
//
void set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size);
//
// ORCA: tone applied to the shaded toolpaths, to pay back the light the lighting term,
// the shadow and the SSAO pass each take off. 1.0/1.0, the default, is a no-op; the
// caller decides which of the two it varies with the realistic view setting.
//
void set_tone(float exposure, float saturation);
EViewType get_view_type() const { return m_settings.view_type; }
void set_view_type(EViewType type);
@@ -330,6 +348,13 @@ private:
int m_uni_segments_height_width_angle_tex_id{ -1 };
int m_uni_segments_colors_tex_id{ -1 };
int m_uni_segments_segment_index_tex_id{ -1 };
int m_uni_segments_shadow_map_id{ -1 };
int m_uni_segments_shadow_light_vp_id{ -1 };
int m_uni_segments_shadow_intensity_id{ -1 };
int m_uni_segments_shadow_map_texel_id{ -1 };
int m_uni_segments_exposure_id{ -1 };
int m_uni_segments_saturation_id{ -1 };
int m_uni_segments_bias_scale_id{ -1 };
//
// Caches for OpenGL uniforms id for options shader
//
@@ -469,6 +494,27 @@ private:
size_t m_enabled_options_tex_size{ 0 };
#endif // ENABLE_OPENGL_ES
//
// ORCA: realistic view. Shadow map state set by set_shadow_map(), consumed by the segments
// shader. m_rendering_shadow_casters forces the intensity to 0 for the depth pass, which
// must not sample the very map it is writing.
//
// Defaults past the four texture units render_segments() binds itself, so the sampler never
// aliases one of the buffer textures before the owner of the map has said where it lives.
int m_shadow_map_texture_unit{ 4 };
Mat4x4 m_shadow_light_vp{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
float m_shadow_intensity{ 0.0f };
float m_shadow_map_texel{ 0.0f };
bool m_rendering_shadow_casters{ false };
//
// ORCA: realistic view. Tone set by set_tone(), consumed by the segments shader.
// The identity values leave the shading as it is outside realistic view.
//
float m_exposure{ 1.0f };
float m_saturation{ 1.0f };
void apply_pending_updates();
void update_view_full_range();
void update_color_ranges();
void update_heights_widths();
+23
View File
@@ -1641,6 +1641,29 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height)
m_sequential_view.render_marker(!m_no_render_path, canvas_width, sequential_view_height(canvas_height), m_viewer.get_view_type());
}
void GCodeViewer::render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position)
{
if (!has_data())
return;
m_viewer.render_shadow_casters(
libvgcode::convert(static_cast<Matrix4f>(light_view_matrix.matrix().cast<float>())),
libvgcode::convert(static_cast<Matrix4f>(light_projection_matrix.matrix().cast<float>())),
libvgcode::convert(static_cast<Vec3f>(light_position.cast<float>())));
}
void GCodeViewer::set_shadow_map(int texture_unit, const Transform3d& light_view_projection, float intensity, float texel_size)
{
m_viewer.set_shadow_map(texture_unit,
libvgcode::convert(static_cast<Matrix4f>(light_view_projection.matrix().cast<float>())),
intensity, texel_size);
}
void GCodeViewer::set_tone(float exposure, float saturation)
{
m_viewer.set_tone(exposure, saturation);
}
void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin)
{
if (m_viewer.get_extrusion_roles().empty())
+7
View File
@@ -279,6 +279,13 @@ public:
void render_scene(int canvas_width, int canvas_height);
// Legend, sliders, the marker's position window and the G-code window, all ImGui.
void render_overlay(int canvas_width, int canvas_height, int right_margin);
// ORCA: realistic view. Depth-only pass drawing the toolpaths as the light sees them, into
// the shadow map the caller has bound, and the map they sample back in render_scene.
void render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position);
void set_shadow_map(int texture_unit, const Transform3d& light_view_projection, float intensity, float texel_size);
// ORCA: tone applied to the shaded toolpaths, paying back the light the lighting term,
// the shadow and the SSAO pass each take off. 1.0/1.0 is a no-op.
void set_tone(float exposure, float saturation);
//BBS
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
+97 -109
View File
@@ -2199,8 +2199,8 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
// Recorded by PartPlate::render_icons() below, when it runs.
wxGetApp().plater()->get_partplate_list().clear_hover_tooltip();
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.
// Invalidate the shadow map each frame; the View3D and Preview paths below rebuild it. This
// keeps the Assemble canvas from sampling a stale map with an outdated light matrix.
m_shadow_map_valid = false;
_render_background();
@@ -2251,6 +2251,8 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
_render_selection();
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id);
// Realistic view: the print casts a shadow onto the plate here as it does in View3D.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
// BBS: GUI refactor: add canvas size as parameters
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
}
@@ -7644,11 +7646,20 @@ bool GLCanvas3D::_is_fxaa_enabled() const
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_FXAA_ENABLED);
}
bool GLCanvas3D::_is_realistic_view_enabled() const
{
const AppConfig* cfg = wxGetApp().app_config;
if (cfg == nullptr || !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE))
return false;
// Prepare and Assemble follow the umbrella toggle alone; Preview needs its own opt-in.
return m_canvas_type != ECanvasType::CanvasPreview || cfg->get_bool(SETTING_OPENGL_REALISTIC_PREVIEW);
}
bool GLCanvas3D::_is_ssao_enabled() const
{
if (wxGetApp().app_config == nullptr)
return false;
return wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE) &&
return _is_realistic_view_enabled() &&
wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO);
}
@@ -7803,95 +7814,23 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int 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<Vec3d> 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<float>(build_volume.circle().center.x()), unscaled<float>(build_volume.circle().center.y()));
const float r = unscaled<float>(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<float>(width), 1.0f / static_cast<float>(height)));
shader->set_uniform("z_near", camera.get_near_z());
shader->set_uniform("z_far", camera.get_far_z());
// The shader reconstructs the surface normal from the depth buffer, there being no normal
// target to read: it unprojects a pixel back into view space, then measures the result
// against world +Z expressed in view space to tell a top surface from a wall.
const Matrix4d inv_projection_matrix = camera.get_projection_matrix().matrix().inverse();
shader->set_uniform("inv_projection_matrix", inv_projection_matrix);
const Vec3d up_view = (camera.get_view_matrix().matrix().block<3, 3>(0, 0) * Vec3d::UnitZ()).normalized();
shader->set_uniform("up_view", up_view);
glsafe(::glActiveTexture(GL_TEXTURE0));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_color_texture_id));
@@ -7903,13 +7842,6 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height)
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));
@@ -8154,15 +8086,19 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
{
if (wxGetApp().app_config == nullptr)
return;
if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE))
if (!_is_realistic_view_enabled())
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)
// The preview canvas holds no volumes of its own for FFF. Once slicing has run its printed
// geometry is the G-code toolpaths, which both cast into the map here and sample it back in
// _render_gcode; before slicing there are only shells, and nothing casts at all. View3D and
// SLA preview use m_volumes. The shells are deliberately never casters: they are a
// translucent ghost of the whole object, so they would drop the solid shadow of a print that
// has not been sliced, and at any layer below the last, one that is not there yet.
const bool toolpath_casters = m_canvas_type == ECanvasType::CanvasPreview && m_gcode_viewer.has_data();
if (!toolpath_casters && m_volumes.empty())
return;
if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) {
@@ -8174,10 +8110,30 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
// 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 (toolpath_casters) {
// Merged corner by corner: BoundingBoxf3(min, max) marks itself undefined at zero
// Z extent, which a single layer print gives, and the check below would then drop
// every shadow in the frame.
const BoundingBoxf3& paths_bb = m_gcode_viewer.get_paths_bounding_box();
if ((paths_bb.min.array() <= paths_bb.max.array()).all()) {
obj_bb.merge(paths_bb.min);
obj_bb.merge(paths_bb.max);
}
// Only the enabled layers are drawn, so fitting the map to the whole print wastes
// its depth range and makes contact shadows shift as the slider moves. The z = 0
// shadow is enclosed separately below, so the plate shadow is unaffected.
const std::vector<double> layer_zs = m_gcode_viewer.get_layers_zs();
if (!layer_zs.empty()) {
const size_t top = std::min<size_t>(m_gcode_viewer.get_layers_z_range()[1], layer_zs.size() - 1);
obj_bb.max.z() = std::max(obj_bb.min.z(), std::min(obj_bb.max.z(), layer_zs[top]));
}
}
else {
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
@@ -8299,16 +8255,21 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
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);
if (toolpath_casters)
m_gcode_viewer.render_shadow_casters(Transform3d(light_view), Transform3d(light_proj), eye);
// Only this branch draws through "flat"; the toolpaths bring their own program.
else if (GLShaderProgram* shader = wxGetApp().get_shader("flat"); shader != nullptr) {
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();
}
shader->stop_using();
// Restore state
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
@@ -8517,7 +8478,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
return;
}
const bool realistic_mode = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE);
const bool realistic_mode = _is_realistic_view_enabled();
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);
@@ -8741,7 +8702,34 @@ void GLCanvas3D::_render_wireframe_overlay()
//BBS: GUI refactor: add canvas size as parameters
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
{
// Realistic view: the toolpaths receive the same depth map they were rendered into by
// _render_shadows, which is what gives them object-on-object and self shadows. Intensity 0
// short-circuits the lookup in the shader, so this is inert whenever the map is missing.
const bool receive_shadows = m_shadow_map_valid && m_shadow_map_texture_id != 0 && m_shadow_map_size != 0;
if (receive_shadows) {
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
glsafe(::glActiveTexture(GL_TEXTURE0));
m_gcode_viewer.set_shadow_map(4, m_shadow_light_vp, 0.35f, 1.0f / static_cast<float>(m_shadow_map_size));
}
else
m_gcode_viewer.set_shadow_map(4, Transform3d::Identity(), 0.0f, 0.0f);
// The lighting term leaves the print dimmer and duller than the legend colours. Saturation
// pays back the duller half in both modes; brightness only where something takes light off
// again - realistic view with at least one lossy pass on - else the lift would just clip.
const AppConfig* cfg = wxGetApp().app_config;
const bool lossy_passes = cfg != nullptr && _is_realistic_view_enabled() &&
(cfg->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) || cfg->get_bool(SETTING_OPENGL_PHONG_SSAO));
m_gcode_viewer.set_tone(lossy_passes ? 1.1f : 1.0f, 1.15f);
m_gcode_viewer.render_scene(canvas_width, canvas_height);
if (receive_shadows) {
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
}
}
void GLCanvas3D::_render_gcode_overlay(int canvas_width, int canvas_height)
@@ -9831,7 +9819,7 @@ void GLCanvas3D::_render_canvas_toolbar()
);
create_menu_item( _utf8(L("Realistic View")),
m_canvas_type != ECanvasType::CanvasPreview, // not work on preview
true, // work on all
cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE),
[&cfg]{
cfg->set_bool(SETTING_OPENGL_REALISTIC_MODE, !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE));
+1
View File
@@ -1324,6 +1324,7 @@ private:
void _picking_pass();
void _rectangular_selection_picking_pass();
bool _is_fxaa_enabled() const;
bool _is_realistic_view_enabled() const;
bool _is_ssao_enabled() const;
int _get_effective_fps_cap() const;
bool _is_fps_overlay_enabled() const;
+9
View File
@@ -1932,6 +1932,15 @@ void PreferencesDialog::create_items()
);
g_sizer->Add(item_realistic_phong);
auto item_realistic_preview = create_item_checkbox(
_L("Enable in Preview"),
_L("Also applies realistic view to the Preview canvas, not just Prepare.\n"
"Preview draws the full toolpath geometry, so shadows and SSAO cost considerably"
" more there than on a plain model."),
SETTING_OPENGL_REALISTIC_PREVIEW
);
g_sizer->Add(item_realistic_preview);
auto item_realistic_ssao = create_item_checkbox(
_L("SSAO ambient occlusion"),
_L("Applies SSAO in realistic view."),