Gizmo line styles (#12394)

# Objective

- Adds line styles to bevy gizmos, suggestion of #9400 
- Currently solid and dotted lines are implemented but this can easily
be extended to support dashed lines as well if that is wanted.

## Solution

- Adds the enum `GizmoLineStyle` and uses it in each `GizmoConfig` to
configure the style of the line.
- Each "dot" in a dotted line has the same width and height as the
`line_width` of the corresponding line.

---

## Changelog

- Added `GizmoLineStyle` to `bevy_gizmos`
- Added a `line_style: GizmoLineStyle ` attribute to `GizmoConfig`
- Updated the `lines.wgsl` shader and the pipelines accordingly.

## Migration Guide

- Any manually created `GizmoConfig` must now include the `line_style`
attribute

## Additional information
Some pretty pictures :)

This is the 3d_gizmos example with/without `line_perspective`:
<img width="1440" alt="Screenshot 2024-03-09 at 23 25 53"
src="https://github.com/bevyengine/bevy/assets/62256001/b1b97311-e78d-4de3-8dfe-9e48a35bb27d">
<img width="1440" alt="Screenshot 2024-03-09 at 23 25 39"
src="https://github.com/bevyengine/bevy/assets/62256001/50ee8ecb-5290-484d-ba36-7fd028374f7f">

And the 2d example:
<img width="1440" alt="Screenshot 2024-03-09 at 23 25 06"
src="https://github.com/bevyengine/bevy/assets/62256001/4452168f-d605-4333-bfa5-5461d268b132">

---------

Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
This commit is contained in:
Lynn 2024-03-25 20:10:45 +01:00 committed by GitHub
parent b35974010b
commit 97a5059535
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 122 additions and 23 deletions

View File

@ -30,6 +30,17 @@ pub enum GizmoLineJoint {
Bevel,
}
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, Reflect)]
#[non_exhaustive]
pub enum GizmoLineStyle {
/// A solid line without any decorators
#[default]
Solid,
/// A dotted line
Dotted,
}
/// A trait used to create gizmo configs groups.
///
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
@ -135,6 +146,8 @@ pub struct GizmoConfig {
///
/// Defaults to `false`.
pub line_perspective: bool,
/// Determine the style of gizmo lines.
pub line_style: GizmoLineStyle,
/// How closer to the camera than real geometry the line should be.
///
/// In 2D this setting has no effect and is effectively always -1.
@ -163,6 +176,7 @@ impl Default for GizmoConfig {
enabled: true,
line_width: 2.,
line_perspective: false,
line_style: GizmoLineStyle::Solid,
depth_bias: 0.,
render_layers: Default::default(),
@ -174,6 +188,7 @@ impl Default for GizmoConfig {
#[derive(Component)]
pub(crate) struct GizmoMeshConfig {
pub line_perspective: bool,
pub line_style: GizmoLineStyle,
pub render_layers: RenderLayers,
}
@ -181,6 +196,7 @@ impl From<&GizmoConfig> for GizmoMeshConfig {
fn from(item: &GizmoConfig) -> Self {
GizmoMeshConfig {
line_perspective: item.line_perspective,
line_style: item.line_style,
render_layers: item.render_layers,
}
}

View File

@ -53,7 +53,7 @@ pub mod prelude {
aabb::{AabbGizmoConfigGroup, ShowAabbGizmo},
config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
GizmoLineJoint,
GizmoLineJoint, GizmoLineStyle,
},
gizmos::Gizmos,
light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo},

View File

@ -26,19 +26,20 @@ struct VertexInput {
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
};
const EPSILON: f32 = 4.88e-04;
@vertex
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.)
var positions = array<vec2<f32>, 6>(
vec2(-0.5, 0.),
vec2(-0.5, 1.),
vec2(0.5, 1.),
vec2(-0.5, 0.),
vec2(0.5, 1.),
vec2(0.5, 0.)
);
let position = positions[vertex.index];
@ -49,23 +50,52 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_b);
clip_b = clip_near_plane(clip_b, clip_a);
let clip = mix(clip_a, clip_b, position.z);
let clip = mix(clip_a, clip_b, position.y);
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);
let y_basis = normalize(screen_b - screen_a);
let x_basis = vec2(-y_basis.y, y_basis.x);
var color = mix(vertex.color_a, vertex.color_b, position.z);
var color = mix(vertex.color_a, vertex.color_b, position.y);
var line_width = line_gizmo.line_width;
var alpha = 1.;
var uv: f32;
#ifdef PERSPECTIVE
line_width /= clip.w;
// get height of near clipping plane in world space
let pos0 = view.inverse_projection * vec4(0, -1, 0, 1); // Bottom of the screen
let pos1 = view.inverse_projection * vec4(0, 1, 0, 1); // Top of the screen
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);
// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
let position_a = view.inverse_view_proj * clip_a;
let position_b = view.inverse_view_proj * clip_b;
let world_distance = length(position_a.xyz - position_b.xyz);
// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.
let clipped_offset = length(position_a.xyz - vertex.position_a);
uv = (clipped_offset + position.y * world_distance) * resolution.y / near_clipping_plane_height / line_gizmo.line_width;
#else
// Get the distance of b to the camera along camera axes
let camera_b = view.inverse_projection * clip_b;
// This differentiates between orthographic and perspective cameras.
// For orthographic cameras no depth adaptment (depth_adaptment = 1) is needed.
var depth_adaptment: f32;
if (clip_b.w == 1.0) {
depth_adaptment = 1.0;
}
else {
depth_adaptment = -camera_b.z;
}
uv = position.y * depth_adaptment * length(screen_b - screen_a) / line_gizmo.line_width;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
@ -74,8 +104,8 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
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;
let x_offset = line_width * position.x * x_basis;
let screen = mix(screen_a, screen_b, position.y) + x_offset;
var depth: f32;
if line_gizmo.depth_bias >= 0. {
@ -93,7 +123,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
return VertexOutput(clip_position, color);
return VertexOutput(clip_position, color, uv);
}
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
@ -111,7 +141,9 @@ fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
}
struct FragmentInput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
};
struct FragmentOutput {
@ -119,6 +151,17 @@ struct FragmentOutput {
};
@fragment
fn fragment(in: FragmentInput) -> FragmentOutput {
fn fragment_solid(in: FragmentInput) -> FragmentOutput {
return FragmentOutput(in.color);
}
@fragment
fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
var alpha: f32;
#ifdef PERSPECTIVE
alpha = 1 - floor(in.uv % 2.0);
#else
alpha = 1 - floor((in.uv * in.position.w) % 2.0);
#endif
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}

View File

@ -1,5 +1,5 @@
use crate::{
config::{GizmoLineJoint, GizmoMeshConfig},
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
@ -83,6 +83,7 @@ impl FromWorld for LineGizmoPipeline {
struct LineGizmoPipelineKey {
mesh_key: Mesh2dPipelineKey,
strip: bool,
line_style: GizmoLineStyle,
}
impl SpecializedRenderPipeline for LineGizmoPipeline {
@ -105,6 +106,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
self.uniform_layout.clone(),
];
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
@ -115,7 +121,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
@ -271,6 +277,7 @@ fn queue_line_gizmos_2d(
LineGizmoPipelineKey {
mesh_key,
strip: line_gizmo.strip,
line_style: config.line_style,
},
);

View File

@ -1,6 +1,6 @@
use crate::{
config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts,
line_joint_gizmo_vertex_buffer_layouts, prelude::GizmoLineJoint, DrawLineGizmo,
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
@ -86,6 +86,7 @@ struct LineGizmoPipelineKey {
view_key: MeshPipelineKey,
strip: bool,
perspective: bool,
line_style: GizmoLineStyle,
}
impl SpecializedRenderPipeline for LineGizmoPipeline {
@ -114,6 +115,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
let layout = vec![view_layout, self.uniform_layout.clone()];
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
@ -124,7 +130,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
@ -329,6 +335,7 @@ fn queue_line_gizmos_3d(
view_key,
strip: line_gizmo.strip,
perspective: config.line_perspective,
line_style: config.line_style,
},
);

View File

@ -24,7 +24,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
"Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
Press 'J' or 'K' to cycle through line joints for straight or round gizmos",
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
TextStyle {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 24.,
@ -107,6 +108,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyU) {
config.line_style = match config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyJ) {
config.line_joints = match config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
@ -128,6 +135,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyI) {
my_config.line_style = match my_config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyK) {
my_config.line_joints = match my_config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,

View File

@ -59,6 +59,7 @@ fn setup(
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
Press 'A' to show all AABB boxes\n\
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
TextStyle {
font_size: 20.,
@ -169,6 +170,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyU) {
config.line_style = match config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyJ) {
config.line_joints = match config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
@ -190,6 +197,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyI) {
my_config.line_style = match my_config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyK) {
my_config.line_joints = match my_config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,