双面、深度偏移与 Clearcoat
双面渲染与 cull_mode
默认情况下,Bevy 背面剔除(cull_mode: Some(Face::Back))——三角形只有正面可见。这对封闭几何体(球、立方体)完全够用,但遇到薄片(纸、布、单面墙)就有问题:翻到背面就穿帮了。
cull_mode 和 double_sided 两个字段配合解决这个问题:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, rotate_camera)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let plane = meshes.add(Plane3d::default().mesh().size(2.0, 2.0));
// 默认:cull_mode = Some(Face::Back),单面,背面剔除
commands.spawn((
Mesh3d(plane.clone()),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.8, 0.2, 0.2),
..default()
})),
Transform::from_xyz(-2.5, 1.0, 0.0),
));
// cull_mode = None:两面都渲染,但仍按默认法线着色
commands.spawn((
Mesh3d(plane.clone()),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.2, 0.8, 0.2),
cull_mode: None,
..default()
})),
Transform::from_xyz(0.0, 1.0, 0.0),
));
// double_sided + cull_mode = None:背面法线翻转,光照正确
commands.spawn((
Mesh3d(plane.clone()),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.2, 0.2, 0.8),
double_sided: true,
cull_mode: None,
..default()
})),
Transform::from_xyz(2.5, 1.0, 0.0),
));
// Ground
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.2, 0.2, 0.2),
perceptual_roughness: 1.0,
..default()
})),
));
commands.spawn((
DirectionalLight {
illuminance: light_consts::lux::FULL_DAYLIGHT,
contact_shadows_enabled: true,
..default()
},
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.5, 0.5, 0.0)),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(3.0, 4.0, 6.0).looking_at(Vec3::new(0.0, 0.5, 0.0), Vec3::Y),
));
}
fn rotate_camera(time: Res<Time>, mut query: Query<&mut Transform, With<Camera3d>>) {
for mut transform in &mut query {
let angle = time.elapsed_secs() * 0.25;
let radius = 7.0;
transform.translation.x = angle.sin() * radius;
transform.translation.z = angle.cos() * radius;
transform.translation.y = 4.0;
*transform = transform.looking_at(Vec3::new(0.0, 0.5, 0.0), Vec3::Y);
}
}Listing 24-5:三块平面——默认单面、取消剔除、双面渲染(examples/listing-24-05.rs)
cargo run -p ch24-pbr-materials --example listing-24-05相机绕圈,让你从正反两面观察:
- 左(红):默认行为。
cull_mode = Some(Face::Back),背面不渲染。从背面看,平面消失了。 - 中(绿):
cull_mode = None,正反两面都渲染。但注意:背面的法线方向没变,所以从背面看时光照是反的(凹凸反转)。 - 右(蓝):
double_sided = true+cull_mode = None。背面法线被自动翻转,光照正确。
double_sided 不会自动取消剔除——你需要同时设置 cull_mode: None,否则背面的面片依然被 GPU 丢弃,法线翻转了但面片不存在。反之亦然:只设 cull_mode: None 而不设 double_sided,背面会渲染但光照错误。
Face 类型来自 bevy::render::render_resource::Face,不在 prelude 里。设 cull_mode: None 时不需要它;只有显式指定 Some(Face::Front) 或 Some(Face::Back) 时才需要导入:
use bevy::render::render_resource::Face;深度偏移
两个共面的面片(比如贴花贴在墙上)会出现 Z-fighting——像素在两个面之间闪烁。depth_bias 字段给材质一个深度偏移,正值让物体"靠近"相机:
StandardMaterial {
base_color: Color::srgba(0.0, 0.0, 0.0, 0.5),
alpha_mode: AlphaMode::Blend,
depth_bias: 0.1,
..default()
}depth_bias 影响渲染排序和深度写入,是解决 Z-fighting 的简单手段。但它不是万能的——过大的偏移会让物体看起来"浮"在表面上。更好的做法是在建模时避免共面。
Clearcoat——清漆层
现实世界很多表面有透明涂层:车漆、上釉陶瓷、打蜡的木头、涂了清漆的桌面。clearcoat 在主 PBR 层之上叠了一层薄薄的反射层,模拟这种效果:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, rotate_camera)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let sphere = meshes.add(Sphere::new(0.8).mesh().uv(64, 36));
// 无 clearcoat(对照组)
commands.spawn((
Mesh3d(sphere.clone()),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.8, 0.2, 0.2),
metallic: 0.0,
perceptual_roughness: 0.4,
..default()
})),
Transform::from_xyz(-2.0, 0.8, 0.0),
));
// clearcoat = 1.0,光滑涂层
commands.spawn((
Mesh3d(sphere.clone()),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.8, 0.2, 0.2),
metallic: 0.0,
perceptual_roughness: 0.4,
clearcoat: 1.0,
clearcoat_perceptual_roughness: 0.1,
..default()
})),
Transform::from_xyz(0.0, 0.8, 0.0),
));
// clearcoat = 1.0,粗糙涂层
commands.spawn((
Mesh3d(sphere.clone()),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.8, 0.2, 0.2),
metallic: 0.0,
perceptual_roughness: 0.4,
clearcoat: 1.0,
clearcoat_perceptual_roughness: 0.6,
..default()
})),
Transform::from_xyz(2.0, 0.8, 0.0),
));
// Ground
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(8.0, 8.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.2, 0.2, 0.2),
perceptual_roughness: 1.0,
..default()
})),
));
commands.spawn((
DirectionalLight {
illuminance: light_consts::lux::FULL_DAYLIGHT,
contact_shadows_enabled: true,
..default()
},
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.5, 0.3, 0.0)),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(3.0, 2.5, 5.0).looking_at(Vec3::new(0.0, 0.4, 0.0), Vec3::Y),
));
}
fn rotate_camera(time: Res<Time>, mut query: Query<&mut Transform, With<Camera3d>>) {
for mut transform in &mut query {
let angle = time.elapsed_secs() * 0.3;
let radius = 5.5;
transform.translation.x = angle.sin() * radius;
transform.translation.z = angle.cos() * radius;
transform.translation.y = 2.5;
*transform = transform.looking_at(Vec3::new(0.0, 0.4, 0.0), Vec3::Y);
}
}Listing 24-6:三颗红球——无涂层、光滑涂层、粗糙涂层(examples/listing-24-06.rs)
cargo run -p ch24-pbr-materials --example listing-24-06三个球的底层材质完全相同(红色、非金属、粗糙度 0.4):
- 左:无 clearcoat(
clearcoat = 0.0,默认)。就是普通的红色哑光球。 - 中:
clearcoat = 1.0,clearcoat_perceptual_roughness = 0.1。清漆层光滑,表面多了一层锐利的白色高光——这就是车漆的效果:底下是红色金属漆,上面是一层透明亮漆。 - 右:
clearcoat = 1.0,clearcoat_perceptual_roughness = 0.6。清漆层粗糙,高光被摊开——像旧的哑光清漆。
clearcoat 的范围是 [0.0, 1.0],控制涂层的可见强度。clearcoat_perceptual_roughness 控制涂层的粗糙度,用法和主层的 perceptual_roughness 一样。清漆层默认粗糙度是 0.5——如果想要"亮漆"效果,记得把它调低。