diff --git a/Cargo.toml b/Cargo.toml index bc04f24..5ca7ad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,17 +18,18 @@ authors = [ [dependencies] bitflags = "2.3" -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_core_pipeline", "bevy_render", "bevy_asset", ] } +bytemuck = "1.16.1" [dev-dependencies] lazy_static = "1.4.0" rand = "0.8.4" ringbuffer = "0.15" -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_winit", "bevy_pbr", "x11", diff --git a/examples/depth_bias.rs b/examples/depth_bias.rs index 7efd599..fcfa16a 100644 --- a/examples/depth_bias.rs +++ b/examples/depth_bias.rs @@ -80,7 +80,7 @@ fn setup( }), material: materials.add(PolylineMaterial { width: 5.0, - color: Color::RED, + color: bevy::color::palettes::css::RED.into(), depth_bias: -1.0, perspective: false, }), @@ -97,7 +97,7 @@ fn setup( }), material: materials.add(PolylineMaterial { width: 1.0, - color: Color::hsl((bias + 1.0) / 2.0 * 270.0, 1.0, 0.5), + color: Color::hsl((bias + 1.0) / 2.0 * 270.0, 1.0, 0.5).to_linear(), depth_bias: bias, perspective: false, }), diff --git a/examples/linestrip.rs b/examples/linestrip.rs index 3303531..d428021 100644 --- a/examples/linestrip.rs +++ b/examples/linestrip.rs @@ -33,7 +33,7 @@ fn setup( }), material: polyline_materials.add(PolylineMaterial { width: 2.0, - color: Color::RED, + color: bevy::color::palettes::css::RED.into(), perspective: false, // Bias the line toward the camera so the line at the cube-plane intersection is visible depth_bias: -0.0002, @@ -52,7 +52,7 @@ fn setup( // cube commands.spawn(PbrBundle { mesh: meshes.add(Cuboid::from_size(Vec3::ONE)), - material: standard_materials.add(Color::rgb_u8(124, 144, 255)), + material: standard_materials.add(Color::srgb_u8(124, 144, 255)), transform: Transform::from_xyz(0.0, 0.0, 0.0), ..default() }); diff --git a/examples/minimal.rs b/examples/minimal.rs index eb4c004..e5ede7a 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -20,7 +20,7 @@ fn setup( }), material: polyline_materials.add(PolylineMaterial { width: 10.0, - color: Color::RED, + color: bevy::color::palettes::css::RED.into(), perspective: false, ..default() }), diff --git a/examples/nbody.rs b/examples/nbody.rs index ba5a39c..f2f4ba9 100644 --- a/examples/nbody.rs +++ b/examples/nbody.rs @@ -64,7 +64,8 @@ fn setup( }), material: polyline_materials.add(PolylineMaterial { width: (size * 0.1).powf(1.8), - color: Color::hsl(rng.gen_range(0.0..360.0), 1.0, rng.gen_range(1.2..3.0)), + color: Color::hsl(rng.gen_range(0.0..360.0), 1.0, rng.gen_range(1.2..3.0)) + .to_linear(), perspective: true, ..Default::default() }), diff --git a/examples/perspective.rs b/examples/perspective.rs index 0e266a0..315df31 100644 --- a/examples/perspective.rs +++ b/examples/perspective.rs @@ -21,7 +21,7 @@ fn setup( }), material: polyline_materials.add(PolylineMaterial { width: 10.0, - color: Color::RED, + color: bevy::color::palettes::css::RED.into(), perspective: true, ..default() }), diff --git a/src/material.rs b/src/material.rs index f01f9fe..dae8cdd 100644 --- a/src/material.rs +++ b/src/material.rs @@ -4,7 +4,10 @@ use crate::polyline::{ }; use bevy::{ - core_pipeline::core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, + core_pipeline::{ + core_3d::{AlphaMask3d, Opaque3d, Opaque3dBinKey, Transparent3d}, + prepass::OpaqueNoLightmap3dBinKey, + }, ecs::{ query::ROQueryItem, system::{ @@ -16,13 +19,13 @@ use bevy::{ reflect::TypePath, render::{ extract_component::ExtractComponentPlugin, - render_asset::{ - PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages, RenderAssets, - }, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::*, render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderDevice, RenderQueue}, - view::{ExtractedView, ViewUniformOffset, VisibleEntities}, + view::{ + check_visibility, ExtractedView, ViewUniformOffset, VisibilitySystems, VisibleEntities, + }, Render, RenderApp, RenderSet, }, }; @@ -35,7 +38,7 @@ pub struct PolylineMaterial { /// Corresponds to screen pixels when line is positioned nearest the /// camera. pub width: f32, - pub color: Color, + pub color: LinearRgba, /// How closer to the camera than real geometry the line should be. /// /// Value between -1 and 1 (inclusive). @@ -64,7 +67,7 @@ impl Default for PolylineMaterial { fn default() -> Self { Self { width: 10.0, - color: Color::WHITE, + color: Color::WHITE.to_linear(), depth_bias: 0.0, perspective: false, } @@ -83,13 +86,13 @@ impl PolylineMaterial { } #[inline] - fn bind_group(render_asset: &::PreparedAsset) -> &BindGroup { + fn bind_group(render_asset: &GpuPolylineMaterial) -> &BindGroup { &render_asset.bind_group } #[allow(unused_variables)] #[inline] - fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { + fn dynamic_uniform_indices(material: &GpuPolylineMaterial) -> &[u32] { &[] } } @@ -108,33 +111,29 @@ pub struct GpuPolylineMaterial { pub alpha_mode: AlphaMode, } -impl RenderAsset for PolylineMaterial { - type PreparedAsset = GpuPolylineMaterial; +impl RenderAsset for GpuPolylineMaterial { + type SourceAsset = PolylineMaterial; type Param = ( SRes, SRes, SRes, ); - fn asset_usage(&self) -> RenderAssetUsages { - RenderAssetUsages::default() - } - fn prepare_asset( - self, + polyline_material: Self::SourceAsset, (device, queue, polyline_pipeline): &mut bevy::ecs::system::SystemParamItem, - ) -> Result> { + ) -> Result> { let value = PolylineMaterialUniform { - width: self.width, - depth_bias: self.depth_bias, - color: self.color.as_linear_rgba_f32().into(), + width: polyline_material.width, + depth_bias: polyline_material.depth_bias, + color: polyline_material.color.to_f32_array().into(), }; let mut buffer = UniformBuffer::from(value); buffer.write_buffer(device, queue); let Some(buffer_binding) = buffer.binding() else { - return Err(PrepareAssetError::RetryNextUpdate(self)); + return Err(PrepareAssetError::RetryNextUpdate(polyline_material)); }; let bind_group = device.create_bind_group( @@ -143,7 +142,7 @@ impl RenderAsset for PolylineMaterial { &BindGroupEntries::single(buffer_binding), ); - let alpha_mode = if self.color.a() < 1.0 { + let alpha_mode = if polyline_material.color.alpha() < 1.0 { AlphaMode::Blend } else { AlphaMode::Opaque @@ -151,13 +150,15 @@ impl RenderAsset for PolylineMaterial { Ok(GpuPolylineMaterial { buffer, - perspective: self.perspective, + perspective: polyline_material.perspective, alpha_mode, bind_group, }) } } +pub type WithPolyline = With>; + /// Adds the necessary ECS resources and render logic to enable rendering entities using ['PolylineMaterial'] #[derive(Default)] pub struct PolylineMaterialPlugin; @@ -166,11 +167,15 @@ impl Plugin for PolylineMaterialPlugin { fn build(&self, app: &mut App) { app.init_asset::() .add_plugins(ExtractComponentPlugin::>::default()) - .add_plugins(RenderAssetPlugin::::default()); + .add_plugins(RenderAssetPlugin::::default()) + .add_systems( + PostUpdate, + check_visibility::.in_set(VisibilitySystems::CheckVisibility), + ); } fn finish(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::() .add_render_command::() @@ -250,7 +255,7 @@ pub struct SetMaterialBindGroup; impl RenderCommand

for SetMaterialBindGroup { type ViewQuery = (); type ItemQuery = Read>; - type Param = SRes>; + type Param = SRes>; fn render<'w>( _item: &P, @@ -280,15 +285,12 @@ pub fn queue_material_polylines( mut pipelines: ResMut>, pipeline_cache: Res, msaa: Res, - render_materials: Res>, + render_materials: Res>, material_meshes: Query<(&Handle, &PolylineUniform)>, - mut views: Query<( - &ExtractedView, - &VisibleEntities, - &mut RenderPhase, - &mut RenderPhase, - &mut RenderPhase, - )>, + views: Query<(Entity, &ExtractedView, &VisibleEntities)>, + mut opaque_phases: ResMut>, + mut alpha_mask_phases: ResMut>, + mut transparent_phases: ResMut>, ) { let draw_opaque = opaque_draw_functions .read() @@ -303,16 +305,14 @@ pub fn queue_material_polylines( .get_id::() .unwrap(); - for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in - views.iter_mut() - { - let inverse_view_matrix = view.transform.compute_matrix().inverse(); + for (view_entity, view, visible_entities) in &views { + let inverse_view_matrix = view.world_from_view.compute_matrix().inverse(); let inverse_view_row_2 = inverse_view_matrix.row(2); let mut polyline_key = PolylinePipelineKey::from_msaa_samples(msaa.samples()); polyline_key |= PolylinePipelineKey::from_hdr(view.hdr); - for visible_entity in &visible_entities.entities { + for visible_entity in visible_entities.get::() { let Ok((material_handle, polyline_uniform)) = material_meshes.get(*visible_entity) else { continue; @@ -329,51 +329,64 @@ pub fn queue_material_polylines( let pipeline_id = pipelines.specialize(&pipeline_cache, &material_pipeline, polyline_key); + let (mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) = ( + opaque_phases.get_mut(&view_entity), + alpha_mask_phases.get_mut(&view_entity), + transparent_phases.get_mut(&view_entity), + ); + // NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix // gives the z component of translation of the mesh in view space let polyline_z = inverse_view_row_2.dot(polyline_uniform.transform.col(3)); match material.alpha_mode { AlphaMode::Opaque => { - opaque_phase.add(Opaque3d { - entity: *visible_entity, - draw_function: draw_opaque, - pipeline: pipeline_id, - batch_range: 0..1, - dynamic_offset: None, - // The draw command doesn't use a mesh handle so we don't need an `asset_id` - asset_id: AssetId::invalid(), - }); + if let Some(opaque_phase) = opaque_phase.as_mut() { + opaque_phase.add( + Opaque3dBinKey { + pipeline: pipeline_id, + draw_function: draw_opaque, + // The draw command doesn't use a mesh handle so we don't need an `asset_id` + asset_id: AssetId::::invalid().untyped(), + material_bind_group_id: Some(material.bind_group.id()), + lightmap_image: None, + }, + *visible_entity, + BinnedRenderPhaseType::NonMesh, + ); + } } AlphaMode::Mask(_) => { - alpha_mask_phase.add(AlphaMask3d { - entity: *visible_entity, - draw_function: draw_alpha_mask, - pipeline: pipeline_id, - // NOTE: Front-to-back ordering for alpha mask with ascending sort means near should have the - // lowest sort key and getting further away should increase. As we have - // -z in front of the camera, values in view space decrease away from the - // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering - distance: -polyline_z, - batch_range: 0..1, - dynamic_offset: None, - }); + if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { + alpha_mask_phase.add( + OpaqueNoLightmap3dBinKey { + draw_function: draw_alpha_mask, + pipeline: pipeline_id, + asset_id: AssetId::::invalid().untyped(), + material_bind_group_id: Some(material.bind_group.id()), + }, + *visible_entity, + BinnedRenderPhaseType::NonMesh, + ); + } } AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => { - transparent_phase.add(Transparent3d { - entity: *visible_entity, - draw_function: draw_transparent, - pipeline: pipeline_id, - // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the - // lowest sort key and getting closer should increase. As we have - // -z in front of the camera, the largest distance is -far with values increasing toward the - // camera. As such we can just use mesh_z as the distance - distance: polyline_z, - batch_range: 0..1, - dynamic_offset: None, - }); + if let Some(transparent_phase) = transparent_phase.as_mut() { + transparent_phase.add(Transparent3d { + entity: *visible_entity, + draw_function: draw_transparent, + pipeline: pipeline_id, + // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the + // lowest sort key and getting closer should increase. As we have + // -z in front of the camera, the largest distance is -far with values increasing toward the + // camera. As such we can just use mesh_z as the distance + distance: polyline_z, + batch_range: 0..1, + extra_index: PhaseItemExtraIndex::NONE, + }); + } } } } diff --git a/src/polyline.rs b/src/polyline.rs index 2d29962..6cf8b01 100644 --- a/src/polyline.rs +++ b/src/polyline.rs @@ -1,6 +1,5 @@ use crate::material::PolylineMaterial; use bevy::{ - core::cast_slice, ecs::{ query::ROQueryItem, system::{ @@ -12,7 +11,7 @@ use bevy::{ reflect::TypePath, render::{ extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, - render_asset::{RenderAsset, RenderAssetPlugin, RenderAssetUsages, RenderAssets}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{binding_types::uniform_buffer, *}, renderer::RenderDevice, @@ -27,7 +26,7 @@ pub struct PolylineBasePlugin; impl Plugin for PolylineBasePlugin { fn build(&self, app: &mut App) { app.init_asset::() - .add_plugins(RenderAssetPlugin::::default()); + .add_plugins(RenderAssetPlugin::::default()); } } @@ -69,20 +68,16 @@ pub struct Polyline { pub vertices: Vec, } -impl RenderAsset for Polyline { - type PreparedAsset = GpuPolyline; +impl RenderAsset for GpuPolyline { + type SourceAsset = Polyline; type Param = SRes; - fn asset_usage(&self) -> RenderAssetUsages { - RenderAssetUsages::default() - } - fn prepare_asset( - self, + polyline: Self::SourceAsset, render_device: &mut bevy::ecs::system::SystemParamItem, - ) -> Result> { - let vertex_buffer_data = cast_slice(self.vertices.as_slice()); + ) -> Result> { + let vertex_buffer_data = bytemuck::cast_slice(polyline.vertices.as_slice()); let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsages::VERTEX, label: Some("Polyline Vertex Buffer"), @@ -91,7 +86,7 @@ impl RenderAsset for Polyline { Ok(GpuPolyline { vertex_buffer, - vertex_count: self.vertices.len() as u32, + vertex_count: polyline.vertices.len() as u32, }) } } @@ -319,13 +314,15 @@ pub fn prepare_polyline_bind_group( render_device: Res, polyline_uniforms: Res>, ) { - commands.insert_resource(PolylineBindGroup { - value: render_device.create_bind_group( - Some("polyline_bind_group"), - &polyline_pipeline.polyline_layout, - &BindGroupEntries::single(polyline_uniforms.uniforms()), - ), - }); + if let Some(binding) = polyline_uniforms.uniforms().binding() { + commands.insert_resource(PolylineBindGroup { + value: render_device.create_bind_group( + Some("polyline_bind_group"), + &polyline_pipeline.polyline_layout, + &BindGroupEntries::single(binding), + ), + }); + } } #[derive(Component)] @@ -380,7 +377,7 @@ pub struct DrawPolyline; impl RenderCommand

for DrawPolyline { type ViewQuery = (); type ItemQuery = Read>; - type Param = SRes>; + type Param = SRes>; #[inline] fn render<'w>( diff --git a/src/shaders/polyline.wgsl b/src/shaders/polyline.wgsl index 51db16e..47838c2 100644 --- a/src/shaders/polyline.wgsl +++ b/src/shaders/polyline.wgsl @@ -43,8 +43,8 @@ fn vertex(vertex: Vertex) -> VertexOutput { let position = positions[vertex.index]; // algorithm based on https://wwwtyro.net/2019/11/18/instanced-lines.html - var clip0 = view.view_proj * polyline.model * vec4(vertex.point_a, 1.0); - var clip1 = view.view_proj * polyline.model * vec4(vertex.point_b, 1.0); + var clip0 = view.clip_from_world * polyline.model * vec4(vertex.point_a, 1.0); + var clip1 = view.clip_from_world * polyline.model * vec4(vertex.point_b, 1.0); // Manual near plane clipping to avoid errors when doing the perspective divide inside this shader. clip0 = clip_near_plane(clip0, clip1);