mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-23 00:42:33 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e71497738f | ||
|
|
d820303a3f | ||
|
|
f83bfa17ff | ||
|
|
7ca2b9ad9c |
@@ -413,6 +413,14 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
api="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches"
|
||||
# The save step reports success even when its tar failed, so keep
|
||||
# the older entries unless the new one is listed.
|
||||
if ! curl -sSf -H "Authorization: Bearer $GH_TOKEN" \
|
||||
"$api?ref=$GITHUB_REF&key=$CCACHE_ENTRY" \
|
||||
| jq -e --arg entry "$CCACHE_ENTRY" 'any(.actions_caches[]; .key == $entry)' > /dev/null; then
|
||||
echo "$CCACHE_ENTRY was not saved; keeping the older entries."
|
||||
exit 0
|
||||
fi
|
||||
curl -sSf -H "Authorization: Bearer $GH_TOKEN" \
|
||||
"$api?ref=$GITHUB_REF&key=ccache-$CCACHE_LEG-&per_page=100" \
|
||||
| jq -r --arg prefix "ccache-$CCACHE_LEG-" --argjson run "$GITHUB_RUN_ID" \
|
||||
|
||||
@@ -798,6 +798,13 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# The save step reports success even when its tar failed, so keep
|
||||
# the older entries unless the new one is listed.
|
||||
if ! gh cache list --ref "$GITHUB_REF" --key "$CCACHE_ENTRY" --json key \
|
||||
| jq -e --arg entry "$CCACHE_ENTRY" 'any(.[]; .key == $entry)' > /dev/null; then
|
||||
echo "$CCACHE_ENTRY was not saved; keeping the older entries."
|
||||
exit 0
|
||||
fi
|
||||
gh cache list --ref "$GITHUB_REF" --key "ccache-$CCACHE_LEG-" --limit 100 --json id,key \
|
||||
| jq -r --arg prefix "ccache-$CCACHE_LEG-" --argjson run "$GITHUB_RUN_ID" \
|
||||
'.[] | select((.key | ltrimstr($prefix) | split("-")[0] | tonumber?) < $run) | .id' \
|
||||
|
||||
@@ -198,6 +198,6 @@
|
||||
"0.4"
|
||||
],
|
||||
"z_hop_types": [
|
||||
"Normal Lift"
|
||||
"Auto Lift"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -198,6 +198,6 @@
|
||||
"0.4"
|
||||
],
|
||||
"z_hop_types": [
|
||||
"Normal Lift"
|
||||
"Auto Lift"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -198,6 +198,6 @@
|
||||
"0.4"
|
||||
],
|
||||
"z_hop_types": [
|
||||
"Normal Lift"
|
||||
"Auto Lift"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +224,12 @@ void AppConfig::set_defaults()
|
||||
set("preview_dim_previous_layers_brightness", std::to_string(std::max(0, std::min(brightness, 99))));
|
||||
}
|
||||
|
||||
// ORCA: view type the G-code preview opens with. "auto" keeps the automatic choice (Filament for
|
||||
// multi material prints, Line Type for single material ones), "last" restores the view type the user
|
||||
// picked last, any other value is a fixed view type name, see GCodeViewer::view_type_to_config_name().
|
||||
if (get("preview_default_view_type").empty())
|
||||
set("preview_default_view_type", "auto");
|
||||
|
||||
if (get("filaments_area_preferred_count").empty())
|
||||
set("filaments_area_preferred_count", "10");
|
||||
|
||||
@@ -303,6 +309,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();
|
||||
|
||||
+122
-24
@@ -104,6 +104,31 @@ static std::string get_view_type_string(libvgcode::EViewType view_type)
|
||||
return "";
|
||||
}
|
||||
|
||||
// ORCA: Stable, locale independent names used to persist a view type in the application config.
|
||||
// Keep these in sync with the entries of libvgcode::EViewType exposed in the preview combo box.
|
||||
static const std::vector<std::pair<std::string, libvgcode::EViewType>>& view_type_config_map()
|
||||
{
|
||||
static const std::vector<std::pair<std::string, libvgcode::EViewType>> map = {
|
||||
{ "summary", libvgcode::EViewType::Summary },
|
||||
{ "feature_type", libvgcode::EViewType::FeatureType },
|
||||
{ "color_print", libvgcode::EViewType::ColorPrint },
|
||||
{ "speed", libvgcode::EViewType::Speed },
|
||||
{ "actual_speed", libvgcode::EViewType::ActualSpeed },
|
||||
{ "acceleration", libvgcode::EViewType::Acceleration },
|
||||
{ "jerk", libvgcode::EViewType::Jerk },
|
||||
{ "height", libvgcode::EViewType::Height },
|
||||
{ "width", libvgcode::EViewType::Width },
|
||||
{ "volumetric_flow_rate", libvgcode::EViewType::VolumetricFlowRate },
|
||||
{ "actual_volumetric_flow_rate", libvgcode::EViewType::ActualVolumetricFlowRate },
|
||||
{ "layer_time_linear", libvgcode::EViewType::LayerTimeLinear },
|
||||
{ "layer_time_logarithmic", libvgcode::EViewType::LayerTimeLogarithmic },
|
||||
{ "fan_speed", libvgcode::EViewType::FanSpeed },
|
||||
{ "temperature", libvgcode::EViewType::Temperature },
|
||||
{ "pressure_advance", libvgcode::EViewType::PressureAdvance },
|
||||
};
|
||||
return map;
|
||||
}
|
||||
|
||||
// Find an index of a value in a sorted vector, which is in <z-eps, z+eps>.
|
||||
// Returns -1 if there is no such member.
|
||||
static int find_close_layer_idx(const std::vector<double> &zs, double &z, double eps)
|
||||
@@ -1091,9 +1116,7 @@ void GCodeViewer::init(ConfigOptionMode mode, PresetBundle* preset_bundle)
|
||||
// Default view type at first slice.
|
||||
// May be overridden in load() once we know how many tools are actually used in the G-code.
|
||||
m_nozzle_nums = preset_bundle ? preset_bundle->get_printer_extruder_count() : 1;
|
||||
auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::FeatureType);
|
||||
m_view_type_sel = (it != view_type_items.end()) ? std::distance(view_type_items.begin(), it) : 0;
|
||||
set_view_type(libvgcode::EViewType::FeatureType);
|
||||
apply_default_view_type();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished");
|
||||
}
|
||||
@@ -1114,6 +1137,73 @@ void GCodeViewer::set_scale(float scale)
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Preview default view type preference, see "preview_default_view_type" in the application config.
|
||||
std::string GCodeViewer::view_type_to_config_name(libvgcode::EViewType type)
|
||||
{
|
||||
for (const auto& [name, value] : view_type_config_map()) {
|
||||
if (value == type)
|
||||
return name;
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool GCodeViewer::view_type_from_config_name(const std::string& name, libvgcode::EViewType& type)
|
||||
{
|
||||
for (const auto& [config_name, value] : view_type_config_map()) {
|
||||
if (config_name == name) {
|
||||
type = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> GCodeViewer::default_view_type_choices()
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> choices = {
|
||||
{ "auto", _u8L("Automatic") },
|
||||
{ "last", _u8L("Last used") },
|
||||
};
|
||||
for (const auto& [name, type] : view_type_config_map())
|
||||
choices.push_back({ name, get_view_type_string(type) });
|
||||
return choices;
|
||||
}
|
||||
|
||||
void GCodeViewer::select_view_type(libvgcode::EViewType type)
|
||||
{
|
||||
auto it = std::find(view_type_items.begin(), view_type_items.end(), type);
|
||||
m_view_type_sel = (it != view_type_items.end()) ? static_cast<int>(std::distance(view_type_items.begin(), it)) : 0;
|
||||
set_view_type(type);
|
||||
}
|
||||
|
||||
// ORCA: Pick the view type the preview opens with, following the "preview_default_view_type" preference:
|
||||
// a fixed view type, the one the user picked last ("last"), or the automatic choice ("auto", the default)
|
||||
// which shows Filament for multi material prints and Line Type for single material ones.
|
||||
// The default is only (re)applied when it actually changes, so a view type picked by hand survives a reslice.
|
||||
void GCodeViewer::apply_default_view_type()
|
||||
{
|
||||
const std::string preference = wxGetApp().app_config->get("preview_default_view_type");
|
||||
|
||||
std::string key = preference;
|
||||
libvgcode::EViewType type = libvgcode::EViewType::FeatureType;
|
||||
if (preference == "last") {
|
||||
if (!view_type_from_config_name(wxGetApp().app_config->get("preview_last_view_type"), type))
|
||||
type = libvgcode::EViewType::FeatureType;
|
||||
}
|
||||
else if (!view_type_from_config_name(preference, type)) {
|
||||
// "auto", or an unknown value written by a newer version
|
||||
const bool multi_material = m_viewer.get_used_extruders_count() > 1;
|
||||
type = multi_material ? libvgcode::EViewType::ColorPrint : libvgcode::EViewType::FeatureType;
|
||||
key = multi_material ? "auto_multi_material" : "auto_single_material";
|
||||
}
|
||||
|
||||
if (m_applied_default_view_type_key == key)
|
||||
return;
|
||||
|
||||
m_applied_default_view_type_key = key;
|
||||
select_view_type(type);
|
||||
}
|
||||
|
||||
void GCodeViewer::update_by_mode(ConfigOptionMode mode)
|
||||
{
|
||||
view_type_items.clear();
|
||||
@@ -1395,27 +1485,8 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
|
||||
// load_toolpaths(gcode_result, build_volume, exclude_bounding_box);
|
||||
|
||||
// ORCA: Apply smart default view type when extruder count changes.
|
||||
// Multi-color: ColorPrint (Filament), Single-color: FeatureType (Line Type).
|
||||
// User selections persist within same extruder count, defaults reapply on count change.
|
||||
int current_count = m_viewer.get_used_extruders_count();
|
||||
if (current_count > 1) {
|
||||
if (m_last_extruder_count_default_applied != 2) {
|
||||
auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::ColorPrint);
|
||||
if (it != view_type_items.end())
|
||||
m_view_type_sel = std::distance(view_type_items.begin(), it);
|
||||
set_view_type(libvgcode::EViewType::ColorPrint);
|
||||
m_last_extruder_count_default_applied = 2;
|
||||
}
|
||||
} else {
|
||||
if (m_last_extruder_count_default_applied != 1) {
|
||||
auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::FeatureType);
|
||||
if (it != view_type_items.end())
|
||||
m_view_type_sel = std::distance(view_type_items.begin(), it);
|
||||
set_view_type(libvgcode::EViewType::FeatureType);
|
||||
m_last_extruder_count_default_applied = 1;
|
||||
}
|
||||
}
|
||||
// ORCA: Apply the default view type now that we know how many tools the G-code actually uses.
|
||||
apply_default_view_type();
|
||||
|
||||
// BBS: data for rendering color arrangement recommendation
|
||||
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
@@ -1641,6 +1712,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())
|
||||
@@ -3558,6 +3652,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
m_view_type_sel = i;
|
||||
set_view_type(view_type_items[m_view_type_sel]);
|
||||
reset_visible(view_type_items[m_view_type_sel]);
|
||||
// ORCA: remember the pick so the "Last used" preview default can restore it
|
||||
const std::string view_type_name = view_type_to_config_name(view_type_items[m_view_type_sel]);
|
||||
if (!view_type_name.empty())
|
||||
wxGetApp().app_config->set("preview_last_view_type", view_type_name);
|
||||
update_moves_slider();
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
imgui.set_requires_extra_frame();
|
||||
|
||||
@@ -228,7 +228,8 @@ private:
|
||||
std::vector<libvgcode::EViewType> view_type_items;
|
||||
std::vector<std::string> view_type_items_str;
|
||||
int m_view_type_sel = 0;
|
||||
int m_last_extruder_count_default_applied{0}; // 0=unset, 1=single, 2+=multi
|
||||
// ORCA: which default view type was last applied, see apply_default_view_type(). Empty until the first one is applied.
|
||||
std::string m_applied_default_view_type_key;
|
||||
std::vector<EMoveType> options_items;
|
||||
|
||||
bool m_legend_visible{ true };
|
||||
@@ -279,6 +280,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);
|
||||
@@ -324,6 +332,16 @@ public:
|
||||
void set_view_type(libvgcode::EViewType type) {
|
||||
m_viewer.set_view_type(type);
|
||||
}
|
||||
// ORCA: select a view type in the preview combo box and apply it
|
||||
void select_view_type(libvgcode::EViewType type);
|
||||
// ORCA: apply the "preview_default_view_type" preference, see the definition for the supported values
|
||||
void apply_default_view_type();
|
||||
|
||||
// ORCA: stable, locale independent name of a view type, as stored in the application config
|
||||
static std::string view_type_to_config_name(libvgcode::EViewType type);
|
||||
static bool view_type_from_config_name(const std::string& name, libvgcode::EViewType& type);
|
||||
// ORCA: (config value, translated label) pairs for the "preview_default_view_type" preference combo box
|
||||
static std::vector<std::pair<std::string, std::string>> default_view_type_choices();
|
||||
void reset_visible(libvgcode::EViewType type) {
|
||||
if (type == libvgcode::EViewType::FeatureType) {
|
||||
auto roles = m_viewer.get_extrusion_roles();
|
||||
|
||||
+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."),
|
||||
@@ -2019,6 +2028,23 @@ void PreferencesDialog::create_items()
|
||||
//// GRAPHICS > G-code Preview
|
||||
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
|
||||
|
||||
// ORCA: view type the preview opens with
|
||||
std::vector<wxString> PreviewViewTypeLabels;
|
||||
std::vector<std::string> PreviewViewTypeValues;
|
||||
for (const auto& [value, label] : GCodeViewer::default_view_type_choices()) {
|
||||
PreviewViewTypeValues.push_back(value);
|
||||
PreviewViewTypeLabels.push_back(from_u8(label));
|
||||
}
|
||||
auto item_preview_view_type = create_item_combobox(
|
||||
_L("Default view type"),
|
||||
_L("The color scheme the sliced preview opens with.\n"
|
||||
"Automatic: Filament for multi material prints, Line Type for single material ones.\n"
|
||||
"Last used: the view type you selected last.\n"
|
||||
"Any other value always opens that view type.\n"
|
||||
"You can still switch the view type in the preview afterwards."),
|
||||
"preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues);
|
||||
g_sizer->Add(item_preview_view_type);
|
||||
|
||||
auto item_dim_previous_layers = create_item_checkbox(
|
||||
_L("Dim lower layers"),
|
||||
_L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),
|
||||
|
||||
Reference in New Issue
Block a user