bevy_core_pipeline/oit/
mod.rs

1//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.
2
3use bevy_app::prelude::*;
4use bevy_asset::{load_internal_asset, weak_handle, Handle};
5use bevy_ecs::{component::*, prelude::*};
6use bevy_math::UVec2;
7use bevy_platform::collections::HashSet;
8use bevy_platform::time::Instant;
9use bevy_reflect::{std_traits::ReflectDefault, Reflect};
10use bevy_render::{
11    camera::{Camera, ExtractedCamera},
12    extract_component::{ExtractComponent, ExtractComponentPlugin},
13    render_graph::{RenderGraphApp, ViewNodeRunner},
14    render_resource::{
15        BufferUsages, BufferVec, DynamicUniformBuffer, Shader, ShaderType, TextureUsages,
16    },
17    renderer::{RenderDevice, RenderQueue},
18    view::Msaa,
19    Render, RenderApp, RenderSet,
20};
21use bevy_window::PrimaryWindow;
22use resolve::{
23    node::{OitResolveNode, OitResolvePass},
24    OitResolvePlugin,
25};
26use tracing::{trace, warn};
27
28use crate::core_3d::{
29    graph::{Core3d, Node3d},
30    Camera3d,
31};
32
33/// Module that defines the necessary systems to resolve the OIT buffer and render it to the screen.
34pub mod resolve;
35
36/// Shader handle for the shader that draws the transparent meshes to the OIT layers buffer.
37pub const OIT_DRAW_SHADER_HANDLE: Handle<Shader> =
38    weak_handle!("0cd3c764-39b8-437b-86b4-4e45635fc03d");
39
40/// Used to identify which camera will use OIT to render transparent meshes
41/// and to configure OIT.
42// TODO consider supporting multiple OIT techniques like WBOIT, Moment Based OIT,
43// depth peeling, stochastic transparency, ray tracing etc.
44// This should probably be done by adding an enum to this component.
45// We use the same struct to pass on the settings to the drawing shader.
46#[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType)]
47#[reflect(Clone, Default)]
48pub struct OrderIndependentTransparencySettings {
49    /// Controls how many layers will be used to compute the blending.
50    /// The more layers you use the more memory it will use but it will also give better results.
51    /// 8 is generally recommended, going above 32 is probably not worth it in the vast majority of cases
52    pub layer_count: i32,
53    /// Threshold for which fragments will be added to the blending layers.
54    /// This can be tweaked to optimize quality / layers count. Higher values will
55    /// allow lower number of layers and a better performance, compromising quality.
56    pub alpha_threshold: f32,
57}
58
59impl Default for OrderIndependentTransparencySettings {
60    fn default() -> Self {
61        Self {
62            layer_count: 8,
63            alpha_threshold: 0.0,
64        }
65    }
66}
67
68// OrderIndependentTransparencySettings is also a Component. We explicitly implement the trait so
69// we can hook on_add to issue a warning in case `layer_count` is seemingly too high.
70impl Component for OrderIndependentTransparencySettings {
71    const STORAGE_TYPE: StorageType = StorageType::SparseSet;
72    type Mutability = Mutable;
73
74    fn on_add() -> Option<ComponentHook> {
75        Some(|world, context| {
76            if let Some(value) = world.get::<OrderIndependentTransparencySettings>(context.entity) {
77                if value.layer_count > 32 {
78                    warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.",
79                        context.caller.map(|location|format!("{location}: ")).unwrap_or_default(),
80                        value.layer_count
81                    );
82                }
83            }
84        })
85    }
86}
87
88/// A plugin that adds support for Order Independent Transparency (OIT).
89/// This can correctly render some scenes that would otherwise have artifacts due to alpha blending, but uses more memory.
90///
91/// To enable OIT for a camera you need to add the [`OrderIndependentTransparencySettings`] component to it.
92///
93/// If you want to use OIT for your custom material you need to call `oit_draw(position, color)` in your fragment shader.
94/// You also need to make sure that your fragment shader doesn't output any colors.
95///
96/// # Implementation details
97/// This implementation uses 2 passes.
98///
99/// The first pass writes the depth and color of all the fragments to a big buffer.
100/// The buffer contains N layers for each pixel, where N can be set with [`OrderIndependentTransparencySettings::layer_count`].
101/// This pass is essentially a forward pass.
102///
103/// The second pass is a single fullscreen triangle pass that sorts all the fragments then blends them together
104/// and outputs the result to the screen.
105pub struct OrderIndependentTransparencyPlugin;
106impl Plugin for OrderIndependentTransparencyPlugin {
107    fn build(&self, app: &mut App) {
108        load_internal_asset!(
109            app,
110            OIT_DRAW_SHADER_HANDLE,
111            "oit_draw.wgsl",
112            Shader::from_wgsl
113        );
114
115        app.add_plugins((
116            ExtractComponentPlugin::<OrderIndependentTransparencySettings>::default(),
117            OitResolvePlugin,
118        ))
119        .add_systems(Update, check_msaa)
120        .add_systems(Last, configure_depth_texture_usages)
121        .register_type::<OrderIndependentTransparencySettings>();
122
123        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
124            return;
125        };
126
127        render_app.add_systems(
128            Render,
129            prepare_oit_buffers.in_set(RenderSet::PrepareResources),
130        );
131
132        render_app
133            .add_render_graph_node::<ViewNodeRunner<OitResolveNode>>(Core3d, OitResolvePass)
134            .add_render_graph_edges(
135                Core3d,
136                (
137                    Node3d::MainTransparentPass,
138                    OitResolvePass,
139                    Node3d::EndMainPass,
140                ),
141            );
142    }
143
144    fn finish(&self, app: &mut App) {
145        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
146            return;
147        };
148
149        render_app.init_resource::<OitBuffers>();
150    }
151}
152
153// WARN This should only happen for cameras with the [`OrderIndependentTransparencySettings`] component
154// but when multiple cameras are present on the same window
155// bevy reuses the same depth texture so we need to set this on all cameras with the same render target.
156fn configure_depth_texture_usages(
157    p: Query<Entity, With<PrimaryWindow>>,
158    cameras: Query<(&Camera, Has<OrderIndependentTransparencySettings>)>,
159    mut new_cameras: Query<(&mut Camera3d, &Camera), Added<Camera3d>>,
160) {
161    if new_cameras.is_empty() {
162        return;
163    }
164
165    // Find all the render target that potentially uses OIT
166    let primary_window = p.single().ok();
167    let mut render_target_has_oit = <HashSet<_>>::default();
168    for (camera, has_oit) in &cameras {
169        if has_oit {
170            render_target_has_oit.insert(camera.target.normalize(primary_window));
171        }
172    }
173
174    // Update the depth texture usage for cameras with a render target that has OIT
175    for (mut camera_3d, camera) in &mut new_cameras {
176        if render_target_has_oit.contains(&camera.target.normalize(primary_window)) {
177            let mut usages = TextureUsages::from(camera_3d.depth_texture_usages);
178            usages |= TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING;
179            camera_3d.depth_texture_usages = usages.into();
180        }
181    }
182}
183
184fn check_msaa(cameras: Query<&Msaa, With<OrderIndependentTransparencySettings>>) {
185    for msaa in &cameras {
186        if msaa.samples() > 1 {
187            panic!("MSAA is not supported when using OrderIndependentTransparency");
188        }
189    }
190}
191
192/// Holds the buffers that contain the data of all OIT layers.
193/// We use one big buffer for the entire app. Each camera will reuse it so it will
194/// always be the size of the biggest OIT enabled camera.
195#[derive(Resource)]
196pub struct OitBuffers {
197    /// The OIT layers containing depth and color for each fragments.
198    /// This is essentially used as a 3d array where xy is the screen coordinate and z is
199    /// the list of fragments rendered with OIT.
200    pub layers: BufferVec<UVec2>,
201    /// Buffer containing the index of the last layer that was written for each fragment.
202    pub layer_ids: BufferVec<i32>,
203    pub settings: DynamicUniformBuffer<OrderIndependentTransparencySettings>,
204}
205
206impl FromWorld for OitBuffers {
207    fn from_world(world: &mut World) -> Self {
208        let render_device = world.resource::<RenderDevice>();
209        let render_queue = world.resource::<RenderQueue>();
210
211        // initialize buffers with something so there's a valid binding
212
213        let mut layers = BufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
214        layers.set_label(Some("oit_layers"));
215        layers.reserve(1, render_device);
216        layers.write_buffer(render_device, render_queue);
217
218        let mut layer_ids = BufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
219        layer_ids.set_label(Some("oit_layer_ids"));
220        layer_ids.reserve(1, render_device);
221        layer_ids.write_buffer(render_device, render_queue);
222
223        let mut settings = DynamicUniformBuffer::default();
224        settings.set_label(Some("oit_settings"));
225
226        Self {
227            layers,
228            layer_ids,
229            settings,
230        }
231    }
232}
233
234#[derive(Component)]
235pub struct OrderIndependentTransparencySettingsOffset {
236    pub offset: u32,
237}
238
239/// This creates or resizes the oit buffers for each camera.
240/// It will always create one big buffer that's as big as the biggest buffer needed.
241/// Cameras with smaller viewports or less layers will simply use the big buffer and ignore the rest.
242pub fn prepare_oit_buffers(
243    mut commands: Commands,
244    render_device: Res<RenderDevice>,
245    render_queue: Res<RenderQueue>,
246    cameras: Query<
247        (&ExtractedCamera, &OrderIndependentTransparencySettings),
248        (
249            Changed<ExtractedCamera>,
250            Changed<OrderIndependentTransparencySettings>,
251        ),
252    >,
253    camera_oit_uniforms: Query<(Entity, &OrderIndependentTransparencySettings)>,
254    mut buffers: ResMut<OitBuffers>,
255) {
256    // Get the max buffer size for any OIT enabled camera
257    let mut max_layer_ids_size = usize::MIN;
258    let mut max_layers_size = usize::MIN;
259    for (camera, settings) in &cameras {
260        let Some(size) = camera.physical_target_size else {
261            continue;
262        };
263
264        let layer_count = settings.layer_count as usize;
265        let size = (size.x * size.y) as usize;
266        max_layer_ids_size = max_layer_ids_size.max(size);
267        max_layers_size = max_layers_size.max(size * layer_count);
268    }
269
270    // Create or update the layers buffer based on the max size
271    if buffers.layers.capacity() < max_layers_size {
272        let start = Instant::now();
273        buffers.layers.reserve(max_layers_size, &render_device);
274        let remaining = max_layers_size - buffers.layers.capacity();
275        for _ in 0..remaining {
276            buffers.layers.push(UVec2::ZERO);
277        }
278        buffers.layers.write_buffer(&render_device, &render_queue);
279        trace!(
280            "OIT layers buffer updated in {:.01}ms with total size {} MiB",
281            start.elapsed().as_millis(),
282            buffers.layers.capacity() * size_of::<UVec2>() / 1024 / 1024,
283        );
284    }
285
286    // Create or update the layer_ids buffer based on the max size
287    if buffers.layer_ids.capacity() < max_layer_ids_size {
288        let start = Instant::now();
289        buffers
290            .layer_ids
291            .reserve(max_layer_ids_size, &render_device);
292        let remaining = max_layer_ids_size - buffers.layer_ids.capacity();
293        for _ in 0..remaining {
294            buffers.layer_ids.push(0);
295        }
296        buffers
297            .layer_ids
298            .write_buffer(&render_device, &render_queue);
299        trace!(
300            "OIT layer ids buffer updated in {:.01}ms with total size {} MiB",
301            start.elapsed().as_millis(),
302            buffers.layer_ids.capacity() * size_of::<UVec2>() / 1024 / 1024,
303        );
304    }
305
306    if let Some(mut writer) = buffers.settings.get_writer(
307        camera_oit_uniforms.iter().len(),
308        &render_device,
309        &render_queue,
310    ) {
311        for (entity, settings) in &camera_oit_uniforms {
312            let offset = writer.write(settings);
313            commands
314                .entity(entity)
315                .insert(OrderIndependentTransparencySettingsOffset { offset });
316        }
317    }
318}