pub use crate::core::*;
use thiserror::Error;
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum RendererError {
#[error("{0} buffer length must be {1}, actual length is {2}")]
InvalidBufferLength(String, usize, usize),
#[error("Failes partially updating the {0} buffer because it does not exist")]
PartialUpdateFailedMissingBuffer(String),
#[error("the material {0} is required by the geometry {1} but could not be found")]
MissingMaterial(String, String),
#[cfg(feature = "text")]
#[error("Failed to find font with index {0} in the given font collection")]
MissingFont(u32),
#[error("CoreError: {0}")]
CoreError(#[from] CoreError),
}
mod shader_ids;
pub use shader_ids::*;
mod viewer;
pub use viewer::*;
pub mod material;
pub use material::*;
pub mod effect;
pub use effect::*;
pub mod light;
pub use light::*;
pub mod geometry;
pub use geometry::*;
pub mod object;
pub use object::*;
pub mod control;
pub use control::*;
#[cfg(feature = "text")]
mod text;
#[cfg(feature = "text")]
pub use text::*;
macro_rules! impl_render_target_extensions_body {
() => {
pub fn render(
&self,
viewer: impl Viewer,
objects: impl IntoIterator<Item = impl Object>,
lights: &[&dyn Light],
) -> &Self {
self.render_partially(self.scissor_box(), viewer, objects, lights)
}
pub fn render_partially(
&self,
scissor_box: ScissorBox,
viewer: impl Viewer,
objects: impl IntoIterator<Item = impl Object>,
lights: &[&dyn Light],
) -> &Self {
let frustum = Frustum::new(viewer.projection() * viewer.view());
let (mut deferred_objects, mut forward_objects): (Vec<_>, Vec<_>) = objects
.into_iter()
.filter(|o| frustum.contains(o.aabb()))
.partition(|o| o.material_type() == MaterialType::Deferred);
if deferred_objects.len() > 0 {
let geometry_pass_camera = GeometryPassCamera(&viewer);
let viewport = geometry_pass_camera.viewport();
deferred_objects.sort_by(|a, b| cmp_render_order(&geometry_pass_camera, a, b));
let geometry_pass_texture = Texture2DArray::new_empty::<[u8; 4]>(
&self.context,
viewport.width,
viewport.height,
3,
Interpolation::Nearest,
Interpolation::Nearest,
None,
Wrapping::ClampToEdge,
Wrapping::ClampToEdge,
);
let geometry_pass_depth_texture = DepthTexture2D::new::<f32>(
&self.context,
viewport.width,
viewport.height,
Wrapping::ClampToEdge,
Wrapping::ClampToEdge,
);
let gbuffer_layers = [0, 1, 2];
RenderTarget::new(
geometry_pass_texture.as_color_target(&gbuffer_layers, None),
geometry_pass_depth_texture.as_depth_target(),
)
.clear(ClearState::default())
.write::<RendererError>(|| {
for object in deferred_objects {
object.render(&geometry_pass_camera, lights);
}
Ok(())
})
.unwrap();
self.apply_screen_effect_partially(
scissor_box,
&lighting_pass::LightingPassEffect {},
&viewer,
lights,
Some(ColorTexture::Array {
texture: &geometry_pass_texture,
layers: &gbuffer_layers,
}),
Some(DepthTexture::Single(&geometry_pass_depth_texture)),
);
}
forward_objects.sort_by(|a, b| cmp_render_order(&viewer, a, b));
self.write_partially::<RendererError>(scissor_box, || {
for object in forward_objects {
object.render(&viewer, lights);
}
Ok(())
})
.unwrap();
self
}
pub fn render_with_material(
&self,
material: &dyn Material,
viewer: impl Viewer,
geometries: impl IntoIterator<Item = impl Geometry>,
lights: &[&dyn Light],
) -> &Self {
self.render_partially_with_material(
self.scissor_box(),
material,
viewer,
geometries,
lights,
)
}
pub fn render_partially_with_material(
&self,
scissor_box: ScissorBox,
material: &dyn Material,
viewer: impl Viewer,
geometries: impl IntoIterator<Item = impl Geometry>,
lights: &[&dyn Light],
) -> &Self {
let frustum = Frustum::new(viewer.projection() * viewer.view());
if let Err(e) = self.write_partially::<RendererError>(scissor_box, || {
for geometry in geometries
.into_iter()
.filter(|o| frustum.contains(o.aabb()))
{
render_with_material(&self.context, &viewer, geometry, material, lights)?;
}
Ok(())
}) {
panic!("{}", e.to_string());
}
self
}
pub fn render_with_effect(
&self,
effect: &dyn Effect,
viewer: impl Viewer,
geometries: impl IntoIterator<Item = impl Geometry>,
lights: &[&dyn Light],
color_texture: Option<ColorTexture>,
depth_texture: Option<DepthTexture>,
) -> &Self {
self.render_partially_with_effect(
self.scissor_box(),
effect,
viewer,
geometries,
lights,
color_texture,
depth_texture,
)
}
pub fn render_partially_with_effect(
&self,
scissor_box: ScissorBox,
effect: &dyn Effect,
viewer: impl Viewer,
geometries: impl IntoIterator<Item = impl Geometry>,
lights: &[&dyn Light],
color_texture: Option<ColorTexture>,
depth_texture: Option<DepthTexture>,
) -> &Self {
let frustum = Frustum::new(viewer.projection() * viewer.view());
if let Err(e) = self.write_partially::<RendererError>(scissor_box, || {
for geometry in geometries
.into_iter()
.filter(|o| frustum.contains(o.aabb()))
{
render_with_effect(
&self.context,
&viewer,
geometry,
effect,
lights,
color_texture,
depth_texture,
)?;
}
Ok(())
}) {
panic!("{}", e.to_string());
}
self
}
pub fn apply_screen_material(
&self,
material: &dyn Material,
viewer: impl Viewer,
lights: &[&dyn Light],
) -> &Self {
self.apply_screen_material_partially(self.scissor_box(), material, viewer, lights)
}
pub fn apply_screen_material_partially(
&self,
scissor_box: ScissorBox,
material: &dyn Material,
viewer: impl Viewer,
lights: &[&dyn Light],
) -> &Self {
self.write_partially::<RendererError>(scissor_box, || {
apply_screen_material(&self.context, material, viewer, lights);
Ok(())
})
.unwrap();
self
}
pub fn apply_screen_effect(
&self,
effect: &dyn Effect,
viewer: impl Viewer,
lights: &[&dyn Light],
color_texture: Option<ColorTexture>,
depth_texture: Option<DepthTexture>,
) -> &Self {
self.apply_screen_effect_partially(
self.scissor_box(),
effect,
viewer,
lights,
color_texture,
depth_texture,
)
}
pub fn apply_screen_effect_partially(
&self,
scissor_box: ScissorBox,
effect: &dyn Effect,
viewer: impl Viewer,
lights: &[&dyn Light],
color_texture: Option<ColorTexture>,
depth_texture: Option<DepthTexture>,
) -> &Self {
self.write_partially::<RendererError>(scissor_box, || {
apply_screen_effect(
&self.context,
effect,
viewer,
lights,
color_texture,
depth_texture,
);
Ok(())
})
.unwrap();
self
}
};
}
macro_rules! impl_render_target_extensions {
($name:ident < $a:ident : $ta:tt , $b:ident : $tb:tt >) => {
impl<$a: $ta, $b: $tb> $name<$a, $b> {
impl_render_target_extensions_body!();
}
};
($name:ident < $a:ident : $ta:tt >) => {
impl<$a: $ta> $name<$a> {
impl_render_target_extensions_body!();
}
};
($name:ident < $lt:lifetime >) => {
impl<$lt> $name<$lt> {
impl_render_target_extensions_body!();
}
};
($name:ty) => {
impl $name {
impl_render_target_extensions_body!();
}
};
}
impl_render_target_extensions!(RenderTarget<'a>);
impl_render_target_extensions!(ColorTarget<'a>);
impl_render_target_extensions!(DepthTarget<'a>);
impl_render_target_extensions!(
RenderTargetMultisample<C: TextureDataType, D: DepthTextureDataType>
);
impl_render_target_extensions!(ColorTargetMultisample<C: TextureDataType>);
impl_render_target_extensions!(DepthTargetMultisample<D: DepthTextureDataType>);
fn combine_ids(
geometry: GeometryId,
effect_material: EffectMaterialId,
lights: impl Iterator<Item = LightId>,
) -> Vec<u8> {
let mut id = geometry.0.to_le_bytes().to_vec();
id.extend(effect_material.0.to_le_bytes());
id.extend(lights.map(|l| l.0));
id
}
pub fn render_with_material(
context: &Context,
viewer: impl Viewer,
geometry: impl Geometry,
material: impl Material,
lights: &[&dyn Light],
) -> Result<(), RendererError> {
let id = combine_ids(geometry.id(), material.id(), lights.iter().map(|l| l.id()));
let mut programs = context.programs.write().unwrap();
if !programs.contains_key(&id) {
programs.insert(
id.clone(),
Program::from_source(
context,
&geometry.vertex_shader_source(),
&material.fragment_shader_source(lights),
)?,
);
}
let program = programs.get(&id).unwrap();
material.use_uniforms(program, &viewer, lights);
geometry.draw(&viewer, program, material.render_states());
Ok(())
}
pub fn render_with_effect(
context: &Context,
viewer: impl Viewer,
geometry: impl Geometry,
effect: impl Effect,
lights: &[&dyn Light],
color_texture: Option<ColorTexture>,
depth_texture: Option<DepthTexture>,
) -> Result<(), RendererError> {
let id = combine_ids(
geometry.id(),
effect.id(color_texture, depth_texture),
lights.iter().map(|l| l.id()),
);
let mut programs = context.programs.write().unwrap();
if !programs.contains_key(&id) {
programs.insert(
id.clone(),
Program::from_source(
context,
&geometry.vertex_shader_source(),
&effect.fragment_shader_source(lights, color_texture, depth_texture),
)?,
);
}
let program = programs.get(&id).unwrap();
effect.use_uniforms(program, &viewer, lights, color_texture, depth_texture);
geometry.draw(&viewer, program, effect.render_states());
Ok(())
}
pub fn apply_screen_material(
context: &Context,
material: impl Material,
viewer: impl Viewer,
lights: &[&dyn Light],
) {
let id = combine_ids(
GeometryId::Screen,
material.id(),
lights.iter().map(|l| l.id()),
);
let mut programs = context.programs.write().unwrap();
let program = programs.entry(id).or_insert_with(|| {
match Program::from_source(
context,
full_screen_vertex_shader_source(),
&material.fragment_shader_source(lights),
) {
Ok(program) => program,
Err(err) => panic!("{}", err.to_string()),
}
});
material.use_uniforms(program, &viewer, lights);
full_screen_draw(
context,
program,
material.render_states(),
viewer.viewport(),
);
}
pub fn apply_screen_effect(
context: &Context,
effect: impl Effect,
viewer: impl Viewer,
lights: &[&dyn Light],
color_texture: Option<ColorTexture>,
depth_texture: Option<DepthTexture>,
) {
let id = combine_ids(
GeometryId::Screen,
effect.id(color_texture, depth_texture),
lights.iter().map(|l| l.id()),
);
let mut programs = context.programs.write().unwrap();
let program = programs.entry(id).or_insert_with(|| {
match Program::from_source(
context,
full_screen_vertex_shader_source(),
&effect.fragment_shader_source(lights, color_texture, depth_texture),
) {
Ok(program) => program,
Err(err) => panic!("{}", err.to_string()),
}
});
effect.use_uniforms(program, &viewer, lights, color_texture, depth_texture);
full_screen_draw(context, program, effect.render_states(), viewer.viewport());
}
pub fn cmp_render_order(
viewer: impl Viewer,
obj0: impl Object,
obj1: impl Object,
) -> std::cmp::Ordering {
if obj0.material_type() == MaterialType::Transparent
&& obj1.material_type() != MaterialType::Transparent
{
std::cmp::Ordering::Greater
} else if obj0.material_type() != MaterialType::Transparent
&& obj1.material_type() == MaterialType::Transparent
{
std::cmp::Ordering::Less
} else {
let distance_a = viewer.position().distance2(obj0.aabb().center());
let distance_b = viewer.position().distance2(obj1.aabb().center());
if distance_a.is_nan() || distance_b.is_nan() {
distance_a.is_nan().cmp(&distance_b.is_nan()) } else if obj0.material_type() == MaterialType::Transparent {
distance_b.partial_cmp(&distance_a).unwrap()
} else {
distance_a.partial_cmp(&distance_b).unwrap()
}
}
}
pub fn pick(
context: &Context,
camera: &three_d_asset::Camera,
pixel: impl Into<PhysicalPoint> + Copy,
geometries: impl IntoIterator<Item = impl Geometry>,
culling: Cull,
) -> Result<Option<IntersectionResult>, RendererError> {
let pos = camera.position_at_pixel(pixel);
let dir = camera.view_direction_at_pixel(pixel);
ray_intersect(
context,
pos + dir * camera.z_near(),
dir,
camera.z_far() - camera.z_near(),
geometries,
culling,
)
}
#[derive(Debug, Clone, Copy)]
pub struct IntersectionResult {
pub position: Vec3,
pub geometry_id: u32,
pub instance_id: u32,
}
pub fn ray_intersect(
context: &Context,
position: Vec3,
direction: Vec3,
max_depth: f32,
geometries: impl IntoIterator<Item = impl Geometry>,
culling: Cull,
) -> Result<Option<IntersectionResult>, RendererError> {
use crate::core::*;
let viewport = Viewport::new_at_origo(1, 1);
let up = if direction.dot(vec3(1.0, 0.0, 0.0)).abs() > 0.99 {
direction.cross(vec3(0.0, 1.0, 0.0))
} else {
direction.cross(vec3(1.0, 0.0, 0.0))
};
let camera = Camera::new_orthographic(
viewport,
position,
position + direction,
up,
0.01,
0.0,
max_depth,
);
let texture = Texture2D::new_empty::<[f32; 4]>(
context,
viewport.width,
viewport.height,
Interpolation::Nearest,
Interpolation::Nearest,
None,
Wrapping::ClampToEdge,
Wrapping::ClampToEdge,
);
let depth_texture = DepthTexture2D::new::<f32>(
context,
viewport.width,
viewport.height,
Wrapping::ClampToEdge,
Wrapping::ClampToEdge,
);
let mut material = IntersectionMaterial {
..Default::default()
};
material.render_states.cull = culling;
let result = RenderTarget::new(
texture.as_color_target(None),
depth_texture.as_depth_target(),
)
.clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0))
.write::<RendererError>(|| {
for (id, geometry) in geometries.into_iter().enumerate() {
material.geometry_id = id as u32;
render_with_material(context, &camera, &geometry, &material, &[])?;
}
Ok(())
})?
.read_color::<[f32; 4]>()[0];
let depth = result[0];
if depth < 1.0 {
Ok(Some(IntersectionResult {
position: position + direction * depth * max_depth,
geometry_id: result[1].to_bits(),
instance_id: result[2].to_bits(),
}))
} else {
Ok(None)
}
}
struct GeometryPassCamera<T>(T);
impl<T: Viewer> Viewer for GeometryPassCamera<T> {
fn position(&self) -> Vec3 {
self.0.position()
}
fn view(&self) -> Mat4 {
self.0.view()
}
fn projection(&self) -> Mat4 {
self.0.projection()
}
fn viewport(&self) -> Viewport {
Viewport::new_at_origo(self.0.viewport().width, self.0.viewport().height)
}
fn z_near(&self) -> f32 {
self.0.z_near()
}
fn z_far(&self) -> f32 {
self.0.z_far()
}
fn color_mapping(&self) -> ColorMapping {
self.0.color_mapping()
}
fn tone_mapping(&self) -> ToneMapping {
self.0.tone_mapping()
}
}