use wgpu::util::DeviceExt;
use crate::{primitives::textures::Texture, render::render_engine::RenderEngine, ui::style::Color};
#[derive(Debug, Default)]
pub struct MaterialBuilder {
pub albedo_texture: Option<Texture>,
pub roughness_texture: Option<Texture>,
pub emissive_texture: Option<Texture>,
pub normal_texture: Option<Texture>,
pub occlusion_texture: Option<Texture>,
pub albedo_color: Color,
pub emissive_color: Color,
pub metallic_factor: f32,
pub roughness_factor: f32,
pub alpha_mode: f32,
pub alpha_cutoff: f32,
}
impl MaterialBuilder {
pub fn build(self, engine: &RenderEngine) -> Material {
let albedo_buffer = engine.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("material_albedo_buffer"),
contents: bytemuck::cast_slice(&[self.albedo_color.red, self.albedo_color.green, self.albedo_color.blue, self.albedo_color.alpha]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
}
);
let emissive_buffer = engine.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("material_emissive_buffer"),
contents: bytemuck::cast_slice(&[self.emissive_color.red, self.emissive_color.green, self.emissive_color.blue, self.emissive_color.alpha]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
}
);
let metadata_buffer = engine.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("material_metadata_buffer"),
contents: bytemuck::cast_slice(&[self.metallic_factor, self.roughness_factor, self.alpha_mode, self.alpha_cutoff]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
}
);
let empty_texture = create_empty_texture(engine);
let diffuse_texture = if self.albedo_texture.is_some() { self.albedo_texture.as_ref().unwrap() } else { &empty_texture };
let roughness_texture = if self.roughness_texture.is_some() { self.roughness_texture.as_ref().unwrap() } else { &empty_texture };
let emissive_texture = if self.albedo_texture.is_some() { self.emissive_texture.as_ref().unwrap() } else { &empty_texture };
let normal_texture = if self.albedo_texture.is_some() { self.normal_texture.as_ref().unwrap() } else { &empty_texture };
let occlusion_texture = if self.albedo_texture.is_some() { self.occlusion_texture.as_ref().unwrap() } else { &empty_texture };
Material {
bind_group: engine.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("material_bind_group"),
layout: &engine.device.create_bind_group_layout(&Material::BIND_LAYOUT),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view)
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler)
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&roughness_texture.view)
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&roughness_texture.sampler)
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(&emissive_texture.view)
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::Sampler(&emissive_texture.sampler)
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(&normal_texture.view)
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::Sampler(&normal_texture.sampler)
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::TextureView(&occlusion_texture.view)
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::Sampler(&occlusion_texture.sampler)
},
wgpu::BindGroupEntry {
binding: 10,
resource: albedo_buffer.as_entire_binding()
},
wgpu::BindGroupEntry {
binding: 11,
resource: emissive_buffer.as_entire_binding()
},
wgpu::BindGroupEntry {
binding: 12,
resource: metadata_buffer.as_entire_binding()
}
]
})
}
}
}
#[derive(Debug)]
pub struct Material {
bind_group: wgpu::BindGroup
}
impl Material {
pub const BIND_LAYOUT: wgpu::BindGroupLayoutDescriptor<'static> = wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
count: None,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false
}
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
count: None,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false
}
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
count: None,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false
}
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 6,
visibility: wgpu::ShaderStages::FRAGMENT,
count: None,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false
}
},
wgpu::BindGroupLayoutEntry {
binding: 7,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 8,
visibility: wgpu::ShaderStages::FRAGMENT,
count: None,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false
}
},
wgpu::BindGroupLayoutEntry {
binding: 9,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 10,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None
},
count: None
},
wgpu::BindGroupLayoutEntry {
binding: 11,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None
},
count: None
},
wgpu::BindGroupLayoutEntry {
binding: 12,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None
},
count: None
}
],
label: Some("texture_bind_group_layout")
};
pub fn bind<'rpass>(&'rpass self, pass: &mut wgpu::RenderPass<'rpass>, idx: u32) {
pass.set_bind_group(idx, &self.bind_group, &[])
}
}
fn create_empty_texture(engine: &RenderEngine) -> Texture {
Texture::from_bytes(
&engine.device,
&engine.queue,
include_bytes!("empty.png"),
"forte.material.blank_texture",
).expect("Empty texture for Material did not create properly.")
}