forked from 1b-agent/opencadstudio-upstream
fix: X-ray ghost pass for selected wires occluded by other geometry
Selected wires are now visible through occluding objects. A second wire_xray pipeline (depth_compare=Greater, depth_write_enabled=false) renders 25%-alpha ghost copies of selected wires only where they are behind the depth buffer, leaving fully-visible portions at full glow brightness unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
392f4503d8
commit
c27927c2b3
2 files changed
+108
-1
No files matched your search
@@ -25,6 +25,9 @@ const MSAA_SAMPLES: u32 = 4;
|
||||
|
||||
pub struct Pipeline {
|
||||
wire_pipeline: wgpu::RenderPipeline,
|
||||
/// Same shader as wire_pipeline but depth_compare=Greater, depth_write_enabled=false.
|
||||
/// Used to draw ghost copies of selected wires through occluding geometry.
|
||||
wire_xray_pipeline: wgpu::RenderPipeline,
|
||||
hatch_pipeline: wgpu::RenderPipeline,
|
||||
image_pipeline: wgpu::RenderPipeline,
|
||||
mesh_pipeline: wgpu::RenderPipeline,
|
||||
@@ -46,6 +49,8 @@ pub struct Pipeline {
|
||||
/// Cached texture format (needed to recreate MSAA / depth textures on resize).
|
||||
surface_format: wgpu::TextureFormat,
|
||||
gpu_wires: Vec<WireGpu>,
|
||||
/// Ghost copies (25% alpha) of selected wires for the X-ray depth pass.
|
||||
gpu_selected_wires: Vec<WireGpu>,
|
||||
gpu_hatches: Vec<HatchGpu>,
|
||||
/// Wipeout fills — rendered after wires in a separate pass.
|
||||
gpu_wipeouts: Vec<HatchGpu>,
|
||||
@@ -148,6 +153,49 @@ impl Pipeline {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// Xray variant: only draws where fragments are BEHIND the depth buffer
|
||||
// (depth_compare=Greater), without writing depth. Used to ghost selected
|
||||
// wires through occluding geometry at reduced alpha.
|
||||
let wire_xray_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("wire_xray.pipeline"),
|
||||
layout: Some(&wire_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &wire_shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[wire_gpu::WireVertex::layout()],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
cull_mode: None,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: wgpu::CompareFunction::Greater,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &wire_shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// ── Hatch pipeline ─────────────────────────────────────────────────
|
||||
let hatch_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
|
||||
binding,
|
||||
@@ -457,6 +505,7 @@ impl Pipeline {
|
||||
|
||||
Self {
|
||||
wire_pipeline,
|
||||
wire_xray_pipeline,
|
||||
hatch_pipeline,
|
||||
image_pipeline,
|
||||
mesh_pipeline,
|
||||
@@ -474,6 +523,7 @@ impl Pipeline {
|
||||
blit_bind_group,
|
||||
surface_format: format,
|
||||
gpu_wires: vec![],
|
||||
gpu_selected_wires: vec![],
|
||||
gpu_hatches: vec![],
|
||||
gpu_wipeouts: vec![],
|
||||
gpu_images: vec![],
|
||||
@@ -485,6 +535,13 @@ impl Pipeline {
|
||||
|
||||
pub fn upload_wires(&mut self, device: &wgpu::Device, wires: &[WireModel]) {
|
||||
self.gpu_wires = wires.iter().map(|w| WireGpu::new(device, w)).collect();
|
||||
// Ghost copies of selected wires at 25% alpha, drawn through occluding
|
||||
// geometry in the xray pass to keep selected entities fully visible.
|
||||
self.gpu_selected_wires = wires
|
||||
.iter()
|
||||
.filter(|w| w.selected)
|
||||
.map(|w| WireGpu::new_ghost(device, w, 0.25))
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn upload_meshes(&mut self, device: &wgpu::Device, meshes: &[MeshModel]) {
|
||||
@@ -716,6 +773,44 @@ impl Pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pass 7: selected wire X-ray ghost pass ────────────────────────
|
||||
// Draws ghost copies of selected wires only where they are occluded
|
||||
// (depth_compare=Greater). Visible parts are already drawn at full
|
||||
// brightness by the wire pass; this only reveals hidden portions.
|
||||
if !self.gpu_selected_wires.is_empty() {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("wire_xray.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_pipeline(&self.wire_xray_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
for wire in &self.gpu_selected_wires {
|
||||
if wire.vertex_count >= 6 {
|
||||
pass.set_vertex_buffer(0, wire.vertex_buffer.slice(..));
|
||||
pass.draw(0..wire.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Resolve MSAA → clip-sized resolve texture ─────────────────────
|
||||
// Both msaa_view and resolve_view are sized to clip_bounds, so the
|
||||
// resolve does NOT touch any pixels outside the shader widget's area.
|
||||
|
||||
@@ -115,6 +115,18 @@ pub struct WireGpu {
|
||||
|
||||
impl WireGpu {
|
||||
pub fn new(device: &wgpu::Device, wire: &WireModel) -> Self {
|
||||
Self::build(device, wire, wire.color)
|
||||
}
|
||||
|
||||
/// Creates a ghost copy with `alpha` applied on top of the wire's own alpha.
|
||||
/// Used for the X-ray pass: selected wires rendered at reduced opacity through
|
||||
/// occluding geometry.
|
||||
pub fn new_ghost(device: &wgpu::Device, wire: &WireModel, alpha: f32) -> Self {
|
||||
let [r, g, b, a] = wire.color;
|
||||
Self::build(device, wire, [r, g, b, a * alpha])
|
||||
}
|
||||
|
||||
fn build(device: &wgpu::Device, wire: &WireModel, color: [f32; 4]) -> Self {
|
||||
let pat0 = [
|
||||
wire.pattern[0],
|
||||
wire.pattern[1],
|
||||
@@ -178,7 +190,7 @@ impl WireGpu {
|
||||
pos_b: b,
|
||||
which_end,
|
||||
side,
|
||||
color: wire.color,
|
||||
color,
|
||||
distance: dist,
|
||||
half_width,
|
||||
pattern_length: wire.pattern_length,
|
||||
|
||||
Reference in new issue
Block a user