diff --git a/src/common.wgsl b/src/common.wgsl deleted file mode 100644 index 3d39983..0000000 --- a/src/common.wgsl +++ /dev/null @@ -1,14 +0,0 @@ -#define_import_path bevy_mod_outline::common -#import bevy_pbr::mesh_view_bindings -#import bevy_pbr::mesh_types - -@group(1) @binding(0) -var mesh: Mesh; - -fn model_origin_z(plane: vec3, view_proj: mat4x4) -> f32 { - var proj_zw = mat4x2( - view_proj[0].zw, view_proj[1].zw, - view_proj[2].zw, view_proj[3].zw); - var zw = proj_zw * vec4(plane, 1.0); - return zw.x / zw.y; -} \ No newline at end of file diff --git a/src/computed.rs b/src/computed.rs index f47b0da..076080b 100644 --- a/src/computed.rs +++ b/src/computed.rs @@ -3,7 +3,7 @@ use bevy::prelude::*; /// A component for storing the computed depth at which the outline lies. #[derive(Clone, Component, Default)] pub struct ComputedOutlineDepth { - pub(crate) plane: Vec3, + pub(crate) origin: Vec3, } /// A component which specifies that this entity lies at the same depth as its parent. @@ -27,7 +27,7 @@ pub(crate) fn compute_outline_depth( for (mut computed, transform, changed_transform, children) in root_query.iter_mut() { if changed_transform { let matrix = transform.compute_matrix(); - computed.plane = matrix.project_point3(Vec3::ZERO); + computed.origin = matrix.project_point3(Vec3::ZERO); } if let Some((cs, changed_children)) = children { let changed2 = changed_children || changed_transform; diff --git a/src/draw.rs b/src/draw.rs index d87a03e..76ecb1a 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -7,7 +7,9 @@ use bevy::render::view::ExtractedView; use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline}; use crate::pipeline::{OutlinePipeline, PassType}; -use crate::uniforms::{OutlineFragmentUniform, SetOutlineBindGroup, SetOutlineStencilBindGroup}; +use crate::uniforms::{ + OutlineFragmentUniform, SetOutlineStencilBindGroup, SetOutlineVolumeBindGroup, +}; use crate::view_uniforms::SetOutlineViewBindGroup; use crate::OutlineStencil; @@ -15,7 +17,8 @@ pub type DrawStencil = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, - SetOutlineStencilBindGroup<2>, + SetOutlineViewBindGroup<2>, + SetOutlineStencilBindGroup<3>, DrawMesh, ); @@ -68,12 +71,12 @@ pub type DrawOutline = ( SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, SetOutlineViewBindGroup<2>, - SetOutlineBindGroup<3>, + SetOutlineVolumeBindGroup<3>, DrawMesh, ); #[allow(clippy::too_many_arguments)] -pub fn queue_outline_mesh( +pub fn queue_outline_volume_mesh( opaque_draw_functions: Res>, transparent_draw_functions: Res>, outline_pipeline: Res, diff --git a/src/fragment.wgsl b/src/fragment.wgsl new file mode 100644 index 0000000..2f0de9a --- /dev/null +++ b/src/fragment.wgsl @@ -0,0 +1,24 @@ +#ifdef VOLUME + +struct OutlineFragmentUniform { + @align(16) + colour: vec4, +}; + +@group(3) @binding(1) +var fstage: OutlineFragmentUniform; + +@fragment +fn fragment() -> @location(0) vec4 { + return fstage.colour; +} + +#else +// Stencil + +@fragment +fn fragment() { + return; +} + +#endif \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 0355c6b..bd8f08d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,15 +28,15 @@ use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat}; use bevy::render::{RenderApp, RenderStage}; use bevy::transform::TransformSystem; -use crate::draw::{queue_outline_mesh, queue_outline_stencil_mesh, DrawOutline, DrawStencil}; -use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline}; -use crate::pipeline::{ - OutlinePipeline, COMMON_SHADER_HANDLE, OUTLINE_SHADER_HANDLE, STENCIL_SHADER_HANDLE, +use crate::draw::{ + queue_outline_stencil_mesh, queue_outline_volume_mesh, DrawOutline, DrawStencil, }; +use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline}; +use crate::pipeline::{OutlinePipeline, FRAGMENT_SHADER_HANDLE, OUTLINE_SHADER_HANDLE}; use crate::uniforms::{ - extract_outline_stencil_uniforms, extract_outline_uniforms, queue_outline_bind_group, - queue_outline_stencil_bind_group, OutlineFragmentUniform, OutlineStencilUniform, - OutlineVertexUniform, + extract_outline_stencil_uniforms, extract_outline_volume_uniforms, + queue_outline_stencil_bind_group, queue_outline_volume_bind_group, OutlineFragmentUniform, + OutlineStencilUniform, OutlineVolumeUniform, }; use crate::view_uniforms::{ extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform, @@ -109,23 +109,22 @@ 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 ); + load_internal_asset!( + app, + FRAGMENT_SHADER_HANDLE, + "fragment.wgsl", + Shader::from_wgsl + ); app.add_plugin(ExtractComponentPlugin::::extract_visible()) .add_plugin(UniformComponentPlugin::::default()) - .add_plugin(UniformComponentPlugin::::default()) + .add_plugin(UniformComponentPlugin::::default()) .add_plugin(UniformComponentPlugin::::default()) .add_plugin(UniformComponentPlugin::::default()) .add_system_to_stage( @@ -143,7 +142,7 @@ impl Plugin for OutlinePlugin { .add_render_command::() .add_system_to_stage(RenderStage::Extract, extract_outline_view_uniforms) .add_system_to_stage(RenderStage::Extract, extract_outline_stencil_uniforms) - .add_system_to_stage(RenderStage::Extract, extract_outline_uniforms) + .add_system_to_stage(RenderStage::Extract, extract_outline_volume_uniforms) .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) .add_system_to_stage( @@ -152,9 +151,9 @@ impl Plugin for OutlinePlugin { ) .add_system_to_stage(RenderStage::Queue, queue_outline_view_bind_group) .add_system_to_stage(RenderStage::Queue, queue_outline_stencil_bind_group) - .add_system_to_stage(RenderStage::Queue, queue_outline_bind_group) + .add_system_to_stage(RenderStage::Queue, queue_outline_volume_bind_group) .add_system_to_stage(RenderStage::Queue, queue_outline_stencil_mesh) - .add_system_to_stage(RenderStage::Queue, queue_outline_mesh); + .add_system_to_stage(RenderStage::Queue, queue_outline_volume_mesh); let world = &mut app.sub_app_mut(RenderApp).world; let node = OutlineNode::new(world); diff --git a/src/outline.wgsl b/src/outline.wgsl index ae2e012..757c2e7 100644 --- a/src/outline.wgsl +++ b/src/outline.wgsl @@ -1,8 +1,11 @@ -#import bevy_mod_outline::common +#import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_types struct VertexInput { @location(0) position: vec3, +#ifndef OFFSET_ZERO @location(1) normal: vec3, +#endif #ifdef SKINNED @location(2) joint_indexes: vec4, @location(3) joint_weights: vec4, @@ -10,22 +13,18 @@ struct VertexInput { }; struct OutlineViewUniform { -#ifdef ALIGN_16 @align(16) -#endif scale: vec2, }; struct OutlineVertexUniform { @align(16) - plane: vec3, - width: f32, + origin: vec3, + offset: f32, }; -struct OutlineFragmentUniform { - @align(16) - colour: vec4, -}; +@group(1) @binding(0) +var mesh: Mesh; @group(2) @binding(0) var view_uniform: OutlineViewUniform; @@ -33,15 +32,20 @@ var view_uniform: OutlineViewUniform; @group(3) @binding(0) var vstage: OutlineVertexUniform; -@group(3) @binding(1) -var fstage: OutlineFragmentUniform; - fn mat4to3(m: mat4x4) -> mat3x3 { return mat3x3( m[0].xyz, m[1].xyz, m[2].xyz ); } +fn model_origin_z(plane: vec3, view_proj: mat4x4) -> f32 { + var proj_zw = mat4x2( + view_proj[0].zw, view_proj[1].zw, + view_proj[2].zw, view_proj[3].zw); + var zw = proj_zw * vec4(plane, 1.0); + return zw.x / zw.y; +} + @vertex fn vertex(vertex: VertexInput) -> @builtin(position) vec4 { #ifdef SKINNED @@ -49,14 +53,13 @@ fn vertex(vertex: VertexInput) -> @builtin(position) vec4 { #else let model = mesh.model; #endif - var clip_pos = view.view_proj * (model * vec4(vertex.position, 1.0)); - var clip_norm = mat4to3(view.view_proj) * (mat4to3(model) * vertex.normal); - var ndc_pos = clip_pos.xy / clip_pos.w; - var ndc_delta = vstage.width * normalize(clip_norm.xy) * view_uniform.scale; - return vec4(ndc_pos + ndc_delta, model_origin_z(vstage.plane, view.view_proj), 1.0); -} - -@fragment -fn fragment() -> @location(0) vec4 { - return fstage.colour; + let clip_pos = view.view_proj * (model * vec4(vertex.position, 1.0)); + let ndc_pos = clip_pos.xy / clip_pos.w; +#ifdef OFFSET_ZERO + let ndc_delta = vec2(0.0, 0.0); +#else + let clip_norm = mat4to3(view.view_proj) * (mat4to3(model) * vertex.normal); + let ndc_delta = vstage.offset * normalize(clip_norm.xy) * view_uniform.scale; +#endif + return vec4(ndc_pos + ndc_delta, model_origin_z(vstage.origin, view.view_proj), 1.0); } diff --git a/src/pipeline.rs b/src/pipeline.rs index 5a4ee61..be2231f 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -20,19 +20,16 @@ use bevy::{ }, }; -use crate::uniforms::{OutlineFragmentUniform, OutlineStencilUniform, OutlineVertexUniform}; +use crate::uniforms::{OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform}; use crate::view_uniforms::OutlineViewUniform; use crate::ATTRIBUTE_OUTLINE_NORMAL; -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); +pub const FRAGMENT_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 12033806834125368121); + #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum PassType { Stencil, @@ -44,7 +41,7 @@ pub struct OutlinePipeline { mesh_pipeline: MeshPipeline, pub outline_view_bind_group_layout: BindGroupLayout, pub outline_stencil_bind_group_layout: BindGroupLayout, - pub outline_bind_group_layout: BindGroupLayout, + pub outline_volume_bind_group_layout: BindGroupLayout, } impl FromWorld for OutlinePipeline { @@ -66,9 +63,9 @@ impl FromWorld for OutlinePipeline { count: None, }], }); - let outline_bind_group_layout = + let outline_volume_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("outline_bind_group_layout"), + label: Some("outline_volume_bind_group_layout"), entries: &[ BindGroupLayoutEntry { binding: 0, @@ -77,7 +74,7 @@ impl FromWorld for OutlinePipeline { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: BufferSize::new( - OutlineVertexUniform::SHADER_SIZE.get(), + OutlineVolumeUniform::SHADER_SIZE.get(), ), }, count: None, @@ -114,7 +111,7 @@ impl FromWorld for OutlinePipeline { mesh_pipeline, outline_view_bind_group_layout, outline_stencil_bind_group_layout, - outline_bind_group_layout, + outline_volume_bind_group_layout, } } } @@ -130,12 +127,13 @@ impl SpecializedMeshPipeline for OutlinePipeline { let mut targets = vec![]; let mut bind_layouts = vec![self.mesh_pipeline.view_layout.clone()]; let mut buffer_attrs = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)]; - let mut shader_defs = vec![]; + let mut vertex_defs = vec![]; + let mut fragment_defs = vec![]; bind_layouts.push( if mesh_layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && mesh_layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) { - shader_defs.push("SKINNED".to_string()); + vertex_defs.push("SKINNED".to_string()); buffer_attrs.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(2)); buffer_attrs.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(3)); self.mesh_pipeline.skinned_mesh_layout.clone() @@ -143,14 +141,14 @@ impl SpecializedMeshPipeline for OutlinePipeline { self.mesh_pipeline.mesh_layout.clone() }, ); - let shader; + bind_layouts.push(self.outline_view_bind_group_layout.clone()); match pass_type { PassType::Stencil => { - shader = STENCIL_SHADER_HANDLE; + vertex_defs.push("OFFSET_ZERO".to_string()); bind_layouts.push(self.outline_stencil_bind_group_layout.clone()); } PassType::Opaque | PassType::Transparent => { - shader = OUTLINE_SHADER_HANDLE; + fragment_defs.push("VOLUME".to_string()); targets.push(Some(ColorTargetState { format: TextureFormat::bevy_default(), blend: Some(if pass_type == PassType::Transparent { @@ -161,8 +159,7 @@ impl SpecializedMeshPipeline for OutlinePipeline { write_mask: ColorWrites::ALL, })); - bind_layouts.push(self.outline_view_bind_group_layout.clone()); - bind_layouts.push(self.outline_bind_group_layout.clone()); + bind_layouts.push(self.outline_volume_bind_group_layout.clone()); buffer_attrs.push( if mesh_layout.contains(ATTRIBUTE_OUTLINE_NORMAL) { ATTRIBUTE_OUTLINE_NORMAL @@ -176,14 +173,14 @@ impl SpecializedMeshPipeline for OutlinePipeline { let buffers = vec![mesh_layout.get_layout(&buffer_attrs)?]; Ok(RenderPipelineDescriptor { vertex: VertexState { - shader: shader.clone().typed::(), + shader: OUTLINE_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), - shader_defs: shader_defs.clone(), + shader_defs: vertex_defs, buffers, }, fragment: Some(FragmentState { - shader: shader.typed::(), - shader_defs, + shader: FRAGMENT_SHADER_HANDLE.typed::(), + shader_defs: fragment_defs, entry_point: "fragment".into(), targets, }), diff --git a/src/stencil.wgsl b/src/stencil.wgsl deleted file mode 100644 index bc2440d..0000000 --- a/src/stencil.wgsl +++ /dev/null @@ -1,34 +0,0 @@ -#import bevy_mod_outline::common - -struct VertexInput { - @location(0) position: vec3, -#ifdef SKINNED - @location(2) joint_indexes: vec4, - @location(3) joint_weights: vec4, -#endif -}; - -struct OutlineStencilUniform { - @align(16) - plane: vec3, -}; - -@group(2) @binding(0) -var vstage: OutlineStencilUniform; - -@vertex -fn vertex(vertex: VertexInput) -> @builtin(position) vec4 { -#ifdef SKINNED - let model = skin_model(vertex.joint_indexes, vertex.joint_weights); -#else - let model = mesh.model; -#endif - var clip_pos = view.view_proj * (model * vec4(vertex.position, 1.0)); - var ndc_pos = clip_pos.xy / clip_pos.w; - return vec4(ndc_pos, model_origin_z(vstage.plane, view.view_proj), 1.0); -} - -@fragment -fn fragment() { - return; -} diff --git a/src/uniforms.rs b/src/uniforms.rs index 5712725..b86fe01 100644 --- a/src/uniforms.rs +++ b/src/uniforms.rs @@ -15,18 +15,19 @@ use bevy::{ use crate::{pipeline::OutlinePipeline, ComputedOutlineDepth, Outline, OutlineStencil}; -#[derive(Clone, Component, ShaderType)] -pub struct OutlineStencilUniform { - #[align(16)] - pub plane: Vec3, +macro_rules! outline_vertex_uniform { + ($x:ident) => { + #[derive(Clone, Component, ShaderType)] + pub struct $x { + #[align(16)] + pub origin: Vec3, + pub offset: f32, + } + }; } -#[derive(Clone, Component, ShaderType)] -pub struct OutlineVertexUniform { - #[align(16)] - pub plane: Vec3, - pub width: f32, -} +outline_vertex_uniform!(OutlineStencilUniform); +outline_vertex_uniform!(OutlineVolumeUniform); #[derive(Clone, Component, ShaderType)] pub struct OutlineFragmentUniform { @@ -38,7 +39,7 @@ pub struct OutlineStencilBindGroup { pub bind_group: BindGroup, } -pub struct OutlineBindGroup { +pub struct OutlineVolumeBindGroup { pub bind_group: BindGroup, } @@ -48,12 +49,13 @@ pub fn extract_outline_stencil_uniforms( ) { for (entity, computed) in query.iter() { commands.get_or_spawn(entity).insert(OutlineStencilUniform { - plane: computed.plane, + origin: computed.origin, + offset: 0.0, }); } } -pub fn extract_outline_uniforms( +pub fn extract_outline_volume_uniforms( mut commands: Commands, query: Extract>, ) { @@ -63,9 +65,9 @@ pub fn extract_outline_uniforms( } commands .get_or_spawn(entity) - .insert(OutlineVertexUniform { - width: outline.width, - plane: computed.plane, + .insert(OutlineVolumeUniform { + origin: computed.origin, + offset: outline.width, }) .insert(OutlineFragmentUniform { colour: outline.colour.as_linear_rgba_f32().into(), @@ -92,11 +94,11 @@ pub fn queue_outline_stencil_bind_group( } } -pub fn queue_outline_bind_group( +pub fn queue_outline_volume_bind_group( mut commands: Commands, render_device: Res, outline_pipeline: Res, - vertex: Res>, + vertex: Res>, fragment: Res>, ) { if let (Some(vertex_binding), Some(fragment_binding)) = (vertex.binding(), fragment.binding()) { @@ -111,10 +113,10 @@ pub fn queue_outline_bind_group( resource: fragment_binding.clone(), }, ], - label: Some("outline_bind_group"), - layout: &outline_pipeline.outline_bind_group_layout, + label: Some("outline_volume_bind_group"), + layout: &outline_pipeline.outline_volume_bind_group_layout, }); - commands.insert_resource(OutlineBindGroup { bind_group }); + commands.insert_resource(OutlineVolumeBindGroup { bind_group }); } } @@ -137,13 +139,13 @@ impl EntityRenderCommand for SetOutlineStencilBindGroup { } } -pub struct SetOutlineBindGroup(); +pub struct SetOutlineVolumeBindGroup(); -impl EntityRenderCommand for SetOutlineBindGroup { +impl EntityRenderCommand for SetOutlineVolumeBindGroup { type Param = ( - SRes, + SRes, SQuery<( - Read>, + Read>, Read>, )>, );