From 6416d5339686f5b541e6c38414022be02ccdfdea Mon Sep 17 00:00:00 2001 From: Tim Blackbird Date: Sat, 2 Nov 2024 15:08:55 +0100 Subject: [PATCH 1/2] weep --- .../bevy_infinite_grid/src/render/grid.wgsl | 97 +++++++++---------- crates/bevy_infinite_grid/src/render/mod.rs | 27 +++--- 2 files changed, 60 insertions(+), 64 deletions(-) diff --git a/crates/bevy_infinite_grid/src/render/grid.wgsl b/crates/bevy_infinite_grid/src/render/grid.wgsl index 9f65ca1b..88c35cd2 100644 --- a/crates/bevy_infinite_grid/src/render/grid.wgsl +++ b/crates/bevy_infinite_grid/src/render/grid.wgsl @@ -18,11 +18,13 @@ struct InfiniteGridSettings { }; struct View { - projection: mat4x4, - inverse_projection: mat4x4, - view: mat4x4, - inverse_view: mat4x4, + clip_from_view: mat4x4, + view_from_clip: mat4x4, + world_from_view: mat4x4, + view_from_world: mat4x4, world_position: vec3, + world_right: vec3, + world_forward: vec3, }; @group(0) @binding(0) var view: View; @@ -35,7 +37,7 @@ struct Vertex { }; fn unproject_point(p: vec3) -> vec3 { - let unprojected = view.view * view.inverse_projection * vec4(p, 1.0); + let unprojected = view.world_from_view * view.view_from_clip * vec4(p, 1.0); return unprojected.xyz / unprojected.w; } @@ -48,19 +50,19 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { // 0 1 2 1 2 3 - var grid_plane = array, 4>( - vec3(-1., -1., 1.), - vec3(-1., 1., 1.), - vec3(1., -1., 1.), - vec3(1., 1., 1.) + var grid_plane = array( + vec3(-1., -1., 1.), + vec3(-1., 1., 1.), + vec3(1., -1., 1.), + vec3(1., 1., 1.), ); let p = grid_plane[vertex.index].xyz; var out: VertexOutput; - out.clip_position = vec4(p, 1.); + out.clip_position = vec4(p, 1.); out.near_point = unproject_point(p); - out.far_point = unproject_point(vec3(p.xy, 0.001)); // unprojecting on the far plane + out.far_point = unproject_point(vec3(p.xy, 0.001)); // unprojecting on the far plane return out; } @@ -69,6 +71,17 @@ struct FragmentOutput { @builtin(frag_depth) depth: f32, }; +fn raycast_plane(plane_origin: vec3, plane_normal: vec3, ray_origin: vec3, ray_direction: vec3) -> vec3 { + let denominator = dot(ray_direction, plane_normal); + let point_to_point = plane_origin - ray_origin; + let t = dot(plane_normal, point_to_point) / denominator; + return ray_direction * t + ray_origin; +} + +fn log10(a: f32) -> f32 { + return log(a) / log(10.); +} + @fragment fn fragment(in: VertexOutput) -> FragmentOutput { let ray_origin = in.near_point; @@ -76,18 +89,27 @@ fn fragment(in: VertexOutput) -> FragmentOutput { let plane_normal = grid_position.normal; let plane_origin = grid_position.origin; - let denominator = dot(ray_direction, plane_normal); - let point_to_point = plane_origin - ray_origin; - let t = dot(plane_normal, point_to_point) / denominator; - let frag_pos_3d = ray_direction * t + ray_origin; + let frag_pos_3d = raycast_plane(plane_origin, plane_normal, ray_origin, ray_direction); let planar_offset = frag_pos_3d - plane_origin; let rotation_matrix = grid_position.planar_rotation_matrix; let plane_coords = (grid_position.planar_rotation_matrix * planar_offset).xz; + let point_a = raycast_plane(plane_origin, plane_normal, view.world_position, view.world_forward); + let point_b = point_a + view.world_right; + + let view_space_point_a = view.view_from_world * vec4(point_a, 1.); + let view_space_point_b = view.view_from_world * vec4(point_b, 1.); + let view_space_distance = distance(view_space_point_a.xy, view_space_point_b.xy); + + let log10_scale = log10(max(grid_settings.scale, 1. / view_space_distance)); + + let minor_alpha_multiplier = 1. - fract(log10_scale); - let view_space_pos = view.inverse_view * vec4(frag_pos_3d, 1.); - let clip_space_pos = view.projection * view_space_pos; + let scaling = pow(10., floor(log10_scale)); + + let view_space_pos = view.view_from_world * vec4(frag_pos_3d, 1.); + let clip_space_pos = view.clip_from_view * view_space_pos; let clip_depth = clip_space_pos.z / clip_space_pos.w; let real_depth = -view_space_pos.z; @@ -95,39 +117,8 @@ fn fragment(in: VertexOutput) -> FragmentOutput { out.depth = clip_depth; - // Perspective scaling - let camera_distance_from_plane = abs(dot(view.world_position - plane_origin, plane_normal)); - // The base 10 log of the camera distance - let log10_distance = log(max(grid_settings.scale, camera_distance_from_plane)) / log(10.); - - // The scaling to be used when the camera projection has perspective - let perspective_scaling = pow(10., floor(log10_distance)); - - - // Orthographic scaling - - // The height of the view in world units - let view_area_height = 2. / view.projection[1][1]; - - // Who knows what it means? - let cool_magic_number = 300.; - let size = view_area_height / cool_magic_number; - - // The base 10 log of the viewport size - let log10_size = log(max(1., size)) / log(10.); - - // The scaling to be used when the camera projection is orthographic - let orthographic_scaling = pow(10., floor(log10_size)) ; - - - // Equal to 1 when the camera projection is orthographic. Otherwise 0 - let is_orthographic = view.projection[3].w; - - // Choose different scaling methods for perspective and orthographic projections - let scaling = mix(perspective_scaling, orthographic_scaling, is_orthographic); - let scale = grid_settings.scale * scaling; let coord = plane_coords / scale; // use the scale variable to set the distance between the lines let derivative = fwidth(coord); @@ -138,11 +129,11 @@ fn fragment(in: VertexOutput) -> FragmentOutput { let minimumx = min(derivative.x, 1.) * scale; let derivative2 = fwidth(coord * 0.1); - let grid2 = abs(fract((coord * 0.1) - 0.5) - 0.5) / derivative2; - let mg_line = min(grid2.x, grid2.y); + let grid2 = abs(fract(coord * 0.1 - 0.5) - 0.5) / derivative2; + let is_minor_line = step(1., min(grid2.x, grid2.y)); let grid_alpha = 1.0 - min(lne, 1.0); - let base_grid_color = mix(grid_settings.major_line_col, grid_settings.minor_line_col, step(1., mg_line)); + let base_grid_color = mix(grid_settings.major_line_col, grid_settings.minor_line_col * vec4(1., 1., 1., minor_alpha_multiplier), is_minor_line); var grid_color = vec4(base_grid_color.rgb, base_grid_color.a * grid_alpha); let main_axes_half_width = 0.8; @@ -156,7 +147,7 @@ fn fragment(in: VertexOutput) -> FragmentOutput { let dot_fadeout = abs(dot(grid_position.normal, normalize(view.world_position - frag_pos_3d))); let alpha_fadeout = mix(dist_fadeout, 1., dot_fadeout) * min(grid_settings.dot_fadeout_const * dot_fadeout, 1.); - grid_color.a = grid_color.a * alpha_fadeout; + grid_color.a *= alpha_fadeout; out.color = grid_color; return out; diff --git a/crates/bevy_infinite_grid/src/render/mod.rs b/crates/bevy_infinite_grid/src/render/mod.rs index 3898a2ac..0008eb2b 100755 --- a/crates/bevy_infinite_grid/src/render/mod.rs +++ b/crates/bevy_infinite_grid/src/render/mod.rs @@ -41,6 +41,7 @@ const GRID_SHADER_HANDLE: Handle = Handle::weak_from_u128(15204473893972 pub fn render_app_builder(app: &mut App) { load_internal_asset!(app, GRID_SHADER_HANDLE, "grid.wgsl", Shader::from_wgsl); + // app.add_systems(Last, update_grid); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -140,11 +141,13 @@ struct InfiniteGridBindGroup { #[derive(Clone, ShaderType)] pub struct GridViewUniform { - projection: Mat4, - inverse_projection: Mat4, - view: Mat4, - inverse_view: Mat4, + clip_from_view: Mat4, + view_from_clip: Mat4, + world_from_view: Mat4, + view_from_world: Mat4, world_position: Vec3, + world_right: Vec3, + world_forward: Vec3, } #[derive(Resource, Default)] @@ -243,16 +246,18 @@ fn prepare_grid_view_uniforms( ) { view_uniforms.uniforms.clear(); for (entity, camera) in views.iter() { - let projection = camera.clip_from_view; - let view = camera.world_from_view.compute_matrix(); - let inverse_view = view.inverse(); + let clip_from_view = camera.clip_from_view; + let world_from_view = camera.world_from_view.compute_matrix(); + let view_from_world = world_from_view.inverse(); commands.entity(entity).insert(GridViewUniformOffset { offset: view_uniforms.uniforms.push(&GridViewUniform { - projection, - view, - inverse_view, - inverse_projection: projection.inverse(), + clip_from_view, + world_from_view, + view_from_world, + view_from_clip: clip_from_view.inverse(), world_position: camera.world_from_view.translation(), + world_right: camera.world_from_view.right().as_vec3(), + world_forward: camera.world_from_view.forward().as_vec3(), }), }); } From c514c1ae5ec45b32d5e7eaa875be86ce84822240 Mon Sep 17 00:00:00 2001 From: Tim Blackbird Date: Sat, 2 Nov 2024 15:52:58 +0100 Subject: [PATCH 2/2] Add comments --- .../bevy_infinite_grid/src/render/grid.wgsl | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/bevy_infinite_grid/src/render/grid.wgsl b/crates/bevy_infinite_grid/src/render/grid.wgsl index 88c35cd2..1650794a 100644 --- a/crates/bevy_infinite_grid/src/render/grid.wgsl +++ b/crates/bevy_infinite_grid/src/render/grid.wgsl @@ -95,17 +95,35 @@ fn fragment(in: VertexOutput) -> FragmentOutput { let rotation_matrix = grid_position.planar_rotation_matrix; let plane_coords = (grid_position.planar_rotation_matrix * planar_offset).xz; + // TODO Handle ray misses/NaNs + + // To scale the grid, we need to know how far the camera is from the grid plane. The naive + // solution is to simply use the distance, however this breaks down when changing FOV or + // when using an orthographic projection. + // + // Instead, we want a solution that is related to the size of objects on screen. + + // Cast a ray from the camera to the plane and get the point where the ray hits the plane. let point_a = raycast_plane(plane_origin, plane_normal, view.world_position, view.world_forward); + + // Then we offset that hit one world-space unit in the direction of the camera's right. let point_b = point_a + view.world_right; + // Convert the points to view space let view_space_point_a = view.view_from_world * vec4(point_a, 1.); let view_space_point_b = view.view_from_world * vec4(point_b, 1.); + // Take the flat distance between the points in view space let view_space_distance = distance(view_space_point_a.xy, view_space_point_b.xy); + // Finally, we use the relationship that the scale of an object is inversely proportional to + // the distance from the camera. We can now do the reverse - compute a distance based on the + // size in the view. If we are very far from the plane, the two points will be very close + // in the view, if we are very close to the plane, the two objects will be very far apart + // in the view. This will work for any camera projection regardless of the camera's + // translational distance. let log10_scale = log10(max(grid_settings.scale, 1. / view_space_distance)); - let minor_alpha_multiplier = 1. - fract(log10_scale); - + // Floor the scaling to the nearest power of 10. let scaling = pow(10., floor(log10_scale)); let view_space_pos = view.view_from_world * vec4(frag_pos_3d, 1.); @@ -131,6 +149,7 @@ fn fragment(in: VertexOutput) -> FragmentOutput { let derivative2 = fwidth(coord * 0.1); let grid2 = abs(fract(coord * 0.1 - 0.5) - 0.5) / derivative2; let is_minor_line = step(1., min(grid2.x, grid2.y)); + let minor_alpha_multiplier = 1. - fract(log10_scale); let grid_alpha = 1.0 - min(lne, 1.0); let base_grid_color = mix(grid_settings.major_line_col, grid_settings.minor_line_col * vec4(1., 1., 1., minor_alpha_multiplier), is_minor_line);