Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_dev_tools/src/infinite_grid.rs
30635 views
1
//! This module implements an infinite grid with colored major axis.
2
//!
3
//! The rendering is not actually infinite and fades out over a customizable distance to avoid
4
//! artifacts. This fade out is relative to the camera.
5
6
use bevy_app::prelude::*;
7
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
8
use bevy_camera::{
9
prelude::*,
10
visibility::{self, NoFrustumCulling, VisibilityClass},
11
};
12
use bevy_color::{Color, ColorToComponents};
13
use bevy_core_pipeline::{
14
core_3d::{Transparent3d, TransparentSortingInfo3d},
15
FullscreenShader,
16
};
17
use bevy_ecs::{
18
prelude::*,
19
query::ROQueryItem,
20
system::{
21
lifetimeless::{Read, SRes},
22
SystemParamItem,
23
},
24
};
25
use bevy_math::{Mat3, Vec3, Vec4};
26
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
27
use bevy_render::{
28
camera::ExtractedCamera,
29
prelude::*,
30
render_phase::{
31
AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand,
32
RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases,
33
},
34
render_resource::{
35
binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor,
36
BindGroupLayoutEntries, BlendState, ColorTargetState, ColorWrites, CompareFunction,
37
DepthStencilState, DynamicUniformBuffer, FragmentState, MultisampleState, PipelineCache,
38
PrimitiveState, RenderPipelineDescriptor, ShaderStages, ShaderType,
39
SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat,
40
},
41
renderer::{RenderDevice, RenderQueue},
42
sync_world::{RenderEntity, SyncToRenderWorld},
43
view::{ExtractedView, RenderVisibleEntities, ViewUniform, ViewUniformOffset, ViewUniforms},
44
Extract, Render, RenderApp, RenderSystems,
45
};
46
use bevy_shader::Shader;
47
use bevy_transform::components::{GlobalTransform, Transform};
48
49
/// The plugin required to make the infinite grid work
50
pub struct InfiniteGridPlugin;
51
52
impl Plugin for InfiniteGridPlugin {
53
fn build(&self, app: &mut App) {
54
embedded_asset!(app, "infinite_grid.wgsl");
55
app.register_type::<InfiniteGrid>()
56
.register_type::<InfiniteGridSettings>();
57
}
58
59
fn finish(&self, app: &mut App) {
60
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
61
return;
62
};
63
render_app
64
.init_resource::<InfiniteGridUniforms>()
65
.init_resource::<InfiniteGridDisplaySettingsUniforms>()
66
.init_resource::<InfiniteGridPipeline>()
67
.init_resource::<SpecializedRenderPipelines<InfiniteGridPipeline>>()
68
.add_render_command::<Transparent3d, DrawInfiniteGrid>()
69
.add_systems(ExtractSchedule, extract_infinite_grids)
70
.add_systems(
71
Render,
72
prepare_infinite_grids.in_set(RenderSystems::PrepareResources),
73
)
74
.add_systems(
75
Render,
76
(
77
prepare_bind_groups_for_infinite_grids,
78
prepare_view_bind_groups,
79
)
80
.in_set(RenderSystems::PrepareBindGroups),
81
)
82
.add_systems(Render, queue_infinite_grids.in_set(RenderSystems::Queue));
83
}
84
}
85
86
/// The component used to represent an infinite grid.
87
///
88
/// This is intended for use as a ground plane in editor-like tools.
89
#[derive(Component, Default, Reflect)]
90
#[reflect(Component, Default)]
91
#[require(
92
InfiniteGridSettings,
93
Transform,
94
Visibility,
95
VisibilityClass,
96
NoFrustumCulling,
97
SyncToRenderWorld
98
)]
99
#[component(on_add = visibility::add_visibility_class::<InfiniteGrid>)]
100
pub struct InfiniteGrid;
101
102
/// Component to configure the infinite grid
103
///
104
/// This component can be applied directly on the grid entity or on a camera that can see the grid
105
#[derive(Component, Copy, Clone, Reflect)]
106
#[reflect(Component, Default)]
107
pub struct InfiniteGridSettings {
108
/// The color of the X axis
109
pub x_axis_color: Color,
110
/// The color of the Z axis
111
pub z_axis_color: Color,
112
/// The color of the minor lines of the grid
113
pub minor_line_color: Color,
114
/// The color of the major lines of the grid. Every 10th line is considered major
115
pub major_line_color: Color,
116
/// How far the grid will be visible relative to the camera
117
pub fadeout_distance: f32,
118
/// How quickly the grid will fadeout
119
pub dot_fadeout_strength: f32,
120
/// The scale of the distance between the lines. A smaller value increases the distance between
121
/// the lines
122
pub scale: f32,
123
}
124
125
impl Default for InfiniteGridSettings {
126
fn default() -> Self {
127
Self {
128
// These colors are copied from bevy_feathers but we don't need to depend on it just
129
// for that
130
x_axis_color: Color::oklcha(0.5232, 0.1404, 13.84, 1.0),
131
z_axis_color: Color::oklcha(0.4847, 0.1249, 253.08, 1.0),
132
minor_line_color: Color::srgb(0.2, 0.2, 0.2),
133
major_line_color: Color::srgb(0.25, 0.25, 0.25),
134
fadeout_distance: 100.,
135
dot_fadeout_strength: 0.25,
136
scale: 1.0,
137
}
138
}
139
}
140
141
#[derive(Debug, ShaderType)]
142
struct InfiniteGridUniform {
143
rot_matrix: Mat3,
144
offset: Vec3,
145
normal: Vec3,
146
}
147
148
#[derive(Debug, ShaderType)]
149
struct InfiniteGridSettingsUniform {
150
scale: f32,
151
// 1 / fadeout_distance
152
one_over_fadeout_distance: f32,
153
// 1 / dot_fadeout_strength
154
one_over_dot_fadeout: f32,
155
x_axis_color: Vec3,
156
z_axis_color: Vec3,
157
minor_line_color: Vec4,
158
major_line_color: Vec4,
159
}
160
161
impl InfiniteGridSettingsUniform {
162
fn from_settings(settings: &InfiniteGridSettings) -> Self {
163
Self {
164
scale: settings.scale,
165
one_over_fadeout_distance: 1. / settings.fadeout_distance,
166
one_over_dot_fadeout: 1. / settings.dot_fadeout_strength,
167
x_axis_color: settings.x_axis_color.to_linear().to_vec3(),
168
z_axis_color: settings.z_axis_color.to_linear().to_vec3(),
169
minor_line_color: settings.minor_line_color.to_linear().to_vec4(),
170
major_line_color: settings.major_line_color.to_linear().to_vec4(),
171
}
172
}
173
}
174
175
#[derive(Resource, Default)]
176
struct InfiniteGridUniforms {
177
uniforms: DynamicUniformBuffer<InfiniteGridUniform>,
178
}
179
180
#[derive(Resource, Default)]
181
struct InfiniteGridDisplaySettingsUniforms {
182
uniforms: DynamicUniformBuffer<InfiniteGridSettingsUniform>,
183
}
184
185
#[derive(Component)]
186
struct InfiniteGridUniformOffsets {
187
position_offset: u32,
188
settings_offset: u32,
189
}
190
191
#[derive(Component)]
192
struct PerCameraSettingsUniformOffset {
193
offset: u32,
194
}
195
196
#[derive(Resource)]
197
struct InfiniteGridBindGroup {
198
value: BindGroup,
199
}
200
201
#[derive(Component)]
202
struct ViewBindGroup {
203
value: BindGroup,
204
}
205
206
struct DrawInfiniteGridCommand;
207
208
impl<P: PhaseItem> RenderCommand<P> for DrawInfiniteGridCommand {
209
type Param = SRes<InfiniteGridBindGroup>;
210
type ViewQuery = (
211
Read<ViewUniformOffset>,
212
Read<ViewBindGroup>,
213
Option<Read<PerCameraSettingsUniformOffset>>,
214
);
215
type ItemQuery = Read<InfiniteGridUniformOffsets>;
216
217
#[inline]
218
fn render<'w>(
219
_item: &P,
220
(view_uniform, view_bind_group, camera_settings_offset): ROQueryItem<
221
'w,
222
'_,
223
Self::ViewQuery,
224
>,
225
maybe_base_offsets: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
226
bind_group: SystemParamItem<'w, '_, Self::Param>,
227
pass: &mut TrackedRenderPass<'w>,
228
) -> RenderCommandResult {
229
let Some(base_offsets) = maybe_base_offsets else {
230
bevy_log::warn!("InfiniteGridUniformOffsets missing");
231
return RenderCommandResult::Skip;
232
};
233
pass.set_bind_group(0, &view_bind_group.value, &[view_uniform.offset]);
234
pass.set_bind_group(
235
1,
236
&bind_group.into_inner().value,
237
&[
238
base_offsets.position_offset,
239
camera_settings_offset
240
.map(|cs| cs.offset)
241
.unwrap_or(base_offsets.settings_offset),
242
],
243
);
244
pass.draw(0..3, 0..1);
245
RenderCommandResult::Success
246
}
247
}
248
249
type DrawInfiniteGrid = (SetItemPipeline, DrawInfiniteGridCommand);
250
251
fn prepare_view_bind_groups(
252
mut commands: Commands,
253
render_device: Res<RenderDevice>,
254
view_uniforms: Res<ViewUniforms>,
255
pipeline: Res<InfiniteGridPipeline>,
256
pipeline_cache: Res<PipelineCache>,
257
views: Query<Entity, With<ViewUniformOffset>>,
258
) {
259
let Some(binding) = view_uniforms.uniforms.binding() else {
260
return;
261
};
262
for entity in views.iter() {
263
let bind_group = render_device.create_bind_group(
264
"infinite_grid_view_bind_group",
265
&pipeline_cache.get_bind_group_layout(&pipeline.view_layout),
266
&BindGroupEntries::single(binding.clone()),
267
);
268
commands
269
.entity(entity)
270
.insert(ViewBindGroup { value: bind_group });
271
}
272
}
273
274
fn extract_infinite_grids(
275
mut commands: Commands,
276
grids: Extract<Query<(RenderEntity, &InfiniteGridSettings, &GlobalTransform)>>,
277
) {
278
let extracted: Vec<_> = grids
279
.iter()
280
.map(|(entity, grid, transform)| (entity, (*grid, *transform)))
281
.collect();
282
commands.try_insert_batch(extracted);
283
}
284
285
fn prepare_infinite_grids(
286
mut commands: Commands,
287
grids: Query<(Entity, &GlobalTransform, &InfiniteGridSettings)>,
288
cameras: Query<(Entity, &InfiniteGridSettings), With<ExtractedView>>,
289
mut position_uniforms: ResMut<InfiniteGridUniforms>,
290
mut settings_uniforms: ResMut<InfiniteGridDisplaySettingsUniforms>,
291
render_device: Res<RenderDevice>,
292
render_queue: Res<RenderQueue>,
293
) {
294
position_uniforms.uniforms.clear();
295
settings_uniforms.uniforms.clear();
296
for (entity, transform, settings) in &grids {
297
let t = transform.compute_transform();
298
let offset = transform.translation();
299
let normal = transform.up();
300
let rot_matrix = Mat3::from_quat(t.rotation.inverse());
301
commands.entity(entity).insert(InfiniteGridUniformOffsets {
302
position_offset: position_uniforms.uniforms.push(&InfiniteGridUniform {
303
rot_matrix,
304
offset,
305
normal: *normal,
306
}),
307
settings_offset: settings_uniforms
308
.uniforms
309
.push(&InfiniteGridSettingsUniform::from_settings(settings)),
310
});
311
}
312
313
for (entity, settings) in &cameras {
314
commands
315
.entity(entity)
316
.insert(PerCameraSettingsUniformOffset {
317
offset: settings_uniforms
318
.uniforms
319
.push(&InfiniteGridSettingsUniform::from_settings(settings)),
320
});
321
}
322
323
position_uniforms
324
.uniforms
325
.write_buffer(&render_device, &render_queue);
326
327
settings_uniforms
328
.uniforms
329
.write_buffer(&render_device, &render_queue);
330
}
331
332
fn prepare_bind_groups_for_infinite_grids(
333
mut commands: Commands,
334
infinite_grid_uniforms: Res<InfiniteGridUniforms>,
335
settings_uniforms: Res<InfiniteGridDisplaySettingsUniforms>,
336
pipeline: Res<InfiniteGridPipeline>,
337
pipeline_cache: Res<PipelineCache>,
338
render_device: Res<RenderDevice>,
339
) {
340
let Some((infinite_grid_uniform_binding, settings_binding)) = infinite_grid_uniforms
341
.uniforms
342
.binding()
343
.zip(settings_uniforms.uniforms.binding())
344
else {
345
return;
346
};
347
348
let bind_group = render_device.create_bind_group(
349
"infinite_grid_bind_group",
350
&pipeline_cache.get_bind_group_layout(&pipeline.infinite_grid_layout),
351
&BindGroupEntries::sequential((
352
infinite_grid_uniform_binding.clone(),
353
settings_binding.clone(),
354
)),
355
);
356
commands.insert_resource(InfiniteGridBindGroup { value: bind_group });
357
}
358
359
fn queue_infinite_grids(
360
pipeline_cache: Res<PipelineCache>,
361
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
362
pipeline: Res<InfiniteGridPipeline>,
363
mut pipelines: ResMut<SpecializedRenderPipelines<InfiniteGridPipeline>>,
364
infinite_grids: Query<&GlobalTransform, With<InfiniteGridSettings>>,
365
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
366
mut views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa), With<ExtractedCamera>>,
367
) {
368
let Some(draw_function_id) = transparent_draw_functions
369
.read()
370
.get_id::<DrawInfiniteGrid>()
371
else {
372
bevy_log::warn!("Failed to get DrawInfiniteGrid draw_function_id");
373
return;
374
};
375
376
for (view, entities, msaa) in views.iter_mut() {
377
let Some(phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else {
378
continue;
379
};
380
381
let pipeline_id = pipelines.specialize(
382
&pipeline_cache,
383
&pipeline,
384
GridPipelineKey {
385
target_format: view.target_format,
386
sample_count: msaa.samples(),
387
},
388
);
389
390
let Some(render_visible_mesh_entities) = entities.get::<InfiniteGrid>() else {
391
continue;
392
};
393
for (render_entity, main_entity) in render_visible_mesh_entities.iter_visible() {
394
let Ok(transform) = infinite_grids.get(*render_entity) else {
395
continue;
396
};
397
// Don't render if the view is directly on the plane
398
if !plane_check(transform, view.world_from_view.translation()) {
399
continue;
400
}
401
phase.add_retained(Transparent3d {
402
pipeline: pipeline_id,
403
entity: (*render_entity, *main_entity),
404
draw_function: draw_function_id,
405
distance: f32::NEG_INFINITY,
406
batch_range: 0..1,
407
extra_index: PhaseItemExtraIndex::None,
408
indexed: false,
409
sorting_info: TransparentSortingInfo3d::Sorted {
410
mesh_center: Vec3::ZERO,
411
depth_bias: 0.0,
412
},
413
});
414
}
415
}
416
}
417
418
/// Checks if the point is one the plane
419
fn plane_check(plane: &GlobalTransform, point: Vec3) -> bool {
420
plane.up().dot(plane.translation() - point).abs() > f32::EPSILON
421
}
422
423
#[derive(Resource)]
424
struct InfiniteGridPipeline {
425
view_layout: BindGroupLayoutDescriptor,
426
infinite_grid_layout: BindGroupLayoutDescriptor,
427
shader: Handle<Shader>,
428
fullscreen_shader: FullscreenShader,
429
}
430
431
impl FromWorld for InfiniteGridPipeline {
432
fn from_world(world: &mut World) -> Self {
433
let view_layout = BindGroupLayoutDescriptor::new(
434
"infinite_grid_view_bind_group_layout",
435
&BindGroupLayoutEntries::single(
436
ShaderStages::VERTEX | ShaderStages::FRAGMENT,
437
uniform_buffer::<ViewUniform>(true),
438
),
439
);
440
let infinite_grid_layout = BindGroupLayoutDescriptor::new(
441
"infinite_grid_bind_group_layout",
442
&BindGroupLayoutEntries::sequential(
443
ShaderStages::FRAGMENT,
444
(
445
uniform_buffer::<InfiniteGridUniform>(true),
446
uniform_buffer::<InfiniteGridSettingsUniform>(true),
447
),
448
),
449
);
450
let shader = load_embedded_asset!(world.resource::<AssetServer>(), "infinite_grid.wgsl");
451
let fullscreen_shader = world.resource::<FullscreenShader>().clone();
452
453
Self {
454
view_layout,
455
infinite_grid_layout,
456
shader,
457
fullscreen_shader,
458
}
459
}
460
}
461
462
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
463
struct GridPipelineKey {
464
target_format: TextureFormat,
465
sample_count: u32,
466
}
467
468
impl SpecializedRenderPipeline for InfiniteGridPipeline {
469
type Key = GridPipelineKey;
470
471
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
472
RenderPipelineDescriptor {
473
label: Some("infinite_grid_render_pipeline".into()),
474
layout: vec![self.view_layout.clone(), self.infinite_grid_layout.clone()],
475
vertex: self.fullscreen_shader.to_vertex_state(),
476
primitive: PrimitiveState {
477
cull_mode: None,
478
..Default::default()
479
},
480
depth_stencil: Some(DepthStencilState {
481
format: TextureFormat::Depth32Float,
482
depth_write_enabled: Some(false),
483
depth_compare: Some(CompareFunction::Greater),
484
stencil: Default::default(),
485
bias: Default::default(),
486
}),
487
multisample: MultisampleState {
488
count: key.sample_count,
489
..Default::default()
490
},
491
fragment: Some(FragmentState {
492
shader: self.shader.clone(),
493
targets: vec![Some(ColorTargetState {
494
format: key.target_format,
495
blend: Some(BlendState::ALPHA_BLENDING),
496
write_mask: ColorWrites::ALL,
497
})],
498
..Default::default()
499
}),
500
..Default::default()
501
}
502
}
503
}
504
505