Move outline rendering to dedicated pass.
This commit is contained in:
parent
c971bddb1f
commit
2aa76309dd
|
@ -15,7 +15,6 @@ bevy = { version = "0.8", default-features = false, features = [
|
|||
"bevy_asset",
|
||||
"render",
|
||||
] }
|
||||
libm = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
bevy = { version = "0.8", default-features = false, features = [
|
||||
|
@ -24,5 +23,5 @@ bevy = { version = "0.8", default-features = false, features = [
|
|||
] }
|
||||
|
||||
[[example]]
|
||||
name = "torus"
|
||||
path = "examples/torus.rs"
|
||||
name = "shapes"
|
||||
path = "examples/shapes.rs"
|
|
@ -1,4 +1,4 @@
|
|||
use std::f32::consts::TAU;
|
||||
use std::f32::consts::{PI, TAU};
|
||||
|
||||
use bevy::prelude::{shape::Torus, *};
|
||||
|
||||
|
@ -12,20 +12,22 @@ fn main() {
|
|||
.add_plugins(DefaultPlugins)
|
||||
.add_plugin(OutlinePlugin)
|
||||
.add_startup_system(setup)
|
||||
.add_system(rotate_cube)
|
||||
.add_system(wobble)
|
||||
.add_system(orbit)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct TheCube();
|
||||
struct Wobbles;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Orbits;
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut outlines: ResMut<Assets<Outline>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// Spawn cube et al.
|
||||
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()),
|
||||
|
@ -43,11 +45,31 @@ fn setup(
|
|||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
..default()
|
||||
})
|
||||
.insert(outlines.add(Outline {
|
||||
colour: Color::rgba(0.0, 1.0, 0.0, 0.5),
|
||||
.insert(Outline {
|
||||
colour: Color::rgba(0.0, 1.0, 0.0, 1.0),
|
||||
width: 25.0,
|
||||
}))
|
||||
.insert(TheCube());
|
||||
})
|
||||
.insert(OutlineStencil)
|
||||
.insert(Wobbles);
|
||||
commands
|
||||
.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(Torus {
|
||||
radius: 0.3,
|
||||
ring_radius: 0.1,
|
||||
subdivisions_segments: 20,
|
||||
subdivisions_sides: 10,
|
||||
})),
|
||||
material: materials.add(Color::rgb(0.9, 0.1, 0.1).into()),
|
||||
transform: Transform::from_xyz(0.0, 1.2, 2.0)
|
||||
.with_rotation(Quat::from_rotation_x(0.5 * PI)),
|
||||
..default()
|
||||
})
|
||||
.insert(Outline {
|
||||
colour: Color::rgba(1.0, 0.0, 1.0, 0.3),
|
||||
width: 15.0,
|
||||
})
|
||||
.insert(OutlineStencil)
|
||||
.insert(Orbits);
|
||||
commands.spawn_bundle(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 1500.0,
|
||||
|
@ -63,17 +85,13 @@ fn setup(
|
|||
});
|
||||
}
|
||||
|
||||
fn rotate_cube(
|
||||
mut cubes: Query<&mut Transform, With<TheCube>>,
|
||||
timer: Res<Time>,
|
||||
mut t: Local<f32>,
|
||||
) {
|
||||
fn wobble(mut query: Query<&mut Transform, With<Wobbles>>, timer: Res<Time>, mut t: Local<f32>) {
|
||||
let ta = *t;
|
||||
*t = (ta + 0.5 * timer.delta_seconds()) % TAU;
|
||||
let tb = *t;
|
||||
let i1 = tb.cos() - ta.cos();
|
||||
let i2 = ta.sin() - tb.sin();
|
||||
for mut transform in cubes.iter_mut() {
|
||||
for mut transform in query.iter_mut() {
|
||||
transform.rotate(Quat::from_rotation_z(
|
||||
TAU * 20.0 * i1 * timer.delta_seconds(),
|
||||
));
|
||||
|
@ -82,3 +100,12 @@ fn rotate_cube(
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn orbit(mut query: Query<&mut Transform, With<Orbits>>, timer: Res<Time>) {
|
||||
for mut transform in query.iter_mut() {
|
||||
transform.translate_around(
|
||||
Vec3::ZERO,
|
||||
Quat::from_rotation_y(0.4 * timer.delta_seconds()),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#define_import_path bevy_mod_outline::common
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
#import bevy_pbr::mesh_types
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> mesh: Mesh;
|
||||
|
||||
fn model_origin_z() -> f32 {
|
||||
var origin = mesh.model[3];
|
||||
var proj_zw = mat4x2<f32>(
|
||||
view.view_proj[0].zw, view.view_proj[1].zw,
|
||||
view.view_proj[2].zw, view.view_proj[3].zw);
|
||||
var zw = proj_zw * origin;
|
||||
return zw.x / zw.y;
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
use bevy::pbr::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::render_asset::RenderAssets;
|
||||
use bevy::render::render_phase::{DrawFunctions, RenderPhase, SetItemPipeline};
|
||||
use bevy::render::render_resource::{PipelineCache, SpecializedMeshPipelines};
|
||||
use bevy::render::view::ExtractedView;
|
||||
|
||||
use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline};
|
||||
use crate::pipeline::{OutlinePipeline, PassType};
|
||||
use crate::uniforms::{OutlineFragmentUniform, SetOutlineBindGroup};
|
||||
use crate::view_uniforms::SetOutlineViewBindGroup;
|
||||
use crate::OutlineStencil;
|
||||
|
||||
pub type DrawStencil = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
DrawMesh,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_outline_stencil_mesh(
|
||||
stencil_draw_functions: Res<DrawFunctions<StencilOutline>>,
|
||||
stencil_pipeline: Res<OutlinePipeline>,
|
||||
msaa: Res<Msaa>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<OutlinePipeline>>,
|
||||
mut pipeline_cache: ResMut<PipelineCache>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
material_meshes: Query<(Entity, &MeshUniform, &Handle<Mesh>), With<OutlineStencil>>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<StencilOutline>)>,
|
||||
) {
|
||||
let draw_stencil = stencil_draw_functions
|
||||
.read()
|
||||
.get_id::<DrawStencil>()
|
||||
.unwrap();
|
||||
|
||||
let base_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
|
||||
|
||||
for (view, mut stencil_phase) in views.iter_mut() {
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key =
|
||||
base_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = pipelines
|
||||
.specialize(
|
||||
&mut pipeline_cache,
|
||||
&stencil_pipeline,
|
||||
(key, PassType::Stencil),
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
let distance = rangefinder.distance(&mesh_uniform.transform);
|
||||
stencil_phase.add(StencilOutline {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function: draw_stencil,
|
||||
distance,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DrawOutline = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
SetOutlineViewBindGroup<2>,
|
||||
SetOutlineBindGroup<3>,
|
||||
DrawMesh,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_outline_mesh(
|
||||
opaque_draw_functions: Res<DrawFunctions<OpaqueOutline>>,
|
||||
transparent_draw_functions: Res<DrawFunctions<TransparentOutline>>,
|
||||
outline_pipeline: Res<OutlinePipeline>,
|
||||
msaa: Res<Msaa>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<OutlinePipeline>>,
|
||||
mut pipeline_cache: ResMut<PipelineCache>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
material_meshes: Query<(Entity, &MeshUniform, &Handle<Mesh>, &OutlineFragmentUniform)>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
&mut RenderPhase<OpaqueOutline>,
|
||||
&mut RenderPhase<TransparentOutline>,
|
||||
)>,
|
||||
) {
|
||||
let draw_opaque_outline = opaque_draw_functions
|
||||
.read()
|
||||
.get_id::<DrawOutline>()
|
||||
.unwrap();
|
||||
let draw_transparent_outline = transparent_draw_functions
|
||||
.read()
|
||||
.get_id::<DrawOutline>()
|
||||
.unwrap();
|
||||
|
||||
let base_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
|
||||
|
||||
for (view, mut opaque_phase, mut transparent_phase) in views.iter_mut() {
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for (entity, mesh_uniform, mesh_handle, outline_fragment) in material_meshes.iter() {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let transparent = outline_fragment.colour[3] < 1.0;
|
||||
let pass_type;
|
||||
let key = base_key
|
||||
| MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
|
||||
| if transparent {
|
||||
pass_type = PassType::Transparent;
|
||||
MeshPipelineKey::TRANSPARENT_MAIN_PASS
|
||||
} else {
|
||||
pass_type = PassType::Opaque;
|
||||
MeshPipelineKey::NONE
|
||||
};
|
||||
let pipeline = pipelines
|
||||
.specialize(
|
||||
&mut pipeline_cache,
|
||||
&outline_pipeline,
|
||||
(key, pass_type),
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
let distance = rangefinder.distance(&mesh_uniform.transform);
|
||||
if transparent {
|
||||
transparent_phase.add(TransparentOutline {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function: draw_transparent_outline,
|
||||
distance,
|
||||
});
|
||||
} else {
|
||||
opaque_phase.add(OpaqueOutline {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function: draw_opaque_outline,
|
||||
distance,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
422
src/lib.rs
422
src/lib.rs
|
@ -1,356 +1,122 @@
|
|||
use bevy::asset::load_internal_asset;
|
||||
use bevy::core_pipeline::core_3d::{Opaque3d, Transparent3d};
|
||||
use bevy::ecs::system::lifetimeless::{Read, SQuery, SRes};
|
||||
use bevy::ecs::system::SystemParamItem;
|
||||
use bevy::pbr::{
|
||||
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup,
|
||||
};
|
||||
use bevy::ecs::query::QueryItem;
|
||||
use bevy::prelude::*;
|
||||
use bevy::reflect::TypeUuid;
|
||||
use bevy::render::extract_component::ExtractComponentPlugin;
|
||||
use bevy::render::mesh::{MeshVertexBufferLayout, PrimitiveTopology};
|
||||
use bevy::render::render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets};
|
||||
use bevy::render::render_phase::{
|
||||
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
|
||||
SetItemPipeline, TrackedRenderPass,
|
||||
use bevy::render::extract_component::{
|
||||
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
||||
};
|
||||
use bevy::render::render_resource::{
|
||||
AsBindGroup, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
|
||||
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize,
|
||||
DynamicUniformBuffer, Face, PipelineCache, PreparedBindGroup, RenderPipelineDescriptor,
|
||||
ShaderSize, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
||||
SpecializedMeshPipelines,
|
||||
use bevy::render::render_graph::RenderGraph;
|
||||
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
|
||||
use bevy::render::render_resource::SpecializedMeshPipelines;
|
||||
use bevy::render::{RenderApp, RenderStage};
|
||||
|
||||
use crate::draw::{queue_outline_mesh, queue_outline_stencil_mesh, DrawOutline, DrawStencil};
|
||||
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
|
||||
use crate::pipeline::{
|
||||
OutlinePipeline, COMMON_SHADER_HANDLE, OUTLINE_SHADER_HANDLE, STENCIL_SHADER_HANDLE,
|
||||
};
|
||||
use bevy::render::renderer::{RenderDevice, RenderQueue};
|
||||
use bevy::render::texture::FallbackImage;
|
||||
use bevy::render::view::ExtractedView;
|
||||
use bevy::render::{Extract, RenderApp, RenderStage};
|
||||
use libm::nextafterf;
|
||||
use crate::uniforms::{queue_outline_bind_group, OutlineFragmentUniform, OutlineVertexUniform};
|
||||
use crate::view_uniforms::{
|
||||
extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform,
|
||||
};
|
||||
|
||||
mod draw;
|
||||
mod node;
|
||||
mod pipeline;
|
||||
mod uniforms;
|
||||
mod view_uniforms;
|
||||
|
||||
// See https://alexanderameye.github.io/notes/rendering-outlines/
|
||||
|
||||
const OUTLINE_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2101625026478770097);
|
||||
/// A component for stenciling meshes during outline rendering
|
||||
#[derive(Component, Default)]
|
||||
pub struct OutlineStencil;
|
||||
|
||||
/// An asset for rendering outlines around meshes.
|
||||
#[derive(Clone, TypeUuid, AsBindGroup)]
|
||||
#[uuid = "552e416b-2766-4e6a-9ee5-9ebd0e8c0230"]
|
||||
impl ExtractComponent for OutlineStencil {
|
||||
type Query = ();
|
||||
type Filter = With<OutlineStencil>;
|
||||
|
||||
fn extract_component(_item: QueryItem<Self::Query>) -> Self {
|
||||
OutlineStencil
|
||||
}
|
||||
}
|
||||
|
||||
/// A component for rendering outlines around meshes.
|
||||
#[derive(Clone, Component)]
|
||||
pub struct Outline {
|
||||
/// Colour of the outline
|
||||
#[uniform(1, visibility(fragment))]
|
||||
pub colour: Color,
|
||||
/// Width of the outline in logical pixels
|
||||
#[uniform(0, visibility(vertex))]
|
||||
pub width: f32,
|
||||
/// Colour of the outline
|
||||
pub colour: Color,
|
||||
}
|
||||
|
||||
impl RenderAsset for Outline {
|
||||
type ExtractedAsset = Outline;
|
||||
|
||||
type PreparedAsset = GpuOutline;
|
||||
|
||||
type Param = (
|
||||
SRes<RenderDevice>,
|
||||
SRes<OutlinePipeline>,
|
||||
SRes<RenderAssets<Image>>,
|
||||
SRes<FallbackImage>,
|
||||
);
|
||||
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
outline: Self::ExtractedAsset,
|
||||
(render_device, outline_pipeline, images, fallback_image): &mut bevy::ecs::system::SystemParamItem<Self::Param>,
|
||||
) -> Result<
|
||||
Self::PreparedAsset,
|
||||
bevy::render::render_asset::PrepareAssetError<Self::ExtractedAsset>,
|
||||
> {
|
||||
if let Ok(pbg) = outline.as_bind_group(
|
||||
&outline_pipeline.outline_bind_group_layout,
|
||||
render_device,
|
||||
images,
|
||||
fallback_image,
|
||||
) {
|
||||
Ok(GpuOutline {
|
||||
bind_group: pbg,
|
||||
transparent: outline.colour.a() < 1.0,
|
||||
})
|
||||
} else {
|
||||
Err(PrepareAssetError::RetryNextUpdate(outline))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, ShaderType)]
|
||||
struct ViewSizeUniform {
|
||||
logical_size: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ViewSizeUniforms {
|
||||
pub uniforms: DynamicUniformBuffer<ViewSizeUniform>,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ViewSizeUniformOffset {
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
struct GpuViewSize {
|
||||
bind_group: BindGroup,
|
||||
}
|
||||
|
||||
pub struct GpuOutline {
|
||||
bind_group: PreparedBindGroup<Outline>,
|
||||
transparent: bool,
|
||||
}
|
||||
|
||||
/// Adds support for the [Outline] asset type.
|
||||
/// Adds support for rendering outlines.
|
||||
pub struct OutlinePlugin;
|
||||
|
||||
impl Plugin for OutlinePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, COMMON_SHADER_HANDLE, "common.wgsl", Shader::from_wgsl);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
STENCIL_SHADER_HANDLE,
|
||||
"stencil.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
OUTLINE_SHADER_HANDLE,
|
||||
"outline.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
app.add_asset::<Outline>()
|
||||
.add_plugin(ExtractComponentPlugin::<Handle<Outline>>::default())
|
||||
.add_plugin(RenderAssetPlugin::<Outline>::default())
|
||||
|
||||
app.add_plugin(ExtractComponentPlugin::<OutlineStencil>::extract_visible())
|
||||
.add_plugin(ExtractComponentPlugin::<OutlineVertexUniform>::default())
|
||||
.add_plugin(ExtractComponentPlugin::<OutlineFragmentUniform>::default())
|
||||
.add_plugin(UniformComponentPlugin::<OutlineVertexUniform>::default())
|
||||
.add_plugin(UniformComponentPlugin::<OutlineFragmentUniform>::default())
|
||||
.add_plugin(UniformComponentPlugin::<OutlineViewUniform>::default())
|
||||
.sub_app_mut(RenderApp)
|
||||
.add_render_command::<Opaque3d, DrawOutline>()
|
||||
.add_render_command::<Transparent3d, DrawOutline>()
|
||||
.init_resource::<DrawFunctions<StencilOutline>>()
|
||||
.init_resource::<DrawFunctions<OpaqueOutline>>()
|
||||
.init_resource::<DrawFunctions<TransparentOutline>>()
|
||||
.init_resource::<OutlinePipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<OutlinePipeline>>()
|
||||
.init_resource::<ViewSizeUniforms>()
|
||||
.add_system_to_stage(RenderStage::Extract, extract_view_size_uniforms)
|
||||
.add_system_to_stage(RenderStage::Prepare, prepare_view_size_uniforms)
|
||||
.add_system_to_stage(RenderStage::Queue, queue_outline);
|
||||
}
|
||||
}
|
||||
|
||||
type DrawOutline = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
SetViewSizeBindGroup<2>,
|
||||
SetOutlineBindGroup<3>,
|
||||
DrawMesh,
|
||||
);
|
||||
|
||||
struct SetViewSizeBindGroup<const I: usize>();
|
||||
|
||||
impl<const I: usize> EntityRenderCommand for SetViewSizeBindGroup<I> {
|
||||
type Param = (SRes<GpuViewSize>, SQuery<Read<ViewSizeUniformOffset>>);
|
||||
#[inline]
|
||||
fn render<'w>(
|
||||
view: Entity,
|
||||
_item: Entity,
|
||||
(gpu_view_size, offset_query): SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let uniform_offset = offset_query.get_inner(view).unwrap();
|
||||
pass.set_bind_group(
|
||||
I,
|
||||
&gpu_view_size.into_inner().bind_group,
|
||||
&[uniform_offset.offset],
|
||||
);
|
||||
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
struct SetOutlineBindGroup<const I: usize>();
|
||||
|
||||
impl<const I: usize> EntityRenderCommand for SetOutlineBindGroup<I> {
|
||||
type Param = (SRes<RenderAssets<Outline>>, SQuery<Read<Handle<Outline>>>);
|
||||
fn render<'w>(
|
||||
_view: Entity,
|
||||
item: Entity,
|
||||
(outlines, query): SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let outline_handle = query.get(item).unwrap();
|
||||
let outline = outlines.into_inner().get(outline_handle).unwrap();
|
||||
pass.set_bind_group(I, &outline.bind_group.bind_group, &[]);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_view_size_uniforms(
|
||||
mut commands: Commands,
|
||||
query: Extract<Query<(Entity, &Camera), With<Camera3d>>>,
|
||||
) {
|
||||
for (entity, camera) in query.iter() {
|
||||
if !camera.is_active {
|
||||
continue;
|
||||
}
|
||||
if let Some(size) = camera.logical_viewport_size() {
|
||||
commands
|
||||
.get_or_spawn(entity)
|
||||
.insert(ViewSizeUniform { logical_size: size });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_view_size_uniforms(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
outline_pipeline: Res<OutlinePipeline>,
|
||||
mut view_size_uniforms: ResMut<ViewSizeUniforms>,
|
||||
views: Query<(Entity, &ViewSizeUniform)>,
|
||||
) {
|
||||
view_size_uniforms.uniforms.clear();
|
||||
for (entity, view_size_uniform) in views.iter() {
|
||||
let view_size_uniforms = ViewSizeUniformOffset {
|
||||
offset: view_size_uniforms.uniforms.push(view_size_uniform.clone()),
|
||||
};
|
||||
commands.entity(entity).insert(view_size_uniforms);
|
||||
}
|
||||
|
||||
view_size_uniforms
|
||||
.uniforms
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
|
||||
if let Some(view_binding) = view_size_uniforms.uniforms.binding() {
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: view_binding.clone(),
|
||||
}],
|
||||
label: Some("outline_view_size_bind_group"),
|
||||
layout: &outline_pipeline.view_size_bind_group_layout,
|
||||
});
|
||||
commands.insert_resource(GpuViewSize { bind_group });
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn queue_outline(
|
||||
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
|
||||
outline_pipeline: Res<OutlinePipeline>,
|
||||
msaa: Res<Msaa>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<OutlinePipeline>>,
|
||||
mut pipeline_cache: ResMut<PipelineCache>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_outlines: Res<RenderAssets<Outline>>,
|
||||
material_meshes: Query<(Entity, &MeshUniform, &Handle<Mesh>, &Handle<Outline>)>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
&mut RenderPhase<Opaque3d>,
|
||||
&mut RenderPhase<Transparent3d>,
|
||||
)>,
|
||||
) {
|
||||
let draw_opaque_outline = opaque_3d_draw_functions
|
||||
.read()
|
||||
.get_id::<DrawOutline>()
|
||||
.unwrap();
|
||||
let draw_transparent_outline = transparent_3d_draw_functions
|
||||
.read()
|
||||
.get_id::<DrawOutline>()
|
||||
.unwrap();
|
||||
|
||||
let base_key = MeshPipelineKey::from_msaa_samples(msaa.samples)
|
||||
| MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList);
|
||||
|
||||
for (view, mut opaque_phase, mut transparent_phase) in views.iter_mut() {
|
||||
let inverse_view_matrix = view.transform.compute_matrix().inverse();
|
||||
let inverse_view_row_2 = inverse_view_matrix.row(2);
|
||||
for (entity, mesh_uniform, mesh_handle, outline_handle) in material_meshes.iter() {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
if let Some(outline) = render_outlines.get(outline_handle) {
|
||||
let key = if outline.transparent {
|
||||
base_key | MeshPipelineKey::TRANSPARENT_MAIN_PASS
|
||||
} else {
|
||||
base_key
|
||||
};
|
||||
let pipeline = pipelines
|
||||
.specialize(&mut pipeline_cache, &outline_pipeline, key, &mesh.layout)
|
||||
.unwrap();
|
||||
// Increase distance to just behind the non-outline mesh
|
||||
let distance = nextafterf(
|
||||
inverse_view_row_2.dot(mesh_uniform.transform.col(3)),
|
||||
f32::NEG_INFINITY,
|
||||
);
|
||||
if outline.transparent {
|
||||
transparent_phase.add(Transparent3d {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function: draw_transparent_outline,
|
||||
distance,
|
||||
});
|
||||
} else {
|
||||
opaque_phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function: draw_opaque_outline,
|
||||
distance: -distance,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutlinePipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
view_size_bind_group_layout: BindGroupLayout,
|
||||
outline_bind_group_layout: BindGroupLayout,
|
||||
}
|
||||
|
||||
impl FromWorld for OutlinePipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let world = world.cell();
|
||||
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap().clone();
|
||||
let render_device = world.get_resource::<RenderDevice>().unwrap();
|
||||
let view_size_bind_group_layout =
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
label: Some("outline_view_size_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(ViewSizeUniform::SHADER_SIZE.get()),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
let outline_bind_group_layout = Outline::bind_group_layout(&render_device);
|
||||
OutlinePipeline {
|
||||
mesh_pipeline,
|
||||
view_size_bind_group_layout,
|
||||
outline_bind_group_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for OutlinePipeline {
|
||||
type Key = MeshPipelineKey;
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
|
||||
descriptor.primitive.cull_mode = Some(Face::Front);
|
||||
descriptor.vertex.shader = OUTLINE_SHADER_HANDLE.typed();
|
||||
descriptor.fragment.as_mut().unwrap().shader = OUTLINE_SHADER_HANDLE.typed();
|
||||
descriptor.layout = Some(vec![
|
||||
self.mesh_pipeline.view_layout.clone(),
|
||||
self.mesh_pipeline.mesh_layout.clone(),
|
||||
self.view_size_bind_group_layout.clone(),
|
||||
self.outline_bind_group_layout.clone(),
|
||||
]);
|
||||
Ok(descriptor)
|
||||
.add_render_command::<StencilOutline, DrawStencil>()
|
||||
.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::PhaseSort, sort_phase_system::<StencilOutline>)
|
||||
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<OpaqueOutline>)
|
||||
.add_system_to_stage(
|
||||
RenderStage::PhaseSort,
|
||||
sort_phase_system::<TransparentOutline>,
|
||||
)
|
||||
.add_system_to_stage(RenderStage::Queue, queue_outline_view_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);
|
||||
|
||||
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(OutlineNode::NAME, node);
|
||||
draw_3d_graph
|
||||
.add_node_edge(
|
||||
bevy::core_pipeline::core_3d::graph::node::MAIN_PASS,
|
||||
OutlineNode::NAME,
|
||||
)
|
||||
.unwrap();
|
||||
draw_3d_graph
|
||||
.add_slot_edge(
|
||||
draw_3d_graph.input_node().unwrap().id,
|
||||
bevy::core_pipeline::core_3d::graph::input::VIEW_ENTITY,
|
||||
OutlineNode::NAME,
|
||||
OutlineNode::IN_VIEW,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
use std::cmp::Reverse;
|
||||
|
||||
use bevy::ecs::system::lifetimeless::Read;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::camera::ExtractedCamera;
|
||||
use bevy::render::render_graph::{NodeRunError, SlotInfo, SlotType};
|
||||
use bevy::render::render_phase::{
|
||||
CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem,
|
||||
RenderPhase, TrackedRenderPass,
|
||||
};
|
||||
use bevy::render::render_resource::{
|
||||
CachedRenderPipelineId, LoadOp, Operations, RenderPassDepthStencilAttachment,
|
||||
RenderPassDescriptor,
|
||||
};
|
||||
use bevy::render::view::{ExtractedView, ViewDepthTexture, ViewTarget};
|
||||
use bevy::render::{
|
||||
render_graph::{Node, RenderGraphContext},
|
||||
renderer::RenderContext,
|
||||
};
|
||||
use bevy::utils::FloatOrd;
|
||||
|
||||
pub struct StencilOutline {
|
||||
pub distance: f32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for StencilOutline {
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
Reverse(FloatOrd(self.distance))
|
||||
}
|
||||
|
||||
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
|
||||
self.draw_function
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityPhaseItem for StencilOutline {
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for StencilOutline {
|
||||
#[inline]
|
||||
fn cached_pipeline(&self) -> CachedRenderPipelineId {
|
||||
self.pipeline
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpaqueOutline {
|
||||
pub distance: f32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for OpaqueOutline {
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
Reverse(FloatOrd(self.distance))
|
||||
}
|
||||
|
||||
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
|
||||
self.draw_function
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityPhaseItem for OpaqueOutline {
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for OpaqueOutline {
|
||||
#[inline]
|
||||
fn cached_pipeline(&self) -> CachedRenderPipelineId {
|
||||
self.pipeline
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TransparentOutline {
|
||||
pub distance: f32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for TransparentOutline {
|
||||
type SortKey = FloatOrd;
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
FloatOrd(self.distance)
|
||||
}
|
||||
|
||||
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
|
||||
self.draw_function
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityPhaseItem for TransparentOutline {
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for TransparentOutline {
|
||||
#[inline]
|
||||
fn cached_pipeline(&self) -> CachedRenderPipelineId {
|
||||
self.pipeline
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutlineNode {
|
||||
query: QueryState<
|
||||
(
|
||||
Read<ExtractedCamera>,
|
||||
Read<RenderPhase<StencilOutline>>,
|
||||
Read<RenderPhase<OpaqueOutline>>,
|
||||
Read<RenderPhase<TransparentOutline>>,
|
||||
Read<Camera3d>,
|
||||
Read<ViewTarget>,
|
||||
Read<ViewDepthTexture>,
|
||||
),
|
||||
With<ExtractedView>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl OutlineNode {
|
||||
pub const NAME: &'static str = "outline_node";
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: world.query_filtered(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for OutlineNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let (camera, stencil_phase, opaque_phase, transparent_phase, camera_3d, target, depth) =
|
||||
match self.query.get_manual(world, view_entity) {
|
||||
Ok(query) => query,
|
||||
Err(_) => {
|
||||
return Ok(());
|
||||
} // No window
|
||||
};
|
||||
|
||||
// Always run stencil pass to ensure depth buffer is cleared
|
||||
{
|
||||
let pass_descriptor = RenderPassDescriptor {
|
||||
label: Some("outline_stencil_pass"),
|
||||
color_attachments: &[],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||
view: &depth.view,
|
||||
depth_ops: Some(Operations {
|
||||
load: camera_3d.depth_load_op.clone().into(),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
};
|
||||
let draw_functions = world.resource::<DrawFunctions<StencilOutline>>();
|
||||
let render_pass = render_context
|
||||
.command_encoder
|
||||
.begin_render_pass(&pass_descriptor);
|
||||
let mut draw_functions = draw_functions.write();
|
||||
let mut tracked_pass = TrackedRenderPass::new(render_pass);
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
tracked_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
for item in &stencil_phase.items {
|
||||
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
|
||||
draw_function.draw(world, &mut tracked_pass, view_entity, item);
|
||||
}
|
||||
}
|
||||
|
||||
if !opaque_phase.items.is_empty() {
|
||||
let pass_descriptor = RenderPassDescriptor {
|
||||
label: Some("outline_opaque_pass"),
|
||||
color_attachments: &[Some(target.get_color_attachment(Operations {
|
||||
load: LoadOp::Load,
|
||||
store: true,
|
||||
}))],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||
view: &depth.view,
|
||||
depth_ops: Some(Operations {
|
||||
load: LoadOp::Load,
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
};
|
||||
let draw_functions = world.resource::<DrawFunctions<OpaqueOutline>>();
|
||||
let render_pass = render_context
|
||||
.command_encoder
|
||||
.begin_render_pass(&pass_descriptor);
|
||||
let mut draw_functions = draw_functions.write();
|
||||
let mut tracked_pass = TrackedRenderPass::new(render_pass);
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
tracked_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
for item in &opaque_phase.items {
|
||||
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
|
||||
draw_function.draw(world, &mut tracked_pass, view_entity, item);
|
||||
}
|
||||
}
|
||||
|
||||
if !transparent_phase.items.is_empty() {
|
||||
let pass_descriptor = RenderPassDescriptor {
|
||||
label: Some("outline_transparent_pass"),
|
||||
color_attachments: &[Some(target.get_color_attachment(Operations {
|
||||
load: LoadOp::Load,
|
||||
store: true,
|
||||
}))],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||
view: &depth.view,
|
||||
depth_ops: Some(Operations {
|
||||
load: LoadOp::Load,
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
};
|
||||
let draw_functions = world.resource::<DrawFunctions<TransparentOutline>>();
|
||||
let render_pass = render_context
|
||||
.command_encoder
|
||||
.begin_render_pass(&pass_descriptor);
|
||||
let mut draw_functions = draw_functions.write();
|
||||
let mut tracked_pass = TrackedRenderPass::new(render_pass);
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
tracked_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
for item in &transparent_phase.items {
|
||||
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
|
||||
draw_function.draw(world, &mut tracked_pass, view_entity, item);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
#import bevy_pbr::mesh_types
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
#import bevy_mod_outline::common
|
||||
|
||||
struct Vertex {
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) normal: vec3<f32>,
|
||||
};
|
||||
|
@ -10,29 +9,26 @@ struct VertexOutput {
|
|||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct ViewSizeUniforms {
|
||||
logical_size: vec2<f32>,
|
||||
struct OutlineViewUniform {
|
||||
scale: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexStageData {
|
||||
struct OutlineVertexUniform {
|
||||
width: f32,
|
||||
};
|
||||
|
||||
struct FragmentStageData {
|
||||
struct OutlineFragmentUniform {
|
||||
colour: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> mesh: Mesh;
|
||||
|
||||
@group(2) @binding(0)
|
||||
var<uniform> view_size: ViewSizeUniforms;
|
||||
var<uniform> view_uniform: OutlineViewUniform;
|
||||
|
||||
@group(3) @binding(0)
|
||||
var<uniform> vstage: VertexStageData;
|
||||
var<uniform> vstage: OutlineVertexUniform;
|
||||
|
||||
@group(3) @binding(1)
|
||||
var<uniform> fstage: FragmentStageData;
|
||||
var<uniform> fstage: OutlineFragmentUniform;
|
||||
|
||||
fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
|
||||
return mat3x3<f32>(
|
||||
|
@ -41,12 +37,12 @@ fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
|
|||
}
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
var clip_pos = view.view_proj * (mesh.model * vec4<f32>(vertex.position, 1.0));
|
||||
var clip_norm = mat4to3(view.view_proj) * (mat4to3(mesh.model) * normalize(vertex.normal));
|
||||
var clip_delta = vec4<f32>(2.0 * vstage.width * normalize(clip_norm.xy) * clip_pos.w / view_size.logical_size, 0.0, 0.0);
|
||||
out.clip_position = clip_pos + clip_delta;
|
||||
var clip_delta = vec2<f32>(vstage.width * normalize(clip_norm.xy) * clip_pos.w * view_uniform.scale);
|
||||
out.clip_position = vec4<f32>((clip_pos.xy + clip_delta) / clip_pos.w, model_origin_z(), 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::reflect::TypeUuid;
|
||||
use bevy::render::render_resource::{
|
||||
BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState,
|
||||
BufferBindingType, BufferSize, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
|
||||
DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PolygonMode,
|
||||
PrimitiveState, ShaderSize, ShaderStages, StencilState, TextureFormat, VertexState,
|
||||
};
|
||||
use bevy::render::renderer::RenderDevice;
|
||||
use bevy::render::texture::BevyDefault;
|
||||
use bevy::{
|
||||
pbr::{MeshPipeline, MeshPipelineKey},
|
||||
render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_resource::{
|
||||
RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::uniforms::{OutlineFragmentUniform, OutlineVertexUniform};
|
||||
use crate::view_uniforms::OutlineViewUniform;
|
||||
|
||||
pub const COMMON_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9448276477068917228);
|
||||
|
||||
pub const STENCIL_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 12033806834125368121);
|
||||
|
||||
pub const OUTLINE_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2101625026478770097);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum PassType {
|
||||
Stencil,
|
||||
Opaque,
|
||||
Transparent,
|
||||
}
|
||||
|
||||
pub struct OutlinePipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
pub outline_view_bind_group_layout: BindGroupLayout,
|
||||
pub outline_bind_group_layout: BindGroupLayout,
|
||||
}
|
||||
|
||||
impl FromWorld for OutlinePipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let world = world.cell();
|
||||
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap().clone();
|
||||
let render_device = world.get_resource::<RenderDevice>().unwrap();
|
||||
let outline_view_bind_group_layout =
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
label: Some("outline_view_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(OutlineViewUniform::SHADER_SIZE.get()),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
let outline_bind_group_layout =
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
label: Some("outline_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(
|
||||
OutlineVertexUniform::SHADER_SIZE.get(),
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: BufferSize::new(
|
||||
OutlineFragmentUniform::SHADER_SIZE.get(),
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
OutlinePipeline {
|
||||
mesh_pipeline,
|
||||
outline_view_bind_group_layout,
|
||||
outline_bind_group_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for OutlinePipeline {
|
||||
type Key = (MeshPipelineKey, PassType);
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
(key, pass_type): Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let mut targets = vec![];
|
||||
let mut bind_layouts = vec![
|
||||
self.mesh_pipeline.view_layout.clone(),
|
||||
self.mesh_pipeline.mesh_layout.clone(),
|
||||
];
|
||||
let mut buffer_attrs = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)];
|
||||
let shader;
|
||||
match pass_type {
|
||||
PassType::Stencil => {
|
||||
shader = STENCIL_SHADER_HANDLE;
|
||||
}
|
||||
PassType::Opaque | PassType::Transparent => {
|
||||
shader = OUTLINE_SHADER_HANDLE;
|
||||
targets.push(Some(ColorTargetState {
|
||||
format: TextureFormat::bevy_default(),
|
||||
blend: Some(if pass_type == PassType::Transparent {
|
||||
BlendState::ALPHA_BLENDING
|
||||
} else {
|
||||
BlendState::REPLACE
|
||||
}),
|
||||
write_mask: ColorWrites::ALL,
|
||||
}));
|
||||
|
||||
bind_layouts.push(self.outline_view_bind_group_layout.clone());
|
||||
bind_layouts.push(self.outline_bind_group_layout.clone());
|
||||
buffer_attrs.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1));
|
||||
}
|
||||
}
|
||||
let buffers = vec![layout.get_layout(&buffer_attrs)?];
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: shader.clone().typed::<Shader>(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: vec![],
|
||||
buffers,
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: shader.clone().typed::<Shader>(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "fragment".into(),
|
||||
targets,
|
||||
}),
|
||||
layout: Some(bind_layouts),
|
||||
primitive: PrimitiveState {
|
||||
front_face: FrontFace::Ccw,
|
||||
cull_mode: Some(Face::Back),
|
||||
unclipped_depth: false,
|
||||
polygon_mode: PolygonMode::Fill,
|
||||
conservative: false,
|
||||
topology: key.primitive_topology(),
|
||||
strip_index_format: None,
|
||||
},
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::Greater,
|
||||
stencil: StencilState::default(),
|
||||
bias: DepthBiasState::default(),
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: Some(Cow::Borrowed("outline_stencil_pipeline")),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#import bevy_mod_outline::common
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
var clip_pos = view.view_proj * (mesh.model * vec4<f32>(vertex.position, 1.0));
|
||||
out.clip_position = vec4<f32>(clip_pos.xy / clip_pos.w, model_origin_z(), 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment() {
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
use bevy::{
|
||||
ecs::{
|
||||
query::QueryItem,
|
||||
system::{
|
||||
lifetimeless::{Read, SQuery, SRes},
|
||||
SystemParamItem,
|
||||
},
|
||||
},
|
||||
prelude::*,
|
||||
render::{
|
||||
extract_component::{ComponentUniforms, DynamicUniformIndex, ExtractComponent},
|
||||
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
|
||||
render_resource::{BindGroup, BindGroupDescriptor, BindGroupEntry, ShaderType},
|
||||
renderer::RenderDevice,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{pipeline::OutlinePipeline, Outline};
|
||||
|
||||
#[derive(Clone, Component, ShaderType)]
|
||||
pub struct OutlineVertexUniform {
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
impl ExtractComponent for OutlineVertexUniform {
|
||||
type Query = Read<Outline>;
|
||||
type Filter = ();
|
||||
|
||||
fn extract_component(item: QueryItem<Self::Query>) -> Self {
|
||||
OutlineVertexUniform { width: item.width }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, ShaderType)]
|
||||
pub struct OutlineFragmentUniform {
|
||||
pub colour: Vec4,
|
||||
}
|
||||
|
||||
impl ExtractComponent for OutlineFragmentUniform {
|
||||
type Query = Read<Outline>;
|
||||
type Filter = ();
|
||||
|
||||
fn extract_component(item: QueryItem<Self::Query>) -> Self {
|
||||
OutlineFragmentUniform {
|
||||
colour: item.colour.as_linear_rgba_f32().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutlineBindGroup {
|
||||
pub bind_group: BindGroup,
|
||||
}
|
||||
|
||||
pub fn queue_outline_bind_group(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
outline_pipeline: Res<OutlinePipeline>,
|
||||
vertex: Res<ComponentUniforms<OutlineVertexUniform>>,
|
||||
fragment: Res<ComponentUniforms<OutlineFragmentUniform>>,
|
||||
) {
|
||||
if let (Some(vertex_binding), Some(fragment_binding)) = (vertex.binding(), fragment.binding()) {
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: vertex_binding.clone(),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: fragment_binding.clone(),
|
||||
},
|
||||
],
|
||||
label: Some("outline_bind_group"),
|
||||
layout: &outline_pipeline.outline_bind_group_layout,
|
||||
});
|
||||
commands.insert_resource(OutlineBindGroup { bind_group });
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetOutlineBindGroup<const I: usize>();
|
||||
|
||||
impl<const I: usize> EntityRenderCommand for SetOutlineBindGroup<I> {
|
||||
type Param = (
|
||||
SRes<OutlineBindGroup>,
|
||||
SQuery<(
|
||||
Read<DynamicUniformIndex<OutlineVertexUniform>>,
|
||||
Read<DynamicUniformIndex<OutlineFragmentUniform>>,
|
||||
)>,
|
||||
);
|
||||
fn render<'w>(
|
||||
_view: Entity,
|
||||
item: Entity,
|
||||
(bind_group, query): SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let (vertex, fragment) = query.get(item).unwrap();
|
||||
pass.set_bind_group(
|
||||
I,
|
||||
&bind_group.into_inner().bind_group,
|
||||
&[vertex.index(), fragment.index()],
|
||||
);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
use bevy::ecs::system::lifetimeless::{Read, SQuery, SRes};
|
||||
use bevy::ecs::system::SystemParamItem;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::extract_component::{ComponentUniforms, DynamicUniformIndex};
|
||||
use bevy::render::render_phase::{
|
||||
EntityRenderCommand, RenderCommandResult, RenderPhase, TrackedRenderPass,
|
||||
};
|
||||
use bevy::render::render_resource::ShaderType;
|
||||
use bevy::render::render_resource::{BindGroup, BindGroupDescriptor, BindGroupEntry};
|
||||
use bevy::render::renderer::RenderDevice;
|
||||
use bevy::render::Extract;
|
||||
|
||||
use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline};
|
||||
use crate::pipeline::OutlinePipeline;
|
||||
|
||||
#[derive(Clone, Component, ShaderType)]
|
||||
pub struct OutlineViewUniform {
|
||||
#[align(16)]
|
||||
scale: Vec2,
|
||||
}
|
||||
|
||||
pub struct OutlineViewBindGroup {
|
||||
bind_group: BindGroup,
|
||||
}
|
||||
|
||||
pub fn extract_outline_view_uniforms(
|
||||
mut commands: Commands,
|
||||
query: Extract<Query<(Entity, &Camera), With<Camera3d>>>,
|
||||
) {
|
||||
for (entity, camera) in query.iter() {
|
||||
if !camera.is_active {
|
||||
continue;
|
||||
}
|
||||
if let Some(size) = camera.logical_viewport_size() {
|
||||
commands
|
||||
.get_or_spawn(entity)
|
||||
.insert(OutlineViewUniform { scale: 2.0 / size })
|
||||
.insert(RenderPhase::<StencilOutline>::default())
|
||||
.insert(RenderPhase::<OpaqueOutline>::default())
|
||||
.insert(RenderPhase::<TransparentOutline>::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_outline_view_bind_group(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
outline_pipeline: Res<OutlinePipeline>,
|
||||
view_uniforms: Res<ComponentUniforms<OutlineViewUniform>>,
|
||||
) {
|
||||
if let Some(view_binding) = view_uniforms.binding() {
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: view_binding.clone(),
|
||||
}],
|
||||
label: Some("outline_view_bind_group"),
|
||||
layout: &outline_pipeline.outline_view_bind_group_layout,
|
||||
});
|
||||
commands.insert_resource(OutlineViewBindGroup { bind_group });
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetOutlineViewBindGroup<const I: usize>();
|
||||
|
||||
impl<const I: usize> EntityRenderCommand for SetOutlineViewBindGroup<I> {
|
||||
type Param = (
|
||||
SRes<OutlineViewBindGroup>,
|
||||
SQuery<Read<DynamicUniformIndex<OutlineViewUniform>>>,
|
||||
);
|
||||
fn render<'w>(
|
||||
view: Entity,
|
||||
_item: Entity,
|
||||
(bind_group, query): SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let view_index = query.get(view).unwrap();
|
||||
pass.set_bind_group(
|
||||
I,
|
||||
&bind_group.into_inner().bind_group,
|
||||
&[view_index.index()],
|
||||
);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue