mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-23 00:42:33 +00:00
Improve preview colors (#15809)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
co-authored by
Rodrigo Faselli
parent
f83bfa17ff
commit
d820303a3f
@@ -7,18 +7,38 @@
|
||||
|
||||
uniform sampler2D color_texture;
|
||||
uniform sampler2D depth_texture;
|
||||
uniform sampler2D normal_texture;
|
||||
uniform vec2 inv_tex_size;
|
||||
uniform float z_near;
|
||||
uniform float z_far;
|
||||
uniform bool is_outline;
|
||||
// The pass has no normal target to read, so the surface normal is reconstructed from the depth
|
||||
// buffer. inv_projection_matrix unprojects a pixel back into view space and up_view is world +Z
|
||||
// expressed in view space, which is what tells a top surface from a wall.
|
||||
uniform mat4 inv_projection_matrix;
|
||||
uniform vec3 up_view;
|
||||
|
||||
varying vec2 tex_coord;
|
||||
|
||||
float linearize_depth(float depth)
|
||||
// Position of the given pixel in view space. Valid under both an orthographic and a perspective
|
||||
// camera, unlike the depth linearization it replaces.
|
||||
vec3 view_pos(vec2 uv)
|
||||
{
|
||||
float z = depth * 2.0 - 1.0;
|
||||
return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near));
|
||||
vec2 c = clamp(uv, vec2(0.0), vec2(1.0));
|
||||
float d = texture2D(depth_texture, c).r;
|
||||
vec4 ndc = vec4(c * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
|
||||
vec4 view = inv_projection_matrix * ndc;
|
||||
return view.xyz / view.w;
|
||||
}
|
||||
|
||||
// Surface normal at the given pixel, from the forward differences of the reconstructed view
|
||||
// position. It rings by a pixel across a depth discontinuity, which is acceptable here: the
|
||||
// normal only weights the occlusion, nothing is shaded with it.
|
||||
vec3 view_normal(vec2 uv, vec3 p)
|
||||
{
|
||||
vec3 px = view_pos(uv + vec2(inv_tex_size.x, 0.0));
|
||||
vec3 py = view_pos(uv + vec2(0.0, inv_tex_size.y));
|
||||
vec3 n = cross(px - p, py - p);
|
||||
float len = length(n);
|
||||
return (len > 1e-8) ? n / len : vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
@@ -28,16 +48,21 @@ void main()
|
||||
return;
|
||||
}
|
||||
vec3 base = texture2D(color_texture, tex_coord).rgb;
|
||||
float depth_center = linearize_depth(texture2D(depth_texture, tex_coord).r);
|
||||
|
||||
// Sample normal at current fragment (range: -1 to 1)
|
||||
vec3 normal_center = texture2D(normal_texture, tex_coord).rgb * 2.0 - 1.0;
|
||||
// Nothing was drawn here: occluding the background would only darken the gradient, and its
|
||||
// reconstructed normal is degenerate anyway.
|
||||
if (texture2D(depth_texture, tex_coord).r >= 0.9999) {
|
||||
gl_FragColor = vec4(base, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 center_pos = view_pos(tex_coord);
|
||||
float depth_center = -center_pos.z;
|
||||
vec3 normal_center = view_normal(tex_coord, center_pos);
|
||||
|
||||
// Calculate how much the surface faces upward
|
||||
// up_factor = 1.0 for surfaces pointing straight up (0,0,1)
|
||||
// up_factor = 0.0 for surfaces pointing down or sideways
|
||||
float up_factor = max(0.0, normal_center.z); // Assuming Z is up axis
|
||||
// Alternative: if Y is up, use normal_center.y
|
||||
// up_factor = 1.0 for surfaces pointing straight up, 0.0 for walls and downward faces
|
||||
float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0);
|
||||
|
||||
// Adaptive sampling radius
|
||||
float radius = mix(2.0, 4.0, depth_center / z_far);
|
||||
@@ -52,39 +77,38 @@ void main()
|
||||
offsets[6] = vec2( 0.0, -1.0);
|
||||
offsets[7] = vec2( 0.707,-0.707);
|
||||
|
||||
// Occlusion is a slope, not a depth difference: how far a neighbour rises out of the
|
||||
// centre's tangent plane over how far away it is. Unlike a raw difference, that sine is
|
||||
// free of camera distance and zoom, so a crease reads the same from any view.
|
||||
const float SLOPE_MIN = 0.08; // ~5 degrees, above the depth-buffer noise of a flat surface
|
||||
const float SLOPE_MAX = 0.60; // ~37 degrees, a full crease
|
||||
|
||||
const float SAMPLE_COUNT = 8.0;
|
||||
float occlusion = 0.0;
|
||||
int valid_samples = 0;
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
vec2 uv = tex_coord + offsets[i] * inv_tex_size * radius;
|
||||
uv = clamp(uv, vec2(0.001), vec2(0.999));
|
||||
|
||||
float sample_depth = linearize_depth(texture2D(depth_texture, uv).r);
|
||||
float depth_diff = max(0.0, depth_center - sample_depth);
|
||||
|
||||
float threshold = 0.015 * (0.5 + depth_center / z_far);
|
||||
float contribution = smoothstep(0.001, threshold, depth_diff);
|
||||
vec3 delta = view_pos(uv) - center_pos;
|
||||
float dist = length(delta);
|
||||
float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0;
|
||||
float contribution = smoothstep(SLOPE_MIN, SLOPE_MAX, rise);
|
||||
|
||||
float diagonal_weight = 1.0 - abs(offsets[i].x * offsets[i].y) * 0.5;
|
||||
occlusion += contribution * diagonal_weight;
|
||||
valid_samples++;
|
||||
}
|
||||
|
||||
if (valid_samples > 0)
|
||||
occlusion /= float(valid_samples);
|
||||
occlusion /= SAMPLE_COUNT;
|
||||
|
||||
// flatter/top-like surfaces get less darkening
|
||||
float ao_intensity = 0.55;
|
||||
float ambient_occlusion = 1.0 - occlusion * ao_intensity;
|
||||
|
||||
// Different min values for top vs bottom surfaces
|
||||
// Different min values for top vs bottom surfaces. The boost that used to follow lifted a
|
||||
// top surface back to within 2% of unoccluded once up_factor became a real normal rather
|
||||
// than a colour, which is where the AO went; the floors alone shape the effect now.
|
||||
float ao_min = mix(0.45, 0.70, up_factor); // Bottom: 0.45, Top: 0.70
|
||||
ambient_occlusion = clamp(ambient_occlusion, ao_min, 1.0);
|
||||
|
||||
// Boost brightness on top surfaces (optional)
|
||||
float brightness_boost = 1.0 + up_factor * 0.15; // 15% extra brightness on top
|
||||
ambient_occlusion = pow(ambient_occlusion, 2.2) * brightness_boost;
|
||||
ambient_occlusion = clamp(ambient_occlusion, 0.45, 1.05);
|
||||
|
||||
gl_FragColor = vec4(base * ambient_occlusion, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,45 @@
|
||||
#version 140
|
||||
|
||||
/**
|
||||
* SSAO Shader - GLSL 140 version with sharp depth threshold
|
||||
* SSAO Shader - GLSL 140 version with a slope-based occlusion test
|
||||
* Only darkens valleys/concave areas, ignores smooth variations
|
||||
*/
|
||||
|
||||
uniform sampler2D color_texture;
|
||||
uniform sampler2D depth_texture;
|
||||
uniform sampler2D normal_texture;
|
||||
uniform float z_near;
|
||||
uniform vec2 inv_tex_size;
|
||||
uniform float z_far;
|
||||
uniform bool is_outline;
|
||||
// The pass has no normal target to read, so the surface normal is reconstructed from the depth
|
||||
// buffer. inv_projection_matrix unprojects a pixel back into view space and up_view is world +Z
|
||||
// expressed in view space, which is what tells a top surface from a wall.
|
||||
uniform mat4 inv_projection_matrix;
|
||||
uniform vec3 up_view;
|
||||
|
||||
in vec2 tex_coord;
|
||||
out vec4 frag_color;
|
||||
|
||||
float linearize_depth(float depth)
|
||||
// Position of the given pixel in view space. Valid under both an orthographic and a perspective
|
||||
// camera, unlike the depth linearization it replaces.
|
||||
vec3 view_pos(ivec2 pixel)
|
||||
{
|
||||
float z = depth * 2.0 - 1.0;
|
||||
return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near));
|
||||
ivec2 p = clamp(pixel, ivec2(0), textureSize(depth_texture, 0) - 1);
|
||||
float d = texelFetch(depth_texture, p, 0).r;
|
||||
vec4 ndc = vec4((vec2(p) + 0.5) * inv_tex_size * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
|
||||
vec4 view = inv_projection_matrix * ndc;
|
||||
return view.xyz / view.w;
|
||||
}
|
||||
|
||||
// Surface normal at the given pixel, from the forward differences of the reconstructed view
|
||||
// position. It rings by a pixel across a depth discontinuity, which is acceptable here: the
|
||||
// normal only weights the occlusion, nothing is shaded with it.
|
||||
vec3 view_normal(ivec2 pixel, vec3 p)
|
||||
{
|
||||
vec3 px = view_pos(pixel + ivec2(1, 0));
|
||||
vec3 py = view_pos(pixel + ivec2(0, 1));
|
||||
vec3 n = cross(px - p, py - p);
|
||||
float len = length(n);
|
||||
return (len > 1e-8) ? n / len : vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
@@ -28,81 +49,72 @@ void main()
|
||||
return;
|
||||
}
|
||||
ivec2 pixel = ivec2(gl_FragCoord.xy);
|
||||
float center_depth = linearize_depth(texelFetch(depth_texture, pixel, 0).r);
|
||||
|
||||
// Sample normal buffer (stored as RGB in 0-1 range, convert to -1 to 1)
|
||||
vec3 normal_center = texelFetch(normal_texture, pixel, 0).rgb * 2.0 - 1.0;
|
||||
normal_center = normalize(normal_center);
|
||||
|
||||
vec3 color = texture(color_texture, tex_coord).rgb;
|
||||
|
||||
// Nothing was drawn here: occluding the background would only darken the gradient, and its
|
||||
// reconstructed normal is degenerate anyway.
|
||||
if (texelFetch(depth_texture, pixel, 0).r >= 0.9999) {
|
||||
frag_color = vec4(color, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 center_pos = view_pos(pixel);
|
||||
float center_depth = -center_pos.z;
|
||||
vec3 normal_center = view_normal(pixel, center_pos);
|
||||
|
||||
// Calculate upward-facing factor (Z-up coordinate system)
|
||||
float up_factor = clamp(normal_center.z * 1.5, 0.0, 1.0);
|
||||
|
||||
float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0);
|
||||
|
||||
// Adaptive radius in pixel space
|
||||
int radius = int(mix(2.0, 4.0, center_depth / z_far));
|
||||
|
||||
// Optimized sampling pattern
|
||||
const ivec2 offsets[12] = ivec2[](
|
||||
const int SAMPLE_COUNT = 12;
|
||||
const ivec2 offsets[SAMPLE_COUNT] = ivec2[](
|
||||
ivec2(1, 0), ivec2(-1, 0), ivec2(0, 1), ivec2(0, -1),
|
||||
ivec2(1, 1), ivec2(-1, 1), ivec2(1, -1), ivec2(-1, -1),
|
||||
ivec2(2, 0), ivec2(-2, 0), ivec2(0, 2), ivec2(0, -2)
|
||||
);
|
||||
|
||||
float occlusion = 0.0;
|
||||
int valid_samples = 0;
|
||||
// Occlusion is a slope, not a depth difference: the sine of the angle a neighbour subtends
|
||||
// above the centre's tangent plane. A raw difference depends on camera distance and zoom,
|
||||
// so no fixed thresholds suit both a 0.2 mm layer step and a 5 mm overhang.
|
||||
const float SLOPE_MIN = 0.08; // ~5 degrees, above the depth-buffer noise of a flat surface
|
||||
const float SLOPE_MAX = 0.60; // ~37 degrees, a full crease
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
float occlusion = 0.0;
|
||||
|
||||
for (int i = 0; i < SAMPLE_COUNT; i++) {
|
||||
// No edge rejection: view_pos clamps, giving a near-zero delta and no occlusion.
|
||||
// Rejecting one side only would bias the denominator against the other.
|
||||
ivec2 sample_pixel = pixel + offsets[i] * radius;
|
||||
|
||||
if (sample_pixel.x < 0 || sample_pixel.y < 0)
|
||||
continue;
|
||||
|
||||
float sample_depth = linearize_depth(texelFetch(depth_texture, sample_pixel, 0).r);
|
||||
|
||||
// Sample normal at neighbor
|
||||
vec3 normal_sample = texelFetch(normal_texture, sample_pixel, 0).rgb * 2.0 - 1.0;
|
||||
|
||||
// Depth difference (positive if neighbor is closer to camera)
|
||||
float depth_diff = center_depth - sample_depth;
|
||||
|
||||
// Sharp depth threshold ===
|
||||
// Minimum depth difference to consider occlusion (ignores small variations)
|
||||
float threshold_min = 0.008; // Higher = only deep valleys get darkened
|
||||
float threshold_max = 0.04; // Transition range for full occlusion
|
||||
vec3 delta = view_pos(sample_pixel) - center_pos;
|
||||
float dist = length(delta);
|
||||
// How far the neighbour rises towards the viewer out of the centre's tangent plane. A
|
||||
// flat surface gives ~0 whatever its orientation, so this also subsumes the separate
|
||||
// planar test the normals were compared for.
|
||||
float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0;
|
||||
|
||||
float contribution = 0.0;
|
||||
if (depth_diff > threshold_min) {
|
||||
if (rise > SLOPE_MIN) {
|
||||
// Abrupt mapping with power curve
|
||||
contribution = (depth_diff - threshold_min) / (threshold_max - threshold_min);
|
||||
contribution = (rise - SLOPE_MIN) / (SLOPE_MAX - SLOPE_MIN);
|
||||
contribution = clamp(contribution, 0.0, 1.0);
|
||||
contribution = pow(contribution, 2.0); // Steeper curve for sharper transition
|
||||
}
|
||||
|
||||
// Reduce occlusion on planar surfaces (similar normals)
|
||||
float normal_similarity = dot(normal_center, normal_sample);
|
||||
float planar_factor = smoothstep(0.75, 0.95, normal_similarity);
|
||||
contribution *= (1.0 - planar_factor * 0.6);
|
||||
|
||||
occlusion += contribution;
|
||||
valid_samples++;
|
||||
}
|
||||
|
||||
if (valid_samples > 0) {
|
||||
// Calculate ambient occlusion factor with higher base intensity
|
||||
float ao_factor = 1.0 - (occlusion / float(valid_samples)) * 0.6;
|
||||
|
||||
// Keep bright areas clean (higher minimum for upward-facing surfaces)
|
||||
float ao_min = mix(0.55, 0.85, up_factor);
|
||||
ao_factor = clamp(ao_factor, ao_min, 1.0);
|
||||
|
||||
// Slight brightness boost for upward-facing surfaces
|
||||
float brightness_boost = 1.0 + up_factor * 0.15;
|
||||
ao_factor = ao_factor * brightness_boost;
|
||||
|
||||
occlusion = ao_factor;
|
||||
} else {
|
||||
occlusion = 1.0;
|
||||
}
|
||||
// Calculate ambient occlusion factor with higher base intensity
|
||||
float ao_factor = 1.0 - (occlusion / float(SAMPLE_COUNT)) * 0.6;
|
||||
|
||||
// Keep bright areas clean (higher minimum for upward-facing surfaces). The old 0.85 floor
|
||||
// and 1.15 boost were set when up_factor came from the colour buffer and read ~0; with a
|
||||
// real normal they capped a top surface at 2% darkening, which hid the AO entirely.
|
||||
float ao_min = mix(0.45, 0.70, up_factor);
|
||||
occlusion = clamp(ao_factor, ao_min, 1.0);
|
||||
|
||||
vec3 color = texture(color_texture, tex_coord).rgb;
|
||||
frag_color = vec4(color * occlusion, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
//
|
||||
// ************************************************************************
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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."),
|
||||
|
||||
Reference in New Issue
Block a user