use super::{Effect, FullscreenQuad};
use crate::context::WgpuContext;
use crate::core::buffer::RawUniformBuffer;
use crate::core::pipeline::PipelineBuilder;
use crate::core::render_states::{BlendState, CullState};
use crate::core::vertex::VertexPC;
use glam::Vec3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FogMode {
Linear,
Exponential,
ExponentialSquared,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct FogUniform {
pub color_mode: [f32; 4],
pub params: [f32; 4],
}
pub struct FogEffect {
pipeline: wgpu::RenderPipeline,
bind_group_layout: wgpu::BindGroupLayout,
uniform_buffer: RawUniformBuffer,
sampler: wgpu::Sampler,
depth_sampler: wgpu::Sampler,
quad: FullscreenQuad,
pub color: Vec3,
pub mode: FogMode,
pub start: f32,
pub end: f32,
pub density: f32,
}
impl FogEffect {
pub fn new(ctx: &WgpuContext, format: wgpu::TextureFormat) -> anyhow::Result<Self> {
let shader = include_str!("../shaders/effects/fog.wgsl");
let bind_group_layout =
ctx.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("fog bind group layout"),
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::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let pipeline = PipelineBuilder::new(ctx)
.label("fog pipeline")
.shader(shader)
.vertex_layout(VertexPC::layout())
.bind_group_layout(&bind_group_layout)
.color_format(format)
.blend(BlendState::Opaque)
.cull(CullState::None)
.build()?;
let uniform_buffer = RawUniformBuffer::new(
ctx,
std::mem::size_of::<FogUniform>() as u64,
Some("fog uniform"),
);
let sampler = ctx.device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("fog color sampler"),
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,
..Default::default()
});
let depth_sampler = ctx.device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("fog depth sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let quad = FullscreenQuad::new(ctx);
Ok(Self {
pipeline,
bind_group_layout,
uniform_buffer,
sampler,
depth_sampler,
quad,
color: Vec3::new(0.5, 0.6, 0.7),
mode: FogMode::Linear,
start: 10.0,
end: 100.0,
density: 0.02,
})
}
pub fn create_bind_group(
&self,
ctx: &WgpuContext,
color_input: &wgpu::TextureView,
depth_input: &wgpu::TextureView,
) -> wgpu::BindGroup {
ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("fog bind group"),
layout: &self.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(color_input),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(depth_input),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.depth_sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: self.uniform_buffer.buffer().as_entire_binding(),
},
],
})
}
pub fn update_uniform(&self, ctx: &WgpuContext, _near: f32, far: f32) {
let mode_value = match self.mode {
FogMode::Linear => 0.0,
FogMode::Exponential => 1.0,
FogMode::ExponentialSquared => 2.0,
};
let uniform = FogUniform {
color_mode: [self.color.x, self.color.y, self.color.z, mode_value],
params: [self.start, self.end, self.density, far],
};
self.uniform_buffer.write(ctx, &uniform);
}
pub fn apply_with_depth(
&self,
ctx: &WgpuContext,
encoder: &mut wgpu::CommandEncoder,
color_input: &wgpu::TextureView,
depth_input: &wgpu::TextureView,
output: &wgpu::TextureView,
) {
let bind_group = self.create_bind_group(ctx, color_input, depth_input);
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("fog pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: output,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &bind_group, &[]);
self.quad.draw(&mut render_pass);
}
}
impl Effect for FogEffect {
fn apply(
&self,
_ctx: &WgpuContext,
_encoder: &mut wgpu::CommandEncoder,
_input: &wgpu::TextureView,
_output: &wgpu::TextureView,
) {
panic!("FogEffect requires depth texture. Use apply_with_depth instead.");
}
}