bevy_mod_outline/src/lib.rs

287 lines
9.8 KiB
Rust

//! This crate provides a Bevy plugin, [`OutlinePlugin`], and associated components for
//! rendering outlines around meshes using the vertex extrusion method.
//!
//! 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.
//!
//! 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 [`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.
//!
//! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual
//! artefacts when outlining meshes with hard edges, see the
//! [`OutlineMeshExt::generate_outline_normals`] function and the
//! [`AutoGenerateOutlineNormalsPlugin`].
use bevy::asset::load_internal_asset;
use bevy::ecs::query::QueryItem;
use bevy::prelude::*;
use bevy::render::extract_component::{
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
};
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::{Render, RenderApp, RenderSet};
use bevy::transform::TransformSystem;
use interpolation::Lerp;
use crate::draw::{
queue_outline_stencil_mesh, queue_outline_volume_mesh, DrawOutline, DrawStencil,
};
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,
};
use crate::view_uniforms::{
extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform,
};
mod computed;
mod draw;
mod generate;
mod node;
mod pipeline;
mod uniforms;
mod view_uniforms;
pub use computed::*;
pub use generate::*;
// See https://alexanderameye.github.io/notes/rendering-outlines/
/// The direction to extrude the vertex when rendering the outline.
pub const ATTRIBUTE_OUTLINE_NORMAL: MeshVertexAttribute =
MeshVertexAttribute::new("Outline_Normal", 1585570526, VertexFormat::Float32x3);
/// Name of the render graph node which draws the outlines.
///
/// This node runs after the main 3D passes and before the UI pass. The name can be used to
/// add additional constraints on node execution order with respect to other passes.
pub const OUTLINE_PASS_NODE_NAME: &str = "bevy_mod_outline_node";
/// A component for stenciling meshes during outline rendering.
#[derive(Clone, Component)]
pub struct OutlineStencil {
/// Enable rendering of the stencil
pub enabled: bool,
/// Offset of the stencil in logical pixels
pub offset: f32,
}
impl Default for OutlineStencil {
fn default() -> Self {
OutlineStencil {
enabled: true,
offset: 0.0,
}
}
}
impl ExtractComponent for OutlineStencil {
type Query = &'static OutlineStencil;
type Filter = ();
type Out = Self;
fn extract_component(item: QueryItem<Self::Query>) -> Option<Self> {
Some(item.clone())
}
}
fn lerp_bool(this: bool, other: bool, scalar: f32) -> bool {
if scalar <= 0.0 {
this
} else if scalar >= 1.0 {
other
} else {
this | other
}
}
impl Lerp for OutlineStencil {
type Scalar = f32;
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
OutlineStencil {
enabled: lerp_bool(self.enabled, other.enabled, *scalar),
offset: self.offset.lerp(&other.offset, scalar),
}
}
}
/// A component for rendering outlines around meshes.
#[derive(Clone, Component, Default)]
pub struct OutlineVolume {
/// Enable rendering of the outline
pub visible: bool,
/// Width of the outline in logical pixels
pub width: f32,
/// Colour of the outline
pub colour: Color,
}
impl Lerp for OutlineVolume {
type Scalar = f32;
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
OutlineVolume {
visible: lerp_bool(self.visible, other.visible, *scalar),
width: self.width.lerp(&other.width, scalar),
colour: {
let [r, g, b, a] = self
.colour
.as_linear_rgba_f32()
.lerp(&other.colour.as_linear_rgba_f32(), scalar);
Color::rgba_linear(r, g, b, a)
},
}
}
}
/// A component for specifying what layer(s) the outline should be rendered for.
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Default)]
pub struct OutlineRenderLayers(pub RenderLayers);
impl ExtractComponent for OutlineRenderLayers {
type Query = (
Option<&'static OutlineRenderLayers>,
Option<&'static RenderLayers>,
);
type Filter = Or<(With<OutlineVolume>, With<OutlineStencil>)>;
type Out = Self;
fn extract_component(
(outline_mask, object_mask): (Option<&OutlineRenderLayers>, Option<&RenderLayers>),
) -> Option<Self> {
Some(
outline_mask
.copied()
.or_else(|| object_mask.copied().map(OutlineRenderLayers))
.unwrap_or_default(),
)
}
}
/// A bundle for rendering stenciled outlines around meshes.
#[derive(Bundle, Clone, Default)]
pub struct OutlineBundle {
pub outline: OutlineVolume,
pub stencil: OutlineStencil,
pub plane: ComputedOutlineDepth,
}
/// A bundle for stenciling meshes in the outlining pass.
#[derive(Bundle, Clone, Default)]
pub struct OutlineStencilBundle {
pub stencil: OutlineStencil,
pub plane: ComputedOutlineDepth,
}
/// Adds support for rendering outlines.
pub struct OutlinePlugin;
impl Plugin for OutlinePlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
OUTLINE_SHADER_HANDLE,
"outline.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
FRAGMENT_SHADER_HANDLE,
"fragment.wgsl",
Shader::from_wgsl
);
app.add_plugins((
ExtractComponentPlugin::<OutlineStencil>::extract_visible(),
ExtractComponentPlugin::<OutlineRenderLayers>::default(),
UniformComponentPlugin::<OutlineStencilUniform>::default(),
UniformComponentPlugin::<OutlineVolumeUniform>::default(),
UniformComponentPlugin::<OutlineFragmentUniform>::default(),
UniformComponentPlugin::<OutlineViewUniform>::default(),
))
.add_systems(
PostUpdate,
compute_outline_depth.after(TransformSystem::TransformPropagate),
)
.sub_app_mut(RenderApp)
.init_resource::<DrawFunctions<StencilOutline>>()
.init_resource::<DrawFunctions<OpaqueOutline>>()
.init_resource::<DrawFunctions<TransparentOutline>>()
.init_resource::<SpecializedMeshPipelines<OutlinePipeline>>()
.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),
)
.add_systems(
Render,
sort_phase_system::<OpaqueOutline>.in_set(RenderSet::PhaseSort),
)
.add_systems(
Render,
sort_phase_system::<TransparentOutline>.in_set(RenderSet::PhaseSort),
)
.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));
let world = &mut app.sub_app_mut(RenderApp).world;
let node = OutlineNode::new(world);
let mut graph = world.resource_mut::<RenderGraph>();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy::core_pipeline::core_3d::graph::NAME)
.unwrap();
draw_3d_graph.add_node(OUTLINE_PASS_NODE_NAME, node);
// Run after main 3D pass, but before UI psss
draw_3d_graph.add_node_edge(
bevy::core_pipeline::core_3d::graph::node::END_MAIN_PASS,
OUTLINE_PASS_NODE_NAME,
);
#[cfg(feature = "bevy_ui")]
draw_3d_graph.add_node_edge(
OUTLINE_PASS_NODE_NAME,
bevy::ui::draw_ui_graph::node::UI_PASS,
);
}
fn finish(&self, app: &mut App) {
app.sub_app_mut(RenderApp)
.init_resource::<OutlinePipeline>();
}
}