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