2024-10-07 23:50:28 +00:00
|
|
|
//! A simple 3D scene showing how alpha blending can break and how order independent transparency (OIT) can fix it.
|
|
|
|
//!
|
|
|
|
//! See [`OrderIndependentTransparencyPlugin`] for the trade-offs of using OIT.
|
|
|
|
//!
|
|
|
|
//! [`OrderIndependentTransparencyPlugin`]: bevy::render::pipeline::OrderIndependentTransparencyPlugin
|
|
|
|
use bevy::{
|
|
|
|
color::palettes::css::{BLUE, GREEN, RED},
|
|
|
|
core_pipeline::oit::OrderIndependentTransparencySettings,
|
|
|
|
prelude::*,
|
|
|
|
render::view::RenderLayers,
|
|
|
|
};
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
App::new()
|
|
|
|
.add_plugins(DefaultPlugins)
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
.add_systems(Update, (toggle_oit, cycle_scenes))
|
|
|
|
.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// set up a simple 3D scene
|
|
|
|
fn setup(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
|
|
) {
|
|
|
|
// camera
|
2024-10-19 01:24:58 +00:00
|
|
|
commands.spawn((
|
|
|
|
Camera3d::default(),
|
|
|
|
Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
|
|
// Add this component to this camera to render transparent meshes using OIT
|
|
|
|
OrderIndependentTransparencySettings::default(),
|
|
|
|
RenderLayers::layer(1),
|
|
|
|
// Msaa currently doesn't work with OIT
|
|
|
|
Msaa::Off,
|
|
|
|
));
|
2024-10-07 23:50:28 +00:00
|
|
|
|
|
|
|
// light
|
|
|
|
commands.spawn((
|
|
|
|
PointLight {
|
|
|
|
shadows_enabled: false,
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
Transform::from_xyz(4.0, 8.0, 4.0),
|
|
|
|
RenderLayers::layer(1),
|
|
|
|
));
|
|
|
|
|
|
|
|
// spawn help text
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
commands
|
2024-10-17 01:01:32 +00:00
|
|
|
.spawn((
|
|
|
|
Text::default(),
|
Merge Style properties into Node. Use ComputedNode for computed properties. (#15975)
# Objective
Continue improving the user experience of our UI Node API in the
direction specified by [Bevy's Next Generation Scene / UI
System](https://github.com/bevyengine/bevy/discussions/14437)
## Solution
As specified in the document above, merge `Style` fields into `Node`,
and move "computed Node fields" into `ComputedNode` (I chose this name
over something like `ComputedNodeLayout` because it currently contains
more than just layout info. If we want to break this up / rename these
concepts, lets do that in a separate PR). `Style` has been removed.
This accomplishes a number of goals:
## Ergonomics wins
Specifying both `Node` and `Style` is now no longer required for
non-default styles
Before:
```rust
commands.spawn((
Node::default(),
Style {
width: Val::Px(100.),
..default()
},
));
```
After:
```rust
commands.spawn(Node {
width: Val::Px(100.),
..default()
});
```
## Conceptual clarity
`Style` was never a comprehensive "style sheet". It only defined "core"
style properties that all `Nodes` shared. Any "styled property" that
couldn't fit that mold had to be in a separate component. A "real" style
system would style properties _across_ components (`Node`, `Button`,
etc). We have plans to build a true style system (see the doc linked
above).
By moving the `Style` fields to `Node`, we fully embrace `Node` as the
driving concept and remove the "style system" confusion.
## Next Steps
* Consider identifying and splitting out "style properties that aren't
core to Node". This should not happen for Bevy 0.15.
---
## Migration Guide
Move any fields set on `Style` into `Node` and replace all `Style`
component usage with `Node`.
Before:
```rust
commands.spawn((
Node::default(),
Style {
width: Val::Px(100.),
..default()
},
));
```
After:
```rust
commands.spawn(Node {
width: Val::Px(100.),
..default()
});
```
For any usage of the "computed node properties" that used to live on
`Node`, use `ComputedNode` instead:
Before:
```rust
fn system(nodes: Query<&Node>) {
for node in &nodes {
let computed_size = node.size();
}
}
```
After:
```rust
fn system(computed_nodes: Query<&ComputedNode>) {
for computed_node in &computed_nodes {
let computed_size = computed_node.size();
}
}
```
2024-10-18 22:25:33 +00:00
|
|
|
Node {
|
2024-10-17 01:01:32 +00:00
|
|
|
position_type: PositionType::Absolute,
|
|
|
|
top: Val::Px(12.0),
|
|
|
|
left: Val::Px(12.0),
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
RenderLayers::layer(1),
|
|
|
|
))
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
.with_children(|p| {
|
|
|
|
p.spawn(TextSpan::new("Press T to toggle OIT\n"));
|
|
|
|
p.spawn(TextSpan::new("OIT Enabled"));
|
|
|
|
p.spawn(TextSpan::new("\nPress C to cycle test scenes"));
|
|
|
|
});
|
2024-10-07 23:50:28 +00:00
|
|
|
|
|
|
|
// spawn default scene
|
|
|
|
spawn_spheres(&mut commands, &mut meshes, &mut materials);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn toggle_oit(
|
|
|
|
mut commands: Commands,
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
text: Single<Entity, With<Text>>,
|
2024-10-07 23:50:28 +00:00
|
|
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
2024-10-13 20:32:06 +00:00
|
|
|
q: Single<(Entity, Has<OrderIndependentTransparencySettings>), With<Camera3d>>,
|
2024-10-15 02:32:34 +00:00
|
|
|
mut text_writer: TextUiWriter,
|
2024-10-07 23:50:28 +00:00
|
|
|
) {
|
|
|
|
if keyboard_input.just_pressed(KeyCode::KeyT) {
|
2024-10-13 20:32:06 +00:00
|
|
|
let (e, has_oit) = *q;
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.**
# Objective
- Implement https://github.com/bevyengine/bevy/discussions/15014
## Solution
This implements [cart's
proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459)
faithfully except for one change. I separated `TextSpan` from
`TextSpan2d` because `TextSpan` needs to require the `GhostNode`
component, which is a `bevy_ui` component only usable by UI.
Extra changes:
- Added `EntityCommands::commands_mut` that returns a mutable reference.
This is a blocker for extension methods that return something other than
`self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable
reference for this reason.
## Testing
- [x] Text examples all work.
---
## Showcase
TODO: showcase-worthy
## Migration Guide
TODO: very breaking
### Accessing text spans by index
Text sections are now text sections on different entities in a
hierarchy, Use the new `TextReader` and `TextWriter` system parameters
to access spans by index.
Before:
```rust
fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
let text = query.single_mut();
text.sections[1].value = format_time(time.elapsed());
}
```
After:
```rust
fn refresh_text(
query: Query<Entity, With<TimeText>>,
mut writer: UiTextWriter,
time: Res<Time>
) {
let entity = query.single();
*writer.text(entity, 1) = format_time(time.elapsed());
}
```
### Iterating text spans
Text spans are now entities in a hierarchy, so the new `UiTextReader`
and `UiTextWriter` system parameters provide ways to iterate that
hierarchy. The `UiTextReader::iter` method will give you a normal
iterator over spans, and `UiTextWriter::for_each` lets you visit each of
the spans.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-09 18:35:36 +00:00
|
|
|
*text_writer.text(*text, 2) = if has_oit {
|
2024-10-07 23:50:28 +00:00
|
|
|
// Removing the component will completely disable OIT for this camera
|
|
|
|
commands
|
|
|
|
.entity(e)
|
|
|
|
.remove::<OrderIndependentTransparencySettings>();
|
|
|
|
"OIT disabled".to_string()
|
|
|
|
} else {
|
|
|
|
// Adding the component to the camera will render any transparent meshes
|
|
|
|
// with OIT instead of alpha blending
|
|
|
|
commands
|
|
|
|
.entity(e)
|
|
|
|
.insert(OrderIndependentTransparencySettings::default());
|
|
|
|
"OIT enabled".to_string()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn cycle_scenes(
|
|
|
|
mut commands: Commands,
|
|
|
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
|
|
q: Query<Entity, With<Mesh3d>>,
|
|
|
|
mut scene_id: Local<usize>,
|
|
|
|
) {
|
|
|
|
if keyboard_input.just_pressed(KeyCode::KeyC) {
|
|
|
|
// depsawn current scene
|
|
|
|
for e in &q {
|
|
|
|
commands.entity(e).despawn_recursive();
|
|
|
|
}
|
|
|
|
// increment scene_id
|
|
|
|
*scene_id = (*scene_id + 1) % 2;
|
|
|
|
// spawn next scene
|
|
|
|
match *scene_id {
|
|
|
|
0 => spawn_spheres(&mut commands, &mut meshes, &mut materials),
|
|
|
|
1 => spawn_occlusion_test(&mut commands, &mut meshes, &mut materials),
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Spawns 3 overlapping spheres
|
|
|
|
/// Technically, when using `alpha_to_coverage` with MSAA this particular example wouldn't break,
|
|
|
|
/// but it breaks when disabling MSAA and is enough to show the difference between OIT enabled vs disabled.
|
|
|
|
fn spawn_spheres(
|
|
|
|
commands: &mut Commands,
|
|
|
|
meshes: &mut Assets<Mesh>,
|
|
|
|
materials: &mut Assets<StandardMaterial>,
|
|
|
|
) {
|
|
|
|
let pos_a = Vec3::new(-1.0, 0.75, 0.0);
|
|
|
|
let pos_b = Vec3::new(0.0, -0.75, 0.0);
|
|
|
|
let pos_c = Vec3::new(1.0, 0.75, 0.0);
|
|
|
|
|
|
|
|
let offset = Vec3::new(0.0, 0.0, 0.0);
|
|
|
|
|
|
|
|
let sphere_handle = meshes.add(Sphere::new(2.0).mesh());
|
|
|
|
|
|
|
|
let alpha = 0.25;
|
|
|
|
|
|
|
|
let render_layers = RenderLayers::layer(1);
|
|
|
|
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(sphere_handle.clone()),
|
|
|
|
MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
base_color: RED.with_alpha(alpha).into(),
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
..default()
|
|
|
|
})),
|
|
|
|
Transform::from_translation(pos_a + offset),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(sphere_handle.clone()),
|
|
|
|
MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
base_color: GREEN.with_alpha(alpha).into(),
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
..default()
|
|
|
|
})),
|
|
|
|
Transform::from_translation(pos_b + offset),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(sphere_handle.clone()),
|
|
|
|
MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
base_color: BLUE.with_alpha(alpha).into(),
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
..default()
|
|
|
|
})),
|
|
|
|
Transform::from_translation(pos_c + offset),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Spawn a combination of opaque cubes and transparent spheres.
|
|
|
|
/// This is useful to make sure transparent meshes drawn with OIT
|
|
|
|
/// are properly occluded by opaque meshes.
|
|
|
|
fn spawn_occlusion_test(
|
|
|
|
commands: &mut Commands,
|
|
|
|
meshes: &mut Assets<Mesh>,
|
|
|
|
materials: &mut Assets<StandardMaterial>,
|
|
|
|
) {
|
|
|
|
let sphere_handle = meshes.add(Sphere::new(1.0).mesh());
|
|
|
|
let cube_handle = meshes.add(Cuboid::from_size(Vec3::ONE).mesh());
|
|
|
|
let cube_material = materials.add(Color::srgb(0.8, 0.7, 0.6));
|
|
|
|
|
|
|
|
let render_layers = RenderLayers::layer(1);
|
|
|
|
|
|
|
|
// front
|
|
|
|
let x = -2.5;
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(cube_handle.clone()),
|
|
|
|
MeshMaterial3d(cube_material.clone()),
|
|
|
|
Transform::from_xyz(x, 0.0, 2.0),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(sphere_handle.clone()),
|
|
|
|
MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
base_color: RED.with_alpha(0.5).into(),
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
..default()
|
|
|
|
})),
|
|
|
|
Transform::from_xyz(x, 0., 0.),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
|
|
|
|
// intersection
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(cube_handle.clone()),
|
|
|
|
MeshMaterial3d(cube_material.clone()),
|
|
|
|
Transform::from_xyz(x, 0.0, 1.0),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(sphere_handle.clone()),
|
|
|
|
MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
base_color: RED.with_alpha(0.5).into(),
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
..default()
|
|
|
|
})),
|
|
|
|
Transform::from_xyz(0., 0., 0.),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
|
|
|
|
// back
|
|
|
|
let x = 2.5;
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(cube_handle.clone()),
|
|
|
|
MeshMaterial3d(cube_material.clone()),
|
|
|
|
Transform::from_xyz(x, 0.0, -2.0),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(sphere_handle.clone()),
|
|
|
|
MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
base_color: RED.with_alpha(0.5).into(),
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
..default()
|
|
|
|
})),
|
|
|
|
Transform::from_xyz(x, 0., 0.),
|
|
|
|
render_layers.clone(),
|
|
|
|
));
|
|
|
|
}
|