use std::borrow::Cow;
use glam::Mat4;
use crate::color::Color;
use crate::engine::Engine;
use crate::handle::Handle;
use crate::light3d::Material3D;
use crate::platform::types::TextureResource;
use crate::skeleton::{AnimationPlayer, MAX_BONES};
use super::shader_standard::{Mesh3DShader, ResolvedDraw3DSkinned};
use super::skinned::{SkinnedMesh3D, SkinnedMesh3DVertex};
pub struct Draw3DSkinned {
pub mesh: SkinnedMesh3D,
pub model: Mat4,
pub texture: Option<Handle>,
pub color: Color,
pub material: Material3D,
pub bone_matrices: Vec<Mat4>,
}
impl Draw3DSkinned {
pub fn new(mesh: SkinnedMesh3D, model: Mat4, player: &AnimationPlayer) -> Self {
Self {
mesh,
model,
texture: None,
color: crate::color::WHITE,
material: Material3D::default(),
bone_matrices: player.bone_matrices().to_vec(),
}
}
pub fn with_texture(mut self, texture: Handle) -> Self {
self.texture = Some(texture);
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn with_material(mut self, material: Material3D) -> Self {
self.material = material;
self
}
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
struct SkinnedMesh3DUniform {
mvp: [[f32; 4]; 4],
model: [[f32; 4]; 4],
camera_pos: [f32; 4],
diffuse: [f32; 4],
specular: [f32; 4],
emissive: [f32; 4],
ambient: [f32; 4],
light_dir: [f32; 4],
light_color: [f32; 4],
light_pos: [f32; 4],
light_params: [f32; 4],
shininess: f32,
has_texture: f32,
_padding: [f32; 2],
}
pub struct SkinnedMesh3DShader {
pipeline: wgpu::RenderPipeline,
uniform_buffer: wgpu::Buffer,
bone_buffer: wgpu::Buffer,
#[allow(dead_code)]
bind_group_layout: wgpu::BindGroupLayout,
bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout,
default_texture_bind_group: wgpu::BindGroup,
texture_bind_group_cache: hashbrown::HashMap<usize, wgpu::BindGroup>,
}
impl SkinnedMesh3DShader {
pub fn new(g: &Engine) -> Self {
let state = g.backend_state();
let device = &state.device;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("mesh3d_skinned.shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
"../../assets/shaders/mesh3d_skinned.wgsl"
))),
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("skinned_mesh3d.uniform"),
size: std::mem::size_of::<SkinnedMesh3DUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bone_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("skinned_mesh3d.bones"),
size: (MAX_BONES * std::mem::size_of::<[[f32; 4]; 4]>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("skinned_mesh3d.bgl"),
entries: &[
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: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("skinned_mesh3d.bg"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: bone_buffer.as_entire_binding(),
},
],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("skinned_mesh3d.texture_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let default_texture = Mesh3DShader::create_default_texture(device, &state.queue);
let default_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("skinned_mesh3d.default_texture_bg"),
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&default_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&default_texture.sampler),
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("skinned_mesh3d.pipeline_layout"),
bind_group_layouts: &[&bind_group_layout, &texture_bind_group_layout],
push_constant_ranges: &[],
});
let vertex_layout = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<SkinnedMesh3DVertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x3,
offset: 12,
shader_location: 1,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 24,
shader_location: 2,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Uint32x4,
offset: 32,
shader_location: 3,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x4,
offset: 48,
shader_location: 4,
},
],
};
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("skinned_mesh3d.pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[vertex_layout],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: state.surface_view_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Greater, stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
Self {
pipeline,
uniform_buffer,
bone_buffer,
bind_group_layout,
bind_group,
texture_bind_group_layout,
default_texture_bind_group,
texture_bind_group_cache: hashbrown::HashMap::new(),
}
}
pub fn record(
&mut self,
g: &Engine,
color_target: &wgpu::TextureView,
depth_view: &wgpu::TextureView,
draws: &[ResolvedDraw3DSkinned],
encoder: &mut wgpu::CommandEncoder,
should_clear: bool,
) {
if draws.is_empty() {
return;
}
let state = g.backend_state();
let lighting = g.lighting();
let camera = g.camera3d();
let aspect = g.view_size().x / g.view_size().y;
let view_proj = camera.view_proj(aspect);
let load_op = if should_clear {
wgpu::LoadOp::Clear(wgpu::Color::BLACK)
} else {
wgpu::LoadOp::Load
};
let depth_load_op = if should_clear {
wgpu::LoadOp::Clear(0.0) } else {
wgpu::LoadOp::Load
};
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("skinned_mesh3d.pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_target,
resolve_target: None,
ops: wgpu::Operations {
load: load_op,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: depth_load_op,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
});
pass.set_pipeline(&self.pipeline);
for draw in draws {
let (light_dir, light_color, light_pos, light_params) =
Self::get_light_params(lighting);
let uniform = SkinnedMesh3DUniform {
mvp: (view_proj * draw.model).to_cols_array_2d(),
model: draw.model.to_cols_array_2d(),
camera_pos: [camera.eye.x, camera.eye.y, camera.eye.z, 1.0],
diffuse: [
draw.material.diffuse.r * draw.color.r,
draw.material.diffuse.g * draw.color.g,
draw.material.diffuse.b * draw.color.b,
draw.material.diffuse.a * draw.color.a,
],
specular: [
draw.material.specular.r,
draw.material.specular.g,
draw.material.specular.b,
draw.material.specular.a,
],
emissive: [
draw.material.emissive.r,
draw.material.emissive.g,
draw.material.emissive.b,
draw.material.emissive.a,
],
ambient: [
lighting.ambient.r,
lighting.ambient.g,
lighting.ambient.b,
lighting.ambient.a,
],
light_dir,
light_color,
light_pos,
light_params,
shininess: draw.material.shininess,
has_texture: if draw.texture_bind_group.is_some() {
1.0
} else {
0.0
},
_padding: [0.0, 0.0],
};
state
.queue
.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
let bone_data: Vec<[[f32; 4]; 4]> = draw
.bone_matrices
.iter()
.map(|m| m.to_cols_array_2d())
.collect();
let mut bone_buffer_data = vec![[[0.0f32; 4]; 4]; MAX_BONES];
for (i, mat) in bone_data.iter().enumerate().take(MAX_BONES) {
bone_buffer_data[i] = *mat;
}
state.queue.write_buffer(
&self.bone_buffer,
0,
bytemuck::cast_slice(&bone_buffer_data),
);
pass.set_bind_group(0, &self.bind_group, &[]);
if let Some(texture_bg) = &draw.texture_bind_group {
pass.set_bind_group(1, texture_bg, &[]);
} else {
pass.set_bind_group(1, &self.default_texture_bind_group, &[]);
}
pass.set_vertex_buffer(0, draw.mesh.vertex.slice(..));
pass.set_index_buffer(draw.mesh.index.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..draw.mesh.index_count, 0, 0..1);
}
}
fn get_light_params(
lighting: &crate::light3d::LightingState,
) -> ([f32; 4], [f32; 4], [f32; 4], [f32; 4]) {
let light = lighting.lights.iter().find_map(|l| l.as_ref());
if let Some(light) = light {
use crate::light3d::LightKind;
let (light_type, radius, inner_angle, outer_angle) = match light.kind {
LightKind::Directional => (0.0, 0.0, 0.0, 0.0),
LightKind::Point { radius } => (1.0, radius, 0.0, 0.0),
LightKind::Spot {
radius,
inner_angle,
outer_angle,
} => (2.0, radius, inner_angle, outer_angle),
};
(
[light.direction.x, light.direction.y, light.direction.z, 0.0],
[light.color.r, light.color.g, light.color.b, light.color.a],
[
light.position.x,
light.position.y,
light.position.z,
light_type,
],
[radius, inner_angle, outer_angle, light.intensity],
)
} else {
(
[-0.5, -1.0, -0.5, 0.0],
[1.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
)
}
}
pub fn get_or_create_texture_bind_group(
&mut self,
g: &Engine,
texture: &TextureResource,
) -> &wgpu::BindGroup {
let key = std::ptr::from_ref(&texture.texture) as usize;
if !self.texture_bind_group_cache.contains_key(&key) {
let state = g.backend_state();
let bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("skinned_mesh3d.texture_bg"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&texture.sampler),
},
],
});
self.texture_bind_group_cache.insert(key, bind_group);
}
self.texture_bind_group_cache.get(&key).unwrap()
}
}