Files
game_engine/crates/voltex_renderer/src/tonemap.rs
tolelom 6c938999e4 feat(renderer): add HDR target, Bloom resources, and ACES tonemap
- Add hdr.rs with HdrTarget (Rgba16Float render target) and HDR_FORMAT constant
- Add bloom.rs with BloomResources (5-level mip chain), BloomUniform, and mip_sizes()
- Add tonemap.rs with TonemapUniform and CPU-side aces_tonemap() for testing
- Export all new types from lib.rs
- 33 tests passing (26 existing + 3 bloom + 4 tonemap)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 13:47:19 +09:00

72 lines
2.0 KiB
Rust

use bytemuck::{Pod, Zeroable};
/// Uniform buffer for the tonemap pass.
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct TonemapUniform {
/// Bloom contribution weight.
pub bloom_intensity: f32,
/// Pre-tonemap exposure multiplier.
pub exposure: f32,
pub _padding: [f32; 2],
}
impl Default for TonemapUniform {
fn default() -> Self {
Self {
bloom_intensity: 0.5,
exposure: 1.0,
_padding: [0.0; 2],
}
}
}
/// CPU implementation of the ACES filmic tonemap curve (for testing / CPU-side work).
///
/// Formula: clamp((x*(2.51*x+0.03))/(x*(2.43*x+0.59)+0.14), 0, 1)
pub fn aces_tonemap(x: f32) -> f32 {
let num = x * (2.51 * x + 0.03);
let den = x * (2.43 * x + 0.59) + 0.14;
(num / den).clamp(0.0, 1.0)
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aces_zero() {
// aces(0) should be ≈ 0
assert!(aces_tonemap(0.0).abs() < 1e-5, "aces(0) = {}", aces_tonemap(0.0));
}
#[test]
fn aces_one() {
// aces(1) ≈ 0.80 with the standard formula
// clamp((1*(2.51+0.03))/(1*(2.43+0.59)+0.14), 0, 1) = 2.54/3.16 ≈ 0.8038
let v = aces_tonemap(1.0);
assert!(
(v - 0.8038).abs() < 0.001,
"aces(1) = {}, expected ≈ 0.8038",
v
);
}
#[test]
fn aces_large() {
// aces(10) should be very close to 1.0 (saturated)
let v = aces_tonemap(10.0);
assert!(v > 0.999, "aces(10) = {}, expected ≈ 1.0", v);
}
#[test]
fn tonemap_uniform_default() {
let u = TonemapUniform::default();
assert!((u.bloom_intensity - 0.5).abs() < f32::EPSILON);
assert!((u.exposure - 1.0).abs() < f32::EPSILON);
assert_eq!(u._padding, [0.0f32; 2]);
}
}