Unify stencil and volume shaders.

This commit is contained in:
Robin KAY 2022-11-18 00:50:52 +00:00
parent 95b3a5b298
commit e86c6d6c60
9 changed files with 122 additions and 142 deletions

View File

@ -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<uniform> mesh: Mesh;
fn model_origin_z(plane: vec3<f32>, view_proj: mat4x4<f32>) -> f32 {
var proj_zw = mat4x2<f32>(
view_proj[0].zw, view_proj[1].zw,
view_proj[2].zw, view_proj[3].zw);
var zw = proj_zw * vec4<f32>(plane, 1.0);
return zw.x / zw.y;
}

View File

@ -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;

View File

@ -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<DrawFunctions<OpaqueOutline>>,
transparent_draw_functions: Res<DrawFunctions<TransparentOutline>>,
outline_pipeline: Res<OutlinePipeline>,

24
src/fragment.wgsl Normal file
View File

@ -0,0 +1,24 @@
#ifdef VOLUME
struct OutlineFragmentUniform {
@align(16)
colour: vec4<f32>,
};
@group(3) @binding(1)
var<uniform> fstage: OutlineFragmentUniform;
@fragment
fn fragment() -> @location(0) vec4<f32> {
return fstage.colour;
}
#else
// Stencil
@fragment
fn fragment() {
return;
}
#endif

View File

@ -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::<OutlineStencil>::extract_visible())
.add_plugin(UniformComponentPlugin::<OutlineStencilUniform>::default())
.add_plugin(UniformComponentPlugin::<OutlineVertexUniform>::default())
.add_plugin(UniformComponentPlugin::<OutlineVolumeUniform>::default())
.add_plugin(UniformComponentPlugin::<OutlineFragmentUniform>::default())
.add_plugin(UniformComponentPlugin::<OutlineViewUniform>::default())
.add_system_to_stage(
@ -143,7 +142,7 @@ impl Plugin for OutlinePlugin {
.add_render_command::<TransparentOutline, DrawOutline>()
.add_system_to_stage(RenderStage::Extract, extract_outline_view_uniforms)
.add_system_to_stage(RenderStage::Extract, extract_outline_stencil_uniforms)
.add_system_to_stage(RenderStage::Extract, extract_outline_uniforms)
.add_system_to_stage(RenderStage::Extract, extract_outline_volume_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(
@ -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);

View File

@ -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<f32>,
#ifndef OFFSET_ZERO
@location(1) normal: vec3<f32>,
#endif
#ifdef SKINNED
@location(2) joint_indexes: vec4<u32>,
@location(3) joint_weights: vec4<f32>,
@ -10,22 +13,18 @@ struct VertexInput {
};
struct OutlineViewUniform {
#ifdef ALIGN_16
@align(16)
#endif
scale: vec2<f32>,
};
struct OutlineVertexUniform {
@align(16)
plane: vec3<f32>,
width: f32,
origin: vec3<f32>,
offset: f32,
};
struct OutlineFragmentUniform {
@align(16)
colour: vec4<f32>,
};
@group(1) @binding(0)
var<uniform> mesh: Mesh;
@group(2) @binding(0)
var<uniform> view_uniform: OutlineViewUniform;
@ -33,15 +32,20 @@ var<uniform> view_uniform: OutlineViewUniform;
@group(3) @binding(0)
var<uniform> vstage: OutlineVertexUniform;
@group(3) @binding(1)
var<uniform> fstage: OutlineFragmentUniform;
fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
return mat3x3<f32>(
m[0].xyz, m[1].xyz, m[2].xyz
);
}
fn model_origin_z(plane: vec3<f32>, view_proj: mat4x4<f32>) -> f32 {
var proj_zw = mat4x2<f32>(
view_proj[0].zw, view_proj[1].zw,
view_proj[2].zw, view_proj[3].zw);
var zw = proj_zw * vec4<f32>(plane, 1.0);
return zw.x / zw.y;
}
@vertex
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
#ifdef SKINNED
@ -49,14 +53,13 @@ fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
#else
let model = mesh.model;
#endif
var clip_pos = view.view_proj * (model * vec4<f32>(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<f32>(ndc_pos + ndc_delta, model_origin_z(vstage.plane, view.view_proj), 1.0);
}
@fragment
fn fragment() -> @location(0) vec4<f32> {
return fstage.colour;
let clip_pos = view.view_proj * (model * vec4<f32>(vertex.position, 1.0));
let ndc_pos = clip_pos.xy / clip_pos.w;
#ifdef OFFSET_ZERO
let ndc_delta = vec2<f32>(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<f32>(ndc_pos + ndc_delta, model_origin_z(vstage.origin, view.view_proj), 1.0);
}

View File

@ -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>(),
shader: OUTLINE_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
shader_defs: vertex_defs,
buffers,
},
fragment: Some(FragmentState {
shader: shader.typed::<Shader>(),
shader_defs,
shader: FRAGMENT_SHADER_HANDLE.typed::<Shader>(),
shader_defs: fragment_defs,
entry_point: "fragment".into(),
targets,
}),

View File

@ -1,34 +0,0 @@
#import bevy_mod_outline::common
struct VertexInput {
@location(0) position: vec3<f32>,
#ifdef SKINNED
@location(2) joint_indexes: vec4<u32>,
@location(3) joint_weights: vec4<f32>,
#endif
};
struct OutlineStencilUniform {
@align(16)
plane: vec3<f32>,
};
@group(2) @binding(0)
var<uniform> vstage: OutlineStencilUniform;
@vertex
fn vertex(vertex: VertexInput) -> @builtin(position) vec4<f32> {
#ifdef SKINNED
let model = skin_model(vertex.joint_indexes, vertex.joint_weights);
#else
let model = mesh.model;
#endif
var clip_pos = view.view_proj * (model * vec4<f32>(vertex.position, 1.0));
var ndc_pos = clip_pos.xy / clip_pos.w;
return vec4<f32>(ndc_pos, model_origin_z(vstage.plane, view.view_proj), 1.0);
}
@fragment
fn fragment() {
return;
}

View File

@ -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<Query<(Entity, &Outline, &ComputedOutlineDepth)>>,
) {
@ -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<RenderDevice>,
outline_pipeline: Res<OutlinePipeline>,
vertex: Res<ComponentUniforms<OutlineVertexUniform>>,
vertex: Res<ComponentUniforms<OutlineVolumeUniform>>,
fragment: Res<ComponentUniforms<OutlineFragmentUniform>>,
) {
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<const I: usize> EntityRenderCommand for SetOutlineStencilBindGroup<I> {
}
}
pub struct SetOutlineBindGroup<const I: usize>();
pub struct SetOutlineVolumeBindGroup<const I: usize>();
impl<const I: usize> EntityRenderCommand for SetOutlineBindGroup<I> {
impl<const I: usize> EntityRenderCommand for SetOutlineVolumeBindGroup<I> {
type Param = (
SRes<OutlineBindGroup>,
SRes<OutlineVolumeBindGroup>,
SQuery<(
Read<DynamicUniformIndex<OutlineVertexUniform>>,
Read<DynamicUniformIndex<OutlineVolumeUniform>>,
Read<DynamicUniformIndex<OutlineFragmentUniform>>,
)>,
);