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