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",
|
"bevy_asset",
|
||||||
"render",
|
"render",
|
||||||
] }
|
] }
|
||||||
libm = "0.2"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bevy = { version = "0.8", default-features = false, features = [
|
bevy = { version = "0.8", default-features = false, features = [
|
||||||
@ -24,5 +23,5 @@ bevy = { version = "0.8", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "torus"
|
name = "shapes"
|
||||||
path = "examples/torus.rs"
|
path = "examples/shapes.rs"
|
@ -1,4 +1,4 @@
|
|||||||
use std::f32::consts::TAU;
|
use std::f32::consts::{PI, TAU};
|
||||||
|
|
||||||
use bevy::prelude::{shape::Torus, *};
|
use bevy::prelude::{shape::Torus, *};
|
||||||
|
|
||||||
@ -12,20 +12,22 @@ fn main() {
|
|||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugin(OutlinePlugin)
|
.add_plugin(OutlinePlugin)
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
.add_system(rotate_cube)
|
.add_system(wobble)
|
||||||
|
.add_system(orbit)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct TheCube();
|
struct Wobbles;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Orbits;
|
||||||
|
|
||||||
fn setup(
|
fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut outlines: ResMut<Assets<Outline>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
// Spawn cube et al.
|
|
||||||
commands.spawn_bundle(PbrBundle {
|
commands.spawn_bundle(PbrBundle {
|
||||||
mesh: meshes.add(Mesh::from(bevy::prelude::shape::Plane { size: 5.0 })),
|
mesh: meshes.add(Mesh::from(bevy::prelude::shape::Plane { size: 5.0 })),
|
||||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
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),
|
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(outlines.add(Outline {
|
.insert(Outline {
|
||||||
colour: Color::rgba(0.0, 1.0, 0.0, 0.5),
|
colour: Color::rgba(0.0, 1.0, 0.0, 1.0),
|
||||||
width: 25.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 {
|
commands.spawn_bundle(PointLightBundle {
|
||||||
point_light: PointLight {
|
point_light: PointLight {
|
||||||
intensity: 1500.0,
|
intensity: 1500.0,
|
||||||
@ -63,17 +85,13 @@ fn setup(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_cube(
|
fn wobble(mut query: Query<&mut Transform, With<Wobbles>>, timer: Res<Time>, mut t: Local<f32>) {
|
||||||
mut cubes: Query<&mut Transform, With<TheCube>>,
|
|
||||||
timer: Res<Time>,
|
|
||||||
mut t: Local<f32>,
|
|
||||||
) {
|
|
||||||
let ta = *t;
|
let ta = *t;
|
||||||
*t = (ta + 0.5 * timer.delta_seconds()) % TAU;
|
*t = (ta + 0.5 * timer.delta_seconds()) % TAU;
|
||||||
let tb = *t;
|
let tb = *t;
|
||||||
let i1 = tb.cos() - ta.cos();
|
let i1 = tb.cos() - ta.cos();
|
||||||
let i2 = ta.sin() - tb.sin();
|
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(
|
transform.rotate(Quat::from_rotation_z(
|
||||||
TAU * 20.0 * i1 * timer.delta_seconds(),
|
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()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
15
src/common.wgsl
Normal file
15
src/common.wgsl
Normal file
@ -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;
|
||||||
|
}
|
143
src/draw.rs
Normal file
143
src/draw.rs
Normal file
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
410
src/lib.rs
410
src/lib.rs
@ -1,356 +1,122 @@
|
|||||||
use bevy::asset::load_internal_asset;
|
use bevy::asset::load_internal_asset;
|
||||||
use bevy::core_pipeline::core_3d::{Opaque3d, Transparent3d};
|
use bevy::ecs::query::QueryItem;
|
||||||
use bevy::ecs::system::lifetimeless::{Read, SQuery, SRes};
|
|
||||||
use bevy::ecs::system::SystemParamItem;
|
|
||||||
use bevy::pbr::{
|
|
||||||
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup,
|
|
||||||
};
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::reflect::TypeUuid;
|
use bevy::render::extract_component::{
|
||||||
use bevy::render::extract_component::ExtractComponentPlugin;
|
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
||||||
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::render_resource::{
|
use bevy::render::render_graph::RenderGraph;
|
||||||
AsBindGroup, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
|
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
|
||||||
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize,
|
use bevy::render::render_resource::SpecializedMeshPipelines;
|
||||||
DynamicUniformBuffer, Face, PipelineCache, PreparedBindGroup, RenderPipelineDescriptor,
|
use bevy::render::{RenderApp, RenderStage};
|
||||||
ShaderSize, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
|
||||||
SpecializedMeshPipelines,
|
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 crate::uniforms::{queue_outline_bind_group, OutlineFragmentUniform, OutlineVertexUniform};
|
||||||
use bevy::render::texture::FallbackImage;
|
use crate::view_uniforms::{
|
||||||
use bevy::render::view::ExtractedView;
|
extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform,
|
||||||
use bevy::render::{Extract, RenderApp, RenderStage};
|
};
|
||||||
use libm::nextafterf;
|
|
||||||
|
mod draw;
|
||||||
|
mod node;
|
||||||
|
mod pipeline;
|
||||||
|
mod uniforms;
|
||||||
|
mod view_uniforms;
|
||||||
|
|
||||||
// See https://alexanderameye.github.io/notes/rendering-outlines/
|
// See https://alexanderameye.github.io/notes/rendering-outlines/
|
||||||
|
|
||||||
const OUTLINE_SHADER_HANDLE: HandleUntyped =
|
/// A component for stenciling meshes during outline rendering
|
||||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2101625026478770097);
|
#[derive(Component, Default)]
|
||||||
|
pub struct OutlineStencil;
|
||||||
|
|
||||||
/// An asset for rendering outlines around meshes.
|
impl ExtractComponent for OutlineStencil {
|
||||||
#[derive(Clone, TypeUuid, AsBindGroup)]
|
type Query = ();
|
||||||
#[uuid = "552e416b-2766-4e6a-9ee5-9ebd0e8c0230"]
|
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 {
|
pub struct Outline {
|
||||||
/// Colour of the outline
|
|
||||||
#[uniform(1, visibility(fragment))]
|
|
||||||
pub colour: Color,
|
|
||||||
/// Width of the outline in logical pixels
|
/// Width of the outline in logical pixels
|
||||||
#[uniform(0, visibility(vertex))]
|
|
||||||
pub width: f32,
|
pub width: f32,
|
||||||
|
/// Colour of the outline
|
||||||
|
pub colour: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderAsset for Outline {
|
/// Adds support for rendering outlines.
|
||||||
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.
|
|
||||||
pub struct OutlinePlugin;
|
pub struct OutlinePlugin;
|
||||||
|
|
||||||
impl Plugin for OutlinePlugin {
|
impl Plugin for OutlinePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
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!(
|
load_internal_asset!(
|
||||||
app,
|
app,
|
||||||
OUTLINE_SHADER_HANDLE,
|
OUTLINE_SHADER_HANDLE,
|
||||||
"outline.wgsl",
|
"outline.wgsl",
|
||||||
Shader::from_wgsl
|
Shader::from_wgsl
|
||||||
);
|
);
|
||||||
app.add_asset::<Outline>()
|
|
||||||
.add_plugin(ExtractComponentPlugin::<Handle<Outline>>::default())
|
app.add_plugin(ExtractComponentPlugin::<OutlineStencil>::extract_visible())
|
||||||
.add_plugin(RenderAssetPlugin::<Outline>::default())
|
.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)
|
.sub_app_mut(RenderApp)
|
||||||
.add_render_command::<Opaque3d, DrawOutline>()
|
.init_resource::<DrawFunctions<StencilOutline>>()
|
||||||
.add_render_command::<Transparent3d, DrawOutline>()
|
.init_resource::<DrawFunctions<OpaqueOutline>>()
|
||||||
|
.init_resource::<DrawFunctions<TransparentOutline>>()
|
||||||
.init_resource::<OutlinePipeline>()
|
.init_resource::<OutlinePipeline>()
|
||||||
.init_resource::<SpecializedMeshPipelines<OutlinePipeline>>()
|
.init_resource::<SpecializedMeshPipelines<OutlinePipeline>>()
|
||||||
.init_resource::<ViewSizeUniforms>()
|
.add_render_command::<StencilOutline, DrawStencil>()
|
||||||
.add_system_to_stage(RenderStage::Extract, extract_view_size_uniforms)
|
.add_render_command::<OpaqueOutline, DrawOutline>()
|
||||||
.add_system_to_stage(RenderStage::Prepare, prepare_view_size_uniforms)
|
.add_render_command::<TransparentOutline, DrawOutline>()
|
||||||
.add_system_to_stage(RenderStage::Queue, queue_outline);
|
.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);
|
||||||
|
|
||||||
type DrawOutline = (
|
let world = &mut app.sub_app_mut(RenderApp).world;
|
||||||
SetItemPipeline,
|
let node = OutlineNode::new(world);
|
||||||
SetMeshViewBindGroup<0>,
|
|
||||||
SetMeshBindGroup<1>,
|
|
||||||
SetViewSizeBindGroup<2>,
|
|
||||||
SetOutlineBindGroup<3>,
|
|
||||||
DrawMesh,
|
|
||||||
);
|
|
||||||
|
|
||||||
struct SetViewSizeBindGroup<const I: usize>();
|
let mut graph = world.resource_mut::<RenderGraph>();
|
||||||
|
|
||||||
impl<const I: usize> EntityRenderCommand for SetViewSizeBindGroup<I> {
|
let draw_3d_graph = graph
|
||||||
type Param = (SRes<GpuViewSize>, SQuery<Read<ViewSizeUniformOffset>>);
|
.get_sub_graph_mut(bevy::core_pipeline::core_3d::graph::NAME)
|
||||||
#[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();
|
.unwrap();
|
||||||
let draw_transparent_outline = transparent_3d_draw_functions
|
draw_3d_graph.add_node(OutlineNode::NAME, node);
|
||||||
.read()
|
draw_3d_graph
|
||||||
.get_id::<DrawOutline>()
|
.add_node_edge(
|
||||||
|
bevy::core_pipeline::core_3d::graph::node::MAIN_PASS,
|
||||||
|
OutlineNode::NAME,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
draw_3d_graph
|
||||||
let base_key = MeshPipelineKey::from_msaa_samples(msaa.samples)
|
.add_slot_edge(
|
||||||
| MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList);
|
draw_3d_graph.input_node().unwrap().id,
|
||||||
|
bevy::core_pipeline::core_3d::graph::input::VIEW_ENTITY,
|
||||||
for (view, mut opaque_phase, mut transparent_phase) in views.iter_mut() {
|
OutlineNode::NAME,
|
||||||
let inverse_view_matrix = view.transform.compute_matrix().inverse();
|
OutlineNode::IN_VIEW,
|
||||||
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();
|
.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
264
src/node.rs
Normal file
264
src/node.rs
Normal file
@ -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_mod_outline::common
|
||||||
#import bevy_pbr::mesh_view_bindings
|
|
||||||
|
|
||||||
struct Vertex {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) normal: vec3<f32>,
|
@location(1) normal: vec3<f32>,
|
||||||
};
|
};
|
||||||
@ -10,29 +9,26 @@ struct VertexOutput {
|
|||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ViewSizeUniforms {
|
struct OutlineViewUniform {
|
||||||
logical_size: vec2<f32>,
|
scale: vec2<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexStageData {
|
struct OutlineVertexUniform {
|
||||||
width: f32,
|
width: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FragmentStageData {
|
struct OutlineFragmentUniform {
|
||||||
colour: vec4<f32>,
|
colour: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<uniform> mesh: Mesh;
|
|
||||||
|
|
||||||
@group(2) @binding(0)
|
@group(2) @binding(0)
|
||||||
var<uniform> view_size: ViewSizeUniforms;
|
var<uniform> view_uniform: OutlineViewUniform;
|
||||||
|
|
||||||
@group(3) @binding(0)
|
@group(3) @binding(0)
|
||||||
var<uniform> vstage: VertexStageData;
|
var<uniform> vstage: OutlineVertexUniform;
|
||||||
|
|
||||||
@group(3) @binding(1)
|
@group(3) @binding(1)
|
||||||
var<uniform> fstage: FragmentStageData;
|
var<uniform> fstage: OutlineFragmentUniform;
|
||||||
|
|
||||||
fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
|
fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
|
||||||
return mat3x3<f32>(
|
return mat3x3<f32>(
|
||||||
@ -41,12 +37,12 @@ fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
var clip_pos = view.view_proj * (mesh.model * vec4<f32>(vertex.position, 1.0));
|
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_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);
|
var clip_delta = vec2<f32>(vstage.width * normalize(clip_norm.xy) * clip_pos.w * view_uniform.scale);
|
||||||
out.clip_position = clip_pos + clip_delta;
|
out.clip_position = vec4<f32>((clip_pos.xy + clip_delta) / clip_pos.w, model_origin_z(), 1.0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
180
src/pipeline.rs
Normal file
180
src/pipeline.rs
Normal file
@ -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")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
22
src/stencil.wgsl
Normal file
22
src/stencil.wgsl
Normal file
@ -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;
|
||||||
|
}
|
104
src/uniforms.rs
Normal file
104
src/uniforms.rs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
85
src/view_uniforms.rs
Normal file
85
src/view_uniforms.rs
Normal file
@ -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
Block a user