order_independent_transparency/
order_independent_transparency.rs

1//! A simple 3D scene showing how alpha blending can break and how order independent transparency (OIT) can fix it.
2//!
3//! See [`OrderIndependentTransparencyPlugin`] for the trade-offs of using OIT.
4//!
5//! [`OrderIndependentTransparencyPlugin`]: bevy::core_pipeline::oit::OrderIndependentTransparencyPlugin
6use bevy::{
7    camera::visibility::RenderLayers,
8    color::palettes::css::{BLUE, GREEN, RED},
9    core_pipeline::oit::OrderIndependentTransparencySettings,
10    prelude::*,
11};
12
13fn main() {
14    App::new()
15        .add_plugins(DefaultPlugins)
16        .add_systems(Startup, setup)
17        .add_systems(Update, (toggle_oit, cycle_scenes))
18        .run();
19}
20
21/// set up a simple 3D scene
22fn setup(
23    mut commands: Commands,
24    mut meshes: ResMut<Assets<Mesh>>,
25    mut materials: ResMut<Assets<StandardMaterial>>,
26) {
27    // camera
28    commands.spawn((
29        Camera3d::default(),
30        Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
31        // Add this component to this camera to render transparent meshes using OIT
32        OrderIndependentTransparencySettings::default(),
33        RenderLayers::layer(1),
34        // Msaa currently doesn't work with OIT
35        Msaa::Off,
36    ));
37
38    // light
39    commands.spawn((
40        PointLight {
41            shadows_enabled: false,
42            ..default()
43        },
44        Transform::from_xyz(4.0, 8.0, 4.0),
45        RenderLayers::layer(1),
46    ));
47
48    // spawn help text
49    commands.spawn((
50        Text::default(),
51        Node {
52            position_type: PositionType::Absolute,
53            top: px(12),
54            left: px(12),
55            ..default()
56        },
57        RenderLayers::layer(1),
58        children![
59            TextSpan::new("Press T to toggle OIT\n"),
60            TextSpan::new("OIT Enabled"),
61            TextSpan::new("\nPress C to cycle test scenes"),
62        ],
63    ));
64
65    // spawn default scene
66    spawn_spheres(&mut commands, &mut meshes, &mut materials);
67}
68
69fn toggle_oit(
70    mut commands: Commands,
71    text: Single<Entity, With<Text>>,
72    keyboard_input: Res<ButtonInput<KeyCode>>,
73    q: Single<(Entity, Has<OrderIndependentTransparencySettings>), With<Camera3d>>,
74    mut text_writer: TextUiWriter,
75) {
76    if keyboard_input.just_pressed(KeyCode::KeyT) {
77        let (e, has_oit) = *q;
78        *text_writer.text(*text, 2) = if has_oit {
79            // Removing the component will completely disable OIT for this camera
80            commands
81                .entity(e)
82                .remove::<OrderIndependentTransparencySettings>();
83            "OIT disabled".to_string()
84        } else {
85            // Adding the component to the camera will render any transparent meshes
86            // with OIT instead of alpha blending
87            commands
88                .entity(e)
89                .insert(OrderIndependentTransparencySettings::default());
90            "OIT enabled".to_string()
91        };
92    }
93}
94
95fn cycle_scenes(
96    mut commands: Commands,
97    keyboard_input: Res<ButtonInput<KeyCode>>,
98    mut meshes: ResMut<Assets<Mesh>>,
99    mut materials: ResMut<Assets<StandardMaterial>>,
100    q: Query<Entity, With<Mesh3d>>,
101    mut scene_id: Local<usize>,
102) {
103    if keyboard_input.just_pressed(KeyCode::KeyC) {
104        // despawn current scene
105        for e in &q {
106            commands.entity(e).despawn();
107        }
108        // increment scene_id
109        *scene_id = (*scene_id + 1) % 2;
110        // spawn next scene
111        match *scene_id {
112            0 => spawn_spheres(&mut commands, &mut meshes, &mut materials),
113            1 => spawn_occlusion_test(&mut commands, &mut meshes, &mut materials),
114            _ => unreachable!(),
115        }
116    }
117}
118
119/// Spawns 3 overlapping spheres
120/// Technically, when using `alpha_to_coverage` with MSAA this particular example wouldn't break,
121/// but it breaks when disabling MSAA and is enough to show the difference between OIT enabled vs disabled.
122fn spawn_spheres(
123    commands: &mut Commands,
124    meshes: &mut Assets<Mesh>,
125    materials: &mut Assets<StandardMaterial>,
126) {
127    let pos_a = Vec3::new(-1.0, 0.75, 0.0);
128    let pos_b = Vec3::new(0.0, -0.75, 0.0);
129    let pos_c = Vec3::new(1.0, 0.75, 0.0);
130
131    let offset = Vec3::new(0.0, 0.0, 0.0);
132
133    let sphere_handle = meshes.add(Sphere::new(2.0).mesh());
134
135    let alpha = 0.25;
136
137    let render_layers = RenderLayers::layer(1);
138
139    commands.spawn((
140        Mesh3d(sphere_handle.clone()),
141        MeshMaterial3d(materials.add(StandardMaterial {
142            base_color: RED.with_alpha(alpha).into(),
143            alpha_mode: AlphaMode::Blend,
144            ..default()
145        })),
146        Transform::from_translation(pos_a + offset),
147        render_layers.clone(),
148    ));
149    commands.spawn((
150        Mesh3d(sphere_handle.clone()),
151        MeshMaterial3d(materials.add(StandardMaterial {
152            base_color: GREEN.with_alpha(alpha).into(),
153            alpha_mode: AlphaMode::Blend,
154            ..default()
155        })),
156        Transform::from_translation(pos_b + offset),
157        render_layers.clone(),
158    ));
159    commands.spawn((
160        Mesh3d(sphere_handle.clone()),
161        MeshMaterial3d(materials.add(StandardMaterial {
162            base_color: BLUE.with_alpha(alpha).into(),
163            alpha_mode: AlphaMode::Blend,
164            ..default()
165        })),
166        Transform::from_translation(pos_c + offset),
167        render_layers.clone(),
168    ));
169}
170
171/// Spawn a combination of opaque cubes and transparent spheres.
172/// This is useful to make sure transparent meshes drawn with OIT
173/// are properly occluded by opaque meshes.
174fn spawn_occlusion_test(
175    commands: &mut Commands,
176    meshes: &mut Assets<Mesh>,
177    materials: &mut Assets<StandardMaterial>,
178) {
179    let sphere_handle = meshes.add(Sphere::new(1.0).mesh());
180    let cube_handle = meshes.add(Cuboid::from_size(Vec3::ONE).mesh());
181    let cube_material = materials.add(Color::srgb(0.8, 0.7, 0.6));
182
183    let render_layers = RenderLayers::layer(1);
184
185    // front
186    let x = -2.5;
187    commands.spawn((
188        Mesh3d(cube_handle.clone()),
189        MeshMaterial3d(cube_material.clone()),
190        Transform::from_xyz(x, 0.0, 2.0),
191        render_layers.clone(),
192    ));
193    commands.spawn((
194        Mesh3d(sphere_handle.clone()),
195        MeshMaterial3d(materials.add(StandardMaterial {
196            base_color: RED.with_alpha(0.5).into(),
197            alpha_mode: AlphaMode::Blend,
198            ..default()
199        })),
200        Transform::from_xyz(x, 0., 0.),
201        render_layers.clone(),
202    ));
203
204    // intersection
205    commands.spawn((
206        Mesh3d(cube_handle.clone()),
207        MeshMaterial3d(cube_material.clone()),
208        Transform::from_xyz(x, 0.0, 1.0),
209        render_layers.clone(),
210    ));
211    commands.spawn((
212        Mesh3d(sphere_handle.clone()),
213        MeshMaterial3d(materials.add(StandardMaterial {
214            base_color: RED.with_alpha(0.5).into(),
215            alpha_mode: AlphaMode::Blend,
216            ..default()
217        })),
218        Transform::from_xyz(0., 0., 0.),
219        render_layers.clone(),
220    ));
221
222    // back
223    let x = 2.5;
224    commands.spawn((
225        Mesh3d(cube_handle.clone()),
226        MeshMaterial3d(cube_material.clone()),
227        Transform::from_xyz(x, 0.0, -2.0),
228        render_layers.clone(),
229    ));
230    commands.spawn((
231        Mesh3d(sphere_handle.clone()),
232        MeshMaterial3d(materials.add(StandardMaterial {
233            base_color: RED.with_alpha(0.5).into(),
234            alpha_mode: AlphaMode::Blend,
235            ..default()
236        })),
237        Transform::from_xyz(x, 0., 0.),
238        render_layers.clone(),
239    ));
240}