feat(render): Flat/Gouraud shading, WithEdges overlay, paper-viewport mode
Faz 3 — FlatShaded vs GouraudShaded
- Uniforms.flat_shade (f32, 0/1) + mesh shader fragment derivative
bazlı per-triangle face normal hesabı; GouraudShaded ise interpolate
edilen vertex normalini kullanıyor.
Faz 4 — *WithEdges varyantları
- Pipeline::render.show_3d_edges parametresi:
- FlatShaded / GouraudShaded → 3DFACE edge pass'i atlanıyor
- *WithEdges varyantları → shaded fill'in üstüne mesh_wireframe
pipeline ile triangle-edge overlay (LessEqual depth test ile)
- face3d_edges pass'i show_3d_edges ile gate'lendi
Faz 5 — Paper-space viewport entity round-trip
- build_viewport_primitive / build_active_viewport_primitive artık
render_mode parametresi almıyor; viewport entity'sinin kendi
render_mode alanından okuyor. PaperViewportPane'dan render_mode
field'i kaldırıldı. Model layout'taki tab.render_mode hala
pick_list ile yönetiliyor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
56834dfba6
commit
132efe6574
6 changed files with 141 additions and 56 deletions
|
|
@ -1028,6 +1028,7 @@ impl Pipeline {
|
|||
bg_color: [f32; 4],
|
||||
mesh_wireframe: bool,
|
||||
hidden_line: bool,
|
||||
show_3d_edges: bool,
|
||||
) {
|
||||
let vp = clip_bounds;
|
||||
let msaa = &self.msaa_view;
|
||||
|
|
@ -1156,15 +1157,17 @@ impl Pipeline {
|
|||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
// Three draw modes share this pass:
|
||||
// - Solid: `mesh_pipeline` + the triangle index buffer.
|
||||
// - Wireframe: `mesh_wireframe_pipeline` + the pre-built
|
||||
// `wire_index_buffer` (each triangle edge expanded to a
|
||||
// line segment at upload).
|
||||
// - HiddenLine: depth prepass with `mesh_depth_pipeline`
|
||||
// (writes Z, no colour), then wire-edge draw on top.
|
||||
// The wire draw's LessEqual depth test naturally hides
|
||||
// edges occluded by the prepass.
|
||||
// Four draw paths share this pass:
|
||||
// - Solid: `mesh_pipeline` + triangle index buf.
|
||||
// - Wireframe: `mesh_wireframe_pipeline` + the
|
||||
// pre-built `wire_index_buffer`.
|
||||
// - HiddenLine: depth prepass (`mesh_depth_pipeline`,
|
||||
// writes Z, no colour) → wire overlay.
|
||||
// - Solid+Edges: `mesh_pipeline` shaded fill → wire
|
||||
// overlay; LessEqual depth test on the
|
||||
// wire pass keeps the edges crisp on
|
||||
// top of the shaded surface.
|
||||
let want_solid_with_edges = !hidden_line && !mesh_wireframe && show_3d_edges;
|
||||
if hidden_line {
|
||||
pass.set_pipeline(&self.mesh_depth_pipeline);
|
||||
for (i, set) in self.gpu_meshes.iter().enumerate() {
|
||||
|
|
@ -1242,6 +1245,36 @@ impl Pipeline {
|
|||
pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
|
||||
pass.draw_indexed(0..icount, 0, 0..1);
|
||||
}
|
||||
// *WithEdges variants: overlay wire-edge segments on top
|
||||
// of the shaded fill. The LessEqual depth test on the
|
||||
// wireframe pipeline keeps the edges visible at the
|
||||
// fragments that just got written by the solid pass.
|
||||
if want_solid_with_edges {
|
||||
pass.set_pipeline(&self.mesh_wireframe_pipeline);
|
||||
for (i, set) in self.gpu_meshes.iter().enumerate() {
|
||||
if !self.mesh_visible.get(i).copied().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
let level = self
|
||||
.mesh_lod_levels
|
||||
.get(i)
|
||||
.copied()
|
||||
.unwrap_or(0)
|
||||
.min(set.lods.len().saturating_sub(1));
|
||||
let Some(mesh) = set.lods.get(level) else {
|
||||
continue;
|
||||
};
|
||||
if mesh.wire_index_count == 0 {
|
||||
continue;
|
||||
}
|
||||
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
mesh.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
pass.draw_indexed(0..mesh.wire_index_count, 0, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1293,7 +1326,9 @@ impl Pipeline {
|
|||
}
|
||||
|
||||
// ── Pass 5b: 3DFACE edges (batched, possibly multiple chunks) ────
|
||||
if !self.gpu_face3d_edges.is_empty() {
|
||||
// FlatShaded / GouraudShaded hide the 3DFACE outline (the user
|
||||
// chose a clean shaded look); every other mode keeps it.
|
||||
if show_3d_edges && !self.gpu_face3d_edges.is_empty() {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("face3d_edges.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ pub struct Uniforms {
|
|||
/// LWDISPLAY toggle (1.0 = show lineweights, 0.0 = force 1 px).
|
||||
/// Read by the wire shader so the toggle does not require a retessellate.
|
||||
pub lwdisplay_enable: f32,
|
||||
/// 1.0 → mesh fragment shader replaces the interpolated vertex
|
||||
/// normal with `normalize(cross(dpdx(pos), dpdy(pos)))` so each
|
||||
/// triangle gets a uniform shade (FlatShaded mode); 0.0 → keeps the
|
||||
/// per-vertex normal interpolation (GouraudShaded-style).
|
||||
pub flat_shade: f32,
|
||||
/// Pads the struct to 112 B (next multiple of 16) so wgpu's uniform
|
||||
/// alignment rules are satisfied.
|
||||
pub _pad: [f32; 3],
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
|
|
@ -30,6 +38,8 @@ impl Uniforms {
|
|||
viewport_size: [bounds.width, bounds.height],
|
||||
world_per_pixel,
|
||||
lwdisplay_enable: if lwdisplay_enable { 1.0 } else { 0.0 },
|
||||
flat_shade: 0.0,
|
||||
_pad: [0.0; 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue