Skip to content

景深与色散

本节介绍两种模拟相机光学"缺陷"的后处理效果:景深(Depth of Field)让聚焦点之外的物体模糊,色散(Chromatic Aberration)让画面边缘出现颜色分离。两者叠加使用时,画面质感会显著提升。

DepthOfField:景深

景深模拟真实相机的对焦行为——只有焦平面上的物体清晰,距离焦平面越远越模糊。

rust
use bevy::{
    core_pipeline::tonemapping::Tonemapping,
    post_process::{
        bloom::Bloom,
        dof::{DepthOfField, DepthOfFieldMode},
    },
    prelude::*,
};

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

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.spawn((
        Camera3d::default(),
        Tonemapping::TonyMcMapface,
        Bloom::NATURAL,
        Transform::from_xyz(0.0, 2.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
        DepthOfField {
            mode: DepthOfFieldMode::Gaussian,
            focal_distance: 6.0,
            aperture_f_stops: 0.5,
            ..default()
        },
    ));

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

    let red = materials.add(Color::srgb(0.8, 0.1, 0.1));
    let green = materials.add(Color::srgb(0.1, 0.8, 0.1));
    let blue = materials.add(Color::srgb(0.1, 0.1, 0.8));
    let sphere = meshes.add(Sphere::new(0.5));

    let positions = [
        (Vec3::new(-3.0, 0.5, -2.0), red),
        (Vec3::new(0.0, 0.5, 0.0), green),
        (Vec3::new(3.0, 0.5, 2.0), blue),
    ];
    for (pos, mat) in positions {
        commands.spawn((
            Mesh3d(sphere.clone()),
            MeshMaterial3d(mat),
            Transform::from_translation(pos),
        ));
    }

    commands.spawn((
        Mesh3d(meshes.add(Plane3d::default().mesh().size(20.0, 20.0))),
        MeshMaterial3d(materials.add(Color::srgb(0.15, 0.15, 0.15))),
    ));

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

fn adjust_dof(
    mut dof: Single<&mut DepthOfField>,
    mut text: Single<&mut Text>,
    keys: Res<ButtonInput<KeyCode>>,
) {
    if keys.pressed(KeyCode::ArrowUp) {
        dof.focal_distance += 0.05;
    }
    if keys.pressed(KeyCode::ArrowDown) {
        dof.focal_distance = (dof.focal_distance - 0.05).max(0.01);
    }
    if keys.pressed(KeyCode::ArrowRight) {
        dof.aperture_f_stops += 0.01;
    }
    if keys.pressed(KeyCode::ArrowLeft) {
        dof.aperture_f_stops = (dof.aperture_f_stops - 0.01).max(0.05);
    }
    if keys.just_pressed(KeyCode::Space) {
        dof.mode = match dof.mode {
            DepthOfFieldMode::Bokeh => DepthOfFieldMode::Gaussian,
            DepthOfFieldMode::Gaussian => DepthOfFieldMode::Bokeh,
        };
    }

    let mode_name = match dof.mode {
        DepthOfFieldMode::Bokeh => "Bokeh",
        DepthOfFieldMode::Gaussian => "Gaussian",
    };
    text.0 = format!(
        "DoF Mode: {mode_name} (Space)\n\
         Focal distance: {:.2} m (Up/Down)\n\
         Aperture: f/{:.2} (Left/Right)",
        dof.focal_distance, dof.aperture_f_stops,
    );
}

Listing 26-3:景深效果——焦点前后物体的模糊

DepthOfField 组件

字段类型默认值含义
modeDepthOfFieldModeGaussian效果模式
focal_distancef32焦点距离(米)
aperture_f_stopsf32光圈 f 值。越小模糊越强
sensor_heightf320.01866传感器高度(米)。默认 18.66mm,对应 Super 35 胶片
max_circle_of_confusion_diameterf32弥散圆最大直径(像素)
max_depthf32最大考虑深度。防止天空盒产生无限大模糊

DepthOfFieldMode 有两种模式:

  • Gaussian:默认。高斯模糊,速度快,兼容 WebGPU
  • Bokeh:散景模拟,更接近真实镜头效果,但不兼容 WebGPU

aperture_f_stops 越小,景深越浅(背景越模糊)。f/0.5 是极端值,f/8.0 则几乎全画面清晰。实际使用时需要根据场景尺度调整。

与 Bloom 的配合

景深和 Bloom 经常一起使用——Bloom 让高亮区域发光,景深让焦点外的区域模糊,两者叠加产生梦幻般的镜头感。Listing 26-3 同时启用了两者。

ChromaticAberration:色散

色散模拟真实镜头中不同波长的光折射角度略有差异的现象——画面边缘会出现红/蓝颜色分离的条纹。

rust
use bevy::{
    post_process::effect_stack::ChromaticAberration,
    prelude::*,
    camera::Hdr,
};

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

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

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

    let mat = materials.add(StandardMaterial {
        base_color: Color::srgb(0.9, 0.9, 0.9),
        ..default()
    });
    commands.spawn((
        Mesh3d(meshes.add(Sphere::new(1.0))),
        MeshMaterial3d(mat),
        Transform::from_xyz(0.0, 1.0, 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,
            bottom: px(12),
            left: px(12),
            ..default()
        },
    ));
}

fn adjust_chromatic_aberration(
    mut ca: Single<&mut ChromaticAberration>,
    mut text: Single<&mut Text>,
    keys: Res<ButtonInput<KeyCode>>,
) {
    if keys.pressed(KeyCode::ArrowRight) {
        ca.intensity += 0.002;
    }
    if keys.pressed(KeyCode::ArrowLeft) {
        ca.intensity -= 0.002;
    }
    ca.intensity = ca.intensity.clamp(0.0, 0.4);
    text.0 = format!(
        "Chromatic Aberration: {:.3}\n(Left/Right arrows to adjust)",
        ca.intensity,
    );
}

Listing 26-4:色散效果——调整强度观察边缘颜色分离

ChromaticAberration 组件

字段类型默认值含义
intensityf320.02色散强度,占屏幕尺寸的比例
max_samplesu328最大采样数。越大越平滑但越慢
color_lutOption<Handle<Image>>None颜色查找纹理。默认红-绿-蓝三色

intensity 的推荐范围是 0.0–0.4。默认 0.02 是一个微妙的效果,适合大部分场景。超过 0.2 时色散会非常明显,通常用于短暂的冲击效果(如受到攻击时)。

色散效果在画面中心几乎不可见,越靠近边缘越明显——这与真实镜头的行为一致。