From 47831084a55541a6bc9ac2d67c89d04651dd3c22 Mon Sep 17 00:00:00 2001 From: Nathan Date: Thu, 21 Dec 2023 14:20:29 -0700 Subject: [PATCH] feat: render ordering by Z (#18) --- README.md | 8 +-- demo/src/main.rs | 1 - src/lib.rs | 21 +++--- src/renderer/extract.rs | 21 +++--- src/renderer/prepare.rs | 17 +++-- src/renderer/render.rs | 147 ++++++++++++++++------------------------ src/rendertarget.rs | 4 +- 7 files changed, 91 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 98fddf9..c217378 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,7 @@ A bevy plugin which provides rendering support for [lottie](https://lottiefiles. ## Features -- Spawn vector graphics on separate layers - |Layer|Render order| - |---|---| - |Background|Always behind all other layers| - |Ground|2.5D-style render ordering via Y-coordinate| - |Foreground|Always on top of Ground/Background| - |UI|On top of Foreground layer; shows Bevy UI Nodes bundled with a `VelloVector` | +- Spawn vector graphics rendering either in screen-space or world-space coordinates. - Support for fonts - NOTE: to avoid conflict with bevy's built-in font loader, rename fonts used by `bevy-vello` to something else (example: `*.vtff`). This can probably be an improvement in the future. - Debug draw gizmos for the objects local origin (red X) and canvas size (white bounding box) diff --git a/demo/src/main.rs b/demo/src/main.rs index 805ef8a..7e8758f 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -23,7 +23,6 @@ fn setup_vector_graphics(mut commands: Commands, asset_server: ResMut, - /// Configures the draw order within the vello canvas - pub layer: Layer, + /// The coordinate space in which this vector should be rendered. + pub render_mode: RenderMode, /// This object's transform local origin. Enable debug visualizations to visualize (red X) pub origin: Origin, pub transform: Transform, @@ -149,7 +146,7 @@ impl Default for VelloVectorBundle { fn default() -> Self { Self { vector: Default::default(), - layer: Default::default(), + render_mode: RenderMode::WorldSpace, origin: Default::default(), transform: Default::default(), global_transform: Default::default(), @@ -172,7 +169,7 @@ pub struct VelloText { pub struct VelloTextBundle { pub font: Handle, pub text: VelloText, - pub layer: Layer, + pub render_mode: RenderMode, pub transform: Transform, pub global_transform: GlobalTransform, /// User indication of whether an entity is visible @@ -188,7 +185,7 @@ impl Default for VelloTextBundle { Self { font: Default::default(), text: Default::default(), - layer: Layer::Foreground, + render_mode: RenderMode::WorldSpace, transform: Default::default(), global_transform: Default::default(), visibility: Visibility::Inherited, diff --git a/src/renderer/extract.rs b/src/renderer/extract.rs index 0203097..26e7241 100644 --- a/src/renderer/extract.rs +++ b/src/renderer/extract.rs @@ -4,14 +4,14 @@ use bevy::{ window::PrimaryWindow, }; -use crate::{font::VelloFont, ColorPaletteSwap, Layer, Origin, VelloText, VelloVector}; +use crate::{font::VelloFont, ColorPaletteSwap, Origin, RenderMode, VelloText, VelloVector}; #[derive(Component, Clone)] pub struct ExtractedRenderVector { pub vector_handle: Handle, pub render_data: VelloVector, pub transform: GlobalTransform, - pub layer: Layer, + pub render_mode: RenderMode, pub origin: Origin, pub color_pallette_swap: Option, pub ui_node: Option, @@ -22,7 +22,7 @@ pub fn vector_instances( query_vectors: Extract< Query<( &Handle, - &Layer, + &RenderMode, Option<&Origin>, &GlobalTransform, Option<&ColorPaletteSwap>, @@ -35,7 +35,7 @@ pub fn vector_instances( ) { for ( vello_vector_handle, - layer, + render_mode, origin, transform, color_pallette_swap, @@ -50,7 +50,7 @@ pub fn vector_instances( vector_handle: vello_vector_handle.clone(), render_data: asset_data.to_owned(), transform: *transform, - layer: *layer, + render_mode: *render_mode, origin: origin.copied().unwrap_or_default(), color_pallette_swap: color_pallette_swap.cloned(), ui_node: ui_node.cloned(), @@ -65,7 +65,7 @@ pub struct ExtractedRenderText { pub font: Handle, pub text: VelloText, pub transform: GlobalTransform, - pub layer: Layer, + pub render_mode: RenderMode, } impl ExtractComponent for ExtractedRenderText { @@ -73,20 +73,23 @@ impl ExtractComponent for ExtractedRenderText { &'static Handle, &'static VelloText, &'static GlobalTransform, - &'static Layer, + &'static RenderMode, ); type Filter = (); type Out = Self; fn extract_component( - (vello_font_handle, text, transform, layer): bevy::ecs::query::QueryItem<'_, Self::Query>, + (vello_font_handle, text, transform, render_mode): bevy::ecs::query::QueryItem< + '_, + Self::Query, + >, ) -> Option { Some(Self { font: vello_font_handle.clone(), text: text.clone(), transform: *transform, - layer: *layer, + render_mode: *render_mode, }) } } diff --git a/src/renderer/prepare.rs b/src/renderer/prepare.rs index e567c3a..a2a0cbb 100644 --- a/src/renderer/prepare.rs +++ b/src/renderer/prepare.rs @@ -6,7 +6,7 @@ use vello::kurbo::Affine; use crate::{ assets::vector::{Vector, VelloVector}, - ColorPaletteSwap, Layer, + ColorPaletteSwap, RenderMode, }; use super::extract::{ExtractedPixelScale, ExtractedRenderText, ExtractedRenderVector}; @@ -96,12 +96,11 @@ pub fn prepare_vector_affines( None => continue, }; - // The vello scene transform is world-space for all normal vectors and screen-space for UI vectors - let raw_transform = match render_vector.layer { - Layer::UI => { + let raw_transform = match render_vector.render_mode { + RenderMode::ScreenSpace => { let mut model_matrix = world_transform.compute_matrix().mul_scalar(pixel_scale.0); - // Make the UI vector instance sized to fill the entire UI Node box if it's bundled with a Node + // Make the screen space vector instance sized to fill the entire UI Node box if it's bundled with a Node if let Some(node) = &render_vector.ui_node { let fill_scale = node.size() / vector_size; model_matrix.x_axis.x *= fill_scale.x; @@ -112,7 +111,7 @@ pub fn prepare_vector_affines( local_center_matrix.w_axis.y *= -1.0; model_matrix * local_center_matrix } - _ => { + RenderMode::WorldSpace => { let local_matrix = match render_vector.origin { crate::Origin::BottomCenter => local_bottom_center_matrix, crate::Origin::Center => local_center_matrix, @@ -189,9 +188,9 @@ pub fn prepare_text_affines( let view_proj_matrix = projection_mat * view_mat.inverse(); let vello_matrix = ndc_to_pixels_matrix * view_proj_matrix; - let raw_transform = match render_text.layer { - Layer::UI => world_transform.compute_matrix().mul_scalar(pixel_scale.0), - _ => vello_matrix * model_matrix, + let raw_transform = match render_text.render_mode { + RenderMode::ScreenSpace => world_transform.compute_matrix().mul_scalar(pixel_scale.0), + RenderMode::WorldSpace => vello_matrix * model_matrix, }; let transform: [f32; 16] = raw_transform.to_cols_array(); diff --git a/src/renderer/render.rs b/src/renderer/render.rs index d054057..8a18967 100644 --- a/src/renderer/render.rs +++ b/src/renderer/render.rs @@ -11,7 +11,7 @@ use vello::{RenderParams, Scene, SceneBuilder}; use crate::{ assets::vector::{Vector, VelloVector}, font::VelloFont, - Layer, + RenderMode, }; use super::{ @@ -104,100 +104,71 @@ pub fn render_scene( let mut scene = Scene::default(); let mut builder = SceneBuilder::for_scene(&mut scene); - // Background items: z ordered - let mut vector_render_queue: Vec<(_, _)> = render_vectors - .iter() - .filter(|(_, v)| v.layer == Layer::Background) - .collect(); - vector_render_queue.sort_by(|(_, a), (_, b)| { - let a = a.transform.translation().z; - let b = b.transform.translation().z; - a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal) - }); - - // Shadow items: z ordered - let mut shadow_items: Vec<(_, _)> = render_vectors - .iter() - .filter(|(_, v)| v.layer == Layer::Shadow) - .collect(); - shadow_items.sort_by(|(_, a), (_, b)| { - let a = a.transform.translation().z; - let b = b.transform.translation().z; - a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal) - }); - vector_render_queue.append(&mut shadow_items); - - // Ground items: y ordered - let mut middle_items: Vec<(_, _)> = render_vectors - .iter() - .filter(|(_, v)| v.layer == Layer::Ground) - .collect(); - middle_items.sort_by(|(_, a), (_, b)| { - let a = a.transform.translation().y; - let b = b.transform.translation().y; - b.partial_cmp(&a).unwrap_or(std::cmp::Ordering::Equal) - }); - vector_render_queue.append(&mut middle_items); - - // Foreground items: z ordered - let mut fg_items: Vec<(_, _)> = render_vectors - .iter() - .filter(|(_, v)| v.layer == Layer::Foreground) - .collect(); - fg_items.sort_by(|(_, a), (_, b)| { - let a = a.transform.translation().z; - let b = b.transform.translation().z; - a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal) - }); - vector_render_queue.append(&mut fg_items); - - // UI items - let mut ui_items: Vec<(_, _)> = render_vectors - .iter() - .filter(|(_, v)| v.layer == Layer::UI) - .collect(); - ui_items.sort_by(|(_, a), (_, b)| { - let a = a.transform.translation().z; - let b = b.transform.translation().z; - a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal) - }); - vector_render_queue.append(&mut ui_items); + enum RenderItem<'a> { + Vector(&'a ExtractedRenderVector), + Text(&'a ExtractedRenderText), + } + let mut render_queue: Vec<(f32, RenderMode, (&PreparedAffine, RenderItem))> = + render_vectors + .iter() + .map(|(a, b)| { + ( + b.transform.translation().z, + b.render_mode, + (a, RenderItem::Vector(b)), + ) + }) + .collect(); + render_queue.extend(query_render_texts.iter().map(|(a, b)| { + ( + b.transform.translation().z, + b.render_mode, + (a, RenderItem::Text(b)), + ) + })); + + // Sort by render mode with screen space on top, then by z-index + render_queue.sort_by( + |(a_z_index, a_render_mode, _), (b_z_index, b_render_mode, _)| { + let z_index = a_z_index + .partial_cmp(b_z_index) + .unwrap_or(std::cmp::Ordering::Equal); + let render_mode = a_render_mode.cmp(b_render_mode); + + render_mode.then(z_index) + }, + ); // Apply transforms to the respective fragments and add them to the // scene to be rendered - for ( - PreparedAffine(affine), - ExtractedRenderVector { - render_data: vector_data, - .. - }, - ) in vector_render_queue.iter() - { - match &vector_data.data { - Vector::Static(fragment) => { - builder.append(fragment, Some(*affine)); - } - Vector::Animated(composition) => { - velato_renderer.0.render( - composition, - time.elapsed_seconds(), - *affine, - 1.0, - &mut builder, - ); + for (_, _, (&PreparedAffine(affine), render_item)) in render_queue.iter() { + match render_item { + RenderItem::Vector(ExtractedRenderVector { + render_data: vector_data, + .. + }) => match &vector_data.data { + Vector::Static(fragment) => { + builder.append(fragment, Some(affine)); + } + Vector::Animated(composition) => { + velato_renderer.0.render( + composition, + time.elapsed_seconds(), + affine, + 1.0, + &mut builder, + ); + } + }, + RenderItem::Text(ExtractedRenderText { font, text, .. }) => { + if let Some(font) = font_render_assets.get_mut(font) { + font.render_centered(&mut builder, text.size, affine, &text.content); + } } } } - for (&PreparedAffine(affine), ExtractedRenderText { font, text, .. }) in - query_render_texts.iter() - { - if let Some(font) = font_render_assets.get_mut(font) { - font.render_centered(&mut builder, text.size, affine, &text.content); - } - } - - if !vector_render_queue.is_empty() { + if !render_queue.is_empty() { renderer .0 .render_to_texture( diff --git a/src/rendertarget.rs b/src/rendertarget.rs index f61b134..30e8de2 100644 --- a/src/rendertarget.rs +++ b/src/rendertarget.rs @@ -16,7 +16,7 @@ use bevy::{ window::{WindowResized, WindowResolution}, }; -use crate::{renderer::SSRenderTarget, Layer, SSRT_SHADER_HANDLE}; +use crate::{renderer::SSRenderTarget, RenderMode, SSRT_SHADER_HANDLE}; #[derive(Component)] struct MainCamera; @@ -199,7 +199,7 @@ impl Material2d for VelloCanvasMaterial { /// Hide the RenderTarget canvas if there is nothing to render pub fn clear_when_empty( mut query_render_target: Query<&mut Visibility, With>, - render_items: Query<(&mut Layer, &ViewVisibility)>, + render_items: Query<(&mut RenderMode, &ViewVisibility)>, ) { if let Ok(mut visibility) = query_render_target.get_single_mut() { if render_items.is_empty() {