Change outline and stencil properties to be inheritable.
This commit is contained in:
parent
65c7c3356f
commit
600f58ba73
@ -6,7 +6,8 @@ use bevy::{
|
||||
window::close_on_esc,
|
||||
};
|
||||
use bevy_mod_outline::{
|
||||
AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume,
|
||||
AutoGenerateOutlineNormalsPlugin, InheritOutlineBundle, OutlineBundle, OutlinePlugin,
|
||||
OutlineVolume,
|
||||
};
|
||||
|
||||
#[derive(Resource)]
|
||||
@ -65,9 +66,18 @@ fn setup(
|
||||
});
|
||||
|
||||
// Fox
|
||||
commands.spawn(SceneBundle {
|
||||
commands
|
||||
.spawn(SceneBundle {
|
||||
scene: asset_server.load("Fox.glb#Scene0"),
|
||||
..default()
|
||||
})
|
||||
.insert(OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
width: 3.0,
|
||||
colour: Color::RED,
|
||||
},
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
@ -86,14 +96,9 @@ fn setup_scene_once_loaded(
|
||||
{
|
||||
if scene_manager.instance_is_ready(**scene) {
|
||||
for entity in scene_manager.iter_instance_entities(**scene) {
|
||||
commands.entity(entity).insert(OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
width: 3.0,
|
||||
colour: Color::RED,
|
||||
},
|
||||
..default()
|
||||
});
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(InheritOutlineBundle::default());
|
||||
}
|
||||
player.play(animation.0.clone_weak()).repeat();
|
||||
*done = true;
|
||||
|
@ -56,7 +56,18 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
..default()
|
||||
})
|
||||
.insert(Rotates)
|
||||
.insert(ComputedOutlineDepth::default());
|
||||
.insert(OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
width: 7.5,
|
||||
colour: Color::BLUE,
|
||||
},
|
||||
stencil: OutlineStencil {
|
||||
enabled: true,
|
||||
offset: 0.0,
|
||||
},
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
// Once the scene is loaded, start the animation and add an outline
|
||||
@ -73,19 +84,7 @@ fn setup_scene_once_loaded(
|
||||
for entity in scene_manager.iter_instance_entities(**scene) {
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
width: 7.5,
|
||||
colour: Color::BLUE,
|
||||
},
|
||||
stencil: OutlineStencil {
|
||||
enabled: true,
|
||||
offset: 0.0,
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.insert(InheritOutlineDepth);
|
||||
.insert(InheritOutlineBundle::default());
|
||||
if let Ok(name) = name_query.get(entity) {
|
||||
if name.as_str() == "inside" {
|
||||
commands.entity(entity).insert(RotatesHue);
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
use bevy::{prelude::*, scene::SceneInstance};
|
||||
use bevy_mod_outline::{
|
||||
AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume,
|
||||
AutoGenerateOutlineNormalsPlugin, InheritOutlineBundle, OutlineBundle, OutlinePlugin,
|
||||
OutlineVolume,
|
||||
};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
@ -46,9 +47,18 @@ fn setup(asset_server: Res<AssetServer>, mut commands: Commands) {
|
||||
the_wave: asset_server.load("MorphStressTest.gltf#Animation2"),
|
||||
mesh: asset_server.load("MorphStressTest.gltf#Mesh0/Primitive0"),
|
||||
});
|
||||
commands.spawn(SceneBundle {
|
||||
commands
|
||||
.spawn(SceneBundle {
|
||||
scene: asset_server.load("MorphStressTest.gltf#Scene0"),
|
||||
..default()
|
||||
})
|
||||
.insert(OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
width: 3.0,
|
||||
colour: Color::RED,
|
||||
},
|
||||
..default()
|
||||
});
|
||||
commands.spawn(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
@ -78,14 +88,9 @@ fn setup_outlines(
|
||||
if let Ok(scene) = scene_query.get_single() {
|
||||
if scene_manager.instance_is_ready(**scene) {
|
||||
for entity in scene_manager.iter_instance_entities(**scene) {
|
||||
commands.entity(entity).insert(OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
width: 3.0,
|
||||
colour: Color::RED,
|
||||
},
|
||||
..default()
|
||||
});
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(InheritOutlineBundle::default());
|
||||
*has_setup = true;
|
||||
}
|
||||
}
|
||||
|
@ -29,19 +29,6 @@ fn setup(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let outline = OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
colour: Color::WHITE,
|
||||
width: 10.0,
|
||||
},
|
||||
stencil: OutlineStencil {
|
||||
offset: 5.0,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
};
|
||||
|
||||
// Add sphere with child meshes sticking out of it
|
||||
commands
|
||||
.spawn(PbrBundle {
|
||||
@ -57,7 +44,18 @@ fn setup(
|
||||
transform: Transform::from_translation(Vec3::new(0.0, 1.0, 0.0)),
|
||||
..default()
|
||||
})
|
||||
.insert(outline.clone())
|
||||
.insert(OutlineBundle {
|
||||
outline: OutlineVolume {
|
||||
visible: true,
|
||||
colour: Color::WHITE,
|
||||
width: 10.0,
|
||||
},
|
||||
stencil: OutlineStencil {
|
||||
offset: 5.0,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.insert(Rotates)
|
||||
.with_children(|parent| {
|
||||
parent
|
||||
@ -78,8 +76,7 @@ fn setup(
|
||||
.with_translation(Vec3::new(0.0, 0.0, 0.75)),
|
||||
..default()
|
||||
})
|
||||
.insert(outline.clone())
|
||||
.insert(InheritOutlineDepth);
|
||||
.insert(InheritOutlineBundle::default());
|
||||
parent
|
||||
.spawn(PbrBundle {
|
||||
mesh: meshes.add(
|
||||
@ -96,8 +93,7 @@ fn setup(
|
||||
.with_translation(Vec3::new(0.0, 0.0, -0.75)),
|
||||
..default()
|
||||
})
|
||||
.insert(outline.clone())
|
||||
.insert(InheritOutlineDepth);
|
||||
.insert(InheritOutlineBundle::default());
|
||||
});
|
||||
|
||||
// Add plane, light source, and camera
|
||||
|
232
src/computed.rs
232
src/computed.rs
@ -1,76 +1,113 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::{ecs::query::QueryItem, prelude::*};
|
||||
|
||||
use crate::uniforms::DepthMode;
|
||||
use crate::{uniforms::DepthMode, InheritOutline, OutlineMode, OutlineStencil, OutlineVolume};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct ComputedVolume {
|
||||
pub(crate) enabled: bool,
|
||||
pub(crate) offset: f32,
|
||||
pub(crate) colour: Vec4,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct ComputedStencil {
|
||||
pub(crate) enabled: bool,
|
||||
pub(crate) offset: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ComputedMode {
|
||||
pub(crate) world_origin: Vec3,
|
||||
pub(crate) depth_mode: DepthMode,
|
||||
}
|
||||
|
||||
impl Default for ComputedMode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
world_origin: Vec3::NAN,
|
||||
depth_mode: DepthMode::Real,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub(crate) enum Source {
|
||||
#[default]
|
||||
Set,
|
||||
Inherited,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct Sourced<T: Clone + Default> {
|
||||
pub(crate) value: T,
|
||||
pub(crate) source: Source,
|
||||
}
|
||||
|
||||
impl<T: Clone + Default> Sourced<T> {
|
||||
pub fn inherit(value: &T) -> Self {
|
||||
Sourced {
|
||||
value: value.clone(),
|
||||
source: Source::Inherited,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(value: T) -> Self {
|
||||
Sourced {
|
||||
value,
|
||||
source: Source::Set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_changed<U: Component>(&self, tuple: Option<(&U, bool)>) -> bool {
|
||||
tuple.is_some() != matches!(self.source, Source::Set)
|
||||
|| if let Some((_, c)) = tuple { c } else { false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct ComputedInternal {
|
||||
pub(crate) inherited_from: Option<Entity>,
|
||||
pub(crate) volume: Sourced<ComputedVolume>,
|
||||
pub(crate) stencil: Sourced<ComputedStencil>,
|
||||
pub(crate) mode: Sourced<ComputedMode>,
|
||||
}
|
||||
|
||||
/// A component for storing the computed depth at which the outline lies.
|
||||
#[derive(Clone, Component, Default)]
|
||||
pub struct ComputedOutlineDepth {
|
||||
pub(crate) world_origin: Vec3,
|
||||
pub(crate) depth_mode: DepthMode,
|
||||
pub(crate) inherited: Option<Entity>,
|
||||
}
|
||||
pub struct ComputedOutline(pub(crate) Option<ComputedInternal>);
|
||||
|
||||
/// A component which specifies how the outline depth should be computed.
|
||||
#[derive(Clone, Component)]
|
||||
#[non_exhaustive]
|
||||
pub enum SetOutlineDepth {
|
||||
/// A flat plane facing the camera and intersecting the specified point in model-space.
|
||||
Flat { model_origin: Vec3 },
|
||||
/// Real model-space.
|
||||
Real,
|
||||
}
|
||||
|
||||
/// A component which specifies that this outline lies at the same depth as its parent.
|
||||
#[derive(Clone, Component, Default)]
|
||||
pub struct InheritOutlineDepth;
|
||||
type OutlineComponents<'a> = (
|
||||
(&'a GlobalTransform, Changed<GlobalTransform>),
|
||||
Option<(&'a OutlineVolume, Changed<OutlineVolume>)>,
|
||||
Option<(&'a OutlineStencil, Changed<OutlineStencil>)>,
|
||||
Option<(&'a OutlineMode, Changed<OutlineMode>)>,
|
||||
);
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn compute_outline_depth(
|
||||
pub(crate) fn compute_outline(
|
||||
mut root_query: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut ComputedOutlineDepth,
|
||||
&GlobalTransform,
|
||||
Changed<GlobalTransform>,
|
||||
Option<(&SetOutlineDepth, Changed<SetOutlineDepth>)>,
|
||||
&mut ComputedOutline,
|
||||
OutlineComponents,
|
||||
Option<&Children>,
|
||||
),
|
||||
Without<InheritOutlineDepth>,
|
||||
Without<InheritOutline>,
|
||||
>,
|
||||
mut computed_query: Query<&mut ComputedOutlineDepth, With<InheritOutlineDepth>>,
|
||||
mut child_query_mut: Query<(&mut ComputedOutline, OutlineComponents), With<InheritOutline>>,
|
||||
child_query: Query<&Children>,
|
||||
) {
|
||||
for (entity, mut computed, transform, changed_transform, set_depth, children) in
|
||||
root_query.iter_mut()
|
||||
{
|
||||
let changed = !computed.depth_mode.is_valid()
|
||||
|| computed.inherited.is_some()
|
||||
|| changed_transform
|
||||
|| set_depth.filter(|(_, c)| *c).is_some();
|
||||
if changed {
|
||||
let (origin, depth_mode) = if let Some((sd, _)) = set_depth {
|
||||
match sd {
|
||||
SetOutlineDepth::Flat {
|
||||
model_origin: origin,
|
||||
} => (*origin, DepthMode::Flat),
|
||||
SetOutlineDepth::Real => (Vec3::NAN, DepthMode::Real),
|
||||
}
|
||||
} else {
|
||||
(Vec3::ZERO, DepthMode::Flat)
|
||||
};
|
||||
let matrix = transform.compute_matrix();
|
||||
computed.world_origin = matrix.project_point3(origin);
|
||||
computed.depth_mode = depth_mode;
|
||||
computed.inherited = None;
|
||||
}
|
||||
for (entity, mut computed, components, children) in root_query.iter_mut() {
|
||||
let changed = update_computed_outline(&mut computed, components, &default(), None, false);
|
||||
if let Some(cs) = children {
|
||||
let parent_computed = &computed.0.as_ref().unwrap().clone();
|
||||
for child in cs.iter() {
|
||||
propagate_outline_depth(
|
||||
&computed,
|
||||
propagate_computed_outline(
|
||||
parent_computed,
|
||||
changed,
|
||||
entity,
|
||||
*child,
|
||||
&mut computed_query,
|
||||
&mut child_query_mut,
|
||||
&child_query,
|
||||
);
|
||||
}
|
||||
@ -78,31 +115,92 @@ pub(crate) fn compute_outline_depth(
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_outline_depth(
|
||||
root_computed: &ComputedOutlineDepth,
|
||||
mut changed: bool,
|
||||
parent: Entity,
|
||||
fn propagate_computed_outline(
|
||||
parent_computed: &ComputedInternal,
|
||||
parent_changed: bool,
|
||||
parent_entity: Entity,
|
||||
entity: Entity,
|
||||
computed_query: &mut Query<&mut ComputedOutlineDepth, With<InheritOutlineDepth>>,
|
||||
child_query_mut: &mut Query<(&mut ComputedOutline, OutlineComponents), With<InheritOutline>>,
|
||||
child_query: &Query<&Children>,
|
||||
) {
|
||||
if let Ok(mut computed) = computed_query.get_mut(entity) {
|
||||
changed |= !computed.depth_mode.is_valid() | (computed.inherited != Some(parent));
|
||||
if changed {
|
||||
*computed = root_computed.clone();
|
||||
computed.inherited = Some(parent);
|
||||
}
|
||||
if let Ok((mut computed, components)) = child_query_mut.get_mut(entity) {
|
||||
let changed = update_computed_outline(
|
||||
&mut computed,
|
||||
components,
|
||||
parent_computed,
|
||||
Some(parent_entity),
|
||||
parent_changed,
|
||||
);
|
||||
if let Ok(cs) = child_query.get(entity) {
|
||||
let parent_computed = &computed.0.as_ref().unwrap().clone();
|
||||
for child in cs.iter() {
|
||||
propagate_outline_depth(
|
||||
root_computed,
|
||||
propagate_computed_outline(
|
||||
parent_computed,
|
||||
changed,
|
||||
entity,
|
||||
*child,
|
||||
computed_query,
|
||||
child_query_mut,
|
||||
child_query,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_computed_outline(
|
||||
computed: &mut ComputedOutline,
|
||||
((transform, changed_transform), volume, stencil, mode): QueryItem<'_, OutlineComponents>,
|
||||
parent_computed: &ComputedInternal,
|
||||
parent_entity: Option<Entity>,
|
||||
force_update: bool,
|
||||
) -> bool {
|
||||
let changed = force_update
|
||||
|| if let ComputedOutline(Some(computed)) = computed {
|
||||
computed.inherited_from != parent_entity
|
||||
|| (changed_transform && matches!(mode, Some((OutlineMode::FlatVertex { .. }, _))))
|
||||
|| computed.volume.is_changed(volume)
|
||||
|| computed.stencil.is_changed(stencil)
|
||||
|| computed.mode.is_changed(mode)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if changed {
|
||||
*computed = ComputedOutline(Some(ComputedInternal {
|
||||
inherited_from: parent_entity,
|
||||
volume: if let Some((vol, _)) = volume {
|
||||
Sourced::set(ComputedVolume {
|
||||
enabled: vol.visible && vol.colour.a() != 0.0,
|
||||
offset: vol.width,
|
||||
colour: vol.colour.into(),
|
||||
})
|
||||
} else {
|
||||
Sourced::inherit(&parent_computed.volume.value)
|
||||
},
|
||||
stencil: if let Some((sten, _)) = stencil {
|
||||
Sourced::set(ComputedStencil {
|
||||
enabled: sten.enabled,
|
||||
offset: sten.offset,
|
||||
})
|
||||
} else {
|
||||
Sourced::inherit(&parent_computed.stencil.value)
|
||||
},
|
||||
mode: if let Some((m, _)) = mode {
|
||||
Sourced::set(match m {
|
||||
OutlineMode::FlatVertex {
|
||||
model_origin: origin,
|
||||
} => ComputedMode {
|
||||
world_origin: transform.compute_matrix().project_point3(*origin),
|
||||
depth_mode: DepthMode::Flat,
|
||||
},
|
||||
OutlineMode::RealVertex => ComputedMode {
|
||||
world_origin: Vec3::NAN,
|
||||
depth_mode: DepthMode::Real,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Sourced::inherit(&parent_computed.mode.value)
|
||||
},
|
||||
}));
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
31
src/draw.rs
31
src/draw.rs
@ -8,9 +8,8 @@ use bevy::render::view::{ExtractedView, RenderLayers};
|
||||
use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline};
|
||||
use crate::pipeline::{OutlinePipeline, PassType, PipelineKey};
|
||||
use crate::uniforms::{
|
||||
DepthMode, OutlineFragmentUniform, OutlineStencilFlags, OutlineStencilUniform,
|
||||
OutlineVolumeFlags, OutlineVolumeUniform, SetOutlineStencilBindGroup,
|
||||
SetOutlineVolumeBindGroup,
|
||||
ExtractedOutline, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform,
|
||||
SetOutlineStencilBindGroup, SetOutlineVolumeBindGroup,
|
||||
};
|
||||
use crate::view_uniforms::SetOutlineViewBindGroup;
|
||||
use crate::OutlineRenderLayers;
|
||||
@ -34,9 +33,8 @@ pub(crate) fn queue_outline_stencil_mesh(
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
material_meshes: Query<(
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&OutlineStencilUniform,
|
||||
&OutlineStencilFlags,
|
||||
&ExtractedOutline,
|
||||
&OutlineRenderLayers,
|
||||
)>,
|
||||
mut views: Query<(
|
||||
@ -57,21 +55,16 @@ pub(crate) fn queue_outline_stencil_mesh(
|
||||
for (view, mut stencil_phase, view_mask) in views.iter_mut() {
|
||||
let rangefinder = view.rangefinder3d();
|
||||
let view_mask = view_mask.copied().unwrap_or_default();
|
||||
for (entity, mesh_handle, stencil_uniform, stencil_flags, outline_mask) in
|
||||
material_meshes.iter()
|
||||
{
|
||||
for (entity, stencil_uniform, outline, outline_mask) in material_meshes.iter() {
|
||||
if !view_mask.intersects(outline_mask) {
|
||||
continue; // Layer not enabled
|
||||
}
|
||||
if stencil_flags.depth_mode == DepthMode::Invalid {
|
||||
continue; // DepthMode not propagated
|
||||
}
|
||||
let Some(mesh) = render_meshes.get(mesh_handle) else {
|
||||
let Some(mesh) = render_meshes.get(&outline.mesh) else {
|
||||
continue; // No mesh
|
||||
};
|
||||
let key = base_key
|
||||
.with_primitive_topology(mesh.primitive_topology)
|
||||
.with_depth_mode(stencil_flags.depth_mode)
|
||||
.with_depth_mode(outline.depth_mode)
|
||||
.with_offset_zero(stencil_uniform.offset == 0.0)
|
||||
.with_morph_targets(mesh.morph_targets.is_some());
|
||||
let Ok(pipeline) =
|
||||
@ -110,9 +103,8 @@ pub(crate) fn queue_outline_volume_mesh(
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
material_meshes: Query<(
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&OutlineVolumeUniform,
|
||||
&OutlineVolumeFlags,
|
||||
&ExtractedOutline,
|
||||
&OutlineFragmentUniform,
|
||||
&OutlineRenderLayers,
|
||||
)>,
|
||||
@ -137,16 +129,13 @@ pub(crate) fn queue_outline_volume_mesh(
|
||||
for (view, mut opaque_phase, mut transparent_phase, view_mask) in views.iter_mut() {
|
||||
let view_mask = view_mask.copied().unwrap_or_default();
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for (entity, mesh_handle, volume_uniform, volume_flags, fragment_uniform, outline_mask) in
|
||||
for (entity, volume_uniform, outline, fragment_uniform, outline_mask) in
|
||||
material_meshes.iter()
|
||||
{
|
||||
if !view_mask.intersects(outline_mask) {
|
||||
continue; // Layer not enabled
|
||||
}
|
||||
if volume_flags.depth_mode == DepthMode::Invalid {
|
||||
continue; // DepthMode not propagated
|
||||
}
|
||||
let Some(mesh) = render_meshes.get(mesh_handle) else {
|
||||
let Some(mesh) = render_meshes.get(&outline.mesh) else {
|
||||
continue; // No mesh
|
||||
};
|
||||
let transparent = fragment_uniform.colour[3] < 1.0;
|
||||
@ -157,7 +146,7 @@ pub(crate) fn queue_outline_volume_mesh(
|
||||
} else {
|
||||
PassType::Opaque
|
||||
})
|
||||
.with_depth_mode(volume_flags.depth_mode)
|
||||
.with_depth_mode(outline.depth_mode)
|
||||
.with_offset_zero(volume_uniform.offset == 0.0)
|
||||
.with_hdr_format(view.hdr)
|
||||
.with_morph_targets(mesh.morph_targets.is_some());
|
||||
|
94
src/lib.rs
94
src/lib.rs
@ -3,17 +3,19 @@
|
||||
//!
|
||||
//! Outlines are rendered in a seperate pass following the main 3D pass. The effect of this
|
||||
//! pass is to present the outlines in depth sorted order according to the model translation
|
||||
//! of each mesh. This ensures that outlines are not clipped by other geometry.
|
||||
//! of each mesh. This ensures that outlines are not clipped by non-outline geometry.
|
||||
//!
|
||||
//! The [`OutlineVolume`] component will, by itself, cover the original object entirely with
|
||||
//! the outline colour. The [`OutlineStencil`] component must also be added to prevent the body
|
||||
//! of an object from being filled it. This must be added to any entity which needs to appear on
|
||||
//! top of an outline.
|
||||
//!
|
||||
//! The [`OutlineMode`] component specifies the rendering method. Outlines may be flattened into
|
||||
//! a plane in order to further avoid clipping, or left in real space.
|
||||
//!
|
||||
//! The [`OutlineBundle`] and [`OutlineStencilBundle`] bundles can be used to add the right
|
||||
//! components, including the required [`ComputedOutlineDepth`] component. Optionally, the
|
||||
//! [`SetOutlineDepth`] and [`InheritOutlineDepth`] components may also be added to control the
|
||||
//! depth ordering of outlines.
|
||||
//! components, including the required [`ComputedOutline`] component. Outlines can be inherited
|
||||
//! from the parent via the [`InheritOutline`] component and [`InheritOutlineBundle`].
|
||||
//!
|
||||
//! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual
|
||||
//! artefacts when outlining meshes with hard edges, see the
|
||||
@ -29,7 +31,7 @@ use bevy::render::mesh::MeshVertexAttribute;
|
||||
use bevy::render::render_graph::RenderGraph;
|
||||
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
|
||||
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
|
||||
use bevy::render::view::RenderLayers;
|
||||
use bevy::render::view::{RenderLayers, VisibilitySystems};
|
||||
use bevy::render::{Render, RenderApp, RenderSet};
|
||||
use bevy::transform::TransformSystem;
|
||||
use interpolation::Lerp;
|
||||
@ -40,9 +42,8 @@ use crate::draw::{
|
||||
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
|
||||
use crate::pipeline::{OutlinePipeline, FRAGMENT_SHADER_HANDLE, OUTLINE_SHADER_HANDLE};
|
||||
use crate::uniforms::{
|
||||
extract_outline_stencil_uniforms, extract_outline_volume_uniforms,
|
||||
queue_outline_stencil_bind_group, queue_outline_volume_bind_group, OutlineFragmentUniform,
|
||||
OutlineStencilUniform, OutlineVolumeUniform,
|
||||
extract_outline_uniforms, queue_outline_stencil_bind_group, queue_outline_volume_bind_group,
|
||||
set_outline_visibility, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform,
|
||||
};
|
||||
use crate::view_uniforms::{
|
||||
extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform,
|
||||
@ -148,7 +149,7 @@ impl ExtractComponent for OutlineRenderLayers {
|
||||
Option<&'static OutlineRenderLayers>,
|
||||
Option<&'static RenderLayers>,
|
||||
);
|
||||
type Filter = Or<(With<OutlineVolume>, With<OutlineStencil>)>;
|
||||
type Filter = With<ComputedOutline>;
|
||||
type Out = Self;
|
||||
|
||||
fn extract_component(
|
||||
@ -163,19 +164,51 @@ impl ExtractComponent for OutlineRenderLayers {
|
||||
}
|
||||
}
|
||||
|
||||
/// A component which specifies how the outline should be rendered.
|
||||
#[derive(Clone, Component)]
|
||||
#[non_exhaustive]
|
||||
pub enum OutlineMode {
|
||||
/// Vertex extrusion flattened into a plane facing the camera and intersecting the specified
|
||||
/// point in model-space.
|
||||
FlatVertex { model_origin: Vec3 },
|
||||
/// Vertex extrusion in real model-space.
|
||||
RealVertex,
|
||||
}
|
||||
|
||||
impl Default for OutlineMode {
|
||||
fn default() -> Self {
|
||||
OutlineMode::FlatVertex {
|
||||
model_origin: Vec3::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A component for inheriting outlines from the parent entity.
|
||||
#[derive(Clone, Component, Default)]
|
||||
pub struct InheritOutline;
|
||||
|
||||
/// A bundle for rendering stenciled outlines around meshes.
|
||||
#[derive(Bundle, Clone, Default)]
|
||||
pub struct OutlineBundle {
|
||||
pub outline: OutlineVolume,
|
||||
pub stencil: OutlineStencil,
|
||||
pub plane: ComputedOutlineDepth,
|
||||
pub mode: OutlineMode,
|
||||
pub computed: ComputedOutline,
|
||||
}
|
||||
|
||||
/// A bundle for stenciling meshes in the outlining pass.
|
||||
#[derive(Bundle, Clone, Default)]
|
||||
pub struct OutlineStencilBundle {
|
||||
pub stencil: OutlineStencil,
|
||||
pub plane: ComputedOutlineDepth,
|
||||
pub mode: OutlineMode,
|
||||
pub computed: ComputedOutline,
|
||||
}
|
||||
|
||||
/// A bundle for inheriting outlines from the parent entity.
|
||||
#[derive(Bundle, Clone, Default)]
|
||||
pub struct InheritOutlineBundle {
|
||||
pub inherit: InheritOutline,
|
||||
pub computed: ComputedOutline,
|
||||
}
|
||||
|
||||
/// Adds support for rendering outlines.
|
||||
@ -205,7 +238,10 @@ impl Plugin for OutlinePlugin {
|
||||
))
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
compute_outline_depth.after(TransformSystem::TransformPropagate),
|
||||
(
|
||||
compute_outline.after(TransformSystem::TransformPropagate),
|
||||
set_outline_visibility.in_set(VisibilitySystems::CheckVisibility),
|
||||
),
|
||||
)
|
||||
.sub_app_mut(RenderApp)
|
||||
.init_resource::<DrawFunctions<StencilOutline>>()
|
||||
@ -215,35 +251,31 @@ impl Plugin for OutlinePlugin {
|
||||
.add_render_command::<StencilOutline, DrawStencil>()
|
||||
.add_render_command::<OpaqueOutline, DrawOutline>()
|
||||
.add_render_command::<TransparentOutline, DrawOutline>()
|
||||
.add_systems(ExtractSchedule, extract_outline_view_uniforms)
|
||||
.add_systems(ExtractSchedule, extract_outline_stencil_uniforms)
|
||||
.add_systems(ExtractSchedule, extract_outline_volume_uniforms)
|
||||
.add_systems(
|
||||
Render,
|
||||
sort_phase_system::<StencilOutline>.in_set(RenderSet::PhaseSort),
|
||||
ExtractSchedule,
|
||||
(extract_outline_uniforms, extract_outline_view_uniforms),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
sort_phase_system::<OpaqueOutline>.in_set(RenderSet::PhaseSort),
|
||||
(
|
||||
sort_phase_system::<OpaqueOutline>,
|
||||
sort_phase_system::<TransparentOutline>,
|
||||
)
|
||||
.in_set(RenderSet::PhaseSort),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
sort_phase_system::<TransparentOutline>.in_set(RenderSet::PhaseSort),
|
||||
(
|
||||
queue_outline_view_bind_group,
|
||||
queue_outline_stencil_bind_group,
|
||||
queue_outline_volume_bind_group,
|
||||
)
|
||||
.in_set(RenderSet::Queue),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_outline_view_bind_group.in_set(RenderSet::Queue),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_outline_stencil_bind_group.in_set(RenderSet::Queue),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_outline_volume_bind_group.in_set(RenderSet::Queue),
|
||||
)
|
||||
.add_systems(Render, queue_outline_stencil_mesh.in_set(RenderSet::Queue))
|
||||
.add_systems(Render, queue_outline_volume_mesh.in_set(RenderSet::Queue));
|
||||
(queue_outline_stencil_mesh, queue_outline_volume_mesh).in_set(RenderSet::Queue),
|
||||
);
|
||||
|
||||
let world = &mut app.sub_app_mut(RenderApp).world;
|
||||
let node = OutlineNode::new(world);
|
||||
|
@ -13,10 +13,13 @@ use bevy::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
node::StencilOutline, pipeline::OutlinePipeline, ComputedOutlineDepth, OutlineStencil,
|
||||
OutlineVolume,
|
||||
};
|
||||
use crate::{node::StencilOutline, pipeline::OutlinePipeline, ComputedOutline};
|
||||
|
||||
#[derive(Component)]
|
||||
pub(crate) struct ExtractedOutline {
|
||||
pub depth_mode: DepthMode,
|
||||
pub mesh: Handle<Mesh>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, ShaderType)]
|
||||
pub(crate) struct OutlineStencilUniform {
|
||||
@ -38,30 +41,12 @@ pub(crate) struct OutlineFragmentUniform {
|
||||
pub colour: Vec4,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum DepthMode {
|
||||
#[default]
|
||||
Invalid = 0,
|
||||
Flat = 1,
|
||||
Real = 2,
|
||||
}
|
||||
|
||||
impl DepthMode {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
*self != DepthMode::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub(crate) struct OutlineStencilFlags {
|
||||
pub depth_mode: DepthMode,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub(crate) struct OutlineVolumeFlags {
|
||||
pub depth_mode: DepthMode,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct OutlineStencilBindGroup {
|
||||
pub bind_group: BindGroup,
|
||||
@ -72,47 +57,47 @@ pub(crate) struct OutlineVolumeBindGroup {
|
||||
pub bind_group: BindGroup,
|
||||
}
|
||||
|
||||
pub(crate) fn extract_outline_stencil_uniforms(
|
||||
mut commands: Commands,
|
||||
query: Extract<Query<(Entity, &OutlineStencil, &ComputedOutlineDepth)>>,
|
||||
pub(crate) fn set_outline_visibility(
|
||||
mut query: Query<(&mut ComputedVisibility, &ComputedOutline)>,
|
||||
) {
|
||||
for (entity, stencil, computed) in query.iter() {
|
||||
if !stencil.enabled {
|
||||
continue;
|
||||
for (mut visibility, computed) in query.iter_mut() {
|
||||
if let ComputedOutline(Some(computed)) = computed {
|
||||
if computed.volume.value.enabled || computed.stencil.value.enabled {
|
||||
visibility.set_visible_in_view();
|
||||
}
|
||||
}
|
||||
commands
|
||||
.get_or_spawn(entity)
|
||||
.insert(OutlineStencilUniform {
|
||||
origin: computed.world_origin,
|
||||
offset: stencil.offset,
|
||||
})
|
||||
.insert(OutlineStencilFlags {
|
||||
depth_mode: computed.depth_mode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extract_outline_volume_uniforms(
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn extract_outline_uniforms(
|
||||
mut commands: Commands,
|
||||
query: Extract<Query<(Entity, &OutlineVolume, &ComputedOutlineDepth)>>,
|
||||
query: Extract<Query<(Entity, &ComputedOutline, &Handle<Mesh>)>>,
|
||||
) {
|
||||
for (entity, outline, computed) in query.iter() {
|
||||
if !outline.visible || outline.colour.a() == 0.0 {
|
||||
continue;
|
||||
}
|
||||
commands
|
||||
.get_or_spawn(entity)
|
||||
.insert(OutlineVolumeUniform {
|
||||
origin: computed.world_origin,
|
||||
offset: outline.width,
|
||||
for (entity, computed, mesh) in query.iter() {
|
||||
let cmds = &mut commands.get_or_spawn(entity);
|
||||
if let ComputedOutline(Some(computed)) = computed {
|
||||
cmds.insert(ExtractedOutline {
|
||||
depth_mode: computed.mode.value.depth_mode,
|
||||
mesh: mesh.clone_weak(),
|
||||
});
|
||||
if computed.volume.value.enabled {
|
||||
cmds.insert(OutlineVolumeUniform {
|
||||
origin: computed.mode.value.world_origin,
|
||||
offset: computed.volume.value.offset,
|
||||
})
|
||||
.insert(OutlineFragmentUniform {
|
||||
colour: outline.colour.as_linear_rgba_f32().into(),
|
||||
})
|
||||
.insert(OutlineVolumeFlags {
|
||||
depth_mode: computed.depth_mode,
|
||||
colour: computed.volume.value.colour,
|
||||
});
|
||||
}
|
||||
if computed.stencil.value.enabled {
|
||||
cmds.insert(OutlineStencilUniform {
|
||||
origin: computed.mode.value.world_origin,
|
||||
offset: computed.stencil.value.offset,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn queue_outline_stencil_bind_group(
|
||||
|
Loading…
Reference in New Issue
Block a user