Disable shadowmapping on WebGL

This commit is contained in:
Lauri Räsänen 2023-11-05 02:27:38 +02:00
parent 2616b2f5c9
commit ccaf9261cc
7 changed files with 67 additions and 45 deletions

View file

@ -5,6 +5,7 @@ A small [wgpu](https://github.com/gfx-rs/wgpu) renderer written in [Rust](https:
## Try it out ## Try it out
[rasanen.dev/wgpu-renderer](https://rasanen.dev/wgpu-renderer) [rasanen.dev/wgpu-renderer](https://rasanen.dev/wgpu-renderer)
Note: the .wasm is about 50 MB because it embeds Sponza. Note: the .wasm is about 50 MB because it embeds Sponza.
Note: shadowmaps are not enabled on WebGL.
Controls: Controls:
- WASD - Move horizontally - WASD - Move horizontally
@ -18,8 +19,8 @@ Controls:
- PBS - PBS
- glTF models - glTF models
- 1 realtime pointlight - 1 realtime pointlight
- Shadow mapping - Shadow mapping*
- Soft shadows - Soft shadows*
- Simple wgsl preprocessor for includes - Simple wgsl preprocessor for includes
- Runs on WASM and native desktop - Runs on WASM and native desktop
- Tested on: - Tested on:
@ -28,6 +29,8 @@ Controls:
- `Firefox 109.0` - `Firefox 109.0`
- `Chrome 109.0.5414.120` - `Chrome 109.0.5414.120`
*Native only
TODO: TODO:
- Transparency - Transparency
- Restructuring - Restructuring

View file

@ -12,12 +12,14 @@
padding: 0; padding: 0;
background: black; background: black;
} }
canvas { canvas {
background-color: black; background-color: black;
display: block; display: block;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
} }
#loading { #loading {
color: white; color: white;
position: absolute; position: absolute;
@ -29,14 +31,20 @@
</head> </head>
<body> <body>
<h1 id="loading">Loading...</h1> <h1 id="loading">Loading...</h1>
<script type="module"> <script type="module">
import init from "./pkg/wgpu_renderer.js"; import init from "./pkg/wgpu_renderer.js";
// TODO: still on GL, uncomment once WebGPU works
// if (!navigator.gpu) {
// document.getElementById("loading").innerText = "WebGPU not supported :(";
// } else {
init().then(() => { init().then(() => {
console.log("WASM Loaded"); console.log("WASM Loaded");
document.getElementById("loading")?.remove(); document.getElementById("loading").remove();
}); });
</script> // }
</script>
</body> </body>
</html> </html>

View file

@ -13,5 +13,5 @@ fn vs_main(
); );
let world_position = model_matrix * vec4<f32>(model.position, 1.0); let world_position = model_matrix * vec4<f32>(model.position, 1.0);
return light.matrices[light_matrix_index.value] * world_position; return light.matrices[global_uniforms.light_matrix_index] * world_position;
} }

View file

@ -17,14 +17,14 @@ struct Light {
var<uniform> light: Light; var<uniform> light: Light;
// Needs to be 16 bytes in WebGL // Needs to be 16 bytes in WebGL
struct LightMatrixIndex { struct GlobalUniforms {
value: u32, light_matrix_index: u32,
_padding: u32, use_shadowmaps: u32,
_padding1: u32, _padding1: u32,
_padding2: u32, _padding2: u32,
} }
@group(1) @binding(1) @group(1) @binding(1)
var<uniform> light_matrix_index: LightMatrixIndex; var<uniform> global_uniforms: GlobalUniforms;
struct VertexInput { struct VertexInput {
@location(0) position: vec3<f32>, @location(0) position: vec3<f32>,

View file

@ -116,29 +116,36 @@ fn fs_main(vert: VertexOutput) -> @location(0) vec4<f32> {
var total_radiance: vec3<f32>; var total_radiance: vec3<f32>;
var in_light = 0.0; var in_light = 0.0;
for (var i: i32 = 0; i < 6; i++) {
let light_coords = light.matrices[i] * vert.world_position;
let light_dir = normalize(light_coords.xyz); // Depth sampling is broken in WebGL...
let bias = 0.01; // TODO: remove once WebGPU
// z can never be smaller than this inside 90 degree frustum if (global_uniforms.use_shadowmaps > 0u) {
if (light_dir.z < INV_SQRT_3 - bias) { for (var i: i32 = 0; i < 6; i++) {
continue; let light_coords = light.matrices[i] * vert.world_position;
}
// x and y can never be larger than this inside frustum
if (abs(light_dir.y) > INV_SQRT_2 + bias) {
continue;
}
if (abs(light_dir.x) > INV_SQRT_2 + bias) {
continue;
}
in_light = sample_direct_light(i, light_coords); let light_dir = normalize(light_coords.xyz);
// TODO should break even if 0 since we're inside frustum. let bias = 0.01;
// See if causes issues with bias overlap between directions. // z can never be smaller than this inside 90 degree frustum
if (in_light > 0.0) { if (light_dir.z < INV_SQRT_3 - bias) {
break; continue;
}
// x and y can never be larger than this inside frustum
if (abs(light_dir.y) > INV_SQRT_2 + bias) {
continue;
}
if (abs(light_dir.x) > INV_SQRT_2 + bias) {
continue;
}
in_light = sample_direct_light(i, light_coords);
// TODO should break even if 0 since we're inside frustum.
// See if causes issues with bias overlap between directions.
if (in_light > 0.0) {
break;
}
} }
} else {
in_light = 1.0;
} }
if (in_light > 0.0) { if (in_light > 0.0) {

View file

@ -43,10 +43,11 @@ impl LightUniform {
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct LightMatrixUniform { pub struct GlobalUniforms {
pub value: u32, pub light_matrix_index: u32,
// No DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED in WebGL // No DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED in WebGL
_padding: [u32; 3], pub use_shadowmaps: u32,
_padding: [u32; 2],
} }
pub trait DrawLight<'a> { pub trait DrawLight<'a> {

View file

@ -7,7 +7,7 @@ use std::time::Duration;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{event::*, window::Window}; use winit::{event::*, window::Window};
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use crate::core::light::LightMatrixUniform; use crate::core::light::GlobalUniforms;
use super::camera::{Camera, CameraController, CameraUniform}; use super::camera::{Camera, CameraController, CameraUniform};
use super::instance::{Instance, InstanceRaw}; use super::instance::{Instance, InstanceRaw};
@ -45,7 +45,7 @@ pub struct State {
light_depth_bind_group: wgpu::BindGroup, light_depth_bind_group: wgpu::BindGroup,
light_depth_pass: RenderPass, light_depth_pass: RenderPass,
light_depth_texture_target_views: [TextureView; SHADOW_MAP_LAYERS as usize], light_depth_texture_target_views: [TextureView; SHADOW_MAP_LAYERS as usize],
light_matrix_uniform: LightMatrixUniform, light_matrix_uniform: GlobalUniforms,
light_matrix_buffer: wgpu::Buffer, light_matrix_buffer: wgpu::Buffer,
} }
@ -56,7 +56,7 @@ impl State {
let mut size = window.inner_size(); let mut size = window.inner_size();
size.width = size.width.max(1); size.width = size.width.max(1);
size.height = size.height.max(1); size.height = size.height.max(1);
let instance = wgpu::Instance::new(InstanceDescriptor { backends: Backends::all(), ..Default::default() }); let instance = wgpu::Instance::new(InstanceDescriptor { backends: Backends::PRIMARY | Backends::GL, ..Default::default() });
let surface = unsafe { instance.create_surface(window) }.unwrap(); let surface = unsafe { instance.create_surface(window) }.unwrap();
let adapter = instance let adapter = instance
@ -73,13 +73,14 @@ impl State {
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
features: wgpu::Features::default(), features: wgpu::Features::default(),
limits: if cfg!(target_arch = "wasm32") { limits: if cfg!(target_arch = "wasm32") {
// TODO: remove once webgpu?
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
}, },
label: None, label: None,
}, },
None, // Trace path None,
) )
.await .await
.expect("failed to get device"); .expect("failed to get device");
@ -186,7 +187,7 @@ impl State {
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
}); });
let light_matrix_uniform = LightMatrixUniform::default(); let light_matrix_uniform = GlobalUniforms::default();
let light_matrix_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let light_matrix_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Light Matrix UB"), label: Some("Light Matrix UB"),
contents: bytemuck::cast_slice(&[light_matrix_uniform]), contents: bytemuck::cast_slice(&[light_matrix_uniform]),
@ -194,7 +195,7 @@ impl State {
}); });
let light_uniform_size = mem::size_of::<LightUniform>() as u64; let light_uniform_size = mem::size_of::<LightUniform>() as u64;
let light_matrix_uniform_size = mem::size_of::<LightMatrixUniform>() as u64; let light_matrix_uniform_size = mem::size_of::<GlobalUniforms>() as u64;
let light_bind_group_layout = let light_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -209,10 +210,11 @@ impl State {
}, },
count: None, count: None,
}, },
// matrix index // matrix index, use shadowmaps
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 1, binding: 1,
visibility: wgpu::ShaderStages::VERTEX, // TODO: remove fragment vis once no shadowmap uniform
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer { ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform, ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false, has_dynamic_offset: false,
@ -506,7 +508,8 @@ impl State {
// render light to depth textures // render light to depth textures
for i in 0..SHADOW_MAP_LAYERS as usize { for i in 0..SHADOW_MAP_LAYERS as usize {
self.light_matrix_uniform.value = i as u32; self.light_matrix_uniform.light_matrix_index = i as u32;
self.light_matrix_uniform.use_shadowmaps = if cfg!(target_arch = "wasm32") { 0u32 } else { 1u32 };
self.queue.write_buffer( self.queue.write_buffer(
&self.light_matrix_buffer, &self.light_matrix_buffer,
0, 0,