Change outline and stencil properties to be inheritable.

This commit is contained in:
Robin KAY 2023-11-08 21:40:19 +00:00
parent 65c7c3356f
commit 600f58ba73
8 changed files with 341 additions and 232 deletions

View File

@ -6,7 +6,8 @@ use bevy::{
window::close_on_esc, window::close_on_esc,
}; };
use bevy_mod_outline::{ use bevy_mod_outline::{
AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume, AutoGenerateOutlineNormalsPlugin, InheritOutlineBundle, OutlineBundle, OutlinePlugin,
OutlineVolume,
}; };
#[derive(Resource)] #[derive(Resource)]
@ -65,9 +66,18 @@ fn setup(
}); });
// Fox // Fox
commands.spawn(SceneBundle { commands
.spawn(SceneBundle {
scene: asset_server.load("Fox.glb#Scene0"), scene: asset_server.load("Fox.glb#Scene0"),
..default() ..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) { if scene_manager.instance_is_ready(**scene) {
for entity in scene_manager.iter_instance_entities(**scene) { for entity in scene_manager.iter_instance_entities(**scene) {
commands.entity(entity).insert(OutlineBundle { commands
outline: OutlineVolume { .entity(entity)
visible: true, .insert(InheritOutlineBundle::default());
width: 3.0,
colour: Color::RED,
},
..default()
});
} }
player.play(animation.0.clone_weak()).repeat(); player.play(animation.0.clone_weak()).repeat();
*done = true; *done = true;

View File

@ -56,7 +56,18 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default() ..default()
}) })
.insert(Rotates) .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 // 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) { for entity in scene_manager.iter_instance_entities(**scene) {
commands commands
.entity(entity) .entity(entity)
.insert(OutlineBundle { .insert(InheritOutlineBundle::default());
outline: OutlineVolume {
visible: true,
width: 7.5,
colour: Color::BLUE,
},
stencil: OutlineStencil {
enabled: true,
offset: 0.0,
},
..default()
})
.insert(InheritOutlineDepth);
if let Ok(name) = name_query.get(entity) { if let Ok(name) = name_query.get(entity) {
if name.as_str() == "inside" { if name.as_str() == "inside" {
commands.entity(entity).insert(RotatesHue); commands.entity(entity).insert(RotatesHue);

View File

@ -9,7 +9,8 @@
use bevy::{prelude::*, scene::SceneInstance}; use bevy::{prelude::*, scene::SceneInstance};
use bevy_mod_outline::{ use bevy_mod_outline::{
AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume, AutoGenerateOutlineNormalsPlugin, InheritOutlineBundle, OutlineBundle, OutlinePlugin,
OutlineVolume,
}; };
use std::f32::consts::PI; 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"), the_wave: asset_server.load("MorphStressTest.gltf#Animation2"),
mesh: asset_server.load("MorphStressTest.gltf#Mesh0/Primitive0"), mesh: asset_server.load("MorphStressTest.gltf#Mesh0/Primitive0"),
}); });
commands.spawn(SceneBundle { commands
.spawn(SceneBundle {
scene: asset_server.load("MorphStressTest.gltf#Scene0"), scene: asset_server.load("MorphStressTest.gltf#Scene0"),
..default() ..default()
})
.insert(OutlineBundle {
outline: OutlineVolume {
visible: true,
width: 3.0,
colour: Color::RED,
},
..default()
}); });
commands.spawn(DirectionalLightBundle { commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight { directional_light: DirectionalLight {
@ -78,14 +88,9 @@ fn setup_outlines(
if let Ok(scene) = scene_query.get_single() { if let Ok(scene) = scene_query.get_single() {
if scene_manager.instance_is_ready(**scene) { if scene_manager.instance_is_ready(**scene) {
for entity in scene_manager.iter_instance_entities(**scene) { for entity in scene_manager.iter_instance_entities(**scene) {
commands.entity(entity).insert(OutlineBundle { commands
outline: OutlineVolume { .entity(entity)
visible: true, .insert(InheritOutlineBundle::default());
width: 3.0,
colour: Color::RED,
},
..default()
});
*has_setup = true; *has_setup = true;
} }
} }

View File

@ -29,19 +29,6 @@ fn setup(
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, 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 // Add sphere with child meshes sticking out of it
commands commands
.spawn(PbrBundle { .spawn(PbrBundle {
@ -57,7 +44,18 @@ fn setup(
transform: Transform::from_translation(Vec3::new(0.0, 1.0, 0.0)), transform: Transform::from_translation(Vec3::new(0.0, 1.0, 0.0)),
..default() ..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) .insert(Rotates)
.with_children(|parent| { .with_children(|parent| {
parent parent
@ -78,8 +76,7 @@ fn setup(
.with_translation(Vec3::new(0.0, 0.0, 0.75)), .with_translation(Vec3::new(0.0, 0.0, 0.75)),
..default() ..default()
}) })
.insert(outline.clone()) .insert(InheritOutlineBundle::default());
.insert(InheritOutlineDepth);
parent parent
.spawn(PbrBundle { .spawn(PbrBundle {
mesh: meshes.add( mesh: meshes.add(
@ -96,8 +93,7 @@ fn setup(
.with_translation(Vec3::new(0.0, 0.0, -0.75)), .with_translation(Vec3::new(0.0, 0.0, -0.75)),
..default() ..default()
}) })
.insert(outline.clone()) .insert(InheritOutlineBundle::default());
.insert(InheritOutlineDepth);
}); });
// Add plane, light source, and camera // Add plane, light source, and camera

View File

@ -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. /// A component for storing the computed depth at which the outline lies.
#[derive(Clone, Component, Default)] #[derive(Clone, Component, Default)]
pub struct ComputedOutlineDepth { pub struct ComputedOutline(pub(crate) Option<ComputedInternal>);
pub(crate) world_origin: Vec3,
pub(crate) depth_mode: DepthMode,
pub(crate) inherited: Option<Entity>,
}
/// A component which specifies how the outline depth should be computed. type OutlineComponents<'a> = (
#[derive(Clone, Component)] (&'a GlobalTransform, Changed<GlobalTransform>),
#[non_exhaustive] Option<(&'a OutlineVolume, Changed<OutlineVolume>)>,
pub enum SetOutlineDepth { Option<(&'a OutlineStencil, Changed<OutlineStencil>)>,
/// A flat plane facing the camera and intersecting the specified point in model-space. Option<(&'a OutlineMode, Changed<OutlineMode>)>,
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;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub(crate) fn compute_outline_depth( pub(crate) fn compute_outline(
mut root_query: Query< mut root_query: Query<
( (
Entity, Entity,
&mut ComputedOutlineDepth, &mut ComputedOutline,
&GlobalTransform, OutlineComponents,
Changed<GlobalTransform>,
Option<(&SetOutlineDepth, Changed<SetOutlineDepth>)>,
Option<&Children>, 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>, child_query: Query<&Children>,
) { ) {
for (entity, mut computed, transform, changed_transform, set_depth, children) in for (entity, mut computed, components, children) in root_query.iter_mut() {
root_query.iter_mut() let changed = update_computed_outline(&mut computed, components, &default(), None, false);
{
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;
}
if let Some(cs) = children { if let Some(cs) = children {
let parent_computed = &computed.0.as_ref().unwrap().clone();
for child in cs.iter() { for child in cs.iter() {
propagate_outline_depth( propagate_computed_outline(
&computed, parent_computed,
changed, changed,
entity, entity,
*child, *child,
&mut computed_query, &mut child_query_mut,
&child_query, &child_query,
); );
} }
@ -78,31 +115,92 @@ pub(crate) fn compute_outline_depth(
} }
} }
fn propagate_outline_depth( fn propagate_computed_outline(
root_computed: &ComputedOutlineDepth, parent_computed: &ComputedInternal,
mut changed: bool, parent_changed: bool,
parent: Entity, parent_entity: Entity,
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>, child_query: &Query<&Children>,
) { ) {
if let Ok(mut computed) = computed_query.get_mut(entity) { if let Ok((mut computed, components)) = child_query_mut.get_mut(entity) {
changed |= !computed.depth_mode.is_valid() | (computed.inherited != Some(parent)); let changed = update_computed_outline(
if changed { &mut computed,
*computed = root_computed.clone(); components,
computed.inherited = Some(parent); parent_computed,
} Some(parent_entity),
parent_changed,
);
if let Ok(cs) = child_query.get(entity) { if let Ok(cs) = child_query.get(entity) {
let parent_computed = &computed.0.as_ref().unwrap().clone();
for child in cs.iter() { for child in cs.iter() {
propagate_outline_depth( propagate_computed_outline(
root_computed, parent_computed,
changed, changed,
entity, entity,
*child, *child,
computed_query, child_query_mut,
child_query, 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
}

View File

@ -8,9 +8,8 @@ use bevy::render::view::{ExtractedView, RenderLayers};
use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline}; use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline};
use crate::pipeline::{OutlinePipeline, PassType, PipelineKey}; use crate::pipeline::{OutlinePipeline, PassType, PipelineKey};
use crate::uniforms::{ use crate::uniforms::{
DepthMode, OutlineFragmentUniform, OutlineStencilFlags, OutlineStencilUniform, ExtractedOutline, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform,
OutlineVolumeFlags, OutlineVolumeUniform, SetOutlineStencilBindGroup, SetOutlineStencilBindGroup, SetOutlineVolumeBindGroup,
SetOutlineVolumeBindGroup,
}; };
use crate::view_uniforms::SetOutlineViewBindGroup; use crate::view_uniforms::SetOutlineViewBindGroup;
use crate::OutlineRenderLayers; use crate::OutlineRenderLayers;
@ -34,9 +33,8 @@ pub(crate) fn queue_outline_stencil_mesh(
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
material_meshes: Query<( material_meshes: Query<(
Entity, Entity,
&Handle<Mesh>,
&OutlineStencilUniform, &OutlineStencilUniform,
&OutlineStencilFlags, &ExtractedOutline,
&OutlineRenderLayers, &OutlineRenderLayers,
)>, )>,
mut views: Query<( 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() { for (view, mut stencil_phase, view_mask) in views.iter_mut() {
let rangefinder = view.rangefinder3d(); let rangefinder = view.rangefinder3d();
let view_mask = view_mask.copied().unwrap_or_default(); let view_mask = view_mask.copied().unwrap_or_default();
for (entity, mesh_handle, stencil_uniform, stencil_flags, outline_mask) in for (entity, stencil_uniform, outline, outline_mask) in material_meshes.iter() {
material_meshes.iter()
{
if !view_mask.intersects(outline_mask) { if !view_mask.intersects(outline_mask) {
continue; // Layer not enabled continue; // Layer not enabled
} }
if stencil_flags.depth_mode == DepthMode::Invalid { let Some(mesh) = render_meshes.get(&outline.mesh) else {
continue; // DepthMode not propagated
}
let Some(mesh) = render_meshes.get(mesh_handle) else {
continue; // No mesh continue; // No mesh
}; };
let key = base_key let key = base_key
.with_primitive_topology(mesh.primitive_topology) .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_offset_zero(stencil_uniform.offset == 0.0)
.with_morph_targets(mesh.morph_targets.is_some()); .with_morph_targets(mesh.morph_targets.is_some());
let Ok(pipeline) = let Ok(pipeline) =
@ -110,9 +103,8 @@ pub(crate) fn queue_outline_volume_mesh(
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
material_meshes: Query<( material_meshes: Query<(
Entity, Entity,
&Handle<Mesh>,
&OutlineVolumeUniform, &OutlineVolumeUniform,
&OutlineVolumeFlags, &ExtractedOutline,
&OutlineFragmentUniform, &OutlineFragmentUniform,
&OutlineRenderLayers, &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() { for (view, mut opaque_phase, mut transparent_phase, view_mask) in views.iter_mut() {
let view_mask = view_mask.copied().unwrap_or_default(); let view_mask = view_mask.copied().unwrap_or_default();
let rangefinder = view.rangefinder3d(); 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() material_meshes.iter()
{ {
if !view_mask.intersects(outline_mask) { if !view_mask.intersects(outline_mask) {
continue; // Layer not enabled continue; // Layer not enabled
} }
if volume_flags.depth_mode == DepthMode::Invalid { let Some(mesh) = render_meshes.get(&outline.mesh) else {
continue; // DepthMode not propagated
}
let Some(mesh) = render_meshes.get(mesh_handle) else {
continue; // No mesh continue; // No mesh
}; };
let transparent = fragment_uniform.colour[3] < 1.0; let transparent = fragment_uniform.colour[3] < 1.0;
@ -157,7 +146,7 @@ pub(crate) fn queue_outline_volume_mesh(
} else { } else {
PassType::Opaque PassType::Opaque
}) })
.with_depth_mode(volume_flags.depth_mode) .with_depth_mode(outline.depth_mode)
.with_offset_zero(volume_uniform.offset == 0.0) .with_offset_zero(volume_uniform.offset == 0.0)
.with_hdr_format(view.hdr) .with_hdr_format(view.hdr)
.with_morph_targets(mesh.morph_targets.is_some()); .with_morph_targets(mesh.morph_targets.is_some());

View File

@ -3,17 +3,19 @@
//! //!
//! Outlines are rendered in a seperate pass following the main 3D pass. The effect of this //! 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 //! 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 [`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 //! 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 //! of an object from being filled it. This must be added to any entity which needs to appear on
//! top of an outline. //! 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 //! The [`OutlineBundle`] and [`OutlineStencilBundle`] bundles can be used to add the right
//! components, including the required [`ComputedOutlineDepth`] component. Optionally, the //! components, including the required [`ComputedOutline`] component. Outlines can be inherited
//! [`SetOutlineDepth`] and [`InheritOutlineDepth`] components may also be added to control the //! from the parent via the [`InheritOutline`] component and [`InheritOutlineBundle`].
//! depth ordering of outlines.
//! //!
//! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual //! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual
//! artefacts when outlining meshes with hard edges, see the //! 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_graph::RenderGraph;
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}; use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat}; 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::render::{Render, RenderApp, RenderSet};
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use interpolation::Lerp; use interpolation::Lerp;
@ -40,9 +42,8 @@ use crate::draw::{
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline}; use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
use crate::pipeline::{OutlinePipeline, FRAGMENT_SHADER_HANDLE, OUTLINE_SHADER_HANDLE}; use crate::pipeline::{OutlinePipeline, FRAGMENT_SHADER_HANDLE, OUTLINE_SHADER_HANDLE};
use crate::uniforms::{ use crate::uniforms::{
extract_outline_stencil_uniforms, extract_outline_volume_uniforms, extract_outline_uniforms, queue_outline_stencil_bind_group, queue_outline_volume_bind_group,
queue_outline_stencil_bind_group, queue_outline_volume_bind_group, OutlineFragmentUniform, set_outline_visibility, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform,
OutlineStencilUniform, OutlineVolumeUniform,
}; };
use crate::view_uniforms::{ use crate::view_uniforms::{
extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform, extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform,
@ -148,7 +149,7 @@ impl ExtractComponent for OutlineRenderLayers {
Option<&'static OutlineRenderLayers>, Option<&'static OutlineRenderLayers>,
Option<&'static RenderLayers>, Option<&'static RenderLayers>,
); );
type Filter = Or<(With<OutlineVolume>, With<OutlineStencil>)>; type Filter = With<ComputedOutline>;
type Out = Self; type Out = Self;
fn extract_component( 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. /// A bundle for rendering stenciled outlines around meshes.
#[derive(Bundle, Clone, Default)] #[derive(Bundle, Clone, Default)]
pub struct OutlineBundle { pub struct OutlineBundle {
pub outline: OutlineVolume, pub outline: OutlineVolume,
pub stencil: OutlineStencil, pub stencil: OutlineStencil,
pub plane: ComputedOutlineDepth, pub mode: OutlineMode,
pub computed: ComputedOutline,
} }
/// A bundle for stenciling meshes in the outlining pass. /// A bundle for stenciling meshes in the outlining pass.
#[derive(Bundle, Clone, Default)] #[derive(Bundle, Clone, Default)]
pub struct OutlineStencilBundle { pub struct OutlineStencilBundle {
pub stencil: OutlineStencil, 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. /// Adds support for rendering outlines.
@ -205,7 +238,10 @@ impl Plugin for OutlinePlugin {
)) ))
.add_systems( .add_systems(
PostUpdate, PostUpdate,
compute_outline_depth.after(TransformSystem::TransformPropagate), (
compute_outline.after(TransformSystem::TransformPropagate),
set_outline_visibility.in_set(VisibilitySystems::CheckVisibility),
),
) )
.sub_app_mut(RenderApp) .sub_app_mut(RenderApp)
.init_resource::<DrawFunctions<StencilOutline>>() .init_resource::<DrawFunctions<StencilOutline>>()
@ -215,35 +251,31 @@ impl Plugin for OutlinePlugin {
.add_render_command::<StencilOutline, DrawStencil>() .add_render_command::<StencilOutline, DrawStencil>()
.add_render_command::<OpaqueOutline, DrawOutline>() .add_render_command::<OpaqueOutline, DrawOutline>()
.add_render_command::<TransparentOutline, 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( .add_systems(
Render, ExtractSchedule,
sort_phase_system::<StencilOutline>.in_set(RenderSet::PhaseSort), (extract_outline_uniforms, extract_outline_view_uniforms),
) )
.add_systems( .add_systems(
Render, Render,
sort_phase_system::<OpaqueOutline>.in_set(RenderSet::PhaseSort), (
sort_phase_system::<OpaqueOutline>,
sort_phase_system::<TransparentOutline>,
)
.in_set(RenderSet::PhaseSort),
) )
.add_systems( .add_systems(
Render, 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( .add_systems(
Render, Render,
queue_outline_view_bind_group.in_set(RenderSet::Queue), (queue_outline_stencil_mesh, queue_outline_volume_mesh).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));
let world = &mut app.sub_app_mut(RenderApp).world; let world = &mut app.sub_app_mut(RenderApp).world;
let node = OutlineNode::new(world); let node = OutlineNode::new(world);

View File

@ -13,10 +13,13 @@ use bevy::{
}, },
}; };
use crate::{ use crate::{node::StencilOutline, pipeline::OutlinePipeline, ComputedOutline};
node::StencilOutline, pipeline::OutlinePipeline, ComputedOutlineDepth, OutlineStencil,
OutlineVolume, #[derive(Component)]
}; pub(crate) struct ExtractedOutline {
pub depth_mode: DepthMode,
pub mesh: Handle<Mesh>,
}
#[derive(Clone, Component, ShaderType)] #[derive(Clone, Component, ShaderType)]
pub(crate) struct OutlineStencilUniform { pub(crate) struct OutlineStencilUniform {
@ -38,30 +41,12 @@ pub(crate) struct OutlineFragmentUniform {
pub colour: Vec4, pub colour: Vec4,
} }
#[derive(Clone, Copy, Default, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub(crate) enum DepthMode { pub(crate) enum DepthMode {
#[default]
Invalid = 0,
Flat = 1, Flat = 1,
Real = 2, 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)] #[derive(Resource)]
pub(crate) struct OutlineStencilBindGroup { pub(crate) struct OutlineStencilBindGroup {
pub bind_group: BindGroup, pub bind_group: BindGroup,
@ -72,47 +57,47 @@ pub(crate) struct OutlineVolumeBindGroup {
pub bind_group: BindGroup, pub bind_group: BindGroup,
} }
pub(crate) fn extract_outline_stencil_uniforms( pub(crate) fn set_outline_visibility(
mut commands: Commands, mut query: Query<(&mut ComputedVisibility, &ComputedOutline)>,
query: Extract<Query<(Entity, &OutlineStencil, &ComputedOutlineDepth)>>,
) { ) {
for (entity, stencil, computed) in query.iter() { for (mut visibility, computed) in query.iter_mut() {
if !stencil.enabled { if let ComputedOutline(Some(computed)) = computed {
continue; 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, mut commands: Commands,
query: Extract<Query<(Entity, &OutlineVolume, &ComputedOutlineDepth)>>, query: Extract<Query<(Entity, &ComputedOutline, &Handle<Mesh>)>>,
) { ) {
for (entity, outline, computed) in query.iter() { for (entity, computed, mesh) in query.iter() {
if !outline.visible || outline.colour.a() == 0.0 { let cmds = &mut commands.get_or_spawn(entity);
continue; if let ComputedOutline(Some(computed)) = computed {
} cmds.insert(ExtractedOutline {
commands depth_mode: computed.mode.value.depth_mode,
.get_or_spawn(entity) mesh: mesh.clone_weak(),
.insert(OutlineVolumeUniform { });
origin: computed.world_origin, if computed.volume.value.enabled {
offset: outline.width, cmds.insert(OutlineVolumeUniform {
origin: computed.mode.value.world_origin,
offset: computed.volume.value.offset,
}) })
.insert(OutlineFragmentUniform { .insert(OutlineFragmentUniform {
colour: outline.colour.as_linear_rgba_f32().into(), colour: computed.volume.value.colour,
})
.insert(OutlineVolumeFlags {
depth_mode: computed.depth_mode,
}); });
} }
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( pub(crate) fn queue_outline_stencil_bind_group(