Add ComputedOutlinePlane and an example.

This commit is contained in:
Robin KAY 2022-11-17 00:25:54 +00:00
parent 6781476bf0
commit e1c845c434
11 changed files with 339 additions and 31 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_mod_outline"
version = "0.2.4"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "A mesh outlining plugin for Bevy."
@ -26,14 +26,17 @@ bevy = { version = "0.8", default-features = false, features = [
] }
[features]
default = ["align16", "bevy_ui"]
align16 = []
default = ["bevy_ui"]
bevy_ui = ["bevy/bevy_ui", "bevy/bevy_sprite", "bevy/bevy_text"]
[[example]]
name = "shapes"
path = "examples/shapes.rs"
[[example]]
name = "pieces"
path = "examples/pieces.rs"
[[example]]
name = "animated_fox"
path = "examples/animated_fox.rs"

139
examples/pieces.rs Normal file
View File

@ -0,0 +1,139 @@
use std::f32::consts::TAU;
use bevy::{
prelude::{
shape::{Capsule, Torus, UVSphere},
*,
},
window::close_on_esc,
};
use bevy_mod_outline::*;
#[bevy_main]
fn main() {
App::new()
.insert_resource(Msaa { samples: 4 })
.insert_resource(ClearColor(Color::BLACK))
.add_plugins(DefaultPlugins)
.add_plugin(OutlinePlugin)
.add_startup_system(setup)
.add_system(close_on_esc)
.add_system(rotates)
.run();
}
#[derive(Component)]
struct Rotates;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Add sphere with child meshes sticking out of it
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(
UVSphere {
radius: 0.75,
sectors: 30,
stacks: 30,
}
.into(),
),
material: materials.add(Color::rgb(0.9, 0.1, 0.1).into()),
transform: Transform::from_translation(Vec3::new(0.0, 1.0, 0.0)),
..default()
})
.insert_bundle(OutlineBundle {
outline: Outline {
visible: true,
colour: Color::WHITE,
width: 10.0,
},
..default()
})
.insert(Rotates)
.with_children(|parent| {
parent
.spawn_bundle(PbrBundle {
mesh: meshes.add(
Capsule {
radius: 0.2,
rings: 15,
depth: 1.0,
latitudes: 15,
longitudes: 15,
..Default::default()
}
.into(),
),
material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()),
transform: Transform::from_rotation(Quat::from_axis_angle(Vec3::X, TAU / 4.0))
.with_translation(Vec3::new(0.0, 0.0, 0.75)),
..default()
})
.insert_bundle(OutlineBundle {
outline: Outline {
visible: true,
colour: Color::WHITE,
width: 10.0,
},
..default()
})
.insert(InheritOutlinePlane);
parent
.spawn_bundle(PbrBundle {
mesh: meshes.add(
Torus {
radius: 0.5,
ring_radius: 0.1,
subdivisions_segments: 30,
subdivisions_sides: 15,
}
.into(),
),
material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()),
transform: Transform::from_rotation(Quat::from_axis_angle(Vec3::Z, TAU / 4.0))
.with_translation(Vec3::new(0.0, 0.0, -0.75)),
..default()
})
.insert_bundle(OutlineBundle {
outline: Outline {
visible: true,
colour: Color::WHITE,
width: 10.0,
},
..default()
})
.insert(InheritOutlinePlane);
});
// Add plane, light source, and camera
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(bevy::prelude::shape::Plane { size: 5.0 })),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
commands.spawn_bundle(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
commands.spawn_bundle(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}
fn rotates(mut query: Query<&mut Transform, With<Rotates>>, timer: Res<Time>) {
for mut transform in query.iter_mut() {
transform.rotate_axis(Vec3::Y, 0.75 * timer.delta_seconds());
}
}

View File

@ -5,17 +5,10 @@
@group(1) @binding(0)
var<uniform> mesh: Mesh;
#ifdef SKINNED
@group(1) @binding(1)
var<uniform> joint_matrices: SkinnedMesh;
#import bevy_pbr::skinning
#endif
fn model_origin_z(model: mat4x4<f32>, view_proj: mat4x4<f32>) -> f32 {
var origin = model[3];
fn model_origin_z(plane: vec3<f32>, view_proj: mat4x4<f32>) -> f32 {
var proj_zw = mat4x2<f32>(
view_proj[0].zw, view_proj[1].zw,
view_proj[2].zw, view_proj[3].zw);
var zw = proj_zw * origin;
var zw = proj_zw * vec4<f32>(plane, 1.0);
return zw.x / zw.y;
}

71
src/computed.rs Normal file
View File

@ -0,0 +1,71 @@
use bevy::prelude::*;
/// A component for storing the computed plane on which the outline lies.
#[derive(Clone, Component, Default)]
pub struct ComputedOutlinePlane {
pub(crate) plane: Vec3,
}
/// A component which specifies that this entity lies on the same plane as its parent.
#[derive(Clone, Component, Default)]
pub struct InheritOutlinePlane;
#[allow(clippy::type_complexity)]
pub(crate) fn compute_outline_plane(
mut root_query: Query<
(
&mut ComputedOutlinePlane,
&GlobalTransform,
Changed<GlobalTransform>,
Option<(&Children, Changed<Children>)>,
),
Without<InheritOutlinePlane>,
>,
mut computed_query: Query<(&mut ComputedOutlinePlane, Changed<InheritOutlinePlane>)>,
child_query: Query<(&Children, Changed<Children>)>,
) {
for (mut computed, transform, changed_transform, children) in root_query.iter_mut() {
if changed_transform {
let matrix = transform.compute_matrix();
computed.plane = matrix.project_point3(Vec3::ZERO);
}
if let Some((cs, changed_children)) = children {
let changed2 = changed_children || changed_transform;
for child in cs.iter() {
propagate_outline_planes(
&computed,
changed2,
*child,
&mut computed_query,
&child_query,
);
}
}
}
}
fn propagate_outline_planes(
root_computed: &ComputedOutlinePlane,
changed: bool,
entity: Entity,
computed_query: &mut Query<(&mut ComputedOutlinePlane, Changed<InheritOutlinePlane>)>,
child_query: &Query<(&Children, Changed<Children>)>,
) {
if let Ok((mut computed, changed_inherit)) = computed_query.get_mut(entity) {
if changed_inherit || changed {
*computed = root_computed.clone();
}
if let Ok((cs, changed_children)) = child_query.get(entity) {
let changed2 = changed_children || changed_inherit || changed;
for child in cs.iter() {
propagate_outline_planes(
root_computed,
changed2,
*child,
computed_query,
child_query,
);
}
}
}
}

View File

@ -7,7 +7,7 @@ use bevy::render::view::ExtractedView;
use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline};
use crate::pipeline::{OutlinePipeline, PassType};
use crate::uniforms::{OutlineFragmentUniform, SetOutlineBindGroup};
use crate::uniforms::{OutlineFragmentUniform, SetOutlineBindGroup, SetOutlineStencilBindGroup};
use crate::view_uniforms::SetOutlineViewBindGroup;
use crate::OutlineStencil;
@ -15,6 +15,7 @@ pub type DrawStencil = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshBindGroup<1>,
SetOutlineStencilBindGroup<2>,
DrawMesh,
);

View File

@ -26,6 +26,7 @@ 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::{RenderApp, RenderStage};
use bevy::transform::TransformSystem;
use crate::draw::{queue_outline_mesh, queue_outline_stencil_mesh, DrawOutline, DrawStencil};
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
@ -33,13 +34,15 @@ use crate::pipeline::{
OutlinePipeline, COMMON_SHADER_HANDLE, OUTLINE_SHADER_HANDLE, STENCIL_SHADER_HANDLE,
};
use crate::uniforms::{
extract_outline_uniforms, queue_outline_bind_group, OutlineFragmentUniform,
extract_outline_stencil_uniforms, extract_outline_uniforms, queue_outline_bind_group,
queue_outline_stencil_bind_group, OutlineFragmentUniform, OutlineStencilUniform,
OutlineVertexUniform,
};
use crate::view_uniforms::{
extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform,
};
mod computed;
mod draw;
mod generate;
mod node;
@ -47,6 +50,7 @@ mod pipeline;
mod uniforms;
mod view_uniforms;
pub use computed::*;
pub use generate::*;
// See https://alexanderameye.github.io/notes/rendering-outlines/
@ -90,6 +94,14 @@ pub struct Outline {
pub struct OutlineBundle {
pub outline: Outline,
pub stencil: OutlineStencil,
pub plane: ComputedOutlinePlane,
}
/// A bundle for stenciling meshes in the outlining pass.
#[derive(Bundle, Clone, Default)]
pub struct OutlineStencilBundle {
pub stencil: OutlineStencil,
pub plane: ComputedOutlinePlane,
}
/// Adds support for rendering outlines.
@ -112,9 +124,14 @@ impl Plugin for OutlinePlugin {
);
app.add_plugin(ExtractComponentPlugin::<OutlineStencil>::extract_visible())
.add_plugin(UniformComponentPlugin::<OutlineStencilUniform>::default())
.add_plugin(UniformComponentPlugin::<OutlineVertexUniform>::default())
.add_plugin(UniformComponentPlugin::<OutlineFragmentUniform>::default())
.add_plugin(UniformComponentPlugin::<OutlineViewUniform>::default())
.add_system_to_stage(
CoreStage::PostUpdate,
compute_outline_plane.after(TransformSystem::TransformPropagate),
)
.sub_app_mut(RenderApp)
.init_resource::<DrawFunctions<StencilOutline>>()
.init_resource::<DrawFunctions<OpaqueOutline>>()
@ -125,6 +142,7 @@ impl Plugin for OutlinePlugin {
.add_render_command::<OpaqueOutline, DrawOutline>()
.add_render_command::<TransparentOutline, DrawOutline>()
.add_system_to_stage(RenderStage::Extract, extract_outline_view_uniforms)
.add_system_to_stage(RenderStage::Extract, extract_outline_stencil_uniforms)
.add_system_to_stage(RenderStage::Extract, extract_outline_uniforms)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<StencilOutline>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<OpaqueOutline>)
@ -133,6 +151,7 @@ impl Plugin for OutlinePlugin {
sort_phase_system::<TransparentOutline>,
)
.add_system_to_stage(RenderStage::Queue, queue_outline_view_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_outline_stencil_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_outline_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_outline_stencil_mesh)
.add_system_to_stage(RenderStage::Queue, queue_outline_mesh);

View File

@ -17,16 +17,13 @@ struct OutlineViewUniform {
};
struct OutlineVertexUniform {
#ifdef ALIGN_16
@align(16)
#endif
plane: vec3<f32>,
width: f32,
};
struct OutlineFragmentUniform {
#ifdef ALIGN_16
@align(16)
#endif
colour: vec4<f32>,
};
@ -56,7 +53,7 @@ fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
var clip_norm = mat4to3(view.view_proj) * (mat4to3(model) * vertex.normal);
var ndc_pos = clip_pos.xy / clip_pos.w;
var ndc_delta = vstage.width * normalize(clip_norm.xy) * view_uniform.scale;
return vec4<f32>(ndc_pos + ndc_delta, model_origin_z(mesh.model, view.view_proj), 1.0);
return vec4<f32>(ndc_pos + ndc_delta, model_origin_z(vstage.plane, view.view_proj), 1.0);
}
@fragment

View File

@ -20,7 +20,7 @@ use bevy::{
},
};
use crate::uniforms::{OutlineFragmentUniform, OutlineVertexUniform};
use crate::uniforms::{OutlineFragmentUniform, OutlineStencilUniform, OutlineVertexUniform};
use crate::view_uniforms::OutlineViewUniform;
use crate::ATTRIBUTE_OUTLINE_NORMAL;
@ -43,6 +43,7 @@ pub enum PassType {
pub struct OutlinePipeline {
mesh_pipeline: MeshPipeline,
pub outline_view_bind_group_layout: BindGroupLayout,
pub outline_stencil_bind_group_layout: BindGroupLayout,
pub outline_bind_group_layout: BindGroupLayout,
}
@ -95,9 +96,24 @@ impl FromWorld for OutlinePipeline {
},
],
});
let outline_stencil_bind_group_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("outline_stencil_bind_group_layout"),
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(OutlineStencilUniform::SHADER_SIZE.get()),
},
count: None,
}],
});
OutlinePipeline {
mesh_pipeline,
outline_view_bind_group_layout,
outline_stencil_bind_group_layout,
outline_bind_group_layout,
}
}
@ -114,11 +130,7 @@ impl SpecializedMeshPipeline for OutlinePipeline {
let mut targets = vec![];
let mut bind_layouts = vec![self.mesh_pipeline.view_layout.clone()];
let mut buffer_attrs = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)];
let mut shader_defs = if cfg!(feature = "align16") {
vec!["ALIGN_16".to_string()]
} else {
vec![]
};
let mut shader_defs = vec![];
bind_layouts.push(
if mesh_layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
&& mesh_layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
@ -135,6 +147,7 @@ impl SpecializedMeshPipeline for OutlinePipeline {
match pass_type {
PassType::Stencil => {
shader = STENCIL_SHADER_HANDLE;
bind_layouts.push(self.outline_stencil_bind_group_layout.clone());
}
PassType::Opaque | PassType::Transparent => {
shader = OUTLINE_SHADER_HANDLE;

View File

@ -8,6 +8,14 @@ struct VertexInput {
#endif
};
struct OutlineStencilUniform {
@align(16)
plane: vec3<f32>,
};
@group(2) @binding(0)
var<uniform> vstage: OutlineStencilUniform;
@vertex
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
#ifdef SKINNED
@ -17,7 +25,7 @@ fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
#endif
var clip_pos = view.view_proj * (model * vec4<f32>(vertex.position, 1.0));
var ndc_pos = clip_pos.xy / clip_pos.w;
return vec4<f32>(ndc_pos, model_origin_z(mesh.model, view.view_proj), 1.0);
return vec4<f32>(ndc_pos, model_origin_z(vstage.plane, view.view_proj), 1.0);
}
@fragment

View File

@ -13,11 +13,18 @@ use bevy::{
},
};
use crate::{pipeline::OutlinePipeline, Outline};
use crate::{pipeline::OutlinePipeline, ComputedOutlinePlane, Outline, OutlineStencil};
#[derive(Clone, Component, ShaderType)]
pub struct OutlineStencilUniform {
#[cfg_attr(feature = "align16", align(16))]
pub plane: Vec3,
}
#[derive(Clone, Component, ShaderType)]
pub struct OutlineVertexUniform {
#[cfg_attr(feature = "align16", align(16))]
#[align(16)]
pub plane: Vec3,
pub width: f32,
}
@ -27,12 +34,30 @@ pub struct OutlineFragmentUniform {
pub colour: Vec4,
}
pub struct OutlineStencilBindGroup {
pub bind_group: BindGroup,
}
pub struct OutlineBindGroup {
pub bind_group: BindGroup,
}
pub fn extract_outline_uniforms(mut commands: Commands, query: Extract<Query<(Entity, &Outline)>>) {
for (entity, outline) in query.iter() {
pub fn extract_outline_stencil_uniforms(
mut commands: Commands,
query: Extract<Query<(Entity, &ComputedOutlinePlane), With<OutlineStencil>>>,
) {
for (entity, computed) in query.iter() {
commands.get_or_spawn(entity).insert(OutlineStencilUniform {
plane: computed.plane,
});
}
}
pub fn extract_outline_uniforms(
mut commands: Commands,
query: Extract<Query<(Entity, &Outline, &ComputedOutlinePlane)>>,
) {
for (entity, outline, computed) in query.iter() {
if !outline.visible || outline.colour.a() == 0.0 {
continue;
}
@ -40,6 +65,7 @@ pub fn extract_outline_uniforms(mut commands: Commands, query: Extract<Query<(En
.get_or_spawn(entity)
.insert(OutlineVertexUniform {
width: outline.width,
plane: computed.plane,
})
.insert(OutlineFragmentUniform {
colour: outline.colour.as_linear_rgba_f32().into(),
@ -47,6 +73,25 @@ pub fn extract_outline_uniforms(mut commands: Commands, query: Extract<Query<(En
}
}
pub fn queue_outline_stencil_bind_group(
mut commands: Commands,
render_device: Res<RenderDevice>,
outline_pipeline: Res<OutlinePipeline>,
vertex: Res<ComponentUniforms<OutlineStencilUniform>>,
) {
if let Some(vertex_binding) = vertex.binding() {
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: vertex_binding.clone(),
}],
label: Some("outline_stencil_bind_group"),
layout: &outline_pipeline.outline_stencil_bind_group_layout,
});
commands.insert_resource(OutlineStencilBindGroup { bind_group });
}
}
pub fn queue_outline_bind_group(
mut commands: Commands,
render_device: Res<RenderDevice>,
@ -73,6 +118,25 @@ pub fn queue_outline_bind_group(
}
}
pub struct SetOutlineStencilBindGroup<const I: usize>();
impl<const I: usize> EntityRenderCommand for SetOutlineStencilBindGroup<I> {
type Param = (
SRes<OutlineStencilBindGroup>,
SQuery<Read<DynamicUniformIndex<OutlineStencilUniform>>>,
);
fn render<'w>(
_view: Entity,
item: Entity,
(bind_group, query): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let vertex = query.get(item).unwrap();
pass.set_bind_group(I, &bind_group.into_inner().bind_group, &[vertex.index()]);
RenderCommandResult::Success
}
}
pub struct SetOutlineBindGroup<const I: usize>();
impl<const I: usize> EntityRenderCommand for SetOutlineBindGroup<I> {

View File

@ -15,7 +15,7 @@ use crate::pipeline::OutlinePipeline;
#[derive(Clone, Component, ShaderType)]
pub struct OutlineViewUniform {
#[cfg_attr(feature = "align16", align(16))]
#[align(16)]
scale: Vec2,
}