use crate::render::{Texture}; use bytemuck::{Pod, Zeroable}; use glam::camera::lh::proj::directx::perspective; use glam::{Affine3A, EulerRot, Mat3A, Mat4, Vec3, Vec4}; use wgpu::util::DeviceExt; use wgpu::{Device, Queue}; use crate::render::instance::material::MaterialProperties; pub(crate) struct Eye { pub(crate) frame: Affine3A, pub(crate) environment: Environment, aspect_ratio: f32, z_near: f32, z_far: f32, fov_y: f32, pub(crate) environment_buffer: wgpu::Buffer, pub(crate) camera_buffer: wgpu::Buffer, pub(crate) layout: wgpu::BindGroupLayout, pub(crate) group: wgpu::BindGroup, pub sky_pipeline: wgpu::RenderPipeline, } #[repr(C)] #[derive(Pod, Copy, Clone, Zeroable)] pub struct Environment { ambient: Vec4, light: Vec4, dir: Vec4, } impl Eye { pub(crate) fn write(&mut self, queue: &Queue) { let camera = Mat4::from_mat3_translation( self.frame.matrix3.into(), Vec3::from(self.frame.translation), ); let projection = perspective(self.fov_y, self.aspect_ratio, self.z_near, self.z_far); queue.write_buffer( &self.camera_buffer, 0, bytemuck::cast_slice(&[ projection, camera * projection.inverse(), camera.inverse(), camera, ]), ); queue.write_buffer( &self.environment_buffer, 0, bytemuck::cast_slice(&[self.environment]), ); } pub(crate) fn new(device: &Device, config: &wgpu::SurfaceConfiguration, width: u32, height: u32, skybox: Texture) -> Eye { let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Camera Buffer"), contents: bytemuck::cast_slice(&[ Mat4::from_translation(Vec3::new(0.0, 2.0, -8.0)), Mat4::IDENTITY, Mat4::IDENTITY, Mat4::IDENTITY, ]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let dir = Vec3::new(1.0, 0.5, 1.0).normalize(); let environment = Environment { ambient: Vec4::new(0.1, 0.1, 0.1, 0.0), light: Vec4::new(1.0, 1.0, 1.0, 0.0), dir: Vec4::new(dir.x, dir.y, dir.z, 0.0), }; let environment_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(""), contents: bytemuck::cast_slice(&[environment]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_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::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, 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::Texture { multisampled: false, view_dimension: wgpu::TextureViewDimension::D2, sample_type: wgpu::TextureSampleType::Float { filterable: true }, }, count: None, }, ], label: Some("eye_bind_group_layout"), }); let shader = device.create_shader_module(wgpu::include_wgsl!("sky.wgsl")); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), bind_group_layouts: &[Some(&layout)], immediate_size: 0, }); //todo: use a pipeline cache! let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Sky Pipeline"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_sky"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_sky"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { // 4. format: config.format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], }), primitive: wgpu::PrimitiveState { front_face: wgpu::FrontFace::Cw, ..Default::default() }, depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: Some(false), depth_compare: Some(wgpu::CompareFunction::LessEqual), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let group = Eye::bind_group(&layout, device, &camera_buffer, &environment_buffer, skybox); Eye { aspect_ratio: width as f32 / height as f32, frame: Affine3A::IDENTITY, fov_y: 90.0, z_near: 0.1, z_far: 1000.0, camera_buffer, group, layout, environment, environment_buffer, sky_pipeline, } } fn bind_group( layout: &wgpu::BindGroupLayout, device: &wgpu::Device, camera: &wgpu::Buffer, environment: &wgpu::Buffer, skybox: Texture, ) -> wgpu::BindGroup { device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: camera.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: environment.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler( &MaterialProperties::default().sampler(device), ), }, wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&skybox.view), }, ], label: Some("eye_bind_group"), }) } pub fn skybox(&mut self, device: &wgpu::Device, texture: Texture) { self.group = Eye::bind_group( &self.layout, device, &self.camera_buffer, &self.environment_buffer, texture, ) } pub(crate) fn resize(&mut self, queue: &Queue, width: u32, height: u32) { self.aspect_ratio = width as f32 / height as f32; self.write(queue); } pub(crate) fn control(&mut self, delta: Vec3) { self.frame *= Affine3A::from_translation(delta); } pub(crate) fn rotate(&mut self, yaw: f32, pitch: f32) { let (mut y, mut x, z) = self.frame.matrix3.to_euler(EulerRot::YXZ); x = (x + pitch * 0.005).clamp(-1.4, 1.4); y += yaw * 0.005; self.frame.matrix3 = Mat3A::from_euler(EulerRot::YXZ, y, x, z); } }