use std::borrow::Cow;
use glam::Mat4;
use crate::engine::Engine;
use crate::platform::types::TextureResource;
use super::mesh::Mesh3D;
use super::shader_standard::Mesh3DShader;
use super::vertex::Mesh3DVertex;
const PBR_MAX_LIGHTS: usize = 4;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
struct PbrUniform {
mvp: [[f32; 4]; 4],
model: [[f32; 4]; 4],
camera_pos: [f32; 4],
albedo: [f32; 4],
pbr_params: [f32; 4], emissive: [f32; 4],
ambient: [f32; 4],
light_dirs: [[f32; 4]; PBR_MAX_LIGHTS],
light_colors: [[f32; 4]; PBR_MAX_LIGHTS],
light_positions: [[f32; 4]; PBR_MAX_LIGHTS], light_params: [[f32; 4]; PBR_MAX_LIGHTS], has_texture: f32,
_padding: [f32; 3],
}
#[derive(Clone)]
pub struct Draw3DPbr {
pub mesh: Mesh3D,
pub model: Mat4,
pub material: crate::light3d::PbrMaterial,
pub texture: Option<crate::handle::Handle>,
}
impl Draw3DPbr {
pub fn new(mesh: Mesh3D, model: Mat4) -> Self {
Self {
mesh,
model,
material: crate::light3d::PbrMaterial::default(),
texture: None,
}
}
pub fn with_material(mut self, material: crate::light3d::PbrMaterial) -> Self {
self.material = material;
self
}
pub fn with_texture(mut self, texture: crate::handle::Handle) -> Self {
self.texture = Some(texture);
self
}
}
const MAX_PBR_DRAWS: usize = 256;
pub struct Mesh3DPbrShader {
pipeline: wgpu::RenderPipeline,
uniform: wgpu::Buffer,
uniform_alignment: u32,
uniform_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 Mesh3DPbrShader {
pub fn new(g: &Engine) -> Self {
let state = g.backend_state();
let device = &state.device;
let format = state.surface_view_format;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("mesh3d_pbr.shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
"../../assets/shaders/mesh3d_pbr.wgsl"
))),
});
let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment;
let uniform_size = std::mem::size_of::<PbrUniform>() as u32;
let aligned_uniform_size = uniform_size.div_ceil(uniform_alignment) * uniform_alignment;
let uniform = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("mesh3d_pbr.uniform"),
size: (aligned_uniform_size as usize * MAX_PBR_DRAWS) as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("mesh3d_pbr.uniform_bgl"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: wgpu::BufferSize::new(uniform_size as u64),
},
count: None,
}],
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("mesh3d_pbr.uniform_bg"),
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniform,
offset: 0,
size: wgpu::BufferSize::new(uniform_size as u64),
}),
}],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("mesh3d_pbr.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("mesh3d_pbr.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("mesh3d_pbr.pipeline_layout"),
bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("mesh3d_pbr.pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Mesh3DVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x2],
}],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
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,
uniform_alignment,
uniform_bind_group,
texture_bind_group_layout,
default_texture_bind_group,
texture_bind_group_cache: hashbrown::HashMap::new(),
}
}
pub fn frame_start(&mut self) {
self.texture_bind_group_cache.clear();
}
fn create_texture_bind_group(
&mut self,
device: &wgpu::Device,
texture: &TextureResource,
) -> wgpu::BindGroup {
let key = &texture.view as *const _ as usize;
if let Some(bg) = self.texture_bind_group_cache.get(&key) {
return bg.clone();
}
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("mesh3d_pbr.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, bg.clone());
bg
}
#[allow(clippy::too_many_arguments)]
pub fn record(
&mut self,
g: &Engine,
color_target: &wgpu::Texture,
depth_view: &wgpu::TextureView,
view_proj: Mat4,
draws: &[(
&Mesh3D,
Mat4,
Option<&TextureResource>,
&crate::light3d::PbrMaterial,
)],
encoder: &mut wgpu::CommandEncoder,
should_clear: bool,
) {
if draws.is_empty() {
return;
}
let state = g.backend_state();
let camera = g.camera3d();
let lighting = g.lighting();
let uniform_size = std::mem::size_of::<PbrUniform>() as u32;
let aligned_uniform_size =
uniform_size.div_ceil(self.uniform_alignment) * self.uniform_alignment;
let mut light_dirs = [[0.0f32; 4]; PBR_MAX_LIGHTS];
let mut light_colors = [[0.0f32; 4]; PBR_MAX_LIGHTS];
let mut light_positions = [[0.0f32; 4]; PBR_MAX_LIGHTS];
let mut light_params_arr = [[0.0f32; 4]; PBR_MAX_LIGHTS];
for (i, light_opt) in lighting.lights.iter().enumerate() {
if i >= PBR_MAX_LIGHTS {
break;
}
if let Some(light) = light_opt
&& light.enabled
{
let (light_type, radius, inner, outer) = match light.kind {
crate::light3d::LightKind::Directional => (0.0, 0.0, 0.0, 0.0),
crate::light3d::LightKind::Point { radius } => (1.0, radius, 0.0, 0.0),
crate::light3d::LightKind::Spot {
radius,
inner_angle,
outer_angle,
} => (2.0, radius, inner_angle.cos(), outer_angle.cos()),
};
light_dirs[i] = [light.direction.x, light.direction.y, light.direction.z, 0.0];
light_colors[i] = [light.color.r, light.color.g, light.color.b, light.color.a];
light_positions[i] = [
light.position.x,
light.position.y,
light.position.z,
light_type,
];
light_params_arr[i] = [radius, inner, outer, light.intensity];
}
}
let mut uniform_data = Vec::with_capacity(draws.len() * aligned_uniform_size as usize);
for (_, model, texture_res, material) in draws.iter() {
let mvp = view_proj * *model;
let has_texture = if texture_res.is_some() {
1.0f32
} else {
0.0f32
};
let u = PbrUniform {
mvp: mvp.to_cols_array_2d(),
model: model.to_cols_array_2d(),
camera_pos: [camera.eye.x, camera.eye.y, camera.eye.z, 0.0],
albedo: [
material.albedo.r,
material.albedo.g,
material.albedo.b,
material.albedo.a,
],
pbr_params: [material.metallic, material.roughness, material.ao, 0.0],
emissive: [
material.emissive.r,
material.emissive.g,
material.emissive.b,
material.emissive.a,
],
ambient: [
lighting.ambient.r,
lighting.ambient.g,
lighting.ambient.b,
lighting.ambient.a,
],
light_dirs,
light_colors,
light_positions,
light_params: light_params_arr,
has_texture,
_padding: [0.0; 3],
};
uniform_data.extend_from_slice(bytemuck::bytes_of(&u));
let padding = aligned_uniform_size as usize - std::mem::size_of::<PbrUniform>();
uniform_data.extend(std::iter::repeat_n(0u8, padding));
}
state.queue.write_buffer(&self.uniform, 0, &uniform_data);
let color_view = color_target.create_view(&wgpu::TextureViewDescriptor::default());
let (color_load, depth_load) = if should_clear {
(
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
wgpu::LoadOp::Clear(0.0),
)
} else {
(wgpu::LoadOp::Load, wgpu::LoadOp::Load)
};
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("mesh3d_pbr.pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &color_view,
resolve_target: None,
ops: wgpu::Operations {
load: color_load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: depth_load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
});
pass.set_pipeline(&self.pipeline);
for (i, (mesh, _, texture_res, _)) in draws.iter().enumerate() {
let offset = i as u32 * aligned_uniform_size;
pass.set_bind_group(0, &self.uniform_bind_group, &[offset]);
if let Some(tex_res) = texture_res {
let tex_bg = self.create_texture_bind_group(&state.device, tex_res);
pass.set_bind_group(1, &tex_bg, &[]);
} else {
pass.set_bind_group(1, &self.default_texture_bind_group, &[]);
}
pass.set_vertex_buffer(0, mesh.vertex.slice(..));
pass.set_index_buffer(mesh.index.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..mesh.index_count, 0, 0..1);
}
}
}