Skip to main content

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::Camera3d;
5use bevy_ecs::{component::*, prelude::*};
6use bevy_log::trace;
7use bevy_math::UVec2;
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_resource::{
14        BufferUsages, DynamicUniformBuffer, ShaderType, TextureUsages, UniformBuffer,
15        UninitBufferVec,
16    },
17    renderer::{RenderDevice, RenderQueue},
18    view::Msaa,
19    Render, RenderApp, RenderStartup, RenderSystems,
20};
21use bevy_shader::load_shader_library;
22use resolve::OitResolvePlugin;
23
24use crate::{
25    core_3d::main_transparent_pass_3d,
26    oit::resolve::{node::oit_resolve, OitResolvePipelineId},
27    schedule::{Core3d, Core3dSystems},
28};
29
30/// Module that defines the necessary systems to resolve the OIT buffer and render it to the screen.
31pub mod resolve;
32
33/// Used to identify which camera will use OIT to render transparent meshes
34/// and to configure OIT.
35// TODO consider supporting multiple OIT techniques like WBOIT, Moment Based OIT,
36// depth peeling, stochastic transparency, ray tracing etc.
37// This should probably be done by adding an enum to this component.
38// We use the same struct to pass on the settings to the drawing shader.
39#[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType, Component)]
40#[extract_component_sync_target((Self, OrderIndependentTransparencySettingsOffset, OitResolvePipelineId))]
41#[reflect(Clone, Default)]
42pub struct OrderIndependentTransparencySettings {
43    /// Controls how many fragments will be exactly sorted.
44    /// If the scene has more fragments than this, they will be merged approximately.
45    /// More sorted fragments is more accurate but will be slower.
46    pub sorted_fragment_max_count: u32,
47    /// The average fragments per pixel stored in the buffer. This should be bigger enough otherwise the fragments will be discarded.
48    /// Higher values increase memory usage.
49    pub fragments_per_pixel_average: f32,
50    /// Threshold for which fragments will be added to the blending layers.
51    /// This can be tweaked to optimize quality / layers count. Higher values will
52    /// allow lower number of layers and a better performance, compromising quality.
53    pub alpha_threshold: f32,
54}
55
56impl Default for OrderIndependentTransparencySettings {
57    fn default() -> Self {
58        Self {
59            sorted_fragment_max_count: 8,
60            fragments_per_pixel_average: 4.0,
61            alpha_threshold: 0.0,
62        }
63    }
64}
65
66/// A plugin that adds support for Order Independent Transparency (OIT).
67/// This can correctly render some scenes that would otherwise have artifacts due to alpha blending, but uses more memory.
68///
69/// To enable OIT for a camera you need to add the [`OrderIndependentTransparencySettings`] component to it.
70///
71/// If you want to use OIT for your custom material you need to call `oit_draw(position, color)` in your fragment shader.
72/// You also need to make sure that your fragment shader doesn't output any colors.
73///
74/// # Implementation details
75/// This implementation uses 2 passes.
76///
77/// The first pass constructs a linked list which stores depth and color of all fragments in a big buffer.
78/// The linked list capacity can be set with [`OrderIndependentTransparencySettings::fragments_per_pixel_average`].
79/// This pass is essentially a forward pass.
80///
81/// The second pass is a single fullscreen triangle pass that sorts all the fragments then blends them together
82/// and outputs the result to the screen.
83pub struct OrderIndependentTransparencyPlugin;
84impl Plugin for OrderIndependentTransparencyPlugin {
85    fn build(&self, app: &mut App) {
86        load_shader_library!(app, "oit_draw.wgsl");
87
88        app.add_plugins((
89            ExtractComponentPlugin::<OrderIndependentTransparencySettings>::default(),
90            OitResolvePlugin,
91        ))
92        .add_systems(Update, check_msaa);
93
94        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
95            return;
96        };
97
98        render_app
99            .add_systems(RenderStartup, init_oit_buffers)
100            .add_systems(
101                Render,
102                (
103                    configure_camera_depth_usages
104                        .in_set(RenderSystems::PrepareViews)
105                        .ambiguous_with(RenderSystems::PrepareViews),
106                    prepare_oit_buffers.in_set(RenderSystems::PrepareResources),
107                ),
108            );
109
110        render_app.add_systems(
111            Core3d,
112            oit_resolve
113                .after(main_transparent_pass_3d)
114                .in_set(Core3dSystems::MainPass),
115        );
116    }
117}
118
119fn configure_camera_depth_usages(
120    mut cameras: Query<
121        &mut Camera3d,
122        (
123            Changed<Camera3d>,
124            With<OrderIndependentTransparencySettings>,
125        ),
126    >,
127) {
128    for mut camera in &mut cameras {
129        camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
130    }
131}
132
133fn check_msaa(cameras: Query<&Msaa, With<OrderIndependentTransparencySettings>>) {
134    for msaa in &cameras {
135        if msaa.samples() > 1 {
136            panic!("MSAA is not supported when using OrderIndependentTransparency");
137        }
138    }
139}
140
141#[derive(Clone, Copy, ShaderType)]
142pub struct OitFragmentNode {
143    pub color: u32,
144    pub depth_alpha: u32,
145    pub next: u32,
146}
147
148/// Holds the buffers that contain the data of all OIT layers.
149/// We use one big buffer for the entire app. Each camera will reuse it so it will
150/// always be the size of the biggest OIT enabled camera.
151#[derive(Resource)]
152pub struct OitBuffers {
153    pub settings: DynamicUniformBuffer<OrderIndependentTransparencySettings>,
154    pub nodes_capacity: UniformBuffer<u32>,
155    /// OIT nodes buffer contains color, depth and linked next node for each fragments.
156    pub nodes: UninitBufferVec<OitFragmentNode>,
157    /// OIT heads buffer contains the head that pointers nodes buffer, essentially used as a 2d array where xy is the screen coordinate.
158    /// We don't use storage texture as it requires native only [`bevy_render::settings::WgpuFeatures::TEXTURE_ATOMIC`].
159    pub heads: UninitBufferVec<u32>,
160    pub atomic_counter: UninitBufferVec<u32>,
161}
162
163impl OitBuffers {
164    fn create_nodes_buffer(
165        size: usize,
166        render_device: &RenderDevice,
167    ) -> UninitBufferVec<OitFragmentNode> {
168        let mut nodes = UninitBufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
169        nodes.set_label(Some("oit_nodes"));
170        nodes.reserve(size, render_device);
171        nodes
172    }
173
174    fn create_heads_buffer(size: usize, render_device: &RenderDevice) -> UninitBufferVec<u32> {
175        let mut nodes = UninitBufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
176        nodes.set_label(Some("oit_heads"));
177        nodes.reserve(size, render_device);
178        nodes
179    }
180}
181
182pub fn init_oit_buffers(
183    mut commands: Commands,
184    render_device: Res<RenderDevice>,
185    render_queue: Res<RenderQueue>,
186) {
187    // initialize buffers with something so there's a valid binding
188
189    let mut nodes_capacity = UniformBuffer::default();
190    nodes_capacity.set_label(Some("oit_nodes_capacity"));
191    nodes_capacity.set(1);
192    nodes_capacity.write_buffer(&render_device, &render_queue);
193
194    let nodes = OitBuffers::create_nodes_buffer(1, &render_device);
195
196    let heads = OitBuffers::create_heads_buffer(1, &render_device);
197
198    let mut atomic_counter = UninitBufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
199    atomic_counter.set_label(Some("oit_atomic_counter"));
200    atomic_counter.reserve(1, &render_device);
201
202    let mut settings = DynamicUniformBuffer::default();
203    settings.set_label(Some("oit_settings"));
204
205    commands.insert_resource(OitBuffers {
206        nodes_capacity,
207        nodes,
208        heads,
209        atomic_counter,
210        settings,
211    });
212}
213
214#[derive(Component)]
215pub struct OrderIndependentTransparencySettingsOffset {
216    pub offset: u32,
217}
218
219/// This creates or resizes the oit buffers for each camera.
220/// It will always create one big buffer that's as big as the biggest buffer needed.
221/// Cameras with smaller viewports or less layers will simply use the big buffer and ignore the rest.
222pub fn prepare_oit_buffers(
223    mut commands: Commands,
224    render_device: Res<RenderDevice>,
225    render_queue: Res<RenderQueue>,
226    cameras: Query<
227        (&ExtractedCamera, &OrderIndependentTransparencySettings),
228        (
229            Changed<ExtractedCamera>,
230            Changed<OrderIndependentTransparencySettings>,
231        ),
232    >,
233    camera_oit_uniforms: Query<
234        (Entity, &OrderIndependentTransparencySettings),
235        With<ExtractedCamera>,
236    >,
237    mut buffers: ResMut<OitBuffers>,
238) {
239    let camera_count = camera_oit_uniforms.count();
240    if camera_count == 0 {
241        if buffers.nodes_capacity.get() > &1 {
242            // Release oit buffers if no camera enables OIT.
243            buffers.nodes = OitBuffers::create_nodes_buffer(1, &render_device);
244            buffers.heads = OitBuffers::create_heads_buffer(1, &render_device);
245            buffers.nodes_capacity.set(1);
246            buffers
247                .nodes_capacity
248                .write_buffer(&render_device, &render_queue);
249        }
250        return;
251    }
252
253    // Get the max buffer size for any OIT enabled camera
254    let mut max_size = UVec2::new(0, 0);
255    let mut fragments_per_pixel_average = 0f32;
256    for (camera, settings) in &cameras {
257        let Some(size) = camera.physical_target_size else {
258            continue;
259        };
260        max_size = max_size.max(size);
261        fragments_per_pixel_average =
262            fragments_per_pixel_average.max(settings.fragments_per_pixel_average);
263    }
264
265    // Create or update the heads buffer based on the max size
266    let heads_size = (max_size.x * max_size.y) as usize;
267    if buffers.heads.capacity() != heads_size {
268        let start = Instant::now();
269        buffers.heads = OitBuffers::create_heads_buffer(heads_size, &render_device);
270        trace!(
271            "OIT heads buffer updated in {:.01}ms with total size {} MiB",
272            start.elapsed().as_millis(),
273            (buffers.heads.capacity() * size_of::<u32>()) as f32 / 1024.0 / 1024.0,
274        );
275    }
276
277    // Create or update the nodes buffer based on the max size
278    let nodes_size = ((max_size.x * max_size.y) as f32 * fragments_per_pixel_average) as usize;
279    if buffers.nodes.capacity() != nodes_size {
280        let start = Instant::now();
281        buffers.nodes = OitBuffers::create_nodes_buffer(nodes_size, &render_device);
282        trace!(
283            "OIT nodes buffer updated in {:.01}ms with total size {} MiB",
284            start.elapsed().as_millis(),
285            (buffers.nodes.capacity() * size_of::<OitFragmentNode>()) as f32 / 1024.0 / 1024.0,
286        );
287    }
288
289    buffers.nodes_capacity.set(nodes_size as u32);
290    buffers
291        .nodes_capacity
292        .write_buffer(&render_device, &render_queue);
293
294    if let Some(mut writer) =
295        buffers
296            .settings
297            .get_writer(camera_count, &render_device, &render_queue)
298    {
299        for (entity, settings) in &camera_oit_uniforms {
300            let offset = writer.write(settings);
301            commands
302                .entity(entity)
303                .insert(OrderIndependentTransparencySettingsOffset { offset });
304        }
305    }
306}