bevy_core_pipeline/oit/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.

use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::{component::*, prelude::*};
use bevy_math::UVec2;
use bevy_reflect::Reflect;
use bevy_render::{
    camera::{Camera, ExtractedCamera},
    extract_component::{ExtractComponent, ExtractComponentPlugin},
    render_graph::{RenderGraphApp, ViewNodeRunner},
    render_resource::{
        BufferUsages, BufferVec, DynamicUniformBuffer, Shader, ShaderType, TextureUsages,
    },
    renderer::{RenderDevice, RenderQueue},
    view::Msaa,
    Render, RenderApp, RenderSet,
};
use bevy_utils::{
    tracing::{trace, warn},
    HashSet, Instant,
};
use bevy_window::PrimaryWindow;
use resolve::{
    node::{OitResolveNode, OitResolvePass},
    OitResolvePlugin,
};

use crate::core_3d::{
    graph::{Core3d, Node3d},
    Camera3d,
};

/// Module that defines the necesasry systems to resolve the OIT buffer and render it to the screen.
pub mod resolve;

/// Shader handle for the shader that draws the transparent meshes to the OIT layers buffer.
pub const OIT_DRAW_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4042527984320512);

/// Used to identify which camera will use OIT to render transparent meshes
/// and to configure OIT.
// TODO consider supporting multiple OIT techniques like WBOIT, Moment Based OIT,
// depth peeling, stochastic transparency, ray tracing etc.
// This should probably be done by adding an enum to this component.
// We use the same struct to pass on the settings to the drawing shader.
#[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType)]
pub struct OrderIndependentTransparencySettings {
    /// Controls how many layers will be used to compute the blending.
    /// The more layers you use the more memory it will use but it will also give better results.
    /// 8 is generally recommended, going above 32 is probably not worth it in the vast majority of cases
    pub layer_count: i32,
    /// Threshold for which fragments will be added to the blending layers.
    /// This can be tweaked to optimize quality / layers count. Higher values will
    /// allow lower number of layers and a better performance, compromising quality.
    pub alpha_threshold: f32,
}

impl Default for OrderIndependentTransparencySettings {
    fn default() -> Self {
        Self {
            layer_count: 8,
            alpha_threshold: 0.0,
        }
    }
}

// OrderIndependentTransparencySettings is also a Component. We explicitly implement the trait so
// we can hook on_add to issue a warning in case `layer_count` is seemingly too high.
impl Component for OrderIndependentTransparencySettings {
    const STORAGE_TYPE: StorageType = StorageType::SparseSet;

    fn register_component_hooks(hooks: &mut ComponentHooks) {
        hooks.on_add(|world, entity, _| {
            if let Some(value) = world.get::<OrderIndependentTransparencySettings>(entity) {
                if value.layer_count > 32 {
                    warn!("OrderIndependentTransparencySettings layer_count set to {} might be too high.", value.layer_count);
                }
            }
        });
    }
}

/// A plugin that adds support for Order Independent Transparency (OIT).
/// This can correctly render some scenes that would otherwise have artifacts due to alpha blending, but uses more memory.
///
/// To enable OIT for a camera you need to add the [`OrderIndependentTransparencySettings`] component to it.
///
/// If you want to use OIT for your custom material you need to call `oit_draw(position, color)` in your fragment shader.
/// You also need to make sure that your fragment shader doesn't output any colors.
///
/// # Implementation details
/// This implementation uses 2 passes.
///
/// The first pass writes the depth and color of all the fragments to a big buffer.
/// The buffer contains N layers for each pixel, where N can be set with [`OrderIndependentTransparencySettings::layer_count`].
/// This pass is essentially a forward pass.
///
/// The second pass is a single fullscreen triangle pass that sorts all the fragments then blends them together
/// and outputs the result to the screen.
pub struct OrderIndependentTransparencyPlugin;
impl Plugin for OrderIndependentTransparencyPlugin {
    fn build(&self, app: &mut App) {
        load_internal_asset!(
            app,
            OIT_DRAW_SHADER_HANDLE,
            "oit_draw.wgsl",
            Shader::from_wgsl
        );

        app.add_plugins((
            ExtractComponentPlugin::<OrderIndependentTransparencySettings>::default(),
            OitResolvePlugin,
        ))
        .add_systems(Update, check_msaa)
        .add_systems(Last, configure_depth_texture_usages)
        .register_type::<OrderIndependentTransparencySettings>();

        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app.add_systems(
            Render,
            prepare_oit_buffers.in_set(RenderSet::PrepareResources),
        );

        render_app
            .add_render_graph_node::<ViewNodeRunner<OitResolveNode>>(Core3d, OitResolvePass)
            .add_render_graph_edges(
                Core3d,
                (
                    Node3d::MainTransparentPass,
                    OitResolvePass,
                    Node3d::EndMainPass,
                ),
            );
    }

    fn finish(&self, app: &mut App) {
        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app.init_resource::<OitBuffers>();
    }
}

// WARN This should only happen for cameras with the [`OrderIndependentTransparencySettings`] component
// but when multiple cameras are present on the same window
// bevy reuses the same depth texture so we need to set this on all cameras with the same render target.
fn configure_depth_texture_usages(
    p: Query<Entity, With<PrimaryWindow>>,
    cameras: Query<(&Camera, Has<OrderIndependentTransparencySettings>)>,
    mut new_cameras: Query<(&mut Camera3d, &Camera), Added<Camera3d>>,
) {
    if new_cameras.is_empty() {
        return;
    }

    // Find all the render target that potentially uses OIT
    let primary_window = p.get_single().ok();
    let mut render_target_has_oit = HashSet::new();
    for (camera, has_oit) in &cameras {
        if has_oit {
            render_target_has_oit.insert(camera.target.normalize(primary_window));
        }
    }

    // Update the depth texture usage for cameras with a render target that has OIT
    for (mut camera_3d, camera) in &mut new_cameras {
        if render_target_has_oit.contains(&camera.target.normalize(primary_window)) {
            let mut usages = TextureUsages::from(camera_3d.depth_texture_usages);
            usages |= TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING;
            camera_3d.depth_texture_usages = usages.into();
        }
    }
}

fn check_msaa(cameras: Query<&Msaa, With<OrderIndependentTransparencySettings>>) {
    for msaa in &cameras {
        if msaa.samples() > 1 {
            panic!("MSAA is not supported when using OrderIndependentTransparency");
        }
    }
}

/// Holds the buffers that contain the data of all OIT layers.
/// We use one big buffer for the entire app. Each camaera will reuse it so it will
/// always be the size of the biggest OIT enabled camera.
#[derive(Resource)]
pub struct OitBuffers {
    /// The OIT layers containing depth and color for each fragments.
    /// This is essentially used as a 3d array where xy is the screen coordinate and z is
    /// the list of fragments rendered with OIT.
    pub layers: BufferVec<UVec2>,
    /// Buffer containing the index of the last layer that was written for each fragment.
    pub layer_ids: BufferVec<i32>,
    pub settings: DynamicUniformBuffer<OrderIndependentTransparencySettings>,
}

impl FromWorld for OitBuffers {
    fn from_world(world: &mut World) -> Self {
        let render_device = world.resource::<RenderDevice>();
        let render_queue = world.resource::<RenderQueue>();

        // initialize buffers with something so there's a valid binding

        let mut layers = BufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
        layers.set_label(Some("oit_layers"));
        layers.reserve(1, render_device);
        layers.write_buffer(render_device, render_queue);

        let mut layer_ids = BufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
        layer_ids.set_label(Some("oit_layer_ids"));
        layer_ids.reserve(1, render_device);
        layer_ids.write_buffer(render_device, render_queue);

        let mut settings = DynamicUniformBuffer::default();
        settings.set_label(Some("oit_settings"));

        Self {
            layers,
            layer_ids,
            settings,
        }
    }
}

#[derive(Component)]
pub struct OrderIndependentTransparencySettingsOffset {
    pub offset: u32,
}

/// This creates or resizes the oit buffers for each camera.
/// It will always create one big buffer that's as big as the biggest buffer needed.
/// Cameras with smaller viewports or less layers will simply use the big buffer and ignore the rest.
#[allow(clippy::type_complexity)]
pub fn prepare_oit_buffers(
    mut commands: Commands,
    render_device: Res<RenderDevice>,
    render_queue: Res<RenderQueue>,
    cameras: Query<
        (&ExtractedCamera, &OrderIndependentTransparencySettings),
        (
            Changed<ExtractedCamera>,
            Changed<OrderIndependentTransparencySettings>,
        ),
    >,
    camera_oit_uniforms: Query<(Entity, &OrderIndependentTransparencySettings)>,
    mut buffers: ResMut<OitBuffers>,
) {
    // Get the max buffer size for any OIT enabled camera
    let mut max_layer_ids_size = usize::MIN;
    let mut max_layers_size = usize::MIN;
    for (camera, settings) in &cameras {
        let Some(size) = camera.physical_target_size else {
            continue;
        };

        let layer_count = settings.layer_count as usize;
        let size = (size.x * size.y) as usize;
        max_layer_ids_size = max_layer_ids_size.max(size);
        max_layers_size = max_layers_size.max(size * layer_count);
    }

    // Create or update the layers buffer based on the max size
    if buffers.layers.capacity() < max_layers_size {
        let start = Instant::now();
        buffers.layers.reserve(max_layers_size, &render_device);
        let remaining = max_layers_size - buffers.layers.capacity();
        for _ in 0..remaining {
            buffers.layers.push(UVec2::ZERO);
        }
        buffers.layers.write_buffer(&render_device, &render_queue);
        trace!(
            "OIT layers buffer updated in {:.01}ms with total size {} MiB",
            start.elapsed().as_millis(),
            buffers.layers.capacity() * size_of::<UVec2>() / 1024 / 1024,
        );
    }

    // Create or update the layer_ids buffer based on the max size
    if buffers.layer_ids.capacity() < max_layer_ids_size {
        let start = Instant::now();
        buffers
            .layer_ids
            .reserve(max_layer_ids_size, &render_device);
        let remaining = max_layer_ids_size - buffers.layer_ids.capacity();
        for _ in 0..remaining {
            buffers.layer_ids.push(0);
        }
        buffers
            .layer_ids
            .write_buffer(&render_device, &render_queue);
        trace!(
            "OIT layer ids buffer updated in {:.01}ms with total size {} MiB",
            start.elapsed().as_millis(),
            buffers.layer_ids.capacity() * size_of::<UVec2>() / 1024 / 1024,
        );
    }

    if let Some(mut writer) = buffers.settings.get_writer(
        camera_oit_uniforms.iter().len(),
        &render_device,
        &render_queue,
    ) {
        for (entity, settings) in &camera_oit_uniforms {
            let offset = writer.write(settings);
            commands
                .entity(entity)
                .insert(OrderIndependentTransparencySettingsOffset { offset });
        }
    }
}