Skip to content

阴影:让物体落地

开阴影后,物体会在地面和其他物体上投下影子,这是理解三维空间关系的关键视觉线索。

启用阴影

三种光源都有 contact_shadows_enabled: bool 字段,设为 true 即可开启阴影。

rust
DirectionalLight {
    contact_shadows_enabled: true,
    ..default()
}

点光源和聚光灯同理。

阴影映射资源

阴影的质量受制于阴影贴图的分辨率。Bevy 通过 Resource 类型控制:

光源类型阴影贴图 Resource默认尺寸
DirectionalLight + SpotLightDirectionalLightShadowMap { size }2048
PointLightPointLightShadowMap { size }1024

size 必须是 2 的幂。增大尺寸提高阴影精度但降低性能,减小尺寸则反之。

rust
commands.insert_resource(DirectionalLightShadowMap { size: 4096 });

Adjust bias to avoid artifacts

depth_biasnormal_bias 是两个阴影调优参数:

参数含义DirectionalLight 默认PointLight 默认
shadow_depth_bias深度偏移0.020.08
shadow_normal_bias法线方向偏移1.80.6
  • shadow_depth_bias 过低 → 阴影表面出现条纹斑驳(shadow acne)
  • shadow_depth_bias 过高 → 阴影与物体分离,"飘离"物体(Peter Panning)
  • shadow_normal_bias 沿表面法线方向偏移,按纹素大小自动缩放

两个 bias 需要根据场景具体调优,没有通解。

级联阴影

DirectionalLight 默认使用级联阴影映射(CSM),将视锥沿深度切成多段(cascade),每段独立渲染阴影贴图,使近处阴影精度更高。CascadeShadowConfigBuilder 提供配置:

rust
commands.spawn((
    DirectionalLight { .. },
    CascadeShadowConfigBuilder {
        num_cascades: 4,
        maximum_distance: 20.0,
        first_cascade_far_bound: 4.0,
        ..default()
    }.build(),
));
参数含义
num_cascades级联段数,值越高近处阴影越精细
maximum_distance阴影最大可见距离
first_cascade_far_bound第一段(最近段)的远平面距离

不投阴影的物体

NotShadowCaster 组件标记的实体不产生阴影。可用于性能优化或风格化效果。

阴影过滤方法

ShadowFilteringMethod 是一个组件,挂在相机实体上,控制阴影边框的柔化方式:

方法质量性能
Hardware2x2最快
Gaussian(默认)
Temporal高(配合 TAA)
rust
// ANCHOR: filter
commands.spawn((
    Camera3d::default(),
    ShadowFilteringMethod::Gaussian,
));
// ANCHOR_END: filter

Temporal 模式需要同时启用时域抗锯齿(TAA)才能达到最佳效果。

完整示例

rust
use bevy::{
    light::{CascadeShadowConfigBuilder, NotShadowCaster, ShadowFilteringMethod},
    prelude::*,
};

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

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    // 地面
    commands.spawn((
        Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color: Color::srgb(0.5, 0.5, 0.5),
            perceptual_roughness: 1.0,
            ..default()
        })),
    ));

    // 投阴影的物体
    commands.spawn((
        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color: Color::srgb(0.1, 0.6, 0.9),
            ..default()
        })),
        Transform::from_xyz(-1.5, 0.5, 0.0),
    ));
    commands.spawn((
        Mesh3d(meshes.add(Sphere::new(0.5).mesh().uv(32, 18))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color: Color::srgb(0.9, 0.3, 0.2),
            ..default()
        })),
        Transform::from_xyz(1.5, 0.5, 0.0),
    ));

    // 不投阴影的物体(标记 NotShadowCaster)
    commands.spawn((
        Mesh3d(meshes.add(Cylinder::new(0.3, 0.8))),
        MeshMaterial3d(materials.add(Color::srgb(0.2, 0.8, 0.2))),
        Transform::from_xyz(0.0, 0.4, 1.5),
        NotShadowCaster,
    ));

    // DirectionalLight 配置级联阴影
    commands.spawn((
        DirectionalLight {
            illuminance: light_consts::lux::FULL_DAYLIGHT,
            contact_shadows_enabled: true,
            shadow_depth_bias: 0.02,
            shadow_normal_bias: 1.8,
            ..default()
        },
        Transform::from_xyz(0.0, 5.0, 0.0)
            .looking_at(Vec3::new(0.5, 0.0, -0.5), Vec3::Y),
        CascadeShadowConfigBuilder {
            num_cascades: 4,
            maximum_distance: 15.0,
            first_cascade_far_bound: 3.0,
            ..default()
        }
        .build(),
    ));

    // 相机(带 ShadowFilteringMethod 组件)
    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(4.0, 3.0, 6.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
        ShadowFilteringMethod::Hardware2x2,
    ));
}

fn rotate_light(time: Res<Time>, mut query: Query<&mut Transform, With<DirectionalLight>>) {
    for mut transform in &mut query {
        transform.rotate_y(time.delta_secs() * 0.15);
    }
}

fn toggle_shadow_filter(
    key_input: Res<ButtonInput<KeyCode>>,
    mut query: Query<&mut ShadowFilteringMethod>,
) {
    if key_input.just_pressed(KeyCode::Space) {
        for mut filter in &mut query {
            *filter = match *filter {
                ShadowFilteringMethod::Hardware2x2 => ShadowFilteringMethod::Gaussian,
                ShadowFilteringMethod::Gaussian => ShadowFilteringMethod::Temporal,
                ShadowFilteringMethod::Temporal => ShadowFilteringMethod::Hardware2x2,
            };
            info!("Shadow filtering: {:?}", *filter);
        }
    }
}

Listing 22-5:阴影各项参数的完整演示

运行后空格键循环切换三种阴影过滤方法,可以观察阴影边缘的质量差异。