360 lines
12 KiB
Rust
360 lines
12 KiB
Rust
use crate::{
|
|
camera::{self as cam},
|
|
engine::{self, World},
|
|
graphics_util::draw_cuboid,
|
|
};
|
|
use macroquad::prelude as mq;
|
|
use macroquad::ui::hash;
|
|
|
|
use rapier3d::prelude::*;
|
|
use strum::IntoEnumIterator;
|
|
mod ui;
|
|
|
|
const DEBUG_UI: bool = false;
|
|
const DEPTH_MAP_SIZE: u32 = 1024;
|
|
|
|
const LIGHT_PROJECTION_SIZE: f32 = 40.0;
|
|
const LIGHT_NEAR: f32 = 50.0;
|
|
const LIGHT_FAR: f32 = 400.0;
|
|
|
|
pub struct Light {
|
|
projection_matrix: mq::Mat4,
|
|
view_matrix: mq::Mat4,
|
|
pub position: mq::Vec3,
|
|
pub front: mq::Vec3,
|
|
}
|
|
impl Light {
|
|
pub fn new() -> Light {
|
|
let mut l = Light {
|
|
projection_matrix: mq::Mat4::orthographic_rh(
|
|
-LIGHT_PROJECTION_SIZE,
|
|
LIGHT_PROJECTION_SIZE,
|
|
-LIGHT_PROJECTION_SIZE,
|
|
LIGHT_PROJECTION_SIZE,
|
|
LIGHT_NEAR,
|
|
LIGHT_FAR,
|
|
),
|
|
view_matrix: mq::Mat4::NAN,
|
|
position: mq::Vec3::NAN,
|
|
front: mq::Vec3::NAN,
|
|
};
|
|
l.set_location(mq::vec3(5.0, 10.0, 0.0), mq::Vec3::NEG_Y);
|
|
return l;
|
|
}
|
|
pub fn set_location(&mut self, position: mq::Vec3, front: mq::Vec3) {
|
|
self.position = position;
|
|
self.front = front;
|
|
self.view_matrix =
|
|
mq::Mat4::look_at_rh(self.position, self.position + self.front, mq::Vec3::Y);
|
|
}
|
|
pub fn get_space_matrix(&self) -> mq::Mat4 {
|
|
return self.projection_matrix * self.view_matrix;
|
|
}
|
|
}
|
|
|
|
pub struct Renderer {
|
|
base_material: mq::Material,
|
|
depth_material: mq::Material,
|
|
depth_target: mq::RenderTarget,
|
|
pub light: Light,
|
|
pub camera: Box<dyn cam::CameraController>,
|
|
ui_state: ui::MaterialUi,
|
|
pub enable_shadows: bool,
|
|
}
|
|
|
|
#[derive(strum::EnumIter, Debug)]
|
|
pub enum MaterialUni {
|
|
RenderNormalsBool,
|
|
RenderShadowsBool,
|
|
ShadowStepCount,
|
|
ShadowStepSize,
|
|
ShadowMinBias,
|
|
ShadowFacBias,
|
|
ShadowMaxBias,
|
|
LightPosition,
|
|
LightSpaceMatrix,
|
|
}
|
|
|
|
impl MaterialUni {
|
|
pub fn as_str(&self) -> &'static str {
|
|
use MaterialUni::*;
|
|
match self {
|
|
RenderNormalsBool => "render_normals_bool",
|
|
RenderShadowsBool => "render_shadows_bool",
|
|
ShadowStepCount => "shadow_step_count",
|
|
ShadowStepSize => "shadow_step_size",
|
|
ShadowMinBias => "shadow_min_bias",
|
|
ShadowFacBias => "shadow_fac_bias",
|
|
ShadowMaxBias => "shadow_max_bias",
|
|
LightPosition => "light_position",
|
|
LightSpaceMatrix => "light_space_matrix",
|
|
}
|
|
}
|
|
pub fn as_type(&self) -> mq::UniformType {
|
|
use mq::UniformType::*;
|
|
use MaterialUni::*;
|
|
match self {
|
|
RenderNormalsBool => Int1,
|
|
RenderShadowsBool => Int1,
|
|
ShadowStepCount => Int1,
|
|
ShadowStepSize => Int1,
|
|
ShadowMinBias => Float1,
|
|
ShadowFacBias => Float1,
|
|
ShadowMaxBias => Float1,
|
|
LightPosition => Float3,
|
|
LightSpaceMatrix => Mat4,
|
|
}
|
|
}
|
|
pub fn uniform_list() -> Vec<mq::UniformDesc> {
|
|
let mut ret: Vec<mq::UniformDesc> = Default::default();
|
|
for member in MaterialUni::iter() {
|
|
ret.push(mq::UniformDesc::new(member.as_str(), member.as_type()));
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
impl Renderer {
|
|
pub fn new(camera: Box<dyn cam::CameraController>) -> Renderer {
|
|
let material = mq::load_material(
|
|
mq::ShaderSource::Glsl {
|
|
vertex: include_str!("shaders/shader.vert"),
|
|
fragment: include_str!("shaders/shader.frag"),
|
|
},
|
|
mq::MaterialParams {
|
|
pipeline_params: mq::PipelineParams {
|
|
cull_face: macroquad::miniquad::CullFace::Front,
|
|
depth_test: mq::Comparison::LessOrEqual,
|
|
depth_write: true,
|
|
..Default::default()
|
|
},
|
|
uniforms: MaterialUni::uniform_list(),
|
|
textures: vec![String::from("shadow_map")],
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
material.set_uniform(MaterialUni::RenderNormalsBool.as_str(), 0);
|
|
material.set_uniform(MaterialUni::RenderShadowsBool.as_str(), true as i32);
|
|
|
|
let depth_material = mq::load_material(
|
|
mq::ShaderSource::Glsl {
|
|
vertex: include_str!("shaders/depth.vert"),
|
|
fragment: include_str!("shaders/depth.frag"),
|
|
},
|
|
mq::MaterialParams {
|
|
pipeline_params: mq::PipelineParams {
|
|
// cull_face: macroquad::miniquad::CullFace::Back,
|
|
depth_test: mq::Comparison::LessOrEqual,
|
|
depth_write: true,
|
|
..Default::default()
|
|
},
|
|
uniforms: vec![mq::UniformDesc::new(
|
|
"light_space_matrix",
|
|
mq::UniformType::Mat4,
|
|
)],
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
let depth_map_texture = mq::render_target_ex(
|
|
DEPTH_MAP_SIZE,
|
|
DEPTH_MAP_SIZE,
|
|
mq::RenderTargetParams {
|
|
sample_count: 1,
|
|
depth: true,
|
|
},
|
|
);
|
|
depth_map_texture
|
|
.texture
|
|
.set_filter(mq::FilterMode::Nearest);
|
|
|
|
let light = Light::new();
|
|
let mut r = Renderer {
|
|
base_material: material,
|
|
depth_material,
|
|
depth_target: depth_map_texture,
|
|
light,
|
|
camera,
|
|
ui_state: ui::MaterialUi::new(),
|
|
enable_shadows: true,
|
|
};
|
|
r.light
|
|
.set_location(mq::vec3(10.0, 40.0, 10.0), mq::Vec3::NEG_Y);
|
|
r.apply_config();
|
|
return r;
|
|
}
|
|
pub fn apply_config(&mut self) {
|
|
self.ui_state.apply(&self.base_material);
|
|
self.enable_shadows = self.ui_state.render_shadows_bool;
|
|
}
|
|
pub fn update_camera(&mut self, world: &World) {
|
|
self.camera.update(mq::get_frame_time(), world);
|
|
}
|
|
pub fn update_light(&mut self, world: &World) {
|
|
let light_view = self.light.view_matrix;
|
|
|
|
let mut min_ls = mq::vec3(f32::MAX, 0.0, f32::MAX);
|
|
let mut max_ls = mq::vec3(f32::MIN, 50.0, f32::MIN);
|
|
|
|
// 1. iterate all world objects
|
|
for (handle, _) in world.colliders.iter() {
|
|
let (min_ws, max_ws) = engine::collider_world_aabb(world, handle);
|
|
|
|
// 2. get 8 corners of the world-space AABB
|
|
for &corner in &[
|
|
mq::vec3(min_ws.x, min_ws.y, min_ws.z),
|
|
mq::vec3(min_ws.x, min_ws.y, max_ws.z),
|
|
mq::vec3(min_ws.x, max_ws.y, min_ws.z),
|
|
mq::vec3(min_ws.x, max_ws.y, max_ws.z),
|
|
mq::vec3(max_ws.x, min_ws.y, min_ws.z),
|
|
mq::vec3(max_ws.x, min_ws.y, max_ws.z),
|
|
mq::vec3(max_ws.x, max_ws.y, min_ws.z),
|
|
mq::vec3(max_ws.x, max_ws.y, max_ws.z),
|
|
] {
|
|
// 3. transform to light space
|
|
let p = light_view.transform_point3(corner);
|
|
|
|
min_ls.x = min_ls.x.min(p.x);
|
|
min_ls.y = min_ls.y.min(p.y);
|
|
min_ls.z = min_ls.z.min(p.z);
|
|
|
|
max_ls.x = max_ls.x.max(p.x);
|
|
max_ls.y = max_ls.y.max(p.y);
|
|
max_ls.z = max_ls.z.max(p.z);
|
|
}
|
|
}
|
|
|
|
// 4. build tight-fitting orthographic projection
|
|
self.light.projection_matrix = mq::Mat4::orthographic_rh(
|
|
min_ls.x, max_ls.x, // left/right
|
|
min_ls.y, max_ls.y, // bottom/top
|
|
-max_ls.z, -min_ls.z, // near/far (RH coordinate system)
|
|
);
|
|
|
|
// 5. upload to materials
|
|
let light_space_matrix = self.light.get_space_matrix();
|
|
self.base_material
|
|
.set_uniform("light_position", self.light.position);
|
|
self.base_material
|
|
.set_uniform("light_space_matrix", light_space_matrix);
|
|
self.depth_material
|
|
.set_uniform("light_space_matrix", light_space_matrix);
|
|
}
|
|
|
|
fn render_depth(&mut self, mut world: &mut World) {
|
|
mq::set_camera(&mq::Camera3D {
|
|
position: self.light.position,
|
|
target: self.light.position + self.light.front,
|
|
up: mq::Vec3::Y,
|
|
render_target: Some(self.depth_target.clone()),
|
|
..Default::default()
|
|
});
|
|
mq::gl_use_material(&self.depth_material);
|
|
mq::clear_background(mq::BLACK);
|
|
draw_world(&mut world, None);
|
|
mq::gl_use_default_material();
|
|
mq::set_default_camera();
|
|
|
|
self.base_material
|
|
.set_texture("shadow_map", self.depth_target.texture.clone());
|
|
}
|
|
fn render_default(&mut self, mut world: &mut World) {
|
|
mq::clear_background(mq::DARKGRAY);
|
|
self.camera.apply();
|
|
|
|
draw_world(&mut world, Some(&self.base_material));
|
|
|
|
// mq::draw_sphere(self.light.position, 0.25, None, mq::WHITE);
|
|
|
|
mq::draw_grid(20, 1.5, mq::BLACK, mq::GRAY);
|
|
mq::gl_use_default_material();
|
|
mq::set_default_camera();
|
|
}
|
|
fn render_ui(&mut self) {
|
|
mq::set_default_camera();
|
|
|
|
if DEBUG_UI {
|
|
mq::draw_texture_ex(
|
|
&self.depth_target.texture,
|
|
320.,
|
|
20.,
|
|
mq::WHITE,
|
|
mq::DrawTextureParams {
|
|
flip_y: true,
|
|
dest_size: Some(mq::vec2(200.0, 200.0)),
|
|
..Default::default()
|
|
},
|
|
);
|
|
macroquad::ui::root_ui().window(
|
|
hash!("MaterialWindow"),
|
|
mq::vec2(20.0, 20.0),
|
|
mq::vec2(300.0, 300.0),
|
|
|ui| {
|
|
self.ui_state.ui(ui);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
pub fn draw(&mut self, mut world: &mut World) {
|
|
if self.enable_shadows {
|
|
self.render_depth(&mut world);
|
|
}
|
|
|
|
self.render_default(&mut world);
|
|
|
|
self.render_ui();
|
|
}
|
|
}
|
|
|
|
fn draw_world(world: &mut World, material: Option<&mq::Material>) {
|
|
for (handle, coll) in world.colliders.iter() {
|
|
let position: glam::Vec3 = world.position_of_collider(handle).unwrap().into();
|
|
let rotation = *coll.rotation();
|
|
unsafe {
|
|
let context = mq::get_internal_gl().quad_gl;
|
|
|
|
context.push_model_matrix(mq::Mat4::from_rotation_translation(
|
|
rotation.into(),
|
|
position,
|
|
));
|
|
}
|
|
let color = world.collider_data.get(&handle).unwrap().color;
|
|
match coll.shape().shape_type() {
|
|
ShapeType::Ball => {
|
|
mq::draw_sphere(
|
|
mq::vec3(0.0, 0.0, 0.0),
|
|
coll.shape().as_ball().unwrap().radius,
|
|
None,
|
|
color,
|
|
);
|
|
}
|
|
ShapeType::Cuboid => {
|
|
match material {
|
|
Some(mat) => {
|
|
mq::gl_use_material(&mat);
|
|
}
|
|
None => {}
|
|
}
|
|
draw_cuboid(
|
|
mq::vec3(0.0, 0.0, 0.0),
|
|
(coll.shape().as_cuboid().unwrap().half_extents * 2.0).into(),
|
|
color,
|
|
);
|
|
match material {
|
|
Some(_) => {
|
|
mq::gl_use_default_material();
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
_ => println!("Not implemented.. Skipping"),
|
|
}
|
|
unsafe {
|
|
let context = mq::get_internal_gl().quad_gl;
|
|
context.pop_model_matrix();
|
|
}
|
|
}
|
|
}
|