Skip to content

HDR 与 Tonemapping

显示器能显示的颜色范围是有限的(0.0–1.0),但真实世界的光照强度远超这个范围——太阳、灯泡、自发光材质的亮度可能是普通表面的几百倍。HDR(High Dynamic Range,高动态范围)渲染让这些超高亮度值在计算过程中保留下来,最终通过 Tonemapping(色调映射)压缩回显示器能显示的范围。

启用 HDR

在 Bevy 中,HDR 是一个组件标记——把 Hdr 加到相机实体上即可:

rust
use bevy::{
    core_pipeline::tonemapping::Tonemapping,
    prelude::*,
    camera::Hdr,
};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, switch_tonemapping)
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.spawn((
        Camera3d::default(),
        Hdr,
        Tonemapping::TonyMcMapface,
        Transform::from_xyz(2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    commands.spawn((
        DirectionalLight {
            illuminance: 15000.0,
            ..default()
        },
        Transform::from_rotation(Quat::from_euler(
            EulerRot::ZYX,
            0.0,
            -0.5,
            -0.5,
        )),
    ));

    let bright = materials.add(StandardMaterial {
        base_color: Color::srgb(0.8, 0.2, 0.1),
        emissive: LinearRgba::rgb(5.0, 1.0, 0.5),
        ..default()
    });
    let normal = materials.add(Color::srgb(0.3, 0.5, 0.3));
    let sphere = meshes.add(Sphere::new(0.5));

    commands.spawn((
        Mesh3d(sphere.clone()),
        MeshMaterial3d(bright),
        Transform::from_xyz(-1.2, 0.5, 0.0),
    ));
    commands.spawn((
        Mesh3d(sphere),
        MeshMaterial3d(normal),
        Transform::from_xyz(1.2, 0.5, 0.0),
    ));
    commands.spawn((
        Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
        MeshMaterial3d(materials.add(Color::srgb(0.2, 0.2, 0.2))),
    ));

    commands.spawn((
        Text::default(),
        Node {
            position_type: PositionType::Absolute,
            top: px(12),
            left: px(12),
            ..default()
        },
    ));
}

fn switch_tonemapping(
    mut text: Single<&mut Text>,
    tonemapping: Single<&Tonemapping>,
    keys: Res<ButtonInput<KeyCode>>,
) {
    let name = match **tonemapping {
        Tonemapping::None => "None",
        Tonemapping::Reinhard => "Reinhard",
        Tonemapping::ReinhardLuminance => "ReinhardLuminance",
        Tonemapping::AcesFitted => "AcesFitted",
        Tonemapping::AgX => "AgX",
        Tonemapping::SomewhatBoringDisplayTransform => "SomewhatBoringDisplayTransform",
        Tonemapping::TonyMcMapface => "TonyMcMapface",
        Tonemapping::BlenderFilmic => "BlenderFilmic",
        Tonemapping::KhronosPbrNeutral => "KhronosPbrNeutral",
    };
    text.0 = format!("Tonemapping: {name}\n(1-8 to switch)");
    let _ = keys;
}

Listing 26-1:HDR 相机与 TonyMcMapface 色调映射

Hdr 组件告诉渲染管线使用浮点帧缓冲区,保留超出 0–1 范围的亮度值。没有 Hdr,所有超过 1.0 的颜色会被直接截断,后续的 Bloom 等效果将无法正确工作。

Tonemapping 枚举

Tonemapping 是一个组件,挂在相机实体上,决定如何把 HDR 值压缩到显示器范围。Bevy 内置了多种算法:

枚举值特点
None不做映射,直接截断。高亮区域过曝、颜色失真
Reinhard经典算法,简单但会丢失亮部细节
ReinhardLuminance基于亮度的 Reinhard 变体,保留色相更好
AcesFittedACES 电影级映射,对比度高、色彩风格化明显
AgX中性、低色相偏移,适合写实风格(需 tonemapping_luts feature)
TonyMcMapfaceBevy 默认。中性、低对比度、亮部自然去饱和
BlenderFilmicBlender 默认的电影级映射

默认值是 TonyMcMapface,它在大多数场景下表现均衡。如果想要更强烈的电影感,可以尝试 AcesFitted;追求写实中性则选 AgX

自发光与 HDR 的配合

HDR 渲染的意义在于保留亮度信息。StandardMaterialemissive 字段接受 LinearRgba,其分量可以远超 1.0:

rust
StandardMaterial {
    emissive: LinearRgba::rgb(5.0, 1.0, 0.5),  // 亮度是普通白色的 5 倍
    ..default()
}

这些超亮值在 Tonemapping 之前保留完整精度,是 Bloom 效果的前提——Bloom 需要识别"比周围亮得多"的区域来生成辉光。