feat(render): wireframe modes hide mesh & 3D fills
Wireframe 2D/3D modlarında Solid3D meshlerini edge-only çizmek için LineList topolojili bir mesh wireframe pipeline ve her üçgenin (a,b),(b,c),(c,a) kenarlarına genişletilmiş ayrı bir index buffer eklendi. PolyfaceMesh / PolygonMesh face fill'leri (fill_tris + dolu points) wireframe'de gizleniyor; text greek / MultiLeader gibi 2D fill'ler (points boş) etkilenmiyor. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2145ab8a66
commit
8dbdebeedc
4 changed files with 131 additions and 12 deletions
|
|
@ -62,6 +62,7 @@ impl Face3DGpu {
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
face3d_wires: &[WireModel],
|
face3d_wires: &[WireModel],
|
||||||
all_wires: &[WireModel],
|
all_wires: &[WireModel],
|
||||||
|
keep_3d_mesh_fills: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
|
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
|
||||||
|
|
||||||
|
|
@ -91,10 +92,20 @@ impl Face3DGpu {
|
||||||
// AO-style dim to them would wash out user-picked colors. Wires
|
// AO-style dim to them would wash out user-picked colors. Wires
|
||||||
// with both fill_tris and points (mesh edges + faces) keep the dim
|
// with both fill_tris and points (mesh edges + faces) keep the dim
|
||||||
// so PolyfaceMesh / PolygonMesh still look 3-D-shaded.
|
// so PolyfaceMesh / PolygonMesh still look 3-D-shaded.
|
||||||
|
//
|
||||||
|
// When `keep_3d_mesh_fills` is false (wireframe render modes),
|
||||||
|
// skip wires that carry 3-D mesh face data (`points` + `fill_tris`)
|
||||||
|
// while leaving 2-D fills (text greek, MultiLeader background)
|
||||||
|
// alone — they're not "3D objects" the wireframe toggle should
|
||||||
|
// hide.
|
||||||
for wire in all_wires {
|
for wire in all_wires {
|
||||||
if wire.fill_tris.is_empty() {
|
if wire.fill_tris.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let is_3d_mesh_face = !wire.points.is_empty();
|
||||||
|
if is_3d_mesh_face && !keep_3d_mesh_fills {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let [r, g, b, a] = wire.color;
|
let [r, g, b, a] = wire.color;
|
||||||
let fill_color = if wire.points.is_empty() {
|
let fill_color = if wire.points.is_empty() {
|
||||||
[r, g, b, a]
|
[r, g, b, a]
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,13 @@ pub struct MeshGpu {
|
||||||
pub vertex_buffer: wgpu::Buffer,
|
pub vertex_buffer: wgpu::Buffer,
|
||||||
pub index_buffer: wgpu::Buffer,
|
pub index_buffer: wgpu::Buffer,
|
||||||
pub index_count: u32,
|
pub index_count: u32,
|
||||||
|
/// Line-list index buffer: every triangle `(a, b, c)` from the
|
||||||
|
/// solid index buffer is expanded into three segments
|
||||||
|
/// `(a,b)(b,c)(c,a)`. Used by the wireframe-mode render path so 3D
|
||||||
|
/// solids draw as their triangle edges without needing the
|
||||||
|
/// `POLYGON_MODE_LINE` device feature.
|
||||||
|
pub wire_index_buffer: wgpu::Buffer,
|
||||||
|
pub wire_index_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GPU-side bundle of MeshLodSet — one MeshGpu per available LOD plus
|
/// GPU-side bundle of MeshLodSet — one MeshGpu per available LOD plus
|
||||||
|
|
@ -109,10 +116,27 @@ impl MeshGpu {
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
usage: wgpu::BufferUsages::INDEX,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wireframe-mode index buffer: expand each triangle into its
|
||||||
|
// three edge segments. Allocates ~2× the solid index count but
|
||||||
|
// is cheap compared to mesh tessellation and only happens when
|
||||||
|
// a new mesh is uploaded.
|
||||||
|
let mut wire_indices: Vec<u32> = Vec::with_capacity(mesh.indices.len() * 2);
|
||||||
|
for tri in mesh.indices.chunks_exact(3) {
|
||||||
|
let (a, b, c) = (tri[0], tri[1], tri[2]);
|
||||||
|
wire_indices.extend_from_slice(&[a, b, b, c, c, a]);
|
||||||
|
}
|
||||||
|
let wire_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some(&format!("mesh.wire_ibuf.{}", mesh.name)),
|
||||||
|
contents: bytemuck::cast_slice(&wire_indices),
|
||||||
|
usage: wgpu::BufferUsages::INDEX,
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
index_buffer,
|
index_buffer,
|
||||||
index_count: mesh.indices.len() as u32,
|
index_count: mesh.indices.len() as u32,
|
||||||
|
wire_index_buffer,
|
||||||
|
wire_index_count: wire_indices.len() as u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,11 @@ pub struct Pipeline {
|
||||||
hatch_batched_pipeline: wgpu::RenderPipeline,
|
hatch_batched_pipeline: wgpu::RenderPipeline,
|
||||||
image_pipeline: wgpu::RenderPipeline,
|
image_pipeline: wgpu::RenderPipeline,
|
||||||
mesh_pipeline: wgpu::RenderPipeline,
|
mesh_pipeline: wgpu::RenderPipeline,
|
||||||
|
/// Wireframe variant of the mesh pipeline (LineList topology, same
|
||||||
|
/// vertex layout / shader). Used when the active render mode is
|
||||||
|
/// Wireframe 2D or Wireframe 3D so 3D solids draw as their
|
||||||
|
/// triangle edges instead of filled faces.
|
||||||
|
mesh_wireframe_pipeline: wgpu::RenderPipeline,
|
||||||
face3d_pipeline: wgpu::RenderPipeline,
|
face3d_pipeline: wgpu::RenderPipeline,
|
||||||
uniform_buffer: wgpu::Buffer,
|
uniform_buffer: wgpu::Buffer,
|
||||||
uniform_bind_group: wgpu::BindGroup,
|
uniform_bind_group: wgpu::BindGroup,
|
||||||
|
|
@ -421,6 +426,51 @@ impl Pipeline {
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wireframe variant — same shader / vertex layout / depth state,
|
||||||
|
// only the input topology changes (LineList) and back-face
|
||||||
|
// culling drops out (each triangle edge is shared between two
|
||||||
|
// faces, one of which would otherwise hide the edge).
|
||||||
|
let mesh_wireframe_pipeline =
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("mesh.wireframe.pipeline"),
|
||||||
|
layout: Some(&mesh_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &mesh_shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[mesh_gpu::MeshVertex::layout()],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::LineList,
|
||||||
|
cull_mode: None,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||||
|
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: &mesh_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,
|
||||||
|
});
|
||||||
|
|
||||||
// ── Face3D pipeline ────────────────────────────────────────────────
|
// ── Face3D pipeline ────────────────────────────────────────────────
|
||||||
let face3d_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let face3d_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("face3d.shader"),
|
label: Some("face3d.shader"),
|
||||||
|
|
@ -666,6 +716,7 @@ impl Pipeline {
|
||||||
hatch_batched_pipeline,
|
hatch_batched_pipeline,
|
||||||
image_pipeline,
|
image_pipeline,
|
||||||
mesh_pipeline,
|
mesh_pipeline,
|
||||||
|
mesh_wireframe_pipeline,
|
||||||
face3d_pipeline,
|
face3d_pipeline,
|
||||||
uniform_buffer,
|
uniform_buffer,
|
||||||
uniform_bind_group,
|
uniform_bind_group,
|
||||||
|
|
@ -790,18 +841,32 @@ impl Pipeline {
|
||||||
// outlines stay on the screen regardless of mode.
|
// outlines stay on the screen regardless of mode.
|
||||||
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires);
|
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires);
|
||||||
// Fill buffer: in wireframe mode we drop the 3DFACE quads
|
// Fill buffer: in wireframe mode we drop the 3DFACE quads
|
||||||
// (those come from `face3d_wires.key_vertices`) but keep
|
// (those come from `face3d_wires.key_vertices`) *and* the
|
||||||
// `all_wires.fill_tris` — that channel carries 2D content like
|
// PolyfaceMesh / PolygonMesh face triangles routed through
|
||||||
// text-LOD greek rectangles and PolyfaceMesh face triangles
|
// `all_wires.fill_tris` (Face3DGpu::from_wires recognises them
|
||||||
// which shouldn't disappear with the toggle.
|
// by the wire having both `points` and `fill_tris`). 2-D
|
||||||
|
// fill_tris (text-LOD greek, MultiLeader background — `points`
|
||||||
|
// empty) survive in either mode.
|
||||||
|
let keep_3d_mesh_fills = !wireframe_only;
|
||||||
let face3d_for_fill: &[WireModel] = if wireframe_only { &[] } else { face3d_wires };
|
let face3d_for_fill: &[WireModel] = if wireframe_only { &[] } else { face3d_wires };
|
||||||
|
let has_any_2d_fill = all_wires
|
||||||
|
.iter()
|
||||||
|
.any(|w| !w.fill_tris.is_empty() && w.points.is_empty());
|
||||||
|
let has_any_3d_fill = all_wires
|
||||||
|
.iter()
|
||||||
|
.any(|w| !w.fill_tris.is_empty() && !w.points.is_empty());
|
||||||
let has_fills = !face3d_for_fill.is_empty()
|
let has_fills = !face3d_for_fill.is_empty()
|
||||||
|| all_wires.iter().any(|w| !w.fill_tris.is_empty());
|
|| has_any_2d_fill
|
||||||
|
|| (keep_3d_mesh_fills && has_any_3d_fill);
|
||||||
if !has_fills {
|
if !has_fills {
|
||||||
self.gpu_face3d_fill = None;
|
self.gpu_face3d_fill = None;
|
||||||
} else {
|
} else {
|
||||||
self.gpu_face3d_fill =
|
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(
|
||||||
Some(Face3DGpu::from_wires(device, face3d_for_fill, all_wires));
|
device,
|
||||||
|
face3d_for_fill,
|
||||||
|
all_wires,
|
||||||
|
keep_3d_mesh_fills,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -870,6 +935,7 @@ impl Pipeline {
|
||||||
target: &wgpu::TextureView,
|
target: &wgpu::TextureView,
|
||||||
clip_bounds: Rectangle<u32>,
|
clip_bounds: Rectangle<u32>,
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
|
mesh_wireframe: bool,
|
||||||
) {
|
) {
|
||||||
let vp = clip_bounds;
|
let vp = clip_bounds;
|
||||||
let msaa = &self.msaa_view;
|
let msaa = &self.msaa_view;
|
||||||
|
|
@ -997,7 +1063,16 @@ impl Pipeline {
|
||||||
occlusion_query_set: 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_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||||
|
// Wireframe mode swaps in the LineList pipeline + the
|
||||||
|
// per-mesh `wire_index_buffer` (triangle indices expanded
|
||||||
|
// into edge segments at upload time); everything else stays
|
||||||
|
// identical so the same uniforms / vertex buffers feed both
|
||||||
|
// paths.
|
||||||
|
if mesh_wireframe {
|
||||||
|
pass.set_pipeline(&self.mesh_wireframe_pipeline);
|
||||||
|
} else {
|
||||||
pass.set_pipeline(&self.mesh_pipeline);
|
pass.set_pipeline(&self.mesh_pipeline);
|
||||||
|
}
|
||||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||||
for (i, set) in self.gpu_meshes.iter().enumerate() {
|
for (i, set) in self.gpu_meshes.iter().enumerate() {
|
||||||
if !self.mesh_visible.get(i).copied().unwrap_or(true) {
|
if !self.mesh_visible.get(i).copied().unwrap_or(true) {
|
||||||
|
|
@ -1012,12 +1087,17 @@ impl Pipeline {
|
||||||
let Some(mesh) = set.lods.get(level) else {
|
let Some(mesh) = set.lods.get(level) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if mesh.index_count == 0 {
|
let (ibuf, icount) = if mesh_wireframe {
|
||||||
|
(&mesh.wire_index_buffer, mesh.wire_index_count)
|
||||||
|
} else {
|
||||||
|
(&mesh.index_buffer, mesh.index_count)
|
||||||
|
};
|
||||||
|
if icount == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||||
pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
|
||||||
pass.draw_indexed(0..mesh.index_count, 0, 0..1);
|
pass.draw_indexed(0..icount, 0, 0..1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,11 @@ impl shader::Primitive for Primitive {
|
||||||
target: &iced::wgpu::TextureView,
|
target: &iced::wgpu::TextureView,
|
||||||
clip: &Rectangle<u32>,
|
clip: &Rectangle<u32>,
|
||||||
) {
|
) {
|
||||||
pipeline.render(encoder, target, *clip, self.bg_color);
|
// `mesh_fill` is false for Wireframe 2D / Wireframe 3D — flip
|
||||||
|
// the draw path so meshes use the wireframe pipeline + the
|
||||||
|
// pre-built triangle-edge index buffer.
|
||||||
|
let mesh_wireframe = !self.mesh_fill;
|
||||||
|
pipeline.render(encoder, target, *clip, self.bg_color, mesh_wireframe);
|
||||||
if self.show_viewcube {
|
if self.show_viewcube {
|
||||||
pipeline.viewcube.render(encoder, target, *clip);
|
pipeline.viewcube.render(encoder, target, *clip);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue