diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 2f2ca5fb5..ecef16f6d 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -50,8 +50,6 @@ varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -59,36 +57,7 @@ void main() vec3 color = uniform_color.rgb; float alpha = uniform_color.a; - vec2 intensity_fs = intensity; - vec3 eye_normal_fs = eye_normal; - float world_normal_z_fs = world_normal_z; - if (compute_triangle_normals_in_fs) { - vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); -#ifdef FLIP_TRIANGLE_NORMALS - triangle_normal = -triangle_normal; -#endif - - // First transform the normal into camera space and normalize the result. - eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0); - - intensity_fs = vec2(0.0, 0.0); - intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * model_pos).xyz; - intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0); - intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE; - - // z component of normal vector in world coordinate used for slope shading - world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0; - } - - if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) { + if (slope.actived && world_normal_z < slope.normal_z - EPSILON) { color = vec3(0.7, 0.7, 1.0); alpha = 1.0; } @@ -96,8 +65,8 @@ void main() color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color; #ifdef ENABLE_ENVIRONMENT_MAP if (use_environment_tex) - gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha); + gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha); else #endif - gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha); + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); } diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index 20a142452..d5e74b60b 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -54,26 +54,22 @@ varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { - if (!compute_triangle_normals_in_fs) { - // First transform the normal into camera space and normalize the result. - eye_normal = normalize(gl_NormalMatrix * gl_Normal); + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(gl_NormalMatrix * gl_Normal); - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - } + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; model_pos = gl_Vertex; // Point in homogenous coordinates. @@ -90,8 +86,7 @@ void main() } // z component of normal vector in world coordinate used for slope shading - if (!compute_triangle_normals_in_fs) - world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; gl_Position = ftransform(); // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. diff --git a/resources/shaders/mm_gouraud.fs b/resources/shaders/mm_gouraud.fs new file mode 100644 index 000000000..f7154b419 --- /dev/null +++ b/resources/shaders/mm_gouraud.fs @@ -0,0 +1,55 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const float EPSILON = 0.0001; + +uniform vec4 uniform_color; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + vec3 color = uniform_color.rgb; + float alpha = uniform_color.a; + + vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); +#ifdef FLIP_TRIANGLE_NORMALS + triangle_normal = -triangle_normal; +#endif + + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(gl_NormalMatrix * triangle_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + // x = diffuse, y = specular; + vec2 intensity = vec2(0.0, 0.0); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * model_pos).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); +} diff --git a/resources/shaders/mm_gouraud.vs b/resources/shaders/mm_gouraud.vs new file mode 100644 index 000000000..2847c3136 --- /dev/null +++ b/resources/shaders/mm_gouraud.vs @@ -0,0 +1,23 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform mat4 volume_world_matrix; +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; + +void main() +{ + model_pos = gl_Vertex; + // Point in homogenous coordinates. + vec4 world_pos = volume_world_matrix * gl_Vertex; + + gl_Position = ftransform(); + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 86ec5634f..387c41315 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -456,7 +456,7 @@ private: GLGizmosManager m_gizmos; GLToolbar m_main_toolbar; GLToolbar m_undoredo_toolbar; - ClippingPlane m_clipping_planes[2]; + std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; SlaCap m_sla_caps[2]; @@ -651,6 +651,9 @@ public: void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); } void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; } + bool get_use_clipping_planes() const { return m_use_clipping_planes; } + const std::array &get_clipping_planes() const { return m_clipping_planes; }; + void set_color_by(const std::string& value); void refresh_camera_scene_box(); diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 5dd478b57..65b85564d 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -61,25 +61,23 @@ std::pair GLShadersManager::init() // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor - // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. - // Because of this, objects had darker colors inside the multi-material gizmo. - // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. - if (platform_flavor() == PlatformFlavor::OSXOnArm) - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } #if ENABLE_ENVIRONMENT_MAP - , "ENABLE_ENVIRONMENT_MAP"sv -#endif - }); - else - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } -#if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } + , { "ENABLE_ENVIRONMENT_MAP"sv } #endif ); // used to render variable layers heights in 3d editor valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); // used to render highlight contour around selected triangles inside the multi-material gizmo valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" }); + // Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader. + // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. + // Because of this, objects had darker colors inside the multi-material gizmo. + // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. + if (platform_flavor() == PlatformFlavor::OSXOnArm) + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv}); + else + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}); return { valid, error }; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index e3344ad38..54a92210a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -174,6 +174,43 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) } } +void GLGizmoMmuSegmentation::render_triangles(const Selection &selection, const bool use_polygon_offset_fill) const +{ + ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data(); + auto *shader = wxGetApp().get_shader("mm_gouraud"); + if (!shader) + return; + shader->start_using(); + shader->set_uniform("clipping_plane", clp_data.clp_dataf); + shader->set_uniform("z_range", clp_data.z_range); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix(); + + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); + + shader->set_uniform("volume_world_matrix", trafo_matrix); + m_triangle_selectors[mesh_id]->render(m_imgui); + + glsafe(::glPopMatrix()); + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + } +} + static void render_extruders_combo(const std::string &label, const std::vector &extruders, const std::vector> &extruders_colors, @@ -554,9 +591,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) auto *shader = wxGetApp().get_current_shader(); if (!shader) return; - assert(shader->get_name() == "gouraud"); - ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);}); - shader->set_uniform("compute_triangle_normals_in_fs", true); + assert(shader->get_name() == "mm_gouraud"); for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx) if (m_gizmo_scene.has_VBOs(color_idx)) { @@ -569,7 +604,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) } if (m_paint_contour.has_VBO()) { - ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); + ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); }); shader->stop_using(); auto *contour_shader = wxGetApp().get_shader("mm_contour"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 604edf64d..a9e5f608e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -89,6 +89,8 @@ public: void set_painter_gizmo_data(const Selection& selection) override; + void render_triangles(const Selection& selection, bool use_polygon_offset_fill) const override; + // TriangleSelector::serialization/deserialization has a limit to store 19 different states. // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. // When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 27b68bc93..3fffede8f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -43,12 +43,29 @@ void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) } } +GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const +{ + ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; + // Take care of the clipping plane. The normal of the clipping plane is + // saved with opposite sign than we need to pass to OpenGL (FIXME) + if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + for (size_t i = 0; i < 3; ++i) + clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); + clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); + } + // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) + if (m_c->get_canvas()->get_use_clipping_planes()) { + const std::array &clps = m_c->get_canvas()->get_clipping_planes(); + clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; + } + + return clp_data_out; +} void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool use_polygon_offset_fill) const { - const ModelObject* mo = m_c->selection_info()->model_object(); - ScopeGuard offset_fill_guard([&use_polygon_offset_fill]() { if (use_polygon_offset_fill) glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); @@ -58,27 +75,17 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool glsafe(::glPolygonOffset(-5.0, -5.0)); } - // Take care of the clipping plane. The normal of the clipping plane is - // saved with opposite sign than we need to pass to OpenGL (FIXME) - bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; - float clp_dataf[4] = {0.f, 0.f, 1.f, FLT_MAX}; - if (clipping_plane_active) { - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - for (size_t i=0; i<3; ++i) - clp_dataf[i] = -1.f * float(clp->get_data()[i]); - clp_dataf[3] = float(clp->get_data()[3]); - } - auto *shader = wxGetApp().get_shader("gouraud"); if (! shader) return; shader->start_using(); shader->set_uniform("slope.actived", false); shader->set_uniform("print_box.actived", false); - shader->set_uniform("clipping_plane", clp_dataf, 4); + shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); - int mesh_id = -1; + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; for (const ModelVolume* mv : mo->volumes) { if (! mv->is_model_part()) continue; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index cf12e9359..d02c2030d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -126,7 +126,7 @@ public: virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); protected: - void render_triangles(const Selection& selection, const bool use_polygon_offset_fill = true) const; + virtual void render_triangles(const Selection& selection, bool use_polygon_offset_fill = true) const; void render_cursor() const; void render_cursor_circle() const; void render_cursor_sphere(const Transform3d& trafo) const; @@ -176,6 +176,14 @@ protected: Right }; + struct ClippingPlaneDataWrapper + { + std::array clp_dataf; + std::array z_range; + }; + + ClippingPlaneDataWrapper get_clipping_plane_data() const; + private: bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; void update_raycast_cache(const Vec2d& mouse_position,