Skip to content

Improved grid zoom #142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 62 additions & 52 deletions crates/bevy_infinite_grid/src/render/grid.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ struct InfiniteGridSettings {
};

struct View {
projection: mat4x4<f32>,
inverse_projection: mat4x4<f32>,
view: mat4x4<f32>,
inverse_view: mat4x4<f32>,
clip_from_view: mat4x4<f32>,
view_from_clip: mat4x4<f32>,
world_from_view: mat4x4<f32>,
view_from_world: mat4x4<f32>,
world_position: vec3<f32>,
world_right: vec3<f32>,
world_forward: vec3<f32>,
};

@group(0) @binding(0) var<uniform> view: View;
Expand All @@ -35,7 +37,7 @@ struct Vertex {
};

fn unproject_point(p: vec3<f32>) -> vec3<f32> {
let unprojected = view.view * view.inverse_projection * vec4<f32>(p, 1.0);
let unprojected = view.world_from_view * view.view_from_clip * vec4<f32>(p, 1.0);
return unprojected.xyz / unprojected.w;
}

Expand All @@ -48,19 +50,19 @@ struct VertexOutput {
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
// 0 1 2 1 2 3
var grid_plane = array<vec3<f32>, 4>(
vec3<f32>(-1., -1., 1.),
vec3<f32>(-1., 1., 1.),
vec3<f32>(1., -1., 1.),
vec3<f32>(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<f32>(p, 1.);
out.clip_position = vec4(p, 1.);
out.near_point = unproject_point(p);
out.far_point = unproject_point(vec3<f32>(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;
}

Expand All @@ -69,64 +71,71 @@ struct FragmentOutput {
@builtin(frag_depth) depth: f32,
};

fn raycast_plane(plane_origin: vec3<f32>, plane_normal: vec3<f32>, ray_origin: vec3<f32>, ray_direction: vec3<f32>) -> vec3<f32> {
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;
let ray_direction = normalize(in.far_point - in.near_point);
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;

// TODO Handle ray misses/NaNs

let view_space_pos = view.inverse_view * vec4(frag_pos_3d, 1.);
let clip_space_pos = view.projection * view_space_pos;
let clip_depth = clip_space_pos.z / clip_space_pos.w;
let real_depth = -view_space_pos.z;

var out: 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.);
// 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.

// The scaling to be used when the camera projection has perspective
let perspective_scaling = pow(10., floor(log10_distance));
// 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;

// Orthographic scaling
// 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);

// The height of the view in world units
let view_area_height = 2. / view.projection[1][1];
// 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));

// Who knows what it means?
let cool_magic_number = 300.;
let size = view_area_height / cool_magic_number;
// Floor the scaling to the nearest power of 10.
let scaling = pow(10., floor(log10_scale));

// 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)) ;
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;

var out: FragmentOutput;

// Equal to 1 when the camera projection is orthographic. Otherwise 0
let is_orthographic = view.projection[3].w;
out.depth = clip_depth;

// Choose different scaling methods for perspective and orthographic projections
let scaling = mix(perspective_scaling, orthographic_scaling, is_orthographic);
let camera_distance_from_plane = abs(dot(view.world_position - plane_origin, plane_normal));

let scale = grid_settings.scale * scaling;
let coord = plane_coords / scale; // use the scale variable to set the distance between the lines
Expand All @@ -138,11 +147,12 @@ 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 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, 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;
Expand All @@ -156,7 +166,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;
Expand Down
27 changes: 16 additions & 11 deletions crates/bevy_infinite_grid/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const GRID_SHADER_HANDLE: Handle<Shader> = 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;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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(),
}),
});
}
Expand Down
Loading