render3d: 4× MSAA im wgpu-Renderer — glatte 3D-Kanten
Multisampled Farbtarget (SAMPLE_COUNT=4) + resolve_target in die Surface-View je Frame; Depth-Textur-sample_count synchron auf 4; MSAA-/Depth-Textur werden bei Resize gemeinsam neu erzeugt; multisample.count=4 auf der Render-Pipeline. Nur gpu.rs — mesh/section/openings unberührt. Gates: cargo check (window) + wasm32 --features web grün, cargo test 32/32, --features render 33/33.
This commit is contained in:
@@ -20,6 +20,11 @@ use crate::types::{Camera, Mesh, SlabInput, WallInput, FLOATS_PER_VERTEX};
|
|||||||
/// Tiefenformat des Z-Puffers (32 Bit Float, ueberall verfuegbar).
|
/// Tiefenformat des Z-Puffers (32 Bit Float, ueberall verfuegbar).
|
||||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||||
|
|
||||||
|
/// MSAA-Samples fuer Farb- und Tiefenziel. 4x ist auf allen gaengigen Backends
|
||||||
|
/// (Vulkan/Metal/DX12/GL) fuer die ueblichen Oberflaechenformate verfuegbar und
|
||||||
|
/// gleicht die Treppenstufen an Wandkanten/Silhouetten spuerbar aus.
|
||||||
|
const SAMPLE_COUNT: u32 = 4;
|
||||||
|
|
||||||
/// Uniform-Block, 1:1 zum WGSL-`Globals`-Struct. std140-kompatibel (mat4/vec4 auf
|
/// Uniform-Block, 1:1 zum WGSL-`Globals`-Struct. std140-kompatibel (mat4/vec4 auf
|
||||||
/// 16 Byte ausgerichtet).
|
/// 16 Byte ausgerichtet).
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -69,6 +74,9 @@ pub struct Renderer {
|
|||||||
uniform: wgpu::Buffer,
|
uniform: wgpu::Buffer,
|
||||||
mesh: Option<MeshBuffers>,
|
mesh: Option<MeshBuffers>,
|
||||||
depth: Option<DepthTarget>,
|
depth: Option<DepthTarget>,
|
||||||
|
msaa: Option<MsaaTarget>,
|
||||||
|
/// Farbformat des Render-Ziels (Surface), fuer die MSAA-Zwischentextur.
|
||||||
|
color_format: wgpu::TextureFormat,
|
||||||
globals: Globals,
|
globals: Globals,
|
||||||
/// Loeschfarbe (Hintergrund). Default heller Grauton wie die three.js-Sicht.
|
/// Loeschfarbe (Hintergrund). Default heller Grauton wie die three.js-Sicht.
|
||||||
pub clear_color: wgpu::Color,
|
pub clear_color: wgpu::Color,
|
||||||
@@ -81,6 +89,14 @@ struct DepthTarget {
|
|||||||
height: u32,
|
height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Multisampled Farb-Zwischentextur samt View, an eine bestimmte Groesse
|
||||||
|
/// gebunden. Wird jeden Frame in das eigentliche Ziel (Surface-View) aufgeloest.
|
||||||
|
struct MsaaTarget {
|
||||||
|
view: wgpu::TextureView,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
/// Erzeugt Pipeline + Layout fuer ein gegebenes Farbformat (Surface).
|
/// Erzeugt Pipeline + Layout fuer ein gegebenes Farbformat (Surface).
|
||||||
pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
|
pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
|
||||||
@@ -152,7 +168,11 @@ impl Renderer {
|
|||||||
stencil: wgpu::StencilState::default(),
|
stencil: wgpu::StencilState::default(),
|
||||||
bias: wgpu::DepthBiasState::default(),
|
bias: wgpu::DepthBiasState::default(),
|
||||||
}),
|
}),
|
||||||
multisample: wgpu::MultisampleState::default(),
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: SAMPLE_COUNT,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
multiview_mask: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
@@ -178,6 +198,8 @@ impl Renderer {
|
|||||||
uniform,
|
uniform,
|
||||||
mesh: None,
|
mesh: None,
|
||||||
depth: None,
|
depth: None,
|
||||||
|
msaa: None,
|
||||||
|
color_format,
|
||||||
globals: Globals::default(),
|
globals: Globals::default(),
|
||||||
// #f5f5f5 heller Hintergrund (wie der 2D-Grundriss).
|
// #f5f5f5 heller Hintergrund (wie der 2D-Grundriss).
|
||||||
clear_color: wgpu::Color {
|
clear_color: wgpu::Color {
|
||||||
@@ -250,7 +272,8 @@ impl Renderer {
|
|||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
// Muss zur Sample-Zahl der Farb-/Pipeline-Ziele passen (MSAA).
|
||||||
|
sample_count: SAMPLE_COUNT,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
dimension: wgpu::TextureDimension::D2,
|
||||||
format: DEPTH_FORMAT,
|
format: DEPTH_FORMAT,
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
@@ -264,14 +287,45 @@ impl Renderer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Zeichnet einen Frame in `view` (Surface- oder Offscreen-Textur). Die Kamera
|
/// Stellt sicher, dass eine multisampled Farb-Zwischentextur passend zur
|
||||||
/// liefert die View-Projektions-Matrix; `viewport` bestimmt das Seitenverhaeltnis
|
/// Ziel-Groesse existiert (wird jeden Frame in die Surface-View aufgeloest).
|
||||||
/// und die Tiefenpuffer-Groesse.
|
fn ensure_msaa(&mut self, device: &wgpu::Device, w: u32, h: u32) {
|
||||||
|
let ok = matches!(&self.msaa, Some(m) if m.width == w && m.height == h);
|
||||||
|
if ok {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("msaa.color"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: w.max(1),
|
||||||
|
height: h.max(1),
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: SAMPLE_COUNT,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: self.color_format,
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
self.msaa = Some(MsaaTarget {
|
||||||
|
view,
|
||||||
|
width: w.max(1),
|
||||||
|
height: h.max(1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zeichnet einen Frame und loest ihn in `target` auf (Surface- oder
|
||||||
|
/// Offscreen-Textur). Intern wird in eine multisampled Zwischentextur (MSAA)
|
||||||
|
/// gerendert und am Ende der Pass in `target` aufgeloest. Die Kamera liefert
|
||||||
|
/// die View-Projektions-Matrix; `viewport` bestimmt das Seitenverhaeltnis und
|
||||||
|
/// die Tiefen-/MSAA-Puffer-Groesse.
|
||||||
pub fn render(
|
pub fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
view: &wgpu::TextureView,
|
target: &wgpu::TextureView,
|
||||||
camera: &Camera,
|
camera: &Camera,
|
||||||
viewport: (u32, u32),
|
viewport: (u32, u32),
|
||||||
) {
|
) {
|
||||||
@@ -282,7 +336,9 @@ impl Renderer {
|
|||||||
queue.write_buffer(&self.uniform, 0, bytemuck::bytes_of(&self.globals));
|
queue.write_buffer(&self.uniform, 0, bytemuck::bytes_of(&self.globals));
|
||||||
|
|
||||||
self.ensure_depth(device, w, h);
|
self.ensure_depth(device, w, h);
|
||||||
|
self.ensure_msaa(device, w, h);
|
||||||
let depth_view = &self.depth.as_ref().unwrap().view;
|
let depth_view = &self.depth.as_ref().unwrap().view;
|
||||||
|
let msaa_view = &self.msaa.as_ref().unwrap().view;
|
||||||
|
|
||||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
label: Some("3d.encoder"),
|
label: Some("3d.encoder"),
|
||||||
@@ -291,13 +347,17 @@ impl Renderer {
|
|||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("3d.pass"),
|
label: Some("3d.pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view,
|
// In die multisampled Zwischentextur rendern; wgpu loest sie am
|
||||||
|
// Ende der Pass automatisch in `target` (Surface-View) auf.
|
||||||
|
view: msaa_view,
|
||||||
// Ziel ist eine 2D-Textur (kein 3D-Volumen) -> kein Depth-Slice.
|
// Ziel ist eine 2D-Textur (kein 3D-Volumen) -> kein Depth-Slice.
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: Some(target),
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(self.clear_color),
|
load: wgpu::LoadOp::Clear(self.clear_color),
|
||||||
store: wgpu::StoreOp::Store,
|
// Der multisampled Inhalt selbst wird nach dem Aufloesen nicht
|
||||||
|
// mehr gebraucht — Discard spart Bandbreite.
|
||||||
|
store: wgpu::StoreOp::Discard,
|
||||||
},
|
},
|
||||||
})],
|
})],
|
||||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
|||||||
Reference in New Issue
Block a user