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