Mipmapping an den Texturen
This commit is contained in:
+70
-26
@@ -136,13 +136,18 @@ impl ScenePass {
|
|||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Nearest-Sampler: harte Texel, kein Filtering — PS1.
|
// mag=Nearest hält nah dran die harten Texel (der knackige Look);
|
||||||
|
// min/mipmap=Linear glätten nur die Verkleinerung in der Ferne und
|
||||||
|
// blenden weich zwischen den Mip-Ebenen (kein Mip-Popping). Killt das
|
||||||
|
// Texel-Flimmern hochauflösender Texturen auf der kleinen internen
|
||||||
|
// Auflösung. Mipmaps werden in upload_texture per Box-Filter erzeugt.
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
label: Some("scene nearest"),
|
label: Some("scene"),
|
||||||
address_mode_u: wgpu::AddressMode::Repeat,
|
address_mode_u: wgpu::AddressMode::Repeat,
|
||||||
address_mode_v: wgpu::AddressMode::Repeat,
|
address_mode_v: wgpu::AddressMode::Repeat,
|
||||||
mag_filter: wgpu::FilterMode::Nearest,
|
mag_filter: wgpu::FilterMode::Nearest,
|
||||||
min_filter: wgpu::FilterMode::Nearest,
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let tex_layout = pipeline.get_bind_group_layout(1);
|
let tex_layout = pipeline.get_bind_group_layout(1);
|
||||||
@@ -176,7 +181,8 @@ impl ScenePass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Ein RGBA8-`Image` als GPU-Textur hochladen und die zugehörige
|
/// Ein RGBA8-`Image` als GPU-Textur hochladen und die zugehörige
|
||||||
/// Bind-Group (Textur + Sampler, group 1) bauen.
|
/// Bind-Group (Textur + Sampler, group 1) bauen. Erzeugt die volle
|
||||||
|
/// Mipchain (bis 1×1) per Box-Filter auf der CPU und lädt jede Ebene hoch.
|
||||||
fn upload_texture(
|
fn upload_texture(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
@@ -184,15 +190,16 @@ fn upload_texture(
|
|||||||
sampler: &wgpu::Sampler,
|
sampler: &wgpu::Sampler,
|
||||||
img: &Image,
|
img: &Image,
|
||||||
) -> wgpu::BindGroup {
|
) -> wgpu::BindGroup {
|
||||||
let size = wgpu::Extent3d {
|
// floor(log2(max(w,h))) + 1 = volle Kette bis zur 1×1-Ebene.
|
||||||
width: img.width,
|
let mip_level_count = 32 - img.width.max(img.height).leading_zeros();
|
||||||
height: img.height,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
};
|
|
||||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label: Some("scene texture"),
|
label: Some("scene texture"),
|
||||||
size,
|
size: wgpu::Extent3d {
|
||||||
mip_level_count: 1,
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
dimension: wgpu::TextureDimension::D2,
|
||||||
// Nicht-sRGB: die Quantisierung im Shader erwartet rohe Werte.
|
// Nicht-sRGB: die Quantisierung im Shader erwartet rohe Werte.
|
||||||
@@ -200,21 +207,32 @@ fn upload_texture(
|
|||||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
view_formats: &[],
|
view_formats: &[],
|
||||||
});
|
});
|
||||||
queue.write_texture(
|
|
||||||
wgpu::TexelCopyTextureInfo {
|
// Ebene 0 ist das Originalbild; jede weitere wird aus der vorherigen
|
||||||
texture: &texture,
|
// halbiert. `level` trägt die aktuell hochzuladenden Pixel.
|
||||||
mip_level: 0,
|
let mut level = img.rgba.clone();
|
||||||
origin: wgpu::Origin3d::ZERO,
|
let (mut w, mut h) = (img.width, img.height);
|
||||||
aspect: wgpu::TextureAspect::All,
|
for mip in 0..mip_level_count {
|
||||||
},
|
queue.write_texture(
|
||||||
&img.rgba,
|
wgpu::TexelCopyTextureInfo {
|
||||||
wgpu::TexelCopyBufferLayout {
|
texture: &texture,
|
||||||
offset: 0,
|
mip_level: mip,
|
||||||
bytes_per_row: Some(img.width * 4),
|
origin: wgpu::Origin3d::ZERO,
|
||||||
rows_per_image: Some(img.height),
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
size,
|
&level,
|
||||||
);
|
wgpu::TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(w * 4),
|
||||||
|
rows_per_image: Some(h),
|
||||||
|
},
|
||||||
|
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
||||||
|
);
|
||||||
|
if mip + 1 < mip_level_count {
|
||||||
|
(level, w, h) = downsample(&level, w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let view = texture.create_view(&Default::default());
|
let view = texture.create_view(&Default::default());
|
||||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("scene texture"),
|
label: Some("scene texture"),
|
||||||
@@ -225,3 +243,29 @@ fn upload_texture(
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Eine RGBA8-Mip-Ebene per 2×2-Box-Filter auf die nächstkleinere halbieren.
|
||||||
|
/// Gibt die neuen Pixel samt Maßen zurück. Ungerade Maße werden via `(d+1)/2`
|
||||||
|
/// aufgerundet (sonst ginge die letzte Spalte/Zeile verloren); die fehlende
|
||||||
|
/// Quell-Spalte/-Zeile wird auf den Rand geklemmt, statt den Mittelwert zu
|
||||||
|
/// verfälschen. Mittelung im (gamma-kodierten) Speicherraum — für den
|
||||||
|
/// stilisierten Look unkritisch.
|
||||||
|
fn downsample(src: &[u8], w: u32, h: u32) -> (Vec<u8>, u32, u32) {
|
||||||
|
let (nw, nh) = ((w + 1) / 2, (h + 1) / 2);
|
||||||
|
let mut dst = vec![0u8; (nw * nh * 4) as usize];
|
||||||
|
for y in 0..nh {
|
||||||
|
for x in 0..nw {
|
||||||
|
let (x0, y0) = (2 * x, 2 * y);
|
||||||
|
let x1 = (x0 + 1).min(w - 1);
|
||||||
|
let y1 = (y0 + 1).min(h - 1);
|
||||||
|
let at = |px: u32, py: u32| ((py * w + px) * 4) as usize;
|
||||||
|
let samples = [at(x0, y0), at(x1, y0), at(x0, y1), at(x1, y1)];
|
||||||
|
let di = ((y * nw + x) * 4) as usize;
|
||||||
|
for c in 0..4 {
|
||||||
|
let sum: u32 = samples.iter().map(|&s| src[s + c] as u32).sum();
|
||||||
|
dst[di + c] = ((sum + 2) / 4) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(dst, nw, nh)
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,9 +53,15 @@ fn bayer4(px: vec2u) -> f32 {
|
|||||||
return f32(m[(px.y % 4u) * 4u + (px.x % 4u)]);
|
return f32(m[(px.y % 4u) * 4u + (px.x % 4u)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LOD-Bias für die Mip-Auswahl: negativ = schärfer (greift einen höher
|
||||||
|
// aufgelösten Mip, als die UV-Ableitungen verlangen), positiv = weicher.
|
||||||
|
// 0 = neutral. Bei -1.0 verdoppelt sich praktisch die Texelrate (mehr
|
||||||
|
// Schärfe, etwas mehr Flimmern); -0.5 ist ein sanfter Mittelweg.
|
||||||
|
const MIP_BIAS: f32 = -1.0;
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VsOut) -> @location(0) vec4f {
|
fn fs_main(in: VsOut) -> @location(0) vec4f {
|
||||||
let texel = textureSample(tex, smp, in.uv);
|
let texel = textureSampleBias(tex, smp, in.uv, MIP_BIAS);
|
||||||
if texel.a < 0.5 { discard; } // 1-Bit-Alpha (Cutouts), noch ungenutzt
|
if texel.a < 0.5 { discard; } // 1-Bit-Alpha (Cutouts), noch ungenutzt
|
||||||
|
|
||||||
// RGB555: 31 Stufen pro Kanal. Bayer-Schwelle vor dem Abrunden →
|
// RGB555: 31 Stufen pro Kanal. Bayer-Schwelle vor dem Abrunden →
|
||||||
|
|||||||
Reference in New Issue
Block a user