diff --git a/crates/re_renderer/examples/assets/rerun.obj.zip b/crates/re_renderer/examples/assets/rerun.obj.zip new file mode 100644 index 000000000000..ff26f6d0a8ad Binary files /dev/null and b/crates/re_renderer/examples/assets/rerun.obj.zip differ diff --git a/crates/re_renderer/examples/depth_cloud.rs b/crates/re_renderer/examples/depth_cloud.rs new file mode 100644 index 000000000000..81da75f34a14 --- /dev/null +++ b/crates/re_renderer/examples/depth_cloud.rs @@ -0,0 +1,486 @@ +//! Demonstrates the use of our depth cloud renderer, which will efficiently draw a point cloud +//! using a depth texture and a set of intrinsics. +//! +//! ## Usage +//! +//! Native: +//! ```sh +//! cargo r -p re_renderer --example depth_cloud +//! ``` +//! +//! Web: +//! ```sh +//! cargo run-wasm --example depth_cloud +//! ``` + +use std::f32::consts::PI; + +use glam::Vec3; +use itertools::Itertools; +use macaw::IsoTransform; +use re_renderer::{ + renderer::{ + DepthCloud, DepthCloudDepthData, DepthCloudDrawData, DrawData, GenericSkyboxDrawData, + RectangleDrawData, TexturedRect, + }, + resource_managers::{GpuTexture2DHandle, Texture2DCreationDesc}, + view_builder::{self, Projection, ViewBuilder}, + Color32, LineStripSeriesBuilder, PointCloudBuilder, Rgba, Size, +}; +use winit::event::{ElementState, VirtualKeyCode}; + +mod framework; + +// --- + +// TODO(#1426): unify camera logic between examples. +enum CameraControl { + RotateAroundCenter, + + // TODO(andreas): Only pauses rotation right now. Add camera controller. + Manual, +} + +struct RenderDepthClouds { + depth: DepthTexture, + albedo: AlbedoTexture, + albedo_handle: GpuTexture2DHandle, + + scale: f32, + radius_scale: f32, + intrinsics: glam::Mat3, + + camera_control: CameraControl, + camera_position: glam::Vec3, +} + +impl RenderDepthClouds { + /// Manually backproject the depth texture into a point cloud and render it. + fn draw_backprojected_point_cloud( + &mut self, + re_ctx: &mut re_renderer::RenderContext, + pixels_from_point: f32, + resolution_in_pixel: [u32; 2], + target_location: glam::Vec2, + frame_draw_data: &FD, + image_draw_data: &ID, + ) -> framework::ViewDrawResult + where + FD: DrawData + Sync + Send + Clone + 'static, + ID: DrawData + Sync + Send + Clone + 'static, + { + let Self { + depth, + scale, + radius_scale, + intrinsics, + .. + } = self; + + let focal_length = glam::Vec2::new(intrinsics.x_axis.x, intrinsics.y_axis.y); + let offset = glam::Vec2::new(intrinsics.x_axis.z, intrinsics.y_axis.z); + + let point_cloud_draw_data = { + let num_points = depth.dimensions.x * depth.dimensions.y; + let (points, colors, radii): (Vec<_>, Vec<_>, Vec<_>) = (0..depth.dimensions.y) + .flat_map(|y| (0..depth.dimensions.x).map(move |x| glam::UVec2::new(x, y))) + .map(|texcoords| { + let linear_depth = depth.get_linear( + depth.dimensions.x - texcoords.x - 1, + depth.dimensions.y - texcoords.y - 1, + ); + let pos_in_world = ((texcoords.as_vec2() - offset) * linear_depth + / focal_length) + .extend(linear_depth); + + ( + pos_in_world * *scale, + Color32::from_gray((linear_depth * 255.0) as u8), + Size(linear_depth * *radius_scale), + ) + }) + .multiunzip(); + + let mut builder = PointCloudBuilder::<()>::new(re_ctx); + builder + .batch("backprojected point cloud") + .add_points(num_points as _, points.into_iter()) + .colors(colors.into_iter()) + .radii(radii.into_iter()); + + builder.to_draw_data(re_ctx).unwrap() + }; + + let mut view_builder = ViewBuilder::default(); + view_builder + .setup_view( + re_ctx, + view_builder::TargetConfiguration { + name: "Point Cloud".into(), + resolution_in_pixel, + view_from_world: IsoTransform::look_at_rh( + self.camera_position, + Vec3::ZERO, + Vec3::Y, + ) + .unwrap(), + projection_from_view: Projection::Perspective { + vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, + near_plane_distance: 0.01, + }, + pixels_from_point, + ..Default::default() + }, + ) + .unwrap(); + + let command_buffer = view_builder + .queue_draw(&GenericSkyboxDrawData::new(re_ctx)) + .queue_draw(&point_cloud_draw_data) + .queue_draw(frame_draw_data) + .queue_draw(image_draw_data) + .draw(re_ctx, ecolor::Rgba::TRANSPARENT) + .unwrap(); + + framework::ViewDrawResult { + view_builder, + command_buffer, + target_location, + } + } + + /// Pass the depth texture to our native depth cloud renderer. + fn draw_depth_cloud( + &mut self, + re_ctx: &mut re_renderer::RenderContext, + pixels_from_point: f32, + resolution_in_pixel: [u32; 2], + target_location: glam::Vec2, + frame_draw_data: &FD, + image_draw_data: &ID, + ) -> framework::ViewDrawResult + where + FD: DrawData + Sync + Send + Clone + 'static, + ID: DrawData + Sync + Send + Clone + 'static, + { + let Self { + depth, + scale, + radius_scale, + intrinsics, + .. + } = self; + + let world_from_obj = glam::Mat4::from_cols( + glam::Vec4::NEG_X * *scale, + glam::Vec4::NEG_Y * *scale, + glam::Vec4::Z * *scale, + glam::Vec4::W, + ); + + let depth_cloud_draw_data = DepthCloudDrawData::new( + re_ctx, + &[DepthCloud { + world_from_obj, + depth_camera_intrinsics: *intrinsics, + radius_scale: *radius_scale, + depth_dimensions: depth.dimensions, + depth_data: depth.data.clone(), + }], + ) + .unwrap(); + + let mut view_builder = ViewBuilder::default(); + view_builder + .setup_view( + re_ctx, + view_builder::TargetConfiguration { + name: "Depth Cloud".into(), + resolution_in_pixel, + view_from_world: IsoTransform::look_at_rh( + self.camera_position, + Vec3::ZERO, + Vec3::Y, + ) + .unwrap(), + projection_from_view: Projection::Perspective { + vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, + near_plane_distance: 0.01, + }, + pixels_from_point, + ..Default::default() + }, + ) + .unwrap(); + + let command_buffer = view_builder + .queue_draw(&GenericSkyboxDrawData::new(re_ctx)) + .queue_draw(&depth_cloud_draw_data) + .queue_draw(frame_draw_data) + .queue_draw(image_draw_data) + .draw(re_ctx, ecolor::Rgba::TRANSPARENT) + .unwrap(); + + framework::ViewDrawResult { + view_builder, + command_buffer, + target_location, + } + } +} + +impl framework::Example for RenderDepthClouds { + fn title() -> &'static str { + "Depth clouds" + } + + fn new(re_ctx: &mut re_renderer::RenderContext) -> Self { + re_log::info!("Stop camera movement by pressing 'Space'"); + + let depth = DepthTexture::spiral((640, 480).into()); + let albedo = AlbedoTexture::spiral(depth.dimensions); + + let albedo_handle = re_ctx.texture_manager_2d.create( + &mut re_ctx.gpu_resources.textures, + &Texture2DCreationDesc { + label: "albedo".into(), + data: bytemuck::cast_slice(&albedo.rgba8), + format: wgpu::TextureFormat::Rgba8UnormSrgb, + width: albedo.dimensions.x, + height: albedo.dimensions.y, + }, + ); + + let scale = 50.0; + let radius_scale = 0.1; + + // hardcoded intrinsics for nyud dataset + let focal_length = depth.dimensions.x as f32 * 0.7; + let offset = depth.dimensions.as_vec2() * 0.5; + let intrinsics = glam::Mat3::from_cols( + Vec3::new(focal_length, 0.0, offset.x), + Vec3::new(0.0, focal_length, offset.y), + Vec3::new(0.0, 0.0, 1.0), + ); + + RenderDepthClouds { + depth, + albedo, + albedo_handle, + + scale, + radius_scale, + intrinsics, + + camera_control: CameraControl::RotateAroundCenter, + camera_position: glam::Vec3::ZERO, + } + } + + fn draw( + &mut self, + re_ctx: &mut re_renderer::RenderContext, + resolution: [u32; 2], + time: &framework::Time, + pixels_from_point: f32, + ) -> Vec { + let Self { + albedo, + albedo_handle, + camera_control, + camera_position, + .. + } = self; + + let seconds_since_startup = time.seconds_since_startup(); + if matches!(camera_control, CameraControl::RotateAroundCenter) { + *camera_position = Vec3::new( + seconds_since_startup.sin(), + 0.5, + seconds_since_startup.cos(), + ) * 100.0; + } + + let splits = framework::split_resolution(resolution, 1, 2).collect::>(); + + let frame_size = albedo.dimensions.as_vec2().extend(0.0) / 15.0; + let scale = glam::Mat4::from_scale(frame_size); + let rotation = glam::Mat4::IDENTITY; + let translation_center = glam::Mat4::from_translation(-glam::Vec3::splat(0.5) * frame_size); + let world_from_model = rotation * translation_center * scale; + + let frame_draw_data = { + let mut builder = LineStripSeriesBuilder::<()>::default(); + { + let mut line_batch = builder.batch("frame").world_from_obj(world_from_model); + line_batch.add_box_outline(glam::Affine3A::from_scale_rotation_translation( + glam::Vec3::new(1.0, 1.0, 0.0), + Default::default(), + glam::Vec3::ONE * 0.5, + )); + } + builder.to_draw_data(re_ctx) + }; + + let image_draw_data = RectangleDrawData::new( + re_ctx, + &[TexturedRect { + top_left_corner_position: world_from_model + .transform_point3(glam::Vec3::new(1.0, 1.0, 0.0)), + extent_u: world_from_model.transform_vector3(-glam::Vec3::X), + extent_v: world_from_model.transform_vector3(-glam::Vec3::Y), + texture: albedo_handle.clone(), + texture_filter_magnification: re_renderer::renderer::TextureFilterMag::Nearest, + texture_filter_minification: re_renderer::renderer::TextureFilterMin::Linear, + multiplicative_tint: Rgba::from_white_alpha(0.5), + depth_offset: -1, + }], + ) + .unwrap(); + + vec![ + self.draw_backprojected_point_cloud( + re_ctx, + pixels_from_point, + splits[0].resolution_in_pixel, + splits[0].target_location, + &frame_draw_data, + &image_draw_data, + ), + self.draw_depth_cloud( + re_ctx, + pixels_from_point, + splits[1].resolution_in_pixel, + splits[1].target_location, + &frame_draw_data, + &image_draw_data, + ), + ] + } + + fn on_keyboard_input(&mut self, input: winit::event::KeyboardInput) { + #![allow(clippy::single_match)] + match (input.state, input.virtual_keycode) { + (ElementState::Released, Some(VirtualKeyCode::Space)) => { + self.camera_control = match self.camera_control { + CameraControl::RotateAroundCenter => CameraControl::Manual, + CameraControl::Manual => CameraControl::RotateAroundCenter, + }; + } + _ => {} + } + } +} + +fn main() { + framework::start::(); +} + +// --- + +/// Returns `(position_in_image, linear_depth)`. +fn spiral(dimensions: glam::UVec2) -> impl Iterator { + let size = (dimensions.x * dimensions.y) as usize; + let factor = dimensions.as_vec2() - 1.0; + + let mut i = 0; + let mut angle_rad: f32 = 0.0; + + std::iter::from_fn(move || { + if i < size { + let radius = i as f32 / size as f32; + let pos = glam::Vec2::splat(0.5) + + glam::Vec2::new(angle_rad.cos(), angle_rad.sin()) * 0.5 * radius; + let texcoords = (pos * factor).as_uvec2(); + + i += 1; + angle_rad += 0.001 * PI; + + return Some((texcoords, radius)); + } + + None + }) +} + +// Copied from re_viewer, this will be removed in an upcoming PR that handles colormapping. +// +// Copyright 2019 Google LLC. +// SPDX-License-Identifier: Apache-2.0 +// Authors: +// Colormap Design: Anton Mikhailov (mikhailov@google.com) +// GLSL Approximation: Ruofei Du (ruofei@google.com)/ +// +// TODO(cmc): remove in GPU color maps PR. +#[allow(clippy::excessive_precision)] +fn turbo_color_map(x: f32) -> [u8; 4] { + use glam::{Vec2, Vec4}; + + const RED_VEC4: Vec4 = Vec4::new(0.13572138, 4.61539260, -42.66032258, 132.13108234); + const GREEN_VEC4: Vec4 = Vec4::new(0.09140261, 2.19418839, 4.84296658, -14.18503333); + const BLUE_VEC4: Vec4 = Vec4::new(0.10667330, 12.64194608, -60.58204836, 110.36276771); + const RED_VEC2: Vec2 = Vec2::new(-152.94239396, 59.28637943); + const GREEN_VEC2: Vec2 = Vec2::new(4.27729857, 2.82956604); + const BLURE_VEC2: Vec2 = Vec2::new(-89.90310912, 27.34824973); + + let v4 = glam::vec4(1.0, x, x * x, x * x * x); + let v2 = glam::vec2(v4.z, v4.w) * v4.z; + + // Above sources are not explicit about it but this color is seemingly already in sRGB + // gamma space. + [ + ((v4.dot(RED_VEC4) + v2.dot(RED_VEC2)) * 255.0) as u8, + ((v4.dot(GREEN_VEC4) + v2.dot(GREEN_VEC2)) * 255.0) as u8, + ((v4.dot(BLUE_VEC4) + v2.dot(BLURE_VEC2)) * 255.0) as u8, + 255, + ] +} + +struct DepthTexture { + dimensions: glam::UVec2, + data: DepthCloudDepthData, +} +impl DepthTexture { + pub fn spiral(dimensions: glam::UVec2) -> Self { + let size = (dimensions.x * dimensions.y) as usize; + let mut data = std::iter::repeat(0f32).take(size).collect_vec(); + spiral(dimensions).for_each(|(texcoords, d)| { + data[(texcoords.x + texcoords.y * dimensions.x) as usize] = d; + }); + let data = DepthCloudDepthData::F32(data); + + Self { dimensions, data } + } + + pub fn get_linear(&self, x: u32, y: u32) -> f32 { + match &self.data { + DepthCloudDepthData::U16(data) => { + data[(x + y * self.dimensions.x) as usize] as f32 / u16::MAX as f32 + } + DepthCloudDepthData::F32(data) => data[(x + y * self.dimensions.x) as usize], + } + } +} + +struct AlbedoTexture { + dimensions: glam::UVec2, + rgba8: Vec, +} +impl AlbedoTexture { + pub fn spiral(dimensions: glam::UVec2) -> Self { + let size = (dimensions.x * dimensions.y) as usize; + let mut rgba8 = std::iter::repeat(0).take(size * 4).collect_vec(); + spiral(dimensions).for_each(|(texcoords, d)| { + let idx = ((texcoords.x + texcoords.y * dimensions.x) * 4) as usize; + rgba8[idx..idx + 4].copy_from_slice(turbo_color_map(d).as_slice()); + }); + + Self { dimensions, rgba8 } + } + + #[allow(dead_code)] + pub fn get(&self, x: u32, y: u32) -> [u8; 4] { + let p = &self.rgba8[(x + y * self.dimensions.x) as usize * 4..]; + [p[0], p[1], p[2], p[3]] + } +} diff --git a/crates/re_renderer/examples/multiview.rs b/crates/re_renderer/examples/multiview.rs index 2a663f30ca39..ada770308be4 100644 --- a/crates/re_renderer/examples/multiview.rs +++ b/crates/re_renderer/examples/multiview.rs @@ -207,7 +207,7 @@ impl Example for Multiview { .collect_vec(); let model_mesh_instances = { - let reader = std::io::Cursor::new(include_bytes!("rerun.obj.zip")); + let reader = std::io::Cursor::new(include_bytes!("assets/rerun.obj.zip")); let mut zip = zip::ZipArchive::new(reader).unwrap(); let mut zipped_obj = zip.by_name("rerun.obj").unwrap(); let mut obj_data = Vec::new(); diff --git a/crates/re_renderer/shader/composite.wgsl b/crates/re_renderer/shader/composite.wgsl index 4889db97f357..96313383d665 100644 --- a/crates/re_renderer/shader/composite.wgsl +++ b/crates/re_renderer/shader/composite.wgsl @@ -17,7 +17,7 @@ fn main(in: VertexOutput) -> @location(0) Vec4 { // but are about the location of the texel in the target texture. var input = textureSample(input_texture, nearest_sampler, in.texcoord).rgb; // TODO(andreas): Do something meaningful with values above 1 - input = clamp(input, ZERO, ONE); + input = clamp(input, ZERO.xyz, ONE.xyz); // Convert to srgb - this is necessary since the final eframe output does *not* have an srgb format. // Note that the input here is assumed to be linear - if the input texture was an srgb texture it would have been converted on load. diff --git a/crates/re_renderer/shader/depth_cloud.wgsl b/crates/re_renderer/shader/depth_cloud.wgsl new file mode 100644 index 000000000000..1b84adc4371b --- /dev/null +++ b/crates/re_renderer/shader/depth_cloud.wgsl @@ -0,0 +1,120 @@ +//! Renders a point cloud from a depth texture and a set of intrinsics. +//! +//! See `src/renderer/depth_cloud.rs` for more documentation. + +#import <./global_bindings.wgsl> +#import <./types.wgsl> +#import <./utils/camera.wgsl> +#import <./utils/flags.wgsl> +#import <./utils/size.wgsl> +#import <./utils/sphere_quad.wgsl> +#import <./utils/srgb.wgsl> + +// --- + +struct PointData { + pos_in_world: Vec3, + unresolved_radius: f32, + color: Vec4 +} + +// Backprojects the depth texture using the intrinsics passed in the uniform buffer. +fn compute_point_data(quad_idx: i32) -> PointData { + let wh = textureDimensions(depth_texture); + let texcoords = IVec2(quad_idx % wh.x, quad_idx / wh.x); + + // TODO(cmc): expose knobs to linearize/normalize/flip/cam-to-plane depth. + let norm_linear_depth = textureLoad(depth_texture, texcoords, 0).x; + + // TODO(cmc): support color maps & albedo textures + let color = Vec4(linear_from_srgb(Vec3(norm_linear_depth)), 1.0); + + // TODO(cmc): This assumes a pinhole camera; need to support other kinds at some point. + let intrinsics = transpose(depth_cloud_info.depth_camera_intrinsics); + let focal_length = Vec2(intrinsics[0][0], intrinsics[1][1]); + let offset = Vec2(intrinsics[2][0], intrinsics[2][1]); + + let pos_in_obj = Vec3( + (Vec2(texcoords) - offset) * norm_linear_depth / focal_length, + norm_linear_depth, + ); + + let pos_in_world = depth_cloud_info.world_from_obj * Vec4(pos_in_obj, 1.0); + + var data: PointData; + data.pos_in_world = pos_in_world.xyz; + data.unresolved_radius = norm_linear_depth * depth_cloud_info.radius_scale; + data.color = color; + + return data; +} + +// --- + +struct DepthCloudInfo { + world_from_obj: Mat4, + + /// The intrinsics of the camera used for the projection. + /// + /// Only supports pinhole cameras at the moment. + depth_camera_intrinsics: Mat3, + + /// The scale to apply to the radii of the backprojected points. + radius_scale: f32, +}; +@group(1) @binding(0) +var depth_cloud_info: DepthCloudInfo; + +@group(1) @binding(1) +var depth_texture: texture_2d; + +struct VertexOut { + @builtin(position) pos_in_clip: Vec4, + @location(0) pos_in_world: Vec3, + @location(1) point_pos_in_world: Vec3, + @location(2) point_color: Vec4, + @location(3) point_radius: f32, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut { + let quad_idx = sphere_quad_index(vertex_idx); + + // Compute point data (valid for the entire quad). + let point_data = compute_point_data(quad_idx); + + // Span quad + let quad = sphere_quad_span(vertex_idx, point_data.pos_in_world, point_data.unresolved_radius); + + var out: VertexOut; + out.pos_in_clip = frame.projection_from_world * Vec4(quad.pos_in_world, 1.0); + out.pos_in_world = quad.pos_in_world; + out.point_pos_in_world = point_data.pos_in_world; + out.point_color = point_data.color; + out.point_radius = quad.point_resolved_radius; + + return out; +} + +@fragment +fn fs_main(in: VertexOut) -> @location(0) Vec4 { + // There's easier ways to compute anti-aliasing for when we are in ortho mode since it's + // just circles. + // But it's very nice to have mostly the same code path and this gives us the sphere world + // position along the way. + let ray_in_world = camera_ray_to_world_pos(in.pos_in_world); + + // Sphere intersection with anti-aliasing as described by Iq here + // https://www.shadertoy.com/view/MsSSWV + // (but rearranged and labled to it's easier to understand!) + let d = ray_sphere_distance(ray_in_world, in.point_pos_in_world, in.point_radius); + let smallest_distance_to_sphere = d.x; + let closest_ray_dist = d.y; + let pixel_world_size = approx_pixel_world_size_at(closest_ray_dist); + if smallest_distance_to_sphere > pixel_world_size { + discard; + } + let coverage = 1.0 - saturate(smallest_distance_to_sphere / pixel_world_size); + + return vec4(in.point_color.rgb, coverage); +} diff --git a/crates/re_renderer/shader/global_bindings.wgsl b/crates/re_renderer/shader/global_bindings.wgsl index 97f2efadd76d..3ed977ac25ca 100644 --- a/crates/re_renderer/shader/global_bindings.wgsl +++ b/crates/re_renderer/shader/global_bindings.wgsl @@ -1,5 +1,5 @@ struct FrameUniformBuffer { - view_from_world: mat4x3, + view_from_world: Mat4x3, projection_from_view: Mat4, projection_from_world: Mat4, diff --git a/crates/re_renderer/shader/point_cloud.wgsl b/crates/re_renderer/shader/point_cloud.wgsl index 7eae6f162dda..b3708d704435 100644 --- a/crates/re_renderer/shader/point_cloud.wgsl +++ b/crates/re_renderer/shader/point_cloud.wgsl @@ -3,6 +3,7 @@ #import <./utils/camera.wgsl> #import <./utils/flags.wgsl> #import <./utils/size.wgsl> +#import <./utils/sphere_quad.wgsl> @group(1) @binding(0) var position_data_texture: texture_2d; @@ -52,103 +53,27 @@ fn read_data(idx: i32) -> PointData { return data; } -fn span_quad_perspective( - point_pos: Vec3, - point_radius: f32, - top_bottom: f32, - left_right: f32, - to_camera: Vec3, - camera_distance: f32 -) -> Vec3 { - let distance_to_camera_sq = camera_distance * camera_distance; // (passing on micro-optimization here for splitting this out of earlier length calculation) - let distance_to_camera_inv = 1.0 / camera_distance; - let quad_normal = to_camera * distance_to_camera_inv; - let quad_right = normalize(cross(quad_normal, frame.view_from_world[1].xyz)); // It's spheres so any orthogonal vector would do. - let quad_up = cross(quad_right, quad_normal); - let pos_in_quad = top_bottom * quad_up + left_right * quad_right; - - // But we want to draw pretend-spheres here! - // If camera gets close to a sphere (or the sphere is large) then outlines of the sphere would not fit on a quad with radius r! - // Enlarging the quad is one solution, but then Z gets tricky (== we need to write correct Z and not quad Z to depth buffer) since we may get - // "unnecessary" overlaps. So instead, we change the size _and_ move the sphere closer (using math!) - let radius_sq = point_radius * point_radius; - let camera_offset = radius_sq * distance_to_camera_inv; - var modified_radius = point_radius * distance_to_camera_inv * sqrt(distance_to_camera_sq - radius_sq); - - // We're computing a coverage mask in the fragment shader - make sure the quad doesn't cut off our antialiasing. - // It's fairly subtle but if we don't do this our spheres look slightly squarish - modified_radius += frame.pixel_world_size_from_camera_distance * camera_distance; - - return point_pos + pos_in_quad * modified_radius + camera_offset * quad_normal; - - // normal billboard (spheres are cut off!): - // pos = point_data.pos + pos_in_quad * point_radius; - // only enlarged billboard (works but requires z care even for non-overlapping spheres): - // modified_radius = length(toCamera) * radius / sqrt(distance_to_camera_sq - radius_sq); - // pos = particleCenter + quadPosition * modified_radius; -} - -fn span_quad_orthographic(point_pos: Vec3, point_radius: f32, top_bottom: f32, left_right: f32) -> Vec3 { - let quad_normal = frame.camera_forward; - let quad_right = normalize(cross(quad_normal, frame.view_from_world[1].xyz)); // It's spheres so any orthogonal vector would do. - let quad_up = cross(quad_right, quad_normal); - let pos_in_quad = top_bottom * quad_up + left_right * quad_right; - - // We're computing a coverage mask in the fragment shader - make sure the quad doesn't cut off our antialiasing. - // It's fairly subtle but if we don't do this our spheres look slightly squarish - let radius = point_radius + frame.pixel_world_size_from_camera_distance; - - return point_pos + pos_in_quad * radius; -} - @vertex fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut { - // Basic properties of the vertex we're at. - let quad_idx = i32(vertex_idx) / 6; - let local_idx = vertex_idx % 6u; - let top_bottom = f32(local_idx <= 1u || local_idx == 5u) * 2.0 - 1.0; // 1 for a top vertex, -1 for a bottom vertex. - let left_right = f32(vertex_idx % 2u) * 2.0 - 1.0; // 1 for a right vertex, -1 for a left vertex. + let quad_idx = sphere_quad_index(vertex_idx); // Read point data (valid for the entire quad) let point_data = read_data(quad_idx); - // Resolve radius to a world size. We need the camera distance for this, which is useful later on. - let to_camera = frame.camera_position - point_data.pos; - let camera_distance = length(to_camera); - let radius = unresolved_size_to_world(point_data.unresolved_radius, camera_distance, frame.auto_size_points); // Span quad - var pos: Vec3; - if is_camera_perspective() { - pos = span_quad_perspective(point_data.pos, radius, top_bottom, left_right, to_camera, camera_distance); - } else { - pos = span_quad_orthographic(point_data.pos, radius, top_bottom, left_right); - } + let quad = sphere_quad_span(vertex_idx, point_data.pos, point_data.unresolved_radius); // Output, transform to projection space and done. var out: VertexOut; - out.position = frame.projection_from_world * Vec4(pos, 1.0); + out.position = frame.projection_from_world * Vec4(quad.pos_in_world, 1.0); out.color = point_data.color; - out.radius = radius; - out.world_position = pos; + out.radius = quad.point_resolved_radius; + out.world_position = quad.pos_in_world; out.point_center = point_data.pos; return out; } - -// Returns distance to sphere surface (x) and distance to of closest ray hit (y) -// Via https://iquilezles.org/articles/spherefunctions/ but with more verbose names. -fn sphere_distance(ray: Ray, sphere_origin: Vec3, sphere_radius: f32) -> Vec2 { - let sphere_radius_sq = sphere_radius * sphere_radius; - let sphere_to_origin = ray.origin - sphere_origin; - let b = dot(sphere_to_origin, ray.direction); - let c = dot(sphere_to_origin, sphere_to_origin) - sphere_radius_sq; - let h = b * b - c; - let d = sqrt(max(0.0, sphere_radius_sq - h)) - sphere_radius; - return Vec2(d, -b - sqrt(max(h, 0.0))); -} - - @fragment fn fs_main(in: VertexOut) -> @location(0) Vec4 { // There's easier ways to compute anti-aliasing for when we are in ortho mode since it's just circles. @@ -158,11 +83,11 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 { // Sphere intersection with anti-aliasing as described by Iq here // https://www.shadertoy.com/view/MsSSWV // (but rearranged and labeled to it's easier to understand!) - let d = sphere_distance(ray, in.point_center, in.radius); + let d = ray_sphere_distance(ray, in.point_center, in.radius); let smallest_distance_to_sphere = d.x; let closest_ray_dist = d.y; let pixel_world_size = approx_pixel_world_size_at(closest_ray_dist); - if smallest_distance_to_sphere > pixel_world_size { + if smallest_distance_to_sphere > pixel_world_size { discard; } let coverage = 1.0 - saturate(smallest_distance_to_sphere / pixel_world_size); diff --git a/crates/re_renderer/shader/types.wgsl b/crates/re_renderer/shader/types.wgsl index 77bc28fc9d18..ed9251e549b5 100644 --- a/crates/re_renderer/shader/types.wgsl +++ b/crates/re_renderer/shader/types.wgsl @@ -8,6 +8,8 @@ type UVec4 = vec4; type IVec2 = vec2; type IVec3 = vec3; type IVec4 = vec4; +type Mat3 = mat3x3; +type Mat4x3 = mat4x3; type Mat4 = mat4x4; const f32min = -3.4028235e38; @@ -21,5 +23,5 @@ const X = Vec3(1.0, 0.0, 0.0); const Y = Vec3(0.0, 1.0, 0.0); const Z = Vec3(0.0, 0.0, 1.0); -const ZERO = Vec3(0.0, 0.0, 0.0); -const ONE = Vec3(1.0, 1.0, 1.0); +const ZERO = Vec4(0.0, 0.0, 0.0, 0.0); +const ONE = Vec4(1.0, 1.0, 1.0, 1.0); diff --git a/crates/re_renderer/shader/utils/camera.wgsl b/crates/re_renderer/shader/utils/camera.wgsl index a79c5fb4340b..5778d9cb891f 100644 --- a/crates/re_renderer/shader/utils/camera.wgsl +++ b/crates/re_renderer/shader/utils/camera.wgsl @@ -57,11 +57,22 @@ fn camera_ray_direction_from_screenuv(texcoord: Vec2) -> Vec3 { return normalize(world_space_dir); } +// Returns distance to sphere surface (x) and distance to closest ray hit (y) +// Via https://iquilezles.org/articles/spherefunctions/ but with more verbose names. +fn ray_sphere_distance(ray: Ray, sphere_origin: Vec3, sphere_radius: f32) -> Vec2 { + let sphere_radius_sq = sphere_radius * sphere_radius; + let sphere_to_origin = ray.origin - sphere_origin; + let b = dot(sphere_to_origin, ray.direction); + let c = dot(sphere_to_origin, sphere_to_origin) - sphere_radius_sq; + let h = b * b - c; + let d = sqrt(max(0.0, sphere_radius_sq - h)) - sphere_radius; + return Vec2(d, -b - sqrt(max(h, 0.0))); +} + // Returns the projected size of a pixel at a given distance from the camera. // // This is accurate for objects in the middle of the screen, (depending on the angle) less so at the corners // since an object parallel to the camera (like a conceptual pixel) has a bigger projected surface at higher angles. fn approx_pixel_world_size_at(camera_distance: f32) -> f32 { - return select(frame.pixel_world_size_from_camera_distance, - camera_distance * frame.pixel_world_size_from_camera_distance, is_camera_perspective()); + return select(frame.pixel_world_size_from_camera_distance, camera_distance * frame.pixel_world_size_from_camera_distance, is_camera_perspective()); } diff --git a/crates/re_renderer/shader/utils/sphere_quad.wgsl b/crates/re_renderer/shader/utils/sphere_quad.wgsl new file mode 100644 index 000000000000..bf58de04aeb6 --- /dev/null +++ b/crates/re_renderer/shader/utils/sphere_quad.wgsl @@ -0,0 +1,91 @@ +#import <../global_bindings.wgsl> +#import <../types.wgsl> +#import <./size.wgsl> + +/// Span a quad in a way that guarantees that we'll be able to draw a perspective correct sphere +/// on it. +fn sphere_quad_span_perspective( + point_pos: Vec3, + point_radius: f32, + top_bottom: f32, + left_right: f32, + to_camera: Vec3, + camera_distance: f32 +) -> Vec3 { + let distance_to_camera_sq = camera_distance * camera_distance; // (passing on micro-optimization here for splitting this out of earlier length calculation) + let distance_to_camera_inv = 1.0 / camera_distance; + let quad_normal = to_camera * distance_to_camera_inv; + let quad_right = normalize(cross(quad_normal, frame.view_from_world[1].xyz)); // It's spheres so any orthogonal vector would do. + let quad_up = cross(quad_right, quad_normal); + let pos_in_quad = top_bottom * quad_up + left_right * quad_right; + + // But we want to draw pretend-spheres here! + // If camera gets close to a sphere (or the sphere is large) then outlines of the sphere would not fit on a quad with radius r! + // Enlarging the quad is one solution, but then Z gets tricky (== we need to write correct Z and not quad Z to depth buffer) since we may get + // "unnecessary" overlaps. So instead, we change the size _and_ move the sphere closer (using math!) + let radius_sq = point_radius * point_radius; + let camera_offset = radius_sq * distance_to_camera_inv; + var modified_radius = point_radius * distance_to_camera_inv * sqrt(distance_to_camera_sq - radius_sq); + + // We're computing a coverage mask in the fragment shader - make sure the quad doesn't cut off our antialiasing. + // It's fairly subtle but if we don't do this our spheres look slightly squarish + modified_radius += frame.pixel_world_size_from_camera_distance * camera_distance; + + return point_pos + pos_in_quad * modified_radius + camera_offset * quad_normal; + + // normal billboard (spheres are cut off!): + // pos = point_data.pos + pos_in_quad * point_radius; + // only enlarged billboard (works but requires z care even for non-overlapping spheres): + // modified_radius = length(toCamera) * radius / sqrt(distance_to_camera_sq - radius_sq); + // pos = particleCenter + quadPosition * modified_radius; +} + +/// Span a quad in a way that guarantees that we'll be able to draw an orthographic correct sphere +/// on it. +fn sphere_quad_span_orthographic(point_pos: Vec3, point_radius: f32, top_bottom: f32, left_right: f32) -> Vec3 { + let quad_normal = frame.camera_forward; + let quad_right = normalize(cross(quad_normal, frame.view_from_world[1].xyz)); // It's spheres so any orthogonal vector would do. + let quad_up = cross(quad_right, quad_normal); + let pos_in_quad = top_bottom * quad_up + left_right * quad_right; + + // We're computing a coverage mask in the fragment shader - make sure the quad doesn't cut off our antialiasing. + // It's fairly subtle but if we don't do this our spheres look slightly squarish + let radius = point_radius + frame.pixel_world_size_from_camera_distance; + + return point_pos + pos_in_quad * radius; +} + +/// Returns the index of the current quad. +fn sphere_quad_index(vertex_idx: u32) -> i32 { + return i32(vertex_idx) / 6; +} + +struct SphereQuadData { + pos_in_world: Vec3, + point_resolved_radius: f32, +} + +/// Span a quad onto which perspective correct spheres can be drawn. +/// +/// Spanning is done in perspective or orthographically depending of the state of the global cam. +fn sphere_quad_span(vertex_idx: u32, point_pos: Vec3, point_unresolved_radius: f32) -> SphereQuadData { + // Resolve radius to a world size. We need the camera distance for this, which is useful later on. + let to_camera = frame.camera_position - point_pos; + let camera_distance = length(to_camera); + let radius = unresolved_size_to_world(point_unresolved_radius, camera_distance, frame.auto_size_points); + + // Basic properties of the vertex we're at. + let local_idx = vertex_idx % 6u; + let top_bottom = f32(local_idx <= 1u || local_idx == 5u) * 2.0 - 1.0; // 1 for a top vertex, -1 for a bottom vertex. + let left_right = f32(vertex_idx % 2u) * 2.0 - 1.0; // 1 for a right vertex, -1 for a left vertex. + + // Span quad + var pos: Vec3; + if is_camera_perspective() { + pos = sphere_quad_span_perspective(point_pos, radius, top_bottom, left_right, to_camera, camera_distance); + } else { + pos = sphere_quad_span_orthographic(point_pos, radius, top_bottom, left_right); + } + + return SphereQuadData(pos, radius); +} diff --git a/crates/re_renderer/src/renderer/depth_cloud.rs b/crates/re_renderer/src/renderer/depth_cloud.rs new file mode 100644 index 000000000000..7312d64f6dcf --- /dev/null +++ b/crates/re_renderer/src/renderer/depth_cloud.rs @@ -0,0 +1,375 @@ +//! Renderer that makes it easy to draw point clouds straight out of depth textures. +//! +//! Textures are uploaded just-in-time, no caching. +//! +//! ## Implementation details +//! +//! Since there's no widespread support for bindless textures, this requires one bind group and one +//! draw call per texture. +//! This is a pretty heavy shader though, so the overhead is minimal. +//! +//! The vertex shader backprojects the depth texture using the user-specified intrinsics, and then +//! behaves pretty much exactly like our point cloud renderer (see [`point_cloud.rs`]). + +use smallvec::smallvec; +use std::num::NonZeroU32; + +use crate::{ + allocator::create_and_fill_uniform_buffer_batch, + include_file, + resource_managers::ResourceManagerError, + view_builder::ViewBuilder, + wgpu_resources::{ + BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle, + GpuRenderPipelineHandle, GpuTexture, PipelineLayoutDesc, RenderPipelineDesc, + ShaderModuleDesc, TextureDesc, + }, +}; + +use super::{ + DrawData, DrawPhase, FileResolver, FileSystem, RenderContext, Renderer, SharedRendererData, + WgpuResourcePools, +}; + +// --- + +mod gpu_data { + // - Keep in sync with mirror in depth_cloud.wgsl. + // - See `DepthCloud` for documentation. + #[repr(C, align(256))] + #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] + pub struct DepthCloudInfoUBO { + pub world_from_obj: crate::wgpu_buffer_types::Mat4, + pub depth_camera_intrinsics: crate::wgpu_buffer_types::Mat3, + pub radius_scale: crate::wgpu_buffer_types::F32RowPadded, + + pub end_padding: [crate::wgpu_buffer_types::PaddingRow; 16 - 8], + } +} + +/// The raw data from a depth texture. +/// +/// This is either `u16` or `f32` values; in both cases the data will be uploaded to the shader +/// as-is. +/// For `u16`s, this results in a `Depth16Unorm` texture, otherwise an `R32Float`. +/// +/// The shader assumes that this is normalized, linear, non-flipped depth using the camera +/// position as reference point (not the camera plane!). +// +// TODO(cmc): support more depth data types. +// TODO(cmc): expose knobs to linearize/normalize/flip/cam-to-plane depth. +#[derive(Debug, Clone)] +pub enum DepthCloudDepthData { + U16(Vec), + F32(Vec), +} + +pub struct DepthCloud { + pub world_from_obj: glam::Mat4, + + /// The intrinsics of the camera used for the projection. + /// + /// Only supports pinhole cameras at the moment. + pub depth_camera_intrinsics: glam::Mat3, + + /// The scale to apply to the radii of the backprojected points. + pub radius_scale: f32, + + /// The dimensions of the depth texture in pixels. + pub depth_dimensions: glam::UVec2, + + /// The actual data from the depth texture. + /// + /// See [`DepthCloudDepthData`] for more information. + pub depth_data: DepthCloudDepthData, +} + +impl Default for DepthCloud { + fn default() -> Self { + Self { + world_from_obj: glam::Mat4::IDENTITY, + depth_camera_intrinsics: glam::Mat3::IDENTITY, + radius_scale: 1.0, + depth_dimensions: glam::UVec2::ZERO, + depth_data: DepthCloudDepthData::F32(Vec::new()), + } + } +} + +#[derive(Clone)] +pub struct DepthCloudDrawData { + // Every single point clouds and their respective total number of points. + bind_groups: Vec<(u32, GpuBindGroup)>, +} + +impl DrawData for DepthCloudDrawData { + type Renderer = DepthCloudRenderer; +} + +impl DepthCloudDrawData { + pub fn new( + ctx: &mut RenderContext, + depth_clouds: &[DepthCloud], + ) -> Result { + crate::profile_function!(); + + if depth_clouds.is_empty() { + return Ok(DepthCloudDrawData { + bind_groups: Vec::new(), + }); + } + + let depth_cloud_ubos = create_and_fill_uniform_buffer_batch( + ctx, + "depth_cloud_ubos".into(), + depth_clouds.iter().map(|info| gpu_data::DepthCloudInfoUBO { + world_from_obj: info.world_from_obj.into(), + depth_camera_intrinsics: info.depth_camera_intrinsics.into(), + radius_scale: info.radius_scale.into(), + end_padding: Default::default(), + }), + ); + + let bg_layout = ctx + .renderers + .write() + .get_or_create::<_, DepthCloudRenderer>( + &ctx.shared_renderer_data, + &mut ctx.gpu_resources, + &ctx.device, + &mut ctx.resolver, + ) + .bind_group_layout; + + let mut bind_groups = Vec::with_capacity(depth_clouds.len()); + for (depth_cloud, ubo) in depth_clouds.iter().zip(depth_cloud_ubos.into_iter()) { + let depth_texture = match &depth_cloud.depth_data { + // On native, we can use D16 textures without issues, but they aren't supported on + // the web (and won't ever be on the WebGL backend, see + // https://github.com/gfx-rs/wgpu/issues/3537). + // + // TODO(cmc): use an RG8 texture and unpack it manually in the shader instead. + #[cfg(not(target_arch = "wasm32"))] + DepthCloudDepthData::U16(data) => { + create_and_upload_texture(ctx, depth_cloud, data.as_slice(), false) + } + #[cfg(target_arch = "wasm32")] + DepthCloudDepthData::U16(data) => { + use itertools::Itertools as _; + let dataf32 = data + .as_slice() + .iter() + .map(|d| *d as f32 / u16::MAX as f32) + .collect_vec(); + create_and_upload_texture(ctx, depth_cloud, dataf32.as_slice(), true) + } + DepthCloudDepthData::F32(data) => { + create_and_upload_texture(ctx, depth_cloud, data.as_slice(), false) + } + }; + + bind_groups.push(( + depth_cloud.depth_dimensions.x * depth_cloud.depth_dimensions.y, + ctx.gpu_resources.bind_groups.alloc( + &ctx.device, + &ctx.gpu_resources, + &BindGroupDesc { + label: "depth_cloud_bg".into(), + entries: smallvec![ + ubo, + BindGroupEntry::DefaultTextureView(depth_texture.handle), + ], + layout: bg_layout, + }, + ), + )); + } + + Ok(DepthCloudDrawData { bind_groups }) + } +} + +fn create_and_upload_texture( + ctx: &mut RenderContext, + depth_cloud: &DepthCloud, + data: &[T], + force_32bit: bool, +) -> GpuTexture { + crate::profile_function!(); + + let depth_texture_size = wgpu::Extent3d { + width: depth_cloud.depth_dimensions.x, + height: depth_cloud.depth_dimensions.y, + depth_or_array_layers: 1, + }; + let depth_format = if force_32bit { + wgpu::TextureFormat::R32Float + } else { + match depth_cloud.depth_data { + DepthCloudDepthData::U16(_) => wgpu::TextureFormat::Depth16Unorm, + DepthCloudDepthData::F32(_) => wgpu::TextureFormat::R32Float, + } + }; + let depth_texture_desc = TextureDesc { + label: "depth_texture".into(), + size: depth_texture_size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: depth_format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + }; + let depth_texture = ctx + .gpu_resources + .textures + .alloc(&ctx.device, &depth_texture_desc); + + let format_info = depth_texture_desc.format.describe(); + let width_blocks = depth_cloud.depth_dimensions.x / format_info.block_dimensions.0 as u32; + let bytes_per_row_unaligned = width_blocks * format_info.block_size as u32; + + let mut depth_texture_staging = ctx.cpu_write_gpu_read_belt.lock().allocate::( + &ctx.device, + &ctx.gpu_resources.buffers, + data.len(), + ); + depth_texture_staging.extend_from_slice(data); + + depth_texture_staging.copy_to_texture( + ctx.active_frame.encoder.lock().get(), + wgpu::ImageCopyTexture { + texture: &depth_texture.inner.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + Some(NonZeroU32::new(bytes_per_row_unaligned).expect("invalid bytes per row")), + None, + depth_texture_size, + ); + + depth_texture +} + +pub struct DepthCloudRenderer { + render_pipeline: GpuRenderPipelineHandle, + bind_group_layout: GpuBindGroupLayoutHandle, +} + +impl Renderer for DepthCloudRenderer { + type RendererDrawData = DepthCloudDrawData; + + fn create_renderer( + shared_data: &SharedRendererData, + pools: &mut WgpuResourcePools, + device: &wgpu::Device, + resolver: &mut FileResolver, + ) -> Self { + crate::profile_function!(); + + let bind_group_layout = pools.bind_group_layouts.get_or_create( + device, + &BindGroupLayoutDesc { + label: "depth_cloud_bg_layout".into(), + entries: vec![ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: (std::mem::size_of::() + as u64) + .try_into() + .ok(), + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }, + ); + + let pipeline_layout = pools.pipeline_layouts.get_or_create( + device, + &PipelineLayoutDesc { + label: "depth_cloud_rp_layout".into(), + entries: vec![shared_data.global_bindings.layout, bind_group_layout], + }, + &pools.bind_group_layouts, + ); + + let shader_module = pools.shader_modules.get_or_create( + device, + resolver, + &ShaderModuleDesc { + label: "depth_cloud".into(), + source: include_file!("../../shader/depth_cloud.wgsl"), + }, + ); + + let render_pipeline = pools.render_pipelines.get_or_create( + device, + &RenderPipelineDesc { + label: "depth_cloud_rp".into(), + pipeline_layout, + vertex_entrypoint: "vs_main".into(), + vertex_handle: shader_module, + fragment_entrypoint: "fs_main".into(), + fragment_handle: shader_module, + vertex_buffers: smallvec![], + render_targets: smallvec![Some(ViewBuilder::MAIN_TARGET_COLOR_FORMAT.into())], + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE, + multisample: wgpu::MultisampleState { + // We discard pixels to do the round cutout, therefore we need to + // calculate our own sampling mask. + alpha_to_coverage_enabled: true, + ..ViewBuilder::MAIN_TARGET_DEFAULT_MSAA_STATE + }, + }, + &pools.pipeline_layouts, + &pools.shader_modules, + ); + + DepthCloudRenderer { + render_pipeline, + bind_group_layout, + } + } + + fn draw<'a>( + &self, + pools: &'a WgpuResourcePools, + _phase: DrawPhase, + pass: &mut wgpu::RenderPass<'a>, + draw_data: &'a Self::RendererDrawData, + ) -> anyhow::Result<()> { + crate::profile_function!(); + if draw_data.bind_groups.is_empty() { + return Ok(()); + } + + let pipeline = pools.render_pipelines.get_resource(self.render_pipeline)?; + pass.set_pipeline(pipeline); + + for (num_points, bind_group) in &draw_data.bind_groups { + pass.set_bind_group(1, bind_group, &[]); + pass.draw(0..*num_points * 6, 0..1); + } + + Ok(()) + } +} diff --git a/crates/re_renderer/src/renderer/mod.rs b/crates/re_renderer/src/renderer/mod.rs index 0189b26d8e15..dbe7d37c82b7 100644 --- a/crates/re_renderer/src/renderer/mod.rs +++ b/crates/re_renderer/src/renderer/mod.rs @@ -10,6 +10,11 @@ pub use point_cloud::{ PointCloudVertex, }; +mod depth_cloud; +pub use self::depth_cloud::{ + DepthCloud, DepthCloudDepthData, DepthCloudDrawData, DepthCloudRenderer, +}; + mod test_triangle; pub use test_triangle::TestTriangleDrawData; diff --git a/crates/re_renderer/src/renderer/point_cloud.rs b/crates/re_renderer/src/renderer/point_cloud.rs index 2452267313bc..0637f0e2dae1 100644 --- a/crates/re_renderer/src/renderer/point_cloud.rs +++ b/crates/re_renderer/src/renderer/point_cloud.rs @@ -467,7 +467,8 @@ impl Renderer for PointCloudRenderer { }, depth_stencil: ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE, multisample: wgpu::MultisampleState { - // We discard pixels to do the round cutout, therefore we need to calculate our own sampling mask. + // We discard pixels to do the round cutout, therefore we need to calculate + // our own sampling mask. alpha_to_coverage_enabled: true, ..ViewBuilder::MAIN_TARGET_DEFAULT_MSAA_STATE }, diff --git a/crates/re_renderer/src/wgpu_buffer_types.rs b/crates/re_renderer/src/wgpu_buffer_types.rs index 15686ce0491d..628723a5cd6c 100644 --- a/crates/re_renderer/src/wgpu_buffer_types.rs +++ b/crates/re_renderer/src/wgpu_buffer_types.rs @@ -178,6 +178,25 @@ impl From for Vec4 { } } +#[repr(C, align(16))] +#[derive(Clone, Copy, Zeroable, Pod)] +pub struct Mat3 { + c0: Vec3RowPadded, + c1: Vec3RowPadded, + c2: Vec3RowPadded, +} + +impl From for Mat3 { + #[inline] + fn from(m: glam::Mat3) -> Self { + Self { + c0: m.x_axis.into(), + c1: m.y_axis.into(), + c2: m.z_axis.into(), + } + } +} + #[repr(C, align(16))] #[derive(Clone, Copy, Zeroable, Pod)] pub struct Mat4 { diff --git a/crates/re_renderer/src/workspace_shaders.rs b/crates/re_renderer/src/workspace_shaders.rs index 0d45e91f59da..47b6b0520f8a 100644 --- a/crates/re_renderer/src/workspace_shaders.rs +++ b/crates/re_renderer/src/workspace_shaders.rs @@ -19,6 +19,12 @@ pub fn init() { fs.create_file(virtpath, content).unwrap(); } + { + let virtpath = Path::new("shader/depth_cloud.wgsl"); + let content = include_str!("../shader/depth_cloud.wgsl").into(); + fs.create_file(virtpath, content).unwrap(); + } + { let virtpath = Path::new("shader/generic_skybox.wgsl"); let content = include_str!("../shader/generic_skybox.wgsl").into(); @@ -115,6 +121,12 @@ pub fn init() { fs.create_file(virtpath, content).unwrap(); } + { + let virtpath = Path::new("shader/utils/sphere_quad.wgsl"); + let content = include_str!("../shader/utils/sphere_quad.wgsl").into(); + fs.create_file(virtpath, content).unwrap(); + } + { let virtpath = Path::new("shader/utils/srgb.wgsl"); let content = include_str!("../shader/utils/srgb.wgsl").into();