commit 82b68101b257226447957f078eb1408c489bcb5b Author: mitchellhansen Date: Sat Jan 2 00:10:29 2021 -0800 init diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c63f0c4 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# Can I create the 4-5 interconnected components for a minimal viable game engine? + +probably not diff --git a/shadow/README.md b/shadow/README.md new file mode 100644 index 0000000..62bc83e --- /dev/null +++ b/shadow/README.md @@ -0,0 +1,13 @@ +# shadow + +This animated example demonstrates shadow mapping. + +## To Run + +``` +cargo run --example shadow +``` + +## Screenshots + +![Shadow mapping](./screenshot.png) diff --git a/shadow/bake.vert b/shadow/bake.vert new file mode 100644 index 0000000..e4426b7 --- /dev/null +++ b/shadow/bake.vert @@ -0,0 +1,16 @@ +#version 450 + +layout(location = 0) in ivec4 a_Pos; + +layout(set = 0, binding = 0) uniform Globals { + mat4 u_ViewProj; +}; + +layout(set = 1, binding = 0) uniform Entity { + mat4 u_World; + vec4 u_Color; +}; + +void main() { + gl_Position = u_ViewProj * u_World * vec4(a_Pos); +} diff --git a/shadow/bake.vert.spv b/shadow/bake.vert.spv new file mode 100644 index 0000000..26f1000 Binary files /dev/null and b/shadow/bake.vert.spv differ diff --git a/shadow/forward.frag b/shadow/forward.frag new file mode 100644 index 0000000..8a3e257 --- /dev/null +++ b/shadow/forward.frag @@ -0,0 +1,64 @@ +#version 450 + +const int MAX_LIGHTS = 10; + +layout(location = 0) in vec3 v_Normal; +layout(location = 1) in vec4 v_Position; + +layout(location = 0) out vec4 o_Target; + +struct Light { + mat4 proj; + vec4 pos; + vec4 color; +}; + +layout(set = 0, binding = 0) uniform Globals { + mat4 u_ViewProj; + uvec4 u_NumLights; +}; +layout(set = 0, binding = 1) uniform Lights { + Light u_Lights[MAX_LIGHTS]; +}; +layout(set = 0, binding = 2) uniform texture2DArray t_Shadow; +layout(set = 0, binding = 3) uniform samplerShadow s_Shadow; + +layout(set = 1, binding = 0) uniform Entity { + mat4 u_World; + vec4 u_Color; +}; + +float fetch_shadow(int light_id, vec4 homogeneous_coords) { + if (homogeneous_coords.w <= 0.0) { + return 1.0; + } + // compensate for the Y-flip difference between the NDC and texture coordinates + const vec2 flip_correction = vec2(0.5, -0.5); + // compute texture coordinates for shadow lookup + vec4 light_local = vec4( + homogeneous_coords.xy * flip_correction/homogeneous_coords.w + 0.5, + light_id, + homogeneous_coords.z / homogeneous_coords.w + ); + // do the lookup, using HW PCF and comparison + return texture(sampler2DArrayShadow(t_Shadow, s_Shadow), light_local); +} + +void main() { + vec3 normal = normalize(v_Normal); + vec3 ambient = vec3(0.05, 0.05, 0.05); + // accumulate color + vec3 color = ambient; + for (int i=0; i Vertex { + Vertex { + _pos: [pos[0], pos[1], pos[2], 1], + _normal: [nor[0], nor[1], nor[2], 0], + } +} + +fn create_cube() -> (Vec, Vec) { + let vertex_data = [ + // top (0, 0, 1) + vertex([-1, -1, 1], [0, 0, 1]), + vertex([1, -1, 1], [0, 0, 1]), + vertex([1, 1, 1], [0, 0, 1]), + vertex([-1, 1, 1], [0, 0, 1]), + // bottom (0, 0, -1) + vertex([-1, 1, -1], [0, 0, -1]), + vertex([1, 1, -1], [0, 0, -1]), + vertex([1, -1, -1], [0, 0, -1]), + vertex([-1, -1, -1], [0, 0, -1]), + // right (1, 0, 0) + vertex([1, -1, -1], [1, 0, 0]), + vertex([1, 1, -1], [1, 0, 0]), + vertex([1, 1, 1], [1, 0, 0]), + vertex([1, -1, 1], [1, 0, 0]), + // left (-1, 0, 0) + vertex([-1, -1, 1], [-1, 0, 0]), + vertex([-1, 1, 1], [-1, 0, 0]), + vertex([-1, 1, -1], [-1, 0, 0]), + vertex([-1, -1, -1], [-1, 0, 0]), + // front (0, 1, 0) + vertex([1, 1, -1], [0, 1, 0]), + vertex([-1, 1, -1], [0, 1, 0]), + vertex([-1, 1, 1], [0, 1, 0]), + vertex([1, 1, 1], [0, 1, 0]), + // back (0, -1, 0) + vertex([1, -1, 1], [0, -1, 0]), + vertex([-1, -1, 1], [0, -1, 0]), + vertex([-1, -1, -1], [0, -1, 0]), + vertex([1, -1, -1], [0, -1, 0]), + ]; + + let index_data: &[u16] = &[ + 0, 1, 2, 2, 3, 0, // top + 4, 5, 6, 6, 7, 4, // bottom + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // front + 20, 21, 22, 22, 23, 20, // back + ]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +fn create_plane(size: i8) -> (Vec, Vec) { + let vertex_data = [ + vertex([size, -size, 0], [0, 0, 1]), + vertex([size, size, 0], [0, 0, 1]), + vertex([-size, -size, 0], [0, 0, 1]), + vertex([-size, size, 0], [0, 0, 1]), + ]; + + let index_data: &[u16] = &[0, 1, 2, 2, 1, 3]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +struct Entity { + mx_world: cgmath::Matrix4, + rotation_speed: f32, + color: wgpu::Color, + vertex_buf: Rc, + index_buf: Rc, + index_format: wgpu::IndexFormat, + index_count: usize, + uniform_offset: wgpu::DynamicOffset, +} + +struct Light { + pos: cgmath::Point3, + color: wgpu::Color, + fov: f32, + depth: Range, + target_view: wgpu::TextureView, +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct LightRaw { + proj: [[f32; 4]; 4], + pos: [f32; 4], + color: [f32; 4], +} + +impl Light { + fn to_raw(&self) -> LightRaw { + use cgmath::{Deg, EuclideanSpace, Matrix4, PerspectiveFov, Point3, Vector3}; + + let mx_view = Matrix4::look_at(self.pos, Point3::origin(), Vector3::unit_z()); + let projection = PerspectiveFov { + fovy: Deg(self.fov).into(), + aspect: 1.0, + near: self.depth.start, + far: self.depth.end, + }; + let mx_correction = framework::OPENGL_TO_WGPU_MATRIX; + let mx_view_proj = + mx_correction * cgmath::Matrix4::from(projection.to_perspective()) * mx_view; + LightRaw { + proj: *mx_view_proj.as_ref(), + pos: [self.pos.x, self.pos.y, self.pos.z, 1.0], + color: [ + self.color.r as f32, + self.color.g as f32, + self.color.b as f32, + 1.0, + ], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct ForwardUniforms { + proj: [[f32; 4]; 4], + num_lights: [u32; 4], +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct EntityUniforms { + model: [[f32; 4]; 4], + color: [f32; 4], +} + +#[repr(C)] +struct ShadowUniforms { + proj: [[f32; 4]; 4], +} + +struct Pass { + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + uniform_buf: wgpu::Buffer, +} + +struct Example { + entities: Vec, + lights: Vec, + lights_are_dirty: bool, + shadow_pass: Pass, + forward_pass: Pass, + forward_depth: wgpu::TextureView, + entity_bind_group: wgpu::BindGroup, + light_uniform_buf: wgpu::Buffer, + entity_uniform_buf: wgpu::Buffer, +} + +impl Example { + const MAX_LIGHTS: usize = 10; + const SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + const SHADOW_SIZE: wgpu::Extent3d = wgpu::Extent3d { + width: 512, + height: 512, + depth: Self::MAX_LIGHTS as u32, + }; + const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + + fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4 { + let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 20.0); + let mx_view = cgmath::Matrix4::look_at( + cgmath::Point3::new(3.0f32, -10.0, 6.0), + cgmath::Point3::new(0f32, 0.0, 0.0), + cgmath::Vector3::unit_z(), + ); + let mx_correction = framework::OPENGL_TO_WGPU_MATRIX; + mx_correction * mx_projection * mx_view + } +} + +impl framework::Example for Example { + fn optional_features() -> wgpu::Features { + wgpu::Features::DEPTH_CLAMPING + } + + fn init( + sc_desc: &wgpu::SwapChainDescriptor, + device: &wgpu::Device, + _queue: &wgpu::Queue, + ) -> Self { + // Create the vertex and index buffers + let vertex_size = mem::size_of::(); + let (cube_vertex_data, cube_index_data) = create_cube(); + let cube_vertex_buf = Rc::new(device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Cubes Vertex Buffer"), + contents: bytemuck::cast_slice(&cube_vertex_data), + usage: wgpu::BufferUsage::VERTEX, + }, + )); + + let cube_index_buf = Rc::new(device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Cubes Index Buffer"), + contents: bytemuck::cast_slice(&cube_index_data), + usage: wgpu::BufferUsage::INDEX, + }, + )); + + let (plane_vertex_data, plane_index_data) = create_plane(7); + let plane_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Plane Vertex Buffer"), + contents: bytemuck::cast_slice(&plane_vertex_data), + usage: wgpu::BufferUsage::VERTEX, + }); + + let plane_index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Plane Index Buffer"), + contents: bytemuck::cast_slice(&plane_index_data), + usage: wgpu::BufferUsage::INDEX, + }); + + struct CubeDesc { + offset: cgmath::Vector3, + angle: f32, + scale: f32, + rotation: f32, + } + let cube_descs = [ + CubeDesc { + offset: cgmath::vec3(-2.0, -2.0, 2.0), + angle: 10.0, + scale: 0.7, + rotation: 0.1, + }, + CubeDesc { + offset: cgmath::vec3(2.0, -2.0, 2.0), + angle: 50.0, + scale: 1.3, + rotation: 0.2, + }, + CubeDesc { + offset: cgmath::vec3(-2.0, 2.0, 2.0), + angle: 140.0, + scale: 1.1, + rotation: 0.3, + }, + CubeDesc { + offset: cgmath::vec3(2.0, 2.0, 2.0), + angle: 210.0, + scale: 0.9, + rotation: 0.4, + }, + ]; + + let entity_uniform_size = mem::size_of::() as wgpu::BufferAddress; + let num_entities = 1 + cube_descs.len() as wgpu::BufferAddress; + assert!(entity_uniform_size <= wgpu::BIND_BUFFER_ALIGNMENT); + //Note: dynamic offsets also have to be aligned to `BIND_BUFFER_ALIGNMENT`. + let entity_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: num_entities * wgpu::BIND_BUFFER_ALIGNMENT, + usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + mapped_at_creation: false, + }); + + let index_format = wgpu::IndexFormat::Uint16; + + let mut entities = vec![{ + use cgmath::SquareMatrix; + Entity { + mx_world: cgmath::Matrix4::identity(), + rotation_speed: 0.0, + color: wgpu::Color::WHITE, + vertex_buf: Rc::new(plane_vertex_buf), + index_buf: Rc::new(plane_index_buf), + index_format, + index_count: plane_index_data.len(), + uniform_offset: 0, + } + }]; + + for (i, cube) in cube_descs.iter().enumerate() { + use cgmath::{Decomposed, Deg, InnerSpace, Quaternion, Rotation3}; + + let transform = Decomposed { + disp: cube.offset.clone(), + rot: Quaternion::from_axis_angle(cube.offset.normalize(), Deg(cube.angle)), + scale: cube.scale, + }; + entities.push(Entity { + mx_world: cgmath::Matrix4::from(transform), + rotation_speed: cube.rotation, + color: wgpu::Color::GREEN, + vertex_buf: Rc::clone(&cube_vertex_buf), + index_buf: Rc::clone(&cube_index_buf), + index_format, + index_count: cube_index_data.len(), + uniform_offset: ((i + 1) * wgpu::BIND_BUFFER_ALIGNMENT as usize) as _, + }); + } + + let local_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: wgpu::BufferSize::new(entity_uniform_size), + }, + count: None, + }], + label: None, + }); + let entity_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &local_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &entity_uniform_buf, + offset: 0, + size: wgpu::BufferSize::new(entity_uniform_size), + }, + }], + label: None, + }); + + // Create other resources + let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("shadow"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + ..Default::default() + }); + + let shadow_texture = device.create_texture(&wgpu::TextureDescriptor { + size: Self::SHADOW_SIZE, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::SHADOW_FORMAT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT | wgpu::TextureUsage::SAMPLED, + label: None, + }); + let shadow_view = shadow_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut shadow_target_views = (0..2) + .map(|i| { + Some(shadow_texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("shadow"), + format: None, + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + level_count: None, + base_array_layer: i as u32, + array_layer_count: NonZeroU32::new(1), + })) + }) + .collect::>(); + let lights = vec![ + Light { + pos: cgmath::Point3::new(7.0, -5.0, 10.0), + color: wgpu::Color { + r: 0.5, + g: 1.0, + b: 0.5, + a: 1.0, + }, + fov: 60.0, + depth: 1.0..20.0, + target_view: shadow_target_views[0].take().unwrap(), + }, + Light { + pos: cgmath::Point3::new(-5.0, 7.0, 10.0), + color: wgpu::Color { + r: 1.0, + g: 0.5, + b: 0.5, + a: 1.0, + }, + fov: 45.0, + depth: 1.0..20.0, + target_view: shadow_target_views[1].take().unwrap(), + }, + ]; + let light_uniform_size = + (Self::MAX_LIGHTS * mem::size_of::()) as wgpu::BufferAddress; + let light_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: light_uniform_size, + usage: wgpu::BufferUsage::UNIFORM + | wgpu::BufferUsage::COPY_SRC + | wgpu::BufferUsage::COPY_DST, + mapped_at_creation: false, + }); + + let vertex_attr = wgpu::vertex_attr_array![0 => Char4, 1 => Char4]; + let vb_desc = wgpu::VertexBufferDescriptor { + stride: vertex_size as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &vertex_attr, + }; + + let shadow_pass = { + let uniform_size = mem::size_of::() as wgpu::BufferAddress; + // Create pipeline layout + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, // global + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new(uniform_size), + }, + count: None, + }], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("shadow"), + bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout], + push_constant_ranges: &[], + }); + + let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: uniform_size, + usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + mapped_at_creation: false, + }); + + // Create bind group + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buf.as_entire_binding(), + }], + label: None, + }); + + // Create the render pipeline + let vs_module = device.create_shader_module(&wgpu::include_spirv!("bake.vert.spv")); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("shadow"), + layout: Some(&pipeline_layout), + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: None, + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Ccw, + cull_mode: wgpu::CullMode::Back, + polygon_mode: wgpu::PolygonMode::Fill, + depth_bias: 2, // corresponds to bilinear filtering + depth_bias_slope_scale: 2.0, + depth_bias_clamp: 0.0, + clamp_depth: device.features().contains(wgpu::Features::DEPTH_CLAMPING), + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[], + depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { + format: Self::SHADOW_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilStateDescriptor::default(), + }), + vertex_state: wgpu::VertexStateDescriptor { + index_format: Some(index_format), + vertex_buffers: &[vb_desc.clone()], + }, + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + Pass { + pipeline, + bind_group, + uniform_buf, + } + }; + + let forward_pass = { + // Create pipeline layout + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, // global + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new(mem::size_of::< + ForwardUniforms, + >( + ) + as _), + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, // lights + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new(light_uniform_size), + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Depth, + view_dimension: wgpu::TextureViewDimension::D2Array, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + comparison: true, + filtering: false, + }, + count: None, + }, + ], + label: None, + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("main"), + bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout], + push_constant_ranges: &[], + }); + + let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); + let forward_uniforms = ForwardUniforms { + proj: *mx_total.as_ref(), + num_lights: [lights.len() as u32, 0, 0, 0], + }; + let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Uniform Buffer"), + contents: bytemuck::bytes_of(&forward_uniforms), + usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + }); + + // Create bind group + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: light_uniform_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(&shadow_view), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler(&shadow_sampler), + }, + ], + label: None, + }); + + // Create the render pipeline + let vs_module = device.create_shader_module(&wgpu::include_spirv!("forward.vert.spv")); + let fs_module = device.create_shader_module(&wgpu::include_spirv!("forward.frag.spv")); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("main"), + layout: Some(&pipeline_layout), + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Ccw, + cull_mode: wgpu::CullMode::Back, + ..Default::default() + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[sc_desc.format.into()], + depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { + format: Self::DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilStateDescriptor::default(), + }), + vertex_state: wgpu::VertexStateDescriptor { + index_format: Some(index_format), + vertex_buffers: &[vb_desc], + }, + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + Pass { + pipeline, + bind_group, + uniform_buf, + } + }; + + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: sc_desc.width, + height: sc_desc.height, + depth: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + label: None, + }); + + Example { + entities, + lights, + lights_are_dirty: true, + shadow_pass, + forward_pass, + forward_depth: depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), + light_uniform_buf, + entity_uniform_buf, + entity_bind_group, + } + } + + fn update(&mut self, _event: winit::event::WindowEvent) { + //empty + } + + fn resize( + &mut self, + sc_desc: &wgpu::SwapChainDescriptor, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) { + // update view-projection matrix + let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); + let mx_ref: &[f32; 16] = mx_total.as_ref(); + queue.write_buffer( + &self.forward_pass.uniform_buf, + 0, + bytemuck::cast_slice(mx_ref), + ); + + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: sc_desc.width, + height: sc_desc.height, + depth: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + label: None, + }); + self.forward_depth = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + } + + fn render( + &mut self, + frame: &wgpu::SwapChainTexture, + device: &wgpu::Device, + queue: &wgpu::Queue, + _spawner: &impl futures::task::LocalSpawn, + ) { + // update uniforms + for entity in self.entities.iter_mut() { + if entity.rotation_speed != 0.0 { + let rotation = cgmath::Matrix4::from_angle_x(cgmath::Deg(entity.rotation_speed)); + entity.mx_world = entity.mx_world * rotation; + } + let data = EntityUniforms { + model: entity.mx_world.into(), + color: [ + entity.color.r as f32, + entity.color.g as f32, + entity.color.b as f32, + entity.color.a as f32, + ], + }; + queue.write_buffer( + &self.entity_uniform_buf, + entity.uniform_offset as wgpu::BufferAddress, + bytemuck::bytes_of(&data), + ); + } + + if self.lights_are_dirty { + self.lights_are_dirty = false; + for (i, light) in self.lights.iter().enumerate() { + queue.write_buffer( + &self.light_uniform_buf, + (i * mem::size_of::()) as wgpu::BufferAddress, + bytemuck::bytes_of(&light.to_raw()), + ); + } + } + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.push_debug_group("shadow passes"); + for (i, light) in self.lights.iter().enumerate() { + encoder.push_debug_group(&format!( + "shadow pass {} (light at position {:?})", + i, light.pos + )); + + // The light uniform buffer already has the projection, + // let's just copy it over to the shadow uniform buffer. + encoder.copy_buffer_to_buffer( + &self.light_uniform_buf, + (i * mem::size_of::()) as wgpu::BufferAddress, + &self.shadow_pass.uniform_buf, + 0, + 64, + ); + + encoder.insert_debug_marker("render entities"); + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachmentDescriptor { + attachment: &light.target_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }, + ), + }); + pass.set_pipeline(&self.shadow_pass.pipeline); + pass.set_bind_group(0, &self.shadow_pass.bind_group, &[]); + + for entity in &self.entities { + pass.set_bind_group(1, &self.entity_bind_group, &[entity.uniform_offset]); + pass.set_index_buffer(entity.index_buf.slice(..), entity.index_format); + pass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); + pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); + } + } + + encoder.pop_debug_group(); + } + encoder.pop_debug_group(); + + // forward pass + encoder.push_debug_group("forward rendering pass"); + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: true, + }, + }], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor { + attachment: &self.forward_depth, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: false, + }), + stencil_ops: None, + }), + }); + pass.set_pipeline(&self.forward_pass.pipeline); + pass.set_bind_group(0, &self.forward_pass.bind_group, &[]); + + for entity in &self.entities { + pass.set_bind_group(1, &self.entity_bind_group, &[entity.uniform_offset]); + pass.set_index_buffer(entity.index_buf.slice(..), entity.index_format); + pass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); + pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); + } + } + encoder.pop_debug_group(); + + queue.submit(iter::once(encoder.finish())); + } +} + +fn main() { + framework::run::("shadow"); +} diff --git a/shadow/screenshot.png b/shadow/screenshot.png new file mode 100644 index 0000000..ae07823 Binary files /dev/null and b/shadow/screenshot.png differ