Skip to content

Commit d01db9b

Browse files
hexrollJMS55
andauthored
Adding alpha_threshold to OrderIndependentTransparencySettings for user-level optimization (bevyengine#16090)
# Objective Order independent transparency can filter fragment writes based on the alpha value and it is currently hard-coded to anything higher than 0.0. By making that value configurable, users can optimize fragment writes, potentially reducing the number of layers needed and improving performance in favor of some transparency quality. ## Solution This PR adds `alpha_threshold` to the OrderIndependentTransparencySettings component and uses the struct to configure a corresponding shader uniform. This uniform is then used instead of the hard-coded value. To configure OIT with a custom alpha threshold, use: ```rust fn setup(mut commands: Commands) { commands.spawn(( Camera3d::default(), OrderIndependentTransparencySettings { layer_count: 8, alpha_threshold: 0.2, }, )); } ``` ## Testing I tested this change using the included OIT example, as well as with two additional projects. ## Migration Guide If you previously explicitly initialized OrderIndependentTransparencySettings with your own `layer_count`, you will now have to add either a `..default()` statement or an explicit `alpha_threshold` value: ```rust fn setup(mut commands: Commands) { commands.spawn(( Camera3d::default(), OrderIndependentTransparencySettings { layer_count: 16, ..default() }, )); } ``` --------- Co-authored-by: JMS55 <[email protected]>
1 parent 3fc2bd7 commit d01db9b

File tree

7 files changed

+69
-30
lines changed

7 files changed

+69
-30
lines changed

crates/bevy_core_pipeline/src/oit/mod.rs

+47-16
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22
33
use bevy_app::prelude::*;
44
use bevy_asset::{load_internal_asset, Handle};
5-
use bevy_ecs::prelude::*;
5+
use bevy_ecs::{component::*, prelude::*};
66
use bevy_math::UVec2;
7+
use bevy_reflect::Reflect;
78
use bevy_render::{
89
camera::{Camera, ExtractedCamera},
910
extract_component::{ExtractComponent, ExtractComponentPlugin},
1011
render_graph::{RenderGraphApp, ViewNodeRunner},
11-
render_resource::{BufferUsages, BufferVec, DynamicUniformBuffer, Shader, TextureUsages},
12+
render_resource::{
13+
BufferUsages, BufferVec, DynamicUniformBuffer, Shader, ShaderType, TextureUsages,
14+
},
1215
renderer::{RenderDevice, RenderQueue},
1316
view::Msaa,
1417
Render, RenderApp, RenderSet,
1518
};
16-
use bevy_utils::{tracing::trace, HashSet, Instant};
19+
use bevy_utils::{
20+
tracing::{trace, warn},
21+
HashSet, Instant,
22+
};
1723
use bevy_window::PrimaryWindow;
1824
use resolve::{
1925
node::{OitResolveNode, OitResolvePass},
@@ -36,17 +42,41 @@ pub const OIT_DRAW_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(404252
3642
// TODO consider supporting multiple OIT techniques like WBOIT, Moment Based OIT,
3743
// depth peeling, stochastic transparency, ray tracing etc.
3844
// This should probably be done by adding an enum to this component.
39-
#[derive(Component, Clone, Copy, ExtractComponent)]
45+
// We use the same struct to pass on the settings to the drawing shader.
46+
#[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType)]
4047
pub struct OrderIndependentTransparencySettings {
4148
/// Controls how many layers will be used to compute the blending.
4249
/// 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 16 is probably not worth it in the vast majority of cases
44-
pub layer_count: u8,
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,
4556
}
4657

4758
impl Default for OrderIndependentTransparencySettings {
4859
fn default() -> Self {
49-
Self { layer_count: 8 }
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.
69+
impl 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+
});
5080
}
5181
}
5282

@@ -82,7 +112,8 @@ impl Plugin for OrderIndependentTransparencyPlugin {
82112
OitResolvePlugin,
83113
))
84114
.add_systems(Update, check_msaa)
85-
.add_systems(Last, configure_depth_texture_usages);
115+
.add_systems(Last, configure_depth_texture_usages)
116+
.register_type::<OrderIndependentTransparencySettings>();
86117

87118
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
88119
return;
@@ -164,7 +195,7 @@ pub struct OitBuffers {
164195
pub layers: BufferVec<UVec2>,
165196
/// Buffer containing the index of the last layer that was written for each fragment.
166197
pub layer_ids: BufferVec<i32>,
167-
pub layers_count_uniforms: DynamicUniformBuffer<i32>,
198+
pub settings: DynamicUniformBuffer<OrderIndependentTransparencySettings>,
168199
}
169200

170201
impl FromWorld for OitBuffers {
@@ -184,19 +215,19 @@ impl FromWorld for OitBuffers {
184215
layer_ids.reserve(1, render_device);
185216
layer_ids.write_buffer(render_device, render_queue);
186217

187-
let mut layers_count_uniforms = DynamicUniformBuffer::default();
188-
layers_count_uniforms.set_label(Some("oit_layers_count"));
218+
let mut settings = DynamicUniformBuffer::default();
219+
settings.set_label(Some("oit_settings"));
189220

190221
Self {
191222
layers,
192223
layer_ids,
193-
layers_count_uniforms,
224+
settings,
194225
}
195226
}
196227
}
197228

198229
#[derive(Component)]
199-
pub struct OitLayersCountOffset {
230+
pub struct OrderIndependentTransparencySettingsOffset {
200231
pub offset: u32,
201232
}
202233

@@ -268,16 +299,16 @@ pub fn prepare_oit_buffers(
268299
);
269300
}
270301

271-
if let Some(mut writer) = buffers.layers_count_uniforms.get_writer(
302+
if let Some(mut writer) = buffers.settings.get_writer(
272303
camera_oit_uniforms.iter().len(),
273304
&render_device,
274305
&render_queue,
275306
) {
276307
for (entity, settings) in &camera_oit_uniforms {
277-
let offset = writer.write(&(settings.layer_count as i32));
308+
let offset = writer.write(settings);
278309
commands
279310
.entity(entity)
280-
.insert(OitLayersCountOffset { offset });
311+
.insert(OrderIndependentTransparencySettingsOffset { offset });
281312
}
282313
}
283314
}

crates/bevy_core_pipeline/src/oit/oit_draw.wgsl

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
#define_import_path bevy_core_pipeline::oit
22

3-
#import bevy_pbr::mesh_view_bindings::{view, oit_layers, oit_layer_ids, oit_layers_count}
3+
#import bevy_pbr::mesh_view_bindings::{view, oit_layers, oit_layer_ids, oit_settings}
44

55
#ifdef OIT_ENABLED
66
// Add the fragment to the oit buffer
77
fn oit_draw(position: vec4f, color: vec4f) {
88
// Don't add fully transparent fragments to the list
99
// because we don't want to have to sort them in the resolve pass
10-
// TODO should this be comparing with < espilon ?
11-
if color.a == 0.0 {
10+
if color.a < oit_settings.alpha_threshold {
1211
return;
1312
}
1413
// get the index of the current fragment relative to the screen size
@@ -20,10 +19,10 @@ fn oit_draw(position: vec4f, color: vec4f) {
2019
// gets the layer index of the current fragment
2120
var layer_id = atomicAdd(&oit_layer_ids[screen_index], 1);
2221
// exit early if we've reached the maximum amount of fragments per layer
23-
if layer_id >= oit_layers_count {
22+
if layer_id >= oit_settings.layers_count {
2423
// force to store the oit_layers_count to make sure we don't
2524
// accidentally increase the index above the maximum value
26-
atomicStore(&oit_layer_ids[screen_index], oit_layers_count);
25+
atomicStore(&oit_layer_ids[screen_index], oit_settings.layers_count);
2726
// TODO for tail blending we should return the color here
2827
return;
2928
}

crates/bevy_core_pipeline/src/oit/resolve/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ pub struct OitResolvePipelineId(pub CachedRenderPipelineId);
121121
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
122122
pub struct OitResolvePipelineKey {
123123
hdr: bool,
124-
layer_count: u8,
124+
layer_count: i32,
125125
}
126126

127127
#[allow(clippy::too_many_arguments)]

crates/bevy_pbr/src/render/mesh.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bevy_asset::{load_internal_asset, AssetId};
55
use bevy_core_pipeline::{
66
core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
77
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},
8-
oit::{prepare_oit_buffers, OitLayersCountOffset},
8+
oit::{prepare_oit_buffers, OrderIndependentTransparencySettingsOffset},
99
prepass::MotionVectorPrepass,
1010
};
1111
use bevy_derive::{Deref, DerefMut};
@@ -2198,7 +2198,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
21982198
Read<ViewScreenSpaceReflectionsUniformOffset>,
21992199
Read<ViewEnvironmentMapUniformOffset>,
22002200
Read<MeshViewBindGroup>,
2201-
Option<Read<OitLayersCountOffset>>,
2201+
Option<Read<OrderIndependentTransparencySettingsOffset>>,
22022202
);
22032203
type ItemQuery = ();
22042204

crates/bevy_pbr/src/render/mesh_view_bindings.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,10 @@ fn layout_entries(
377377
// oit_layer_ids,
378378
(32, storage_buffer_sized(false, None)),
379379
// oit_layer_count
380-
(33, uniform_buffer::<i32>(true)),
380+
(
381+
33,
382+
uniform_buffer::<OrderIndependentTransparencySettings>(true),
383+
),
381384
));
382385
}
383386
}
@@ -694,16 +697,16 @@ pub fn prepare_mesh_view_bind_groups(
694697
if let (
695698
Some(oit_layers_binding),
696699
Some(oit_layer_ids_binding),
697-
Some(oit_layers_count_uniforms_binding),
700+
Some(oit_settings_binding),
698701
) = (
699702
oit_buffers.layers.binding(),
700703
oit_buffers.layer_ids.binding(),
701-
oit_buffers.layers_count_uniforms.binding(),
704+
oit_buffers.settings.binding(),
702705
) {
703706
entries = entries.extend_with_indices((
704707
(31, oit_layers_binding.clone()),
705708
(32, oit_layer_ids_binding.clone()),
706-
(33, oit_layers_count_uniforms_binding.clone()),
709+
(33, oit_settings_binding.clone()),
707710
));
708711
}
709712
}

crates/bevy_pbr/src/render/mesh_view_bindings.wgsl

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,5 @@ const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u;
109109
#ifdef OIT_ENABLED
110110
@group(0) @binding(31) var<storage, read_write> oit_layers: array<vec2<u32>>;
111111
@group(0) @binding(32) var<storage, read_write> oit_layer_ids: array<atomic<i32>>;
112-
@group(0) @binding(33) var<uniform> oit_layers_count: i32;
112+
@group(0) @binding(33) var<uniform> oit_settings: types::OrderIndependentTransparencySettings;
113113
#endif OIT_ENABLED

crates/bevy_pbr/src/render/mesh_view_types.wgsl

+7-1
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,10 @@ struct ScreenSpaceReflectionsSettings {
158158
struct EnvironmentMapUniform {
159159
// Transformation matrix for the environment cubemaps in world space.
160160
transform: mat4x4<f32>,
161-
};
161+
};
162+
163+
// Shader version of the order independent transparency settings component.
164+
struct OrderIndependentTransparencySettings {
165+
layers_count: i32,
166+
alpha_threshold: f32,
167+
};

0 commit comments

Comments
 (0)