Pass logical view size to outline shader.

This commit is contained in:
Robin KAY 2022-06-14 02:29:12 +01:00
parent e13eef50cb
commit a960850611
3 changed files with 139 additions and 13 deletions

View File

@ -9,6 +9,7 @@ use bevy_mod_rounded_box::*;
fn main() {
App::new()
.insert_resource(Msaa { samples: 4 })
.insert_resource(ClearColor(Color::BLACK))
.add_plugins(DefaultPlugins)
.add_plugin(OutlinePlugin)
.add_startup_system(setup)
@ -44,7 +45,7 @@ fn setup(
})
.insert(outlines.add(Outline {
colour: Color::rgba(0.0, 1.0, 0.0, 0.5),
width: 50.0,
width: 25.0,
}))
.insert(TheCube());
commands.spawn_bundle(PointLightBundle {

View File

@ -7,6 +7,7 @@ use bevy::pbr::{
};
use bevy::prelude::*;
use bevy::reflect::TypeUuid;
use bevy::render::camera::{ActiveCamera, Camera3d};
use bevy::render::mesh::{MeshVertexBufferLayout, PrimitiveTopology};
use bevy::render::render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets};
use bevy::render::render_component::ExtractComponentPlugin;
@ -18,10 +19,10 @@ use bevy::render::render_resource::std140::{AsStd140, Std140};
use bevy::render::render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, BufferInitDescriptor, BufferSize,
BufferUsages, Face, PipelineCache, RenderPipelineDescriptor, ShaderStages,
BufferUsages, DynamicUniformVec, Face, PipelineCache, RenderPipelineDescriptor, ShaderStages,
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
};
use bevy::render::renderer::RenderDevice;
use bevy::render::renderer::{RenderDevice, RenderQueue};
use bevy::render::view::ExtractedView;
use bevy::render::{RenderApp, RenderStage};
use libm::nextafterf;
@ -37,7 +38,7 @@ const OUTLINE_SHADER_HANDLE: HandleUntyped =
pub struct Outline {
/// Colour of the outline
pub colour: Color,
/// Width of the outline in pixels
/// Width of the outline in logical pixels
pub width: f32,
}
@ -76,7 +77,7 @@ impl RenderAsset for Outline {
});
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
label: Some("outline_bind_group"),
layout: &outline_pipeline.bind_group_layout,
layout: &outline_pipeline.outline_bind_group_layout,
entries: &[
BindGroupEntry {
binding: 0,
@ -97,6 +98,26 @@ impl RenderAsset for Outline {
}
}
#[derive(Clone, Component, AsStd140)]
struct ViewSizeUniform {
logical_size: Vec2,
}
#[derive(Default)]
struct ViewSizeUniforms {
pub uniforms: DynamicUniformVec<ViewSizeUniform>,
}
#[derive(Component)]
struct ViewSizeUniformOffset {
pub offset: u32,
}
#[derive(Component)]
struct GpuViewSize {
bind_group: BindGroup,
}
#[derive(Clone, AsStd140)]
struct VertexStageData {
width: f32,
@ -133,6 +154,9 @@ impl Plugin for OutlinePlugin {
.add_render_command::<Transparent3d, DrawOutline>()
.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);
}
}
@ -141,10 +165,29 @@ type DrawOutline = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshBindGroup<1>,
SetOutlineBindGroup<2>,
SetOutlineViewBindGroup<2>,
SetOutlineBindGroup<3>,
DrawMesh,
);
struct SetOutlineViewBindGroup<const I: usize>();
impl<const I: usize> EntityRenderCommand for SetOutlineViewBindGroup<I> {
type Param = SQuery<(Read<ViewSizeUniformOffset>, Read<GpuViewSize>)>;
#[inline]
fn render<'w>(
view: Entity,
_item: Entity,
view_query: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let (view_size_uniform, gpu_view_size) = view_query.get_inner(view).unwrap();
pass.set_bind_group(I, &gpu_view_size.bind_group, &[view_size_uniform.offset]);
RenderCommandResult::Success
}
}
struct SetOutlineBindGroup<const I: usize>();
impl<const I: usize> EntityRenderCommand for SetOutlineBindGroup<I> {
@ -162,6 +205,62 @@ impl<const I: usize> EntityRenderCommand for SetOutlineBindGroup<I> {
}
}
fn extract_view_size_uniforms(
mut commands: Commands,
windows: Res<Windows>,
images: Res<Assets<Image>>,
active_camera: Res<ActiveCamera<Camera3d>>,
query: Query<&Camera, With<Camera3d>>,
) {
if let Some(entity) = active_camera.get() {
if let Ok(camera) = query.get(entity) {
if let Some(size) = camera.target.get_logical_size(&windows, &images) {
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,
});
for (entity, _) in views.iter() {
commands.entity(entity).insert(GpuViewSize {
bind_group: bind_group.clone(),
});
}
}
}
#[allow(clippy::too_many_arguments)]
fn queue_outline(
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
@ -232,7 +331,8 @@ fn queue_outline(
pub struct OutlinePipeline {
mesh_pipeline: MeshPipeline,
bind_group_layout: BindGroupLayout,
view_size_bind_group_layout: BindGroupLayout,
outline_bind_group_layout: BindGroupLayout,
}
impl FromWorld for OutlinePipeline {
@ -240,9 +340,25 @@ impl FromWorld for OutlinePipeline {
let world = world.cell();
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap().clone();
let render_device = world.get_resource::<RenderDevice>().unwrap();
let bind_group_layout =
let view_size_bind_group_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("outline_pipeline_bind_group_layout"),
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::std140_size_static() as u64
),
},
count: None,
}],
});
let outline_bind_group_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("outline_bind_group_layout"),
entries: &[
BindGroupLayoutEntry {
binding: 0,
@ -272,7 +388,8 @@ impl FromWorld for OutlinePipeline {
});
OutlinePipeline {
mesh_pipeline,
bind_group_layout,
view_size_bind_group_layout,
outline_bind_group_layout,
}
}
}
@ -292,7 +409,8 @@ impl SpecializedMeshPipeline for OutlinePipeline {
descriptor.layout = Some(vec![
self.mesh_pipeline.view_layout.clone(),
self.mesh_pipeline.mesh_layout.clone(),
self.bind_group_layout.clone(),
self.view_size_bind_group_layout.clone(),
self.outline_bind_group_layout.clone(),
]);
Ok(descriptor)
}

View File

@ -10,6 +10,10 @@ struct VertexOutput {
[[builtin(position)]] clip_position: vec4<f32>;
};
struct ViewSizeUniforms {
logical_size: vec2<f32>;
};
struct VertexStageData {
width: f32;
};
@ -22,9 +26,12 @@ struct FragmentStageData {
var<uniform> mesh: Mesh;
[[group(2), binding(0)]]
var<uniform> view_size: ViewSizeUniforms;
[[group(3), binding(0)]]
var<uniform> vstage: VertexStageData;
[[group(2), binding(1)]]
[[group(3), binding(1)]]
var<uniform> fstage: FragmentStageData;
fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
@ -38,7 +45,7 @@ fn vertex(vertex: Vertex) -> 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>(vstage.width * normalize(clip_norm.xy) * clip_pos.w / vec2<f32>(view.width, view.height), 0.0, 0.0);
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;
return out;
}