Draw the IDEX/IQEX ghosts in the X-Ray pass that replaced their own

X-Ray takes both shaded passes over and returns, and it draws only the volume
collection, which the ghosts are not part of. They disappeared while their
picking pass, hover tooltip and filament picker all kept answering, so a plate
in a parallel mode offered an invisible click target.

They go through the X-Ray shader rather than their own, so a ghost reads as one
more see-through body, which is what the mode is for. The pass supplies z_range
and clipping_plane itself, since the volume collection sets them for the whole
pass it runs and the vertex shader discards everything outside z_range. It also
draws two-sided, so a hollow ghost shows its far wall like a real body does, and
hands the shader an opaque colour: X-Ray derives coverage from the view angle
and multiplies the colour's alpha into it, so a ghost carrying its own
translucency as well would composite far fainter than the body it mirrors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-09-28 17:31:27 -04:00
co-authored by Claude Opus 5
parent 299093844f
commit 4e65b741cc
2 changed files with 48 additions and 6 deletions
+44 -4
View File
@@ -7634,10 +7634,11 @@ void GLCanvas3D::_picking_pass_imex_ghosts()
}
}
void GLCanvas3D::_render_imex_ghosts()
void GLCanvas3D::_render_imex_ghosts(bool xray_pass)
{
// Called from inside _render_objects' Transparent branch while the gouraud
// shader is bound and globals (z_far/z_near/z_range/clipping_plane) are set.
// Draws through whichever shader the caller bound: the shaded pass leaves gouraud current,
// the X-Ray pass its own program. Both are fed per volume below; the uniforms a pass sets
// once for itself are the caller's (see _render_imex_ghosts_xray for the X-Ray pass's).
// GLVolume::render() only binds its mesh, so we must set the per-volume
// matrices AND uniform_color that GLVolumeCollection::render would normally
// set; otherwise ghosts pick up whatever the last main volume left behind.
@@ -7695,7 +7696,14 @@ void GLCanvas3D::_render_imex_ghosts()
// GLModel::render() pushes its own data.color into uniform_color,
// so we must stamp the ghost's color onto the model before render
// or it draws black. Pattern matches 3DScene.cpp:1099.
g->model.set_color(g->render_color);
// X-Ray derives its own coverage from view angle and multiplies the colour's alpha
// into it, so a ghost carrying its translucency as well composites about three times
// fainter than the body it mirrors. Hand that pass an opaque colour and let its
// density be the only source of translucency.
ColorRGBA ghost_color = g->render_color;
if (xray_pass)
ghost_color.a(1.0f);
g->model.set_color(ghost_color);
g->render();
}
}
@@ -8753,6 +8761,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
if (_is_xray_view_active()) {
if (type == GLVolumeCollection::ERenderType::Opaque)
_render_xray_volumes();
else
_render_imex_ghosts_xray();
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
return;
}
@@ -8941,6 +8951,36 @@ void GLCanvas3D::_render_xray_volumes()
glsafe(::glDepthMask(GL_TRUE));
}
// The IDEX/IQEX ghosts are not in m_volumes, so the X-Ray pass that replaces both shaded passes
// does not reach them on its own. They go through the X-Ray shader here rather than their own,
// which is what makes a ghost read as one more see-through body.
void GLCanvas3D::_render_imex_ghosts_xray()
{
GLShaderProgram* shader = wxGetApp().get_shader("xray");
if (shader == nullptr)
return;
// The blend and depth state _render_xray_volumes() uses, plus its two-sided drawing: the
// shader shades back faces too, so a hollow ghost shows its far wall like a real body does.
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glDisable(GL_CULL_FACE));
shader->start_using();
// _render_imex_ghosts() sets the per-volume matrices and the color, but the two uniforms
// the volume collection would have set for the whole pass are this pass's to supply, and
// the vertex shader discards everything outside z_range - an unset one hides every ghost.
shader->set_uniform("z_range", m_volumes.get_z_range());
shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane());
_render_imex_ghosts(/*xray_pass=*/true);
shader->stop_using();
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(GL_TRUE));
}
void GLCanvas3D::_render_wireframe_overlay()
{
if (!wxGetApp().plater()->is_show_wireframe())
+4 -2
View File
@@ -1373,8 +1373,10 @@ private:
void _render_cad_grid(const Transform3d& view_matrix, const Transform3d& projection_matrix);
//BBS: add outline drawing logic
void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true);
// IMEX ghost volumes owned by PartPlate; rendered in the transparent pass.
void _render_imex_ghosts();
// IMEX ghost volumes owned by PartPlate, drawn through the shader the caller bound:
// the shaded pass's transparent half, or the X-Ray pass below.
void _render_imex_ghosts(bool xray_pass = false);
void _render_imex_ghosts_xray();
// IMEX ghost hover tooltip: filament swatch + label drawn as an ImGui overlay.
void _render_imex_ghost_tooltip();
void _render_wireframe_overlay();