mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
Instanced line rendering for gizmos based on bevy_polyline
(#8427)
# Objective Adopt code from [bevy_polyline](https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline) for gizmo line-rendering. This adds configurable width and perspective rendering for the lines. Many thanks to @mtsr for the initial work on bevy_polyline. Thanks to @aevyrie for maintaining it, @nicopap for adding the depth_bias feature and the other [contributors](https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline/graphs/contributors) for squashing bugs and keeping bevy_polyline up-to-date. #### Before ![Before](https://user-images.githubusercontent.com/29694403/232831591-a8e6ed0c-3a09-4413-80fa-74cb8e0d33dd.png) #### After - with line perspective ![After](https://user-images.githubusercontent.com/29694403/232831692-ba7cbeb7-e63a-4f8e-9b1b-1b80c668f149.png) Line perspective is not on by default because with perspective there is no default line width that works for every scene. <details><summary>After - without line perspective</summary> <p> ![After - no perspective](https://user-images.githubusercontent.com/29694403/232836344-0dbfb4c8-09b7-4cf5-95f9-a4c26f38dca3.png) </p> </details> Somewhat unexpectedly, the performance is improved with this PR. At 200,000 lines in many_gizmos I get ~110 FPS on main and ~200 FPS with this PR. I'm guessing this is a CPU side difference as I would expect the rendering technique to be more expensive on the GPU to some extent, but I am not entirely sure. --------- Co-authored-by: Jonas Matser <github@jonasmatser.nl> Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Nicola Papale <nico@nicopap.ch> Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
This commit is contained in:
parent
3fba34c9e6
commit
001b3eb97c
9 changed files with 652 additions and 336 deletions
|
@ -8,6 +8,9 @@ repository = "https://github.com/bevyengine/bevy"
|
|||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[features]
|
||||
webgl = []
|
||||
|
||||
[dependencies]
|
||||
# Bevy
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.11.0-dev", optional = true }
|
||||
|
|
|
@ -19,36 +19,40 @@
|
|||
use std::mem;
|
||||
|
||||
use bevy_app::{Last, Plugin, Update};
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
|
||||
use bevy_asset::{load_internal_asset, AddAsset, Assets, Handle, HandleUntyped};
|
||||
use bevy_core::cast_slice;
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChanges,
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
query::Without,
|
||||
query::{ROQueryItem, Without},
|
||||
reflect::ReflectComponent,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
system::{
|
||||
lifetimeless::{Read, SRes},
|
||||
Commands, Query, Res, ResMut, Resource, SystemParamItem,
|
||||
},
|
||||
};
|
||||
use bevy_math::Mat4;
|
||||
use bevy_reflect::{
|
||||
std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect, TypeUuid,
|
||||
std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect, TypePath, TypeUuid,
|
||||
};
|
||||
use bevy_render::{
|
||||
color::Color,
|
||||
mesh::Mesh,
|
||||
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
||||
primitives::Aabb,
|
||||
render_phase::AddRenderCommand,
|
||||
render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
|
||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
|
||||
BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, BufferInitDescriptor,
|
||||
BufferUsages, Shader, ShaderStages, ShaderType, VertexAttribute, VertexBufferLayout,
|
||||
VertexFormat, VertexStepMode,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
use bevy_pbr::MeshUniform;
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
use bevy_sprite::{Mesh2dHandle, Mesh2dUniform};
|
||||
|
||||
pub mod gizmos;
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
|
@ -74,7 +78,10 @@ impl Plugin for GizmoPlugin {
|
|||
fn build(&self, app: &mut bevy_app::App) {
|
||||
load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.init_resource::<MeshHandles>()
|
||||
app.add_plugin(UniformComponentPlugin::<LineGizmoUniform>::default())
|
||||
.add_asset::<LineGizmo>()
|
||||
.add_plugin(RenderAssetPlugin::<LineGizmo>::default())
|
||||
.init_resource::<LineGizmoHandles>()
|
||||
.init_resource::<GizmoConfig>()
|
||||
.init_resource::<GizmoStorage>()
|
||||
.add_systems(Last, update_gizmo_meshes)
|
||||
|
@ -88,47 +95,35 @@ impl Plugin for GizmoPlugin {
|
|||
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
||||
|
||||
render_app.add_systems(ExtractSchedule, extract_gizmo_data);
|
||||
render_app
|
||||
.add_systems(ExtractSchedule, extract_gizmo_data)
|
||||
.add_systems(Render, queue_line_gizmo_bind_group.in_set(RenderSet::Queue));
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
{
|
||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||
use pipeline_2d::*;
|
||||
|
||||
render_app
|
||||
.add_render_command::<Transparent2d, DrawGizmoLines>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoLinePipeline>>()
|
||||
.add_systems(Render, queue_gizmos_2d.in_set(RenderSet::Queue));
|
||||
}
|
||||
|
||||
app.add_plugin(pipeline_2d::LineGizmo2dPlugin);
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
{
|
||||
use bevy_core_pipeline::core_3d::Opaque3d;
|
||||
use pipeline_3d::*;
|
||||
|
||||
render_app
|
||||
.add_render_command::<Opaque3d, DrawGizmoLines>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoPipeline>>()
|
||||
.add_systems(Render, queue_gizmos_3d.in_set(RenderSet::Queue));
|
||||
}
|
||||
app.add_plugin(pipeline_3d::LineGizmo3dPlugin);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
{
|
||||
use pipeline_2d::*;
|
||||
let render_device = render_app.world.resource::<RenderDevice>();
|
||||
let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::VERTEX,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: Some(LineGizmoUniform::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("LineGizmoUniform layout"),
|
||||
});
|
||||
|
||||
render_app.init_resource::<GizmoLinePipeline>();
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
{
|
||||
use pipeline_3d::*;
|
||||
|
||||
render_app.init_resource::<GizmoPipeline>();
|
||||
}
|
||||
render_app.insert_resource(LineGizmoUniformBindgroupLayout { layout });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,12 +134,29 @@ pub struct GizmoConfig {
|
|||
///
|
||||
/// Defaults to `true`.
|
||||
pub enabled: bool,
|
||||
/// Draw gizmos on top of everything else, ignoring depth.
|
||||
/// Line width specified in pixels.
|
||||
///
|
||||
/// This setting only affects 3D. In 2D, gizmos are always drawn on top.
|
||||
/// If `line_perspective` is `true` then this is the size in pixels at the camera's near plane.
|
||||
///
|
||||
/// Defaults to `2.0`.
|
||||
pub line_width: f32,
|
||||
/// Apply perspective to gizmo lines.
|
||||
///
|
||||
/// This setting only affects 3D, non-orhographic cameras.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
pub on_top: bool,
|
||||
pub line_perspective: bool,
|
||||
/// How closer to the camera than real geometry the line should be.
|
||||
///
|
||||
/// Value between -1 and 1 (inclusive).
|
||||
/// * 0 means that there is no change to the line position when rendering
|
||||
/// * 1 means it is furthest away from camera as possible
|
||||
/// * -1 means that it will always render in front of other things.
|
||||
///
|
||||
/// This is typically useful if you are drawing wireframes on top of polygons
|
||||
/// and your wireframe is z-fighting (flickering on/off) with your main model.
|
||||
/// You would set this value to a negative number close to 0.0.
|
||||
pub depth_bias: f32,
|
||||
/// Configuration for the [`AabbGizmo`].
|
||||
pub aabb: AabbGizmoConfig,
|
||||
}
|
||||
|
@ -153,7 +165,9 @@ impl Default for GizmoConfig {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
on_top: false,
|
||||
line_width: 2.,
|
||||
line_perspective: false,
|
||||
depth_bias: 0.,
|
||||
aabb: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -227,77 +241,59 @@ fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {
|
|||
)
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct MeshHandles {
|
||||
list: Option<Handle<Mesh>>,
|
||||
strip: Option<Handle<Mesh>>,
|
||||
#[derive(Resource, Default)]
|
||||
struct LineGizmoHandles {
|
||||
list: Option<Handle<LineGizmo>>,
|
||||
strip: Option<Handle<LineGizmo>>,
|
||||
}
|
||||
|
||||
impl FromWorld for MeshHandles {
|
||||
fn from_world(_world: &mut World) -> Self {
|
||||
MeshHandles {
|
||||
list: None,
|
||||
strip: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct GizmoMesh;
|
||||
|
||||
fn update_gizmo_meshes(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut handles: ResMut<MeshHandles>,
|
||||
mut line_gizmos: ResMut<Assets<LineGizmo>>,
|
||||
mut handles: ResMut<LineGizmoHandles>,
|
||||
mut storage: ResMut<GizmoStorage>,
|
||||
) {
|
||||
if storage.list_positions.is_empty() {
|
||||
handles.list = None;
|
||||
} else if let Some(handle) = handles.list.as_ref() {
|
||||
let list_mesh = meshes.get_mut(handle).unwrap();
|
||||
let list = line_gizmos.get_mut(handle).unwrap();
|
||||
|
||||
let positions = mem::take(&mut storage.list_positions);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
|
||||
let colors = mem::take(&mut storage.list_colors);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
list.positions = mem::take(&mut storage.list_positions);
|
||||
list.colors = mem::take(&mut storage.list_colors);
|
||||
} else {
|
||||
let mut list_mesh = Mesh::new(PrimitiveTopology::LineList);
|
||||
let mut list = LineGizmo {
|
||||
strip: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let positions = mem::take(&mut storage.list_positions);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
list.positions = mem::take(&mut storage.list_positions);
|
||||
list.colors = mem::take(&mut storage.list_colors);
|
||||
|
||||
let colors = mem::take(&mut storage.list_colors);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
|
||||
handles.list = Some(meshes.add(list_mesh));
|
||||
handles.list = Some(line_gizmos.add(list));
|
||||
}
|
||||
|
||||
if storage.strip_positions.is_empty() {
|
||||
handles.strip = None;
|
||||
} else if let Some(handle) = handles.strip.as_ref() {
|
||||
let strip_mesh = meshes.get_mut(handle).unwrap();
|
||||
let strip = line_gizmos.get_mut(handle).unwrap();
|
||||
|
||||
let positions = mem::take(&mut storage.strip_positions);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
|
||||
let colors = mem::take(&mut storage.strip_colors);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
strip.positions = mem::take(&mut storage.strip_positions);
|
||||
strip.colors = mem::take(&mut storage.strip_colors);
|
||||
} else {
|
||||
let mut strip_mesh = Mesh::new(PrimitiveTopology::LineStrip);
|
||||
let mut strip = LineGizmo {
|
||||
strip: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let positions = mem::take(&mut storage.strip_positions);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
strip.positions = mem::take(&mut storage.strip_positions);
|
||||
strip.colors = mem::take(&mut storage.strip_colors);
|
||||
|
||||
let colors = mem::take(&mut storage.strip_colors);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
|
||||
handles.strip = Some(meshes.add(strip_mesh));
|
||||
handles.strip = Some(line_gizmos.add(strip));
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_gizmo_data(
|
||||
mut commands: Commands,
|
||||
handles: Extract<Res<MeshHandles>>,
|
||||
handles: Extract<Res<LineGizmoHandles>>,
|
||||
config: Extract<Res<GizmoConfig>>,
|
||||
) {
|
||||
if config.is_changed() {
|
||||
|
@ -308,35 +304,206 @@ fn extract_gizmo_data(
|
|||
return;
|
||||
}
|
||||
|
||||
let transform = Mat4::IDENTITY;
|
||||
let inverse_transpose_model = transform.inverse().transpose();
|
||||
commands.spawn_batch(
|
||||
[handles.list.clone(), handles.strip.clone()]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(move |handle| {
|
||||
(
|
||||
GizmoMesh,
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
(
|
||||
handle.clone_weak(),
|
||||
MeshUniform {
|
||||
flags: 0,
|
||||
transform,
|
||||
previous_transform: transform,
|
||||
inverse_transpose_model,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
(
|
||||
Mesh2dHandle(handle),
|
||||
Mesh2dUniform {
|
||||
flags: 0,
|
||||
transform,
|
||||
inverse_transpose_model,
|
||||
},
|
||||
),
|
||||
)
|
||||
}),
|
||||
);
|
||||
for handle in [&handles.list, &handles.strip].into_iter().flatten() {
|
||||
commands.spawn((
|
||||
LineGizmoUniform {
|
||||
line_width: config.line_width,
|
||||
depth_bias: config.depth_bias,
|
||||
#[cfg(feature = "webgl")]
|
||||
_padding: Default::default(),
|
||||
},
|
||||
handle.clone_weak(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, ShaderType, Clone, Copy)]
|
||||
struct LineGizmoUniform {
|
||||
line_width: f32,
|
||||
depth_bias: f32,
|
||||
/// WebGL2 structs must be 16 byte aligned.
|
||||
#[cfg(feature = "webgl")]
|
||||
_padding: bevy_math::Vec2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, TypeUuid, TypePath)]
|
||||
#[uuid = "02b99cbf-bb26-4713-829a-aee8e08dedc0"]
|
||||
struct LineGizmo {
|
||||
positions: Vec<[f32; 3]>,
|
||||
colors: Vec<[f32; 4]>,
|
||||
/// Whether this gizmo's topology is a line-strip or line-list
|
||||
strip: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct GpuLineGizmo {
|
||||
position_buffer: Buffer,
|
||||
color_buffer: Buffer,
|
||||
vertex_count: u32,
|
||||
strip: bool,
|
||||
}
|
||||
|
||||
impl RenderAsset for LineGizmo {
|
||||
type ExtractedAsset = LineGizmo;
|
||||
|
||||
type PreparedAsset = GpuLineGizmo;
|
||||
|
||||
type Param = SRes<RenderDevice>;
|
||||
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
line_gizmo: Self::ExtractedAsset,
|
||||
render_device: &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
let position_buffer_data = cast_slice(&line_gizmo.positions);
|
||||
let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
usage: BufferUsages::VERTEX,
|
||||
label: Some("LineGizmo Position Buffer"),
|
||||
contents: position_buffer_data,
|
||||
});
|
||||
|
||||
let color_buffer_data = cast_slice(&line_gizmo.colors);
|
||||
let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
usage: BufferUsages::VERTEX,
|
||||
label: Some("LineGizmo Color Buffer"),
|
||||
contents: color_buffer_data,
|
||||
});
|
||||
|
||||
Ok(GpuLineGizmo {
|
||||
position_buffer,
|
||||
color_buffer,
|
||||
vertex_count: line_gizmo.positions.len() as u32,
|
||||
strip: line_gizmo.strip,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct LineGizmoUniformBindgroupLayout {
|
||||
layout: BindGroupLayout,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct LineGizmoUniformBindgroup {
|
||||
bindgroup: BindGroup,
|
||||
}
|
||||
|
||||
fn queue_line_gizmo_bind_group(
|
||||
mut commands: Commands,
|
||||
line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
|
||||
render_device: Res<RenderDevice>,
|
||||
line_gizmo_uniforms: Res<ComponentUniforms<LineGizmoUniform>>,
|
||||
) {
|
||||
if let Some(binding) = line_gizmo_uniforms.uniforms().binding() {
|
||||
commands.insert_resource(LineGizmoUniformBindgroup {
|
||||
bindgroup: render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: binding,
|
||||
}],
|
||||
label: Some("LineGizmoUniform bindgroup"),
|
||||
layout: &line_gizmo_uniform_layout.layout,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct SetLineGizmoBindGroup<const I: usize>;
|
||||
impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I> {
|
||||
type ViewWorldQuery = ();
|
||||
type ItemWorldQuery = Read<DynamicUniformIndex<LineGizmoUniform>>;
|
||||
type Param = SRes<LineGizmoUniformBindgroup>;
|
||||
|
||||
#[inline]
|
||||
fn render<'w>(
|
||||
_item: &P,
|
||||
_view: ROQueryItem<'w, Self::ViewWorldQuery>,
|
||||
uniform_index: ROQueryItem<'w, Self::ItemWorldQuery>,
|
||||
bind_group: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
pass.set_bind_group(
|
||||
I,
|
||||
&bind_group.into_inner().bindgroup,
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
struct DrawLineGizmo;
|
||||
impl<P: PhaseItem> RenderCommand<P> for DrawLineGizmo {
|
||||
type ViewWorldQuery = ();
|
||||
type ItemWorldQuery = Read<Handle<LineGizmo>>;
|
||||
type Param = SRes<RenderAssets<LineGizmo>>;
|
||||
|
||||
#[inline]
|
||||
fn render<'w>(
|
||||
_item: &P,
|
||||
_view: ROQueryItem<'w, Self::ViewWorldQuery>,
|
||||
handle: ROQueryItem<'w, Self::ItemWorldQuery>,
|
||||
line_gizmos: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else {
|
||||
return RenderCommandResult::Failure;
|
||||
};
|
||||
|
||||
pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..));
|
||||
pass.set_vertex_buffer(1, line_gizmo.color_buffer.slice(..));
|
||||
|
||||
let instances = if line_gizmo.strip {
|
||||
u32::max(line_gizmo.vertex_count, 1) - 1
|
||||
} else {
|
||||
line_gizmo.vertex_count / 2
|
||||
};
|
||||
|
||||
pass.draw(0..6, 0..instances);
|
||||
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
|
||||
let stride_multiplier = if strip { 1 } else { 2 };
|
||||
use VertexFormat::*;
|
||||
vec![
|
||||
// Positions
|
||||
VertexBufferLayout {
|
||||
array_stride: Float32x3.size() * stride_multiplier,
|
||||
step_mode: VertexStepMode::Instance,
|
||||
attributes: vec![
|
||||
VertexAttribute {
|
||||
format: Float32x3,
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
},
|
||||
VertexAttribute {
|
||||
format: Float32x3,
|
||||
offset: Float32x3.size(),
|
||||
shader_location: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Colors
|
||||
VertexBufferLayout {
|
||||
array_stride: Float32x4.size() * stride_multiplier,
|
||||
step_mode: VertexStepMode::Instance,
|
||||
attributes: vec![
|
||||
VertexAttribute {
|
||||
format: Float32x4,
|
||||
offset: 0,
|
||||
shader_location: 2,
|
||||
},
|
||||
VertexAttribute {
|
||||
format: Float32x4,
|
||||
offset: Float32x4.size(),
|
||||
shader_location: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,48 +1,105 @@
|
|||
#ifdef GIZMO_LINES_3D
|
||||
#ifdef GIZMO_3D
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
#else
|
||||
#import bevy_sprite::mesh2d_view_bindings
|
||||
#endif
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) pos: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
struct LineGizmoUniform {
|
||||
line_width: f32,
|
||||
depth_bias: f32,
|
||||
#ifdef SIXTEEN_BYTE_ALIGNMENT
|
||||
// WebGL2 structs must be 16 byte aligned.
|
||||
_padding: vec2<f32>,
|
||||
#endif
|
||||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> line_gizmo: LineGizmoUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position_a: vec3<f32>,
|
||||
@location(1) position_b: vec3<f32>,
|
||||
@location(2) color_a: vec4<f32>,
|
||||
@location(3) color_b: vec4<f32>,
|
||||
@builtin(vertex_index) index: u32,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) pos: vec4<f32>,
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct FragmentOutput {
|
||||
#ifdef GIZMO_LINES_3D
|
||||
@builtin(frag_depth) depth: f32,
|
||||
#endif
|
||||
@location(0) color: vec4<f32>,
|
||||
}
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex(in: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
var positions = array<vec3<f32>, 6>(
|
||||
vec3(0., -0.5, 0.),
|
||||
vec3(0., -0.5, 1.),
|
||||
vec3(0., 0.5, 1.),
|
||||
vec3(0., -0.5, 0.),
|
||||
vec3(0., 0.5, 1.),
|
||||
vec3(0., 0.5, 0.)
|
||||
);
|
||||
let position = positions[vertex.index];
|
||||
|
||||
out.pos = view.view_proj * vec4<f32>(in.pos, 1.0);
|
||||
out.color = in.color;
|
||||
// algorithm based on https://wwwtyro.net/2019/11/18/instanced-lines.html
|
||||
let clip_a = view.view_proj * vec4(vertex.position_a, 1.);
|
||||
let clip_b = view.view_proj * vec4(vertex.position_b, 1.);
|
||||
let clip = mix(clip_a, clip_b, position.z);
|
||||
|
||||
return out;
|
||||
let resolution = view.viewport.zw;
|
||||
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
|
||||
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
|
||||
|
||||
let x_basis = normalize(screen_a - screen_b);
|
||||
let y_basis = vec2(-x_basis.y, x_basis.x);
|
||||
|
||||
var color = mix(vertex.color_a, vertex.color_b, position.z);
|
||||
|
||||
var line_width = line_gizmo.line_width;
|
||||
var alpha = 1.;
|
||||
|
||||
#ifdef PERSPECTIVE
|
||||
line_width /= clip.w;
|
||||
#endif
|
||||
|
||||
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
|
||||
if line_width < 1. {
|
||||
color.a *= line_width;
|
||||
line_width = 1.;
|
||||
}
|
||||
|
||||
let offset = line_width * (position.x * x_basis + position.y * y_basis);
|
||||
let screen = mix(screen_a, screen_b, position.z) + offset;
|
||||
|
||||
var depth: f32;
|
||||
if line_gizmo.depth_bias >= 0. {
|
||||
depth = clip.z * (1. - line_gizmo.depth_bias);
|
||||
} else {
|
||||
let epsilon = 4.88e-04;
|
||||
// depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
|
||||
// and when equal to 0.0, it is exactly equal to depth.
|
||||
// the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0
|
||||
// clip.w represents the near plane in homogenous clip space in bevy, having a depth
|
||||
// of this value means nothing can be in front of this
|
||||
// The reason this uses an exponential function is that it makes it much easier for the
|
||||
// user to chose a value that is convinient for them
|
||||
depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - epsilon));
|
||||
}
|
||||
|
||||
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
|
||||
|
||||
return VertexOutput(clip_position, color);
|
||||
}
|
||||
|
||||
struct FragmentInput {
|
||||
@location(0) color: vec4<f32>,
|
||||
};
|
||||
|
||||
struct FragmentOutput {
|
||||
@location(0) color: vec4<f32>,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> FragmentOutput {
|
||||
var out: FragmentOutput;
|
||||
|
||||
#ifdef GIZMO_LINES_3D
|
||||
#ifdef DEPTH_TEST
|
||||
out.depth = in.pos.z;
|
||||
#else
|
||||
out.depth = 1.0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
out.color = in.color;
|
||||
return out;
|
||||
fn fragment(in: FragmentInput) -> FragmentOutput {
|
||||
return FragmentOutput(in.color);
|
||||
}
|
||||
|
|
|
@ -1,68 +1,101 @@
|
|||
use crate::{
|
||||
line_gizmo_vertex_buffer_layouts, DrawLineGizmo, LineGizmo, LineGizmoUniformBindgroupLayout,
|
||||
SetLineGizmoBindGroup, LINE_SHADER_HANDLE,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||
|
||||
use bevy_ecs::{
|
||||
prelude::Entity,
|
||||
query::With,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_render::{
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_sprite::*;
|
||||
use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup};
|
||||
use bevy_utils::FloatOrd;
|
||||
|
||||
use crate::{GizmoMesh, LINE_SHADER_HANDLE};
|
||||
pub struct LineGizmo2dPlugin;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct GizmoLinePipeline {
|
||||
mesh_pipeline: Mesh2dPipeline,
|
||||
shader: Handle<Shader>,
|
||||
impl Plugin for LineGizmo2dPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
|
||||
|
||||
render_app
|
||||
.add_render_command::<Transparent2d, DrawLineGizmo2d>()
|
||||
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
|
||||
.add_systems(Render, queue_line_gizmos_2d.in_set(RenderSet::Queue));
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
|
||||
|
||||
render_app.init_resource::<LineGizmoPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for GizmoLinePipeline {
|
||||
#[derive(Clone, Resource)]
|
||||
struct LineGizmoPipeline {
|
||||
mesh_pipeline: Mesh2dPipeline,
|
||||
uniform_layout: BindGroupLayout,
|
||||
}
|
||||
|
||||
impl FromWorld for LineGizmoPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
GizmoLinePipeline {
|
||||
LineGizmoPipeline {
|
||||
mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
uniform_layout: render_world
|
||||
.resource::<LineGizmoUniformBindgroupLayout>()
|
||||
.layout
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for GizmoLinePipeline {
|
||||
type Key = Mesh2dPipelineKey;
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
struct LineGizmoPipelineKey {
|
||||
mesh_key: Mesh2dPipelineKey,
|
||||
strip: bool,
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
type Key = LineGizmoPipelineKey;
|
||||
|
||||
let format = if key.contains(Mesh2dPipelineKey::HDR) {
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
};
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
let shader_defs = vec![
|
||||
#[cfg(feature = "webgl")]
|
||||
"SIXTEEN_BYTE_ALIGNMENT".into(),
|
||||
];
|
||||
|
||||
let layout = vec![
|
||||
self.mesh_pipeline.view_layout.clone(),
|
||||
self.uniform_layout.clone(),
|
||||
];
|
||||
|
||||
RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: vec![],
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
shader_defs: shader_defs.clone(),
|
||||
buffers: line_gizmo_vertex_buffer_layouts(key.strip),
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader_defs: vec![],
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
|
@ -70,57 +103,61 @@ impl SpecializedMeshPipeline for GizmoLinePipeline {
|
|||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
layout: vec![self.mesh_pipeline.view_layout.clone()],
|
||||
primitive: PrimitiveState {
|
||||
topology: key.primitive_topology(),
|
||||
..Default::default()
|
||||
},
|
||||
layout,
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
count: key.mesh_key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: Some("LineGizmo Pipeline 2D".into()),
|
||||
push_constant_ranges: vec![],
|
||||
label: Some("gizmo_2d_pipeline".into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawGizmoLines = (
|
||||
type DrawLineGizmo2d = (
|
||||
SetItemPipeline,
|
||||
SetMesh2dViewBindGroup<0>,
|
||||
SetMesh2dBindGroup<1>,
|
||||
DrawMesh2d,
|
||||
SetLineGizmoBindGroup<1>,
|
||||
DrawLineGizmo,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn queue_gizmos_2d(
|
||||
fn queue_line_gizmos_2d(
|
||||
draw_functions: Res<DrawFunctions<Transparent2d>>,
|
||||
pipeline: Res<GizmoLinePipeline>,
|
||||
pipeline: Res<LineGizmoPipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut specialized_pipelines: ResMut<SpecializedMeshPipelines<GizmoLinePipeline>>,
|
||||
gpu_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
mesh_handles: Query<(Entity, &Mesh2dHandle), With<GizmoMesh>>,
|
||||
line_gizmos: Query<(Entity, &Handle<LineGizmo>)>,
|
||||
line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent2d>)>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
|
||||
let key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples());
|
||||
for (view, mut phase) in &mut views {
|
||||
let key = key | Mesh2dPipelineKey::from_hdr(view.hdr);
|
||||
for (entity, mesh_handle) in &mesh_handles {
|
||||
let Some(mesh) = gpu_meshes.get(&mesh_handle.0) else { continue; };
|
||||
let draw_function = draw_functions.read().get_id::<DrawLineGizmo2d>().unwrap();
|
||||
|
||||
let key = key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = specialized_pipelines
|
||||
.specialize(&pipeline_cache, &pipeline, key, &mesh.layout)
|
||||
.unwrap();
|
||||
phase.add(Transparent2d {
|
||||
for (view, mut transparent_phase) in &mut views {
|
||||
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
|
||||
| Mesh2dPipelineKey::from_hdr(view.hdr);
|
||||
|
||||
for (entity, handle) in &line_gizmos {
|
||||
let Some(line_gizmo) = line_gizmo_assets.get(handle) else { continue };
|
||||
|
||||
let pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&pipeline,
|
||||
LineGizmoPipelineKey {
|
||||
mesh_key,
|
||||
strip: line_gizmo.strip,
|
||||
},
|
||||
);
|
||||
|
||||
transparent_phase.add(Transparent2d {
|
||||
entity,
|
||||
draw_function,
|
||||
pipeline,
|
||||
sort_key: FloatOrd(f32::MAX),
|
||||
sort_key: FloatOrd(0.),
|
||||
batch_range: None,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,53 +1,83 @@
|
|||
use crate::{
|
||||
line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoConfig, LineGizmo,
|
||||
LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_3d::Opaque3d;
|
||||
use bevy_core_pipeline::core_3d::Transparent3d;
|
||||
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
query::With,
|
||||
prelude::Entity,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_pbr::*;
|
||||
use bevy_render::{
|
||||
mesh::Mesh,
|
||||
render_resource::Shader,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
use bevy_pbr::{
|
||||
MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup, MAX_CASCADES_PER_LIGHT,
|
||||
MAX_DIRECTIONAL_LIGHTS,
|
||||
};
|
||||
use bevy_render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
view::Msaa,
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
use crate::{GizmoConfig, GizmoMesh, LINE_SHADER_HANDLE};
|
||||
pub struct LineGizmo3dPlugin;
|
||||
impl Plugin for LineGizmo3dPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct GizmoPipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
shader: Handle<Shader>,
|
||||
render_app
|
||||
.add_render_command::<Transparent3d, DrawLineGizmo3d>()
|
||||
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
|
||||
.add_systems(Render, queue_line_gizmos_3d.in_set(RenderSet::Queue));
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
|
||||
|
||||
render_app.init_resource::<LineGizmoPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for GizmoPipeline {
|
||||
#[derive(Clone, Resource)]
|
||||
struct LineGizmoPipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
uniform_layout: BindGroupLayout,
|
||||
}
|
||||
|
||||
impl FromWorld for LineGizmoPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
GizmoPipeline {
|
||||
LineGizmoPipeline {
|
||||
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
uniform_layout: render_world
|
||||
.resource::<LineGizmoUniformBindgroupLayout>()
|
||||
.layout
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for GizmoPipeline {
|
||||
type Key = (bool, MeshPipelineKey);
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
struct LineGizmoPipelineKey {
|
||||
mesh_key: MeshPipelineKey,
|
||||
strip: bool,
|
||||
perspective: bool,
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
type Key = LineGizmoPipelineKey;
|
||||
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
let mut shader_defs = vec![
|
||||
"GIZMO_3D".into(),
|
||||
#[cfg(feature = "webgl")]
|
||||
"SIXTEEN_BYTE_ALIGNMENT".into(),
|
||||
];
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
(depth_test, key): Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let mut shader_defs = Vec::new();
|
||||
shader_defs.push("GIZMO_LINES_3D".into());
|
||||
shader_defs.push(ShaderDefVal::Int(
|
||||
"MAX_DIRECTIONAL_LIGHTS".to_string(),
|
||||
MAX_DIRECTIONAL_LIGHTS as i32,
|
||||
|
@ -56,109 +86,106 @@ impl SpecializedMeshPipeline for GizmoPipeline {
|
|||
"MAX_CASCADES_PER_LIGHT".to_string(),
|
||||
MAX_CASCADES_PER_LIGHT as i32,
|
||||
));
|
||||
if depth_test {
|
||||
shader_defs.push("DEPTH_TEST".into());
|
||||
|
||||
if key.perspective {
|
||||
shader_defs.push("PERSPECTIVE".into());
|
||||
}
|
||||
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
let bind_group_layout = match key.msaa_samples() {
|
||||
1 => vec![self.mesh_pipeline.view_layout.clone()],
|
||||
_ => {
|
||||
shader_defs.push("MULTISAMPLED".into());
|
||||
vec![self.mesh_pipeline.view_layout_multisampled.clone()]
|
||||
}
|
||||
};
|
||||
|
||||
let format = if key.contains(MeshPipelineKey::HDR) {
|
||||
let format = if key.mesh_key.contains(MeshPipelineKey::HDR) {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
};
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
let view_layout = if key.mesh_key.msaa_samples() == 1 {
|
||||
self.mesh_pipeline.view_layout.clone()
|
||||
} else {
|
||||
self.mesh_pipeline.view_layout_multisampled.clone()
|
||||
};
|
||||
|
||||
let layout = vec![view_layout, self.uniform_layout.clone()];
|
||||
|
||||
RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
buffers: line_gizmo_vertex_buffer_layouts(key.strip),
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
blend: None,
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
layout: bind_group_layout,
|
||||
primitive: PrimitiveState {
|
||||
topology: key.primitive_topology(),
|
||||
..Default::default()
|
||||
},
|
||||
layout,
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::Greater,
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
stencil: StencilState::default(),
|
||||
bias: DepthBiasState::default(),
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
count: key.mesh_key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: Some("LineGizmo Pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
label: Some("gizmo_3d_pipeline".into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawGizmoLines = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
DrawMesh,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn queue_gizmos_3d(
|
||||
draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
pipeline: Res<GizmoPipeline>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<GizmoPipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
mesh_handles: Query<(Entity, &Handle<Mesh>), With<GizmoMesh>>,
|
||||
config: Res<GizmoConfig>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Opaque3d>)>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
|
||||
let key = MeshPipelineKey::from_msaa_samples(msaa.samples());
|
||||
for (view, mut phase) in &mut views {
|
||||
let key = key | MeshPipelineKey::from_hdr(view.hdr);
|
||||
for (entity, mesh_handle) in &mesh_handles {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = pipelines
|
||||
.specialize(
|
||||
&pipeline_cache,
|
||||
&pipeline,
|
||||
(!config.on_top, key),
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function,
|
||||
distance: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DrawLineGizmo3d = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetLineGizmoBindGroup<1>,
|
||||
DrawLineGizmo,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn queue_line_gizmos_3d(
|
||||
draw_functions: Res<DrawFunctions<Transparent3d>>,
|
||||
pipeline: Res<LineGizmoPipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
msaa: Res<Msaa>,
|
||||
config: Res<GizmoConfig>,
|
||||
line_gizmos: Query<(Entity, &Handle<LineGizmo>)>,
|
||||
line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().get_id::<DrawLineGizmo3d>().unwrap();
|
||||
|
||||
for (view, mut transparent_phase) in &mut views {
|
||||
let mesh_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
|
||||
| MeshPipelineKey::from_hdr(view.hdr);
|
||||
|
||||
for (entity, handle) in &line_gizmos {
|
||||
let Some(line_gizmo) = line_gizmo_assets.get(handle) else { continue };
|
||||
|
||||
let pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&pipeline,
|
||||
LineGizmoPipelineKey {
|
||||
mesh_key,
|
||||
strip: line_gizmo.strip,
|
||||
perspective: config.line_perspective,
|
||||
},
|
||||
);
|
||||
|
||||
transparent_phase.add(Transparent3d {
|
||||
entity,
|
||||
draw_function,
|
||||
pipeline,
|
||||
distance: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ x11 = ["bevy_winit/x11"]
|
|||
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
|
||||
|
||||
# Optimise for WebGL2
|
||||
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"]
|
||||
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl", "bevy_gizmos?/webgl"]
|
||||
|
||||
# enable systems that allow for automated testing on CI
|
||||
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_time/bevy_ci_testing", "bevy_render?/bevy_ci_testing", "bevy_render?/ci_limits"]
|
||||
|
|
|
@ -8,12 +8,21 @@ fn main() {
|
|||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, system)
|
||||
.add_systems(Update, (system, update_config))
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
// text
|
||||
commands.spawn(TextBundle::from_section(
|
||||
"Hold 'Left' or 'Right' to change the line width",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||
font_size: 24.,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
||||
|
@ -45,3 +54,12 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
|||
// 1 and 32, using the arc length as scalar.
|
||||
gizmos.arc_2d(Vec2::ZERO, sin / 10., PI / 2., 350., Color::ORANGE_RED);
|
||||
}
|
||||
|
||||
fn update_config(mut config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
|
||||
if keyboard.pressed(KeyCode::Right) {
|
||||
config.line_width += 5. * time.delta_seconds();
|
||||
}
|
||||
if keyboard.pressed(KeyCode::Left) {
|
||||
config.line_width -= 5. * time.delta_seconds();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging.
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
@ -47,7 +48,9 @@ fn setup(
|
|||
// example instructions
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"Press 't' to toggle drawing gizmos on top of everything else in the scene",
|
||||
"Press 'D' to toggle drawing gizmos on top of everything else in the scene\n\
|
||||
Press 'P' to toggle perspective for line gizmos\n\
|
||||
Hold 'Left' or 'Right' to change the line width",
|
||||
TextStyle {
|
||||
font_size: 20.,
|
||||
..default()
|
||||
|
@ -64,7 +67,7 @@ fn setup(
|
|||
|
||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
||||
gizmos.cuboid(
|
||||
Transform::from_translation(Vec3::Y * -0.5).with_scale(Vec3::new(5., 1., 2.)),
|
||||
Transform::from_translation(Vec3::Y * 0.5).with_scale(Vec3::splat(1.)),
|
||||
Color::BLACK,
|
||||
);
|
||||
gizmos.rect(
|
||||
|
@ -74,12 +77,7 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
|||
Color::GREEN,
|
||||
);
|
||||
|
||||
gizmos.sphere(
|
||||
Vec3::new(1., 0.5, 0.),
|
||||
Quat::IDENTITY,
|
||||
0.5,
|
||||
Color::RED.with_a(0.5),
|
||||
);
|
||||
gizmos.sphere(Vec3::new(1., 0.5, 0.), Quat::IDENTITY, 0.5, Color::RED);
|
||||
|
||||
for y in [0., 0.5, 1.] {
|
||||
gizmos.ray(
|
||||
|
@ -106,8 +104,21 @@ fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>
|
|||
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 2.));
|
||||
}
|
||||
|
||||
fn update_config(mut gizmo_config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>) {
|
||||
if keyboard.just_pressed(KeyCode::T) {
|
||||
gizmo_config.on_top = !gizmo_config.on_top;
|
||||
fn update_config(mut config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
|
||||
if keyboard.just_pressed(KeyCode::D) {
|
||||
config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. };
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::P) {
|
||||
// Toggle line_perspective
|
||||
config.line_perspective ^= true;
|
||||
// Increase the line width when line_perspective is on
|
||||
config.line_width *= if config.line_perspective { 5. } else { 1. / 5. };
|
||||
}
|
||||
|
||||
if keyboard.pressed(KeyCode::Right) {
|
||||
config.line_width += 5. * time.delta_seconds();
|
||||
}
|
||||
if keyboard.pressed(KeyCode::Left) {
|
||||
config.line_width -= 5. * time.delta_seconds();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,6 @@ fn main() {
|
|||
line_count: 50_000,
|
||||
fancy: false,
|
||||
})
|
||||
.insert_resource(GizmoConfig {
|
||||
on_top: false,
|
||||
..default()
|
||||
})
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (input, ui_system));
|
||||
|
||||
|
|
Loading…
Reference in a new issue