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