Skip to content

Commit

Permalink
[re_renderer] Enable 4x MSAA (#276)
Browse files Browse the repository at this point in the history
* 4x MSAA always on
* alpha to coverage for point rendering
* use srgb format for view target and do some renames accordingly
also better document the various color conversions and formats
* update wgpu
  • Loading branch information
Wumpf authored Nov 8, 2022
1 parent eebeab1 commit 852e39b
Show file tree
Hide file tree
Showing 20 changed files with 224 additions and 140 deletions.
31 changes: 21 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ egui-wgpu = { git = "https://github.com/emilk/egui", rev = "8c76b8caff32e47e4419
# Because gltf hasn't published a new version: https://github.com/gltf-rs/gltf/issues/357
gltf = { git = "https://github.com/rerun-io/gltf", rev = "3c14ded73755d1ce9e47010edb06db63cb7e2cca" }

# 2022-10-12 - Eq for DepthBiasState and DepthStencilState
wgpu = { git = "https://github.com/gfx-rs/wgpu.git", ref = "aed3b1ae59ee097901b852d934493c7ec167d344" }
wgpu-core = { git = "https://github.com/gfx-rs/wgpu.git", ref = "aed3b1ae59ee097901b852d934493c7ec167d344" }
# 2022-10-12 - Alpha to coverage support for GLES
wgpu = { git = "https://github.com/gfx-rs/wgpu.git", ref = "a377ae2b7fe6c1c9412751166f0917e617164e49" }
wgpu-core = { git = "https://github.com/gfx-rs/wgpu.git", ref = "a377ae2b7fe6c1c9412751166f0917e617164e49" }
#wgpu = { path = "../wgpu/wgpu" }
4 changes: 2 additions & 2 deletions crates/re_renderer/examples/renderer_standalone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,15 +507,15 @@ struct AppState {
impl AppState {
fn new(re_ctx: &mut RenderContext) -> Self {
let mut rnd = <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(42);
let random_point_range = -2.0_f32..2.0_f32;
let random_point_range = -5.0_f32..5.0_f32;
let random_points = (0..500000)
.map(|_| PointCloudPoint {
position: glam::vec3(
rnd.gen_range(random_point_range.clone()),
rnd.gen_range(random_point_range.clone()),
rnd.gen_range(random_point_range.clone()),
),
radius: rnd.gen_range(0.005..0.025),
radius: rnd.gen_range(0.005..0.05),
srgb_color: [rnd.gen(), rnd.gen(), rnd.gen(), 255],
})
.collect_vec();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ struct VertexOutput {
};

@group(1) @binding(0)
var hdr_texture: texture_2d<f32>;
var input_texture: texture_2d<f32>;

@fragment
fn main(in: VertexOutput) -> @location(0) Vec4 {
// Note that we can't use a simple textureLoad using @builtin(position) here despite the lack of filtering.
// The issue is that positions provided by @builtin(position) are not dependent on the set viewport,
// but are about the location of the texel in the target texture.
let hdr = textureSample(hdr_texture, nearest_sampler, in.texcoord).rgb;
let input = textureSample(input_texture, nearest_sampler, in.texcoord).rgb;
// TODO(andreas): Do something meaningful with values above 1
let hdr = clamp(hdr, vec3<f32>(0.0), vec3<f32>(1.0));
return Vec4(srgb_from_linear(hdr), 1.0);
let input = clamp(input, vec3<f32>(0.0), vec3<f32>(1.0));

// 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.
return Vec4(srgb_from_linear(input), 1.0);
}
2 changes: 2 additions & 0 deletions crates/re_renderer/shader/global_bindings.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ struct FrameUniformBuffer {

camera_position: vec3<f32>,
top_right_screen_corner_in_view: vec2<f32>,
/// Multiply this with a camera distance to get a measure of how wide a pixel is in world units.
pixel_world_size_from_camera_distance: f32,
};
@group(0) @binding(0)
var<uniform> frame: FrameUniformBuffer;
Expand Down
4 changes: 2 additions & 2 deletions crates/re_renderer/shader/lines.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,6 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
@fragment
fn fs_main(in: VertexOut) -> @location(0) Vec4 {
// TODO(andreas): Rounded caps, proper shading/lighting, etc.
let shading = 1.2 - length(in.position_world - in.position_world_line) / in.line_radius;
return in.color * shading ;
let shading = max(0.2, 1.2 - length(in.position_world - in.position_world_line) / in.line_radius);
return in.color * shading;
}
43 changes: 27 additions & 16 deletions crates/re_renderer/shader/point_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
// "unnecessary" overlaps. So instead, we change the size _and_ move the sphere closer (using math!)
let radius_sq = point_data.radius * point_data.radius;
let camera_offset = radius_sq * distance_to_camera_inv;
let modified_radius = point_data.radius * distance_to_camera_inv * sqrt(distance_to_camera_sq - radius_sq);
let pos = point_data.pos + pos_in_quad * modified_radius + camera_offset * quad_normal;
var modified_radius = point_data.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 / distance_to_camera_inv;
let pos = point_data.pos + pos_in_quad * modified_radius * 1.0 + camera_offset * quad_normal;
// normal billboard (spheres are cut off!):
// pos = point_data.pos + pos_in_quad * point_data.radius;
// only enlarged billboard (works but requires z care even for non-overlapping spheres):
Expand All @@ -83,31 +86,39 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
return out;
}

// Return how far the closest intersection point is from ray_origin.
// Returns -1.0 if no intersection happend
fn sphere_intersect(sphere_pos: Vec3, radius_sq: f32, ray_origin: Vec3, ray_dir: Vec3) -> f32 {
let sphere_to_origin = ray_origin - sphere_pos;

// 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_origin: Vec3, ray_dir: Vec3, 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_dir);
let c = dot(sphere_to_origin, sphere_to_origin) - radius_sq;
let discriminant = b * b - c;
if (discriminant < 0.0) {
return -1.0;
}
return -b - sqrt(discriminant);
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 {
// TODO(andreas): Pass around squared radius instead.
let ray_dir = normalize(in.world_position - frame.camera_position);
if sphere_intersect(in.point_center, in.radius * in.radius, frame.camera_position, ray_dir) < 0.0 {

// 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 = sphere_distance(frame.camera_position, ray_dir, in.point_center, in.radius);
let smallest_distance_to_sphere = d.x;
let closest_ray_dist = d.y;
let pixel_world_size = closest_ray_dist * frame.pixel_world_size_from_camera_distance;
if smallest_distance_to_sphere > pixel_world_size {
discard;
}
let coverage = 1.0 - clamp(smallest_distance_to_sphere / pixel_world_size, 0.0, 1.0);

// TODO(andreas): Do we want manipulate the depth buffer depth to actually render spheres?

// TODO(andreas): Proper shading
let shading = min(1.0, 1.2 - distance(in.point_center, in.world_position) / in.radius); // quick and dirty coloring)
return in.color * shading;
let shading = max(0.2, 1.2 - distance(in.point_center, in.world_position) / in.radius); // quick and dirty coloring)
return vec4(in.color.rgb * shading, coverage);
}
2 changes: 1 addition & 1 deletion crates/re_renderer/shader/test_triangle.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var<private> v_colors: array<Vec4, 3> = array<Vec4, 3>(
fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
var out: VertexOut;

out.position = frame.projection_from_world * Vec4(v_positions[v_idx], 0.0, 1.0);
out.position = frame.projection_from_world * Vec4(v_positions[v_idx] * 5.0, 0.0, 1.0);
out.color = v_colors[v_idx];

return out;
Expand Down
5 changes: 4 additions & 1 deletion crates/re_renderer/src/global_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ pub(crate) struct FrameUniformBuffer {
pub camera_position: wgpu_buffer_types::Vec3,

/// View space coordinates of the top right screen corner.
pub top_right_screen_corner_in_view: wgpu_buffer_types::Vec2Padded,
pub top_right_screen_corner_in_view: wgpu_buffer_types::Vec2,
/// Multiply this with a camera distance to get a measure of how wide a pixel is in world units.
pub pixel_world_size_from_camera_distance: f32,
pub _padding: f32,
}

pub(crate) struct GlobalBindings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,42 @@ use super::*;

use smallvec::smallvec;

pub struct Tonemapper {
pub struct Compositor {
render_pipeline: GpuRenderPipelineHandle,
bind_group_layout: GpuBindGroupLayoutHandle,
}

#[derive(Clone)]
pub struct TonemapperDrawable {
/// [`GpuBindGroup`] pointing at the current HDR source and
/// a uniform buffer for describing a tonemapper configuration.
hdr_target_bind_group: GpuBindGroupHandleStrong,
pub struct CompositorDrawable {
/// [`GpuBindGroup`] pointing at the current image source and
/// a uniform buffer for describing a tonemapper/compositor configuration.
bind_group: GpuBindGroupHandleStrong,
}

impl Drawable for TonemapperDrawable {
type Renderer = Tonemapper;
impl Drawable for CompositorDrawable {
type Renderer = Compositor;
}

impl TonemapperDrawable {
impl CompositorDrawable {
pub fn new(
ctx: &mut RenderContext,
device: &wgpu::Device,
hdr_target: &GpuTextureHandleStrong,
target: &GpuTextureHandleStrong,
) -> Self {
let pools = &mut ctx.resource_pools;
let tonemapper = ctx.renderers.get_or_create::<_, Tonemapper>(
let compositor = ctx.renderers.get_or_create::<_, Compositor>(
&ctx.shared_renderer_data,
pools,
device,
&mut ctx.resolver,
);
TonemapperDrawable {
hdr_target_bind_group: pools.bind_groups.alloc(
CompositorDrawable {
bind_group: pools.bind_groups.alloc(
device,
&BindGroupDesc {
label: "tonemapping".into(),
entries: smallvec![BindGroupEntry::DefaultTextureView(**hdr_target)],
layout: tonemapper.bind_group_layout,
label: "compositor".into(),
entries: smallvec![BindGroupEntry::DefaultTextureView(**target)],
layout: compositor.bind_group_layout,
},
&pools.bind_group_layouts,
&pools.textures,
Expand All @@ -58,8 +58,8 @@ impl TonemapperDrawable {
}
}

impl Renderer for Tonemapper {
type DrawData = TonemapperDrawable;
impl Renderer for Compositor {
type DrawData = CompositorDrawable;

fn create_renderer<Fs: FileSystem>(
shared_data: &SharedRendererData,
Expand All @@ -70,12 +70,12 @@ impl Renderer for Tonemapper {
let bind_group_layout = pools.bind_group_layouts.get_or_create(
device,
&BindGroupLayoutDesc {
label: "tonemapping".into(),
label: "compositor".into(),
entries: vec![wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::default(),
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
Expand All @@ -87,11 +87,11 @@ impl Renderer for Tonemapper {
let render_pipeline = pools.render_pipelines.get_or_create(
device,
&RenderPipelineDesc {
label: "tonemapping".into(),
label: "compositor".into(),
pipeline_layout: pools.pipeline_layouts.get_or_create(
device,
&PipelineLayoutDesc {
label: "tonemapping".into(),
label: "compositor".into(),
entries: vec![shared_data.global_bindings.layout, bind_group_layout],
},
&pools.bind_group_layouts,
Expand All @@ -111,7 +111,7 @@ impl Renderer for Tonemapper {
resolver,
&ShaderModuleDesc {
label: "tonemap (fragment)".into(),
source: include_file!("../../shader/tonemap.wgsl"),
source: include_file!("../../shader/composite.wgsl"),
},
),
vertex_buffers: smallvec![],
Expand All @@ -124,7 +124,7 @@ impl Renderer for Tonemapper {
&pools.shader_modules,
);

Tonemapper {
Compositor {
render_pipeline,
bind_group_layout,
}
Expand All @@ -134,12 +134,10 @@ impl Renderer for Tonemapper {
&self,
pools: &'a WgpuResourcePools,
pass: &mut wgpu::RenderPass<'a>,
draw_data: &TonemapperDrawable,
draw_data: &CompositorDrawable,
) -> anyhow::Result<()> {
let pipeline = pools.render_pipelines.get_resource(self.render_pipeline)?;
let bind_group = pools
.bind_groups
.get_resource(&draw_data.hdr_target_bind_group)?;
let bind_group = pools.bind_groups.get_resource(&draw_data.bind_group)?;

pass.set_pipeline(&pipeline.pipeline);
pass.set_bind_group(1, &bind_group.bind_group, &[]);
Expand Down
Loading

0 comments on commit 852e39b

Please sign in to comment.