1pub use crate::core::*;
20
21use thiserror::Error;
22#[derive(Error, Debug)]
26#[allow(missing_docs)]
27pub enum RendererError {
28 #[error("{0} buffer length must be {1}, actual length is {2}")]
29 InvalidBufferLength(String, usize, usize),
30 #[error("Failes partially updating the {0} buffer because it does not exist")]
31 PartialUpdateFailedMissingBuffer(String),
32 #[error("the material {0} is required by the geometry {1} but could not be found")]
33 MissingMaterial(String, String),
34 #[cfg(feature = "text")]
35 #[error("Failed to find font with index {0} in the given font collection")]
36 MissingFont(u32),
37 #[error("CoreError: {0}")]
38 CoreError(#[from] CoreError),
39}
40
41mod shader_ids;
42pub use shader_ids::*;
43
44mod viewer;
45pub use viewer::*;
46
47pub mod material;
48pub use material::*;
49
50pub mod effect;
51pub use effect::*;
52
53pub mod light;
54pub use light::*;
55
56pub mod geometry;
57pub use geometry::*;
58
59pub mod object;
60pub use object::*;
61
62pub mod control;
63pub use control::*;
64
65#[cfg(feature = "text")]
66mod text;
67#[cfg(feature = "text")]
68pub use text::*;
69
70macro_rules! impl_render_target_extensions_body {
71 () => {
72 pub fn render(
78 &self,
79 viewer: impl Viewer,
80 objects: impl IntoIterator<Item = impl Object>,
81 lights: &[&dyn Light],
82 ) -> &Self {
83 self.render_partially(self.scissor_box(), viewer, objects, lights)
84 }
85
86 pub fn render_partially(
92 &self,
93 scissor_box: ScissorBox,
94 viewer: impl Viewer,
95 objects: impl IntoIterator<Item = impl Object>,
96 lights: &[&dyn Light],
97 ) -> &Self {
98 let frustum = Frustum::new(viewer.projection() * viewer.view());
99 let (mut deferred_objects, mut forward_objects): (Vec<_>, Vec<_>) = objects
100 .into_iter()
101 .filter(|o| frustum.contains(o.aabb()))
102 .partition(|o| o.material_type() == MaterialType::Deferred);
103
104 if deferred_objects.len() > 0 {
106 let geometry_pass_camera = GeometryPassCamera(&viewer);
108 let viewport = geometry_pass_camera.viewport();
109 deferred_objects.sort_by(|a, b| cmp_render_order(&geometry_pass_camera, a, b));
110 let geometry_pass_texture = Texture2DArray::new_empty::<[u8; 4]>(
111 &self.context,
112 viewport.width,
113 viewport.height,
114 3,
115 Interpolation::Nearest,
116 Interpolation::Nearest,
117 None,
118 Wrapping::ClampToEdge,
119 Wrapping::ClampToEdge,
120 );
121 let geometry_pass_depth_texture = DepthTexture2D::new::<f32>(
122 &self.context,
123 viewport.width,
124 viewport.height,
125 Wrapping::ClampToEdge,
126 Wrapping::ClampToEdge,
127 );
128 let gbuffer_layers = [0, 1, 2];
129 RenderTarget::new(
130 geometry_pass_texture.as_color_target(&gbuffer_layers, None),
131 geometry_pass_depth_texture.as_depth_target(),
132 )
133 .clear(ClearState::default())
134 .write::<RendererError>(|| {
135 for object in deferred_objects {
136 object.render(&geometry_pass_camera, lights);
137 }
138 Ok(())
139 })
140 .unwrap();
141
142 self.apply_screen_effect_partially(
144 scissor_box,
145 &lighting_pass::LightingPassEffect {},
146 &viewer,
147 lights,
148 Some(ColorTexture::Array {
149 texture: &geometry_pass_texture,
150 layers: &gbuffer_layers,
151 }),
152 Some(DepthTexture::Single(&geometry_pass_depth_texture)),
153 );
154 }
155
156 forward_objects.sort_by(|a, b| cmp_render_order(&viewer, a, b));
158 self.write_partially::<RendererError>(scissor_box, || {
159 for object in forward_objects {
160 object.render(&viewer, lights);
161 }
162 Ok(())
163 })
164 .unwrap();
165 self
166 }
167
168 pub fn render_with_material(
173 &self,
174 material: &dyn Material,
175 viewer: impl Viewer,
176 geometries: impl IntoIterator<Item = impl Geometry>,
177 lights: &[&dyn Light],
178 ) -> &Self {
179 self.render_partially_with_material(
180 self.scissor_box(),
181 material,
182 viewer,
183 geometries,
184 lights,
185 )
186 }
187
188 pub fn render_partially_with_material(
193 &self,
194 scissor_box: ScissorBox,
195 material: &dyn Material,
196 viewer: impl Viewer,
197 geometries: impl IntoIterator<Item = impl Geometry>,
198 lights: &[&dyn Light],
199 ) -> &Self {
200 let frustum = Frustum::new(viewer.projection() * viewer.view());
201 if let Err(e) = self.write_partially::<RendererError>(scissor_box, || {
202 for geometry in geometries
203 .into_iter()
204 .filter(|o| frustum.contains(o.aabb()))
205 {
206 render_with_material(&self.context, &viewer, geometry, material, lights)?;
207 }
208 Ok(())
209 }) {
210 panic!("{}", e.to_string());
211 }
212 self
213 }
214
215 pub fn render_with_effect(
220 &self,
221 effect: &dyn Effect,
222 viewer: impl Viewer,
223 geometries: impl IntoIterator<Item = impl Geometry>,
224 lights: &[&dyn Light],
225 color_texture: Option<ColorTexture>,
226 depth_texture: Option<DepthTexture>,
227 ) -> &Self {
228 self.render_partially_with_effect(
229 self.scissor_box(),
230 effect,
231 viewer,
232 geometries,
233 lights,
234 color_texture,
235 depth_texture,
236 )
237 }
238
239 pub fn render_partially_with_effect(
244 &self,
245 scissor_box: ScissorBox,
246 effect: &dyn Effect,
247 viewer: impl Viewer,
248 geometries: impl IntoIterator<Item = impl Geometry>,
249 lights: &[&dyn Light],
250 color_texture: Option<ColorTexture>,
251 depth_texture: Option<DepthTexture>,
252 ) -> &Self {
253 let frustum = Frustum::new(viewer.projection() * viewer.view());
254 if let Err(e) = self.write_partially::<RendererError>(scissor_box, || {
255 for geometry in geometries
256 .into_iter()
257 .filter(|o| frustum.contains(o.aabb()))
258 {
259 render_with_effect(
260 &self.context,
261 &viewer,
262 geometry,
263 effect,
264 lights,
265 color_texture,
266 depth_texture,
267 )?;
268 }
269 Ok(())
270 }) {
271 panic!("{}", e.to_string());
272 }
273 self
274 }
275
276 pub fn apply_screen_material(
281 &self,
282 material: &dyn Material,
283 viewer: impl Viewer,
284 lights: &[&dyn Light],
285 ) -> &Self {
286 self.apply_screen_material_partially(self.scissor_box(), material, viewer, lights)
287 }
288
289 pub fn apply_screen_material_partially(
294 &self,
295 scissor_box: ScissorBox,
296 material: &dyn Material,
297 viewer: impl Viewer,
298 lights: &[&dyn Light],
299 ) -> &Self {
300 self.write_partially::<RendererError>(scissor_box, || {
301 apply_screen_material(&self.context, material, viewer, lights);
302 Ok(())
303 })
304 .unwrap();
305 self
306 }
307
308 pub fn apply_screen_effect(
313 &self,
314 effect: &dyn Effect,
315 viewer: impl Viewer,
316 lights: &[&dyn Light],
317 color_texture: Option<ColorTexture>,
318 depth_texture: Option<DepthTexture>,
319 ) -> &Self {
320 self.apply_screen_effect_partially(
321 self.scissor_box(),
322 effect,
323 viewer,
324 lights,
325 color_texture,
326 depth_texture,
327 )
328 }
329
330 pub fn apply_screen_effect_partially(
335 &self,
336 scissor_box: ScissorBox,
337 effect: &dyn Effect,
338 viewer: impl Viewer,
339 lights: &[&dyn Light],
340 color_texture: Option<ColorTexture>,
341 depth_texture: Option<DepthTexture>,
342 ) -> &Self {
343 self.write_partially::<RendererError>(scissor_box, || {
344 apply_screen_effect(
345 &self.context,
346 effect,
347 viewer,
348 lights,
349 color_texture,
350 depth_texture,
351 );
352 Ok(())
353 })
354 .unwrap();
355 self
356 }
357 };
358}
359
360macro_rules! impl_render_target_extensions {
361 ($name:ident < $a:ident : $ta:tt , $b:ident : $tb:tt >) => {
363 impl<$a: $ta, $b: $tb> $name<$a, $b> {
364 impl_render_target_extensions_body!();
365 }
366 };
367 ($name:ident < $a:ident : $ta:tt >) => {
369 impl<$a: $ta> $name<$a> {
370 impl_render_target_extensions_body!();
371 }
372 };
373 ($name:ident < $lt:lifetime >) => {
375 impl<$lt> $name<$lt> {
376 impl_render_target_extensions_body!();
377 }
378 };
379 ($name:ty) => {
381 impl $name {
382 impl_render_target_extensions_body!();
383 }
384 };
385}
386
387impl_render_target_extensions!(RenderTarget<'a>);
388impl_render_target_extensions!(ColorTarget<'a>);
389impl_render_target_extensions!(DepthTarget<'a>);
390impl_render_target_extensions!(
391 RenderTargetMultisample<C: TextureDataType, D: DepthTextureDataType>
392);
393impl_render_target_extensions!(ColorTargetMultisample<C: TextureDataType>);
394impl_render_target_extensions!(DepthTargetMultisample<D: DepthTextureDataType>);
395
396fn combine_ids(
400 geometry: GeometryId,
401 effect_material: EffectMaterialId,
402 lights: impl Iterator<Item = LightId>,
403) -> Vec<u8> {
404 let mut id = geometry.0.to_le_bytes().to_vec();
405 id.extend(effect_material.0.to_le_bytes());
406 id.extend(lights.map(|l| l.0));
407 id
408}
409
410pub fn render_with_material(
416 context: &Context,
417 viewer: impl Viewer,
418 geometry: impl Geometry,
419 material: impl Material,
420 lights: &[&dyn Light],
421) -> Result<(), RendererError> {
422 let id = combine_ids(geometry.id(), material.id(), lights.iter().map(|l| l.id()));
423
424 let mut programs = context.programs.write().unwrap();
425 if !programs.contains_key(&id) {
426 programs.insert(
427 id.clone(),
428 Program::from_source(
429 context,
430 &geometry.vertex_shader_source(),
431 &material.fragment_shader_source(lights),
432 )?,
433 );
434 }
435 let program = programs.get(&id).unwrap();
436
437 material.use_uniforms(program, &viewer, lights);
438 geometry.draw(&viewer, program, material.render_states());
439 Ok(())
440}
441
442pub fn render_with_effect(
448 context: &Context,
449 viewer: impl Viewer,
450 geometry: impl Geometry,
451 effect: impl Effect,
452 lights: &[&dyn Light],
453 color_texture: Option<ColorTexture>,
454 depth_texture: Option<DepthTexture>,
455) -> Result<(), RendererError> {
456 let id = combine_ids(
457 geometry.id(),
458 effect.id(color_texture, depth_texture),
459 lights.iter().map(|l| l.id()),
460 );
461
462 let mut programs = context.programs.write().unwrap();
463 if !programs.contains_key(&id) {
464 programs.insert(
465 id.clone(),
466 Program::from_source(
467 context,
468 &geometry.vertex_shader_source(),
469 &effect.fragment_shader_source(lights, color_texture, depth_texture),
470 )?,
471 );
472 }
473 let program = programs.get(&id).unwrap();
474 effect.use_uniforms(program, &viewer, lights, color_texture, depth_texture);
475 geometry.draw(&viewer, program, effect.render_states());
476 Ok(())
477}
478
479pub fn apply_screen_material(
485 context: &Context,
486 material: impl Material,
487 viewer: impl Viewer,
488 lights: &[&dyn Light],
489) {
490 let id = combine_ids(
491 GeometryId::Screen,
492 material.id(),
493 lights.iter().map(|l| l.id()),
494 );
495
496 let mut programs = context.programs.write().unwrap();
497 let program = programs.entry(id).or_insert_with(|| {
498 match Program::from_source(
499 context,
500 full_screen_vertex_shader_source(),
501 &material.fragment_shader_source(lights),
502 ) {
503 Ok(program) => program,
504 Err(err) => panic!("{}", err.to_string()),
505 }
506 });
507 material.use_uniforms(program, &viewer, lights);
508 full_screen_draw(
509 context,
510 program,
511 material.render_states(),
512 viewer.viewport(),
513 );
514}
515
516pub fn apply_screen_effect(
522 context: &Context,
523 effect: impl Effect,
524 viewer: impl Viewer,
525 lights: &[&dyn Light],
526 color_texture: Option<ColorTexture>,
527 depth_texture: Option<DepthTexture>,
528) {
529 let id = combine_ids(
530 GeometryId::Screen,
531 effect.id(color_texture, depth_texture),
532 lights.iter().map(|l| l.id()),
533 );
534
535 let mut programs = context.programs.write().unwrap();
536 let program = programs.entry(id).or_insert_with(|| {
537 match Program::from_source(
538 context,
539 full_screen_vertex_shader_source(),
540 &effect.fragment_shader_source(lights, color_texture, depth_texture),
541 ) {
542 Ok(program) => program,
543 Err(err) => panic!("{}", err.to_string()),
544 }
545 });
546 effect.use_uniforms(program, &viewer, lights, color_texture, depth_texture);
547 full_screen_draw(context, program, effect.render_states(), viewer.viewport());
548}
549
550pub fn cmp_render_order(
556 viewer: impl Viewer,
557 obj0: impl Object,
558 obj1: impl Object,
559) -> std::cmp::Ordering {
560 if obj0.material_type() == MaterialType::Transparent
561 && obj1.material_type() != MaterialType::Transparent
562 {
563 std::cmp::Ordering::Greater
564 } else if obj0.material_type() != MaterialType::Transparent
565 && obj1.material_type() == MaterialType::Transparent
566 {
567 std::cmp::Ordering::Less
568 } else {
569 let distance_a = viewer.position().distance2(obj0.aabb().center());
570 let distance_b = viewer.position().distance2(obj1.aabb().center());
571 if distance_a.is_nan() || distance_b.is_nan() {
572 distance_a.is_nan().cmp(&distance_b.is_nan()) } else if obj0.material_type() == MaterialType::Transparent {
574 distance_b.partial_cmp(&distance_a).unwrap()
575 } else {
576 distance_a.partial_cmp(&distance_b).unwrap()
577 }
578 }
579}
580
581pub fn pick(
588 context: &Context,
589 camera: &three_d_asset::Camera,
590 pixel: impl Into<PhysicalPoint> + Copy,
591 geometries: impl IntoIterator<Item = impl Geometry>,
592 culling: Cull,
593) -> Result<Option<IntersectionResult>, RendererError> {
594 let pos = camera.position_at_pixel(pixel);
595 let dir = camera.view_direction_at_pixel(pixel);
596 ray_intersect(
597 context,
598 pos + dir * camera.z_near(),
599 dir,
600 camera.z_far() - camera.z_near(),
601 geometries,
602 culling,
603 )
604}
605
606#[derive(Debug, Clone, Copy)]
608pub struct IntersectionResult {
609 pub position: Vec3,
611 pub geometry_id: u32,
613 pub instance_id: u32,
616}
617
618pub fn ray_intersect(
623 context: &Context,
624 position: Vec3,
625 direction: Vec3,
626 max_depth: f32,
627 geometries: impl IntoIterator<Item = impl Geometry>,
628 culling: Cull,
629) -> Result<Option<IntersectionResult>, RendererError> {
630 use crate::core::*;
631 let viewport = Viewport::new_at_origo(1, 1);
632 let up = if direction.dot(vec3(1.0, 0.0, 0.0)).abs() > 0.99 {
633 direction.cross(vec3(0.0, 1.0, 0.0))
634 } else {
635 direction.cross(vec3(1.0, 0.0, 0.0))
636 };
637 let camera = Camera::new_orthographic(
638 viewport,
639 position,
640 position + direction,
641 up,
642 0.01,
643 0.0,
644 max_depth,
645 );
646 let texture = Texture2D::new_empty::<[f32; 4]>(
647 context,
648 viewport.width,
649 viewport.height,
650 Interpolation::Nearest,
651 Interpolation::Nearest,
652 None,
653 Wrapping::ClampToEdge,
654 Wrapping::ClampToEdge,
655 );
656 let depth_texture = DepthTexture2D::new::<f32>(
657 context,
658 viewport.width,
659 viewport.height,
660 Wrapping::ClampToEdge,
661 Wrapping::ClampToEdge,
662 );
663 let mut material = IntersectionMaterial {
664 ..Default::default()
665 };
666 material.render_states.cull = culling;
667 let result = RenderTarget::new(
668 texture.as_color_target(None),
669 depth_texture.as_depth_target(),
670 )
671 .clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0))
672 .write::<RendererError>(|| {
673 for (id, geometry) in geometries.into_iter().enumerate() {
674 material.geometry_id = id as u32;
675 render_with_material(context, &camera, &geometry, &material, &[])?;
676 }
677 Ok(())
678 })?
679 .read_color::<[f32; 4]>()[0];
680 let depth = result[0];
681 if depth < 1.0 {
682 Ok(Some(IntersectionResult {
683 position: position + direction * depth * max_depth,
684 geometry_id: result[1].to_bits(),
685 instance_id: result[2].to_bits(),
686 }))
687 } else {
688 Ok(None)
689 }
690}
691
692struct GeometryPassCamera<T>(T);
693
694impl<T: Viewer> Viewer for GeometryPassCamera<T> {
695 fn position(&self) -> Vec3 {
696 self.0.position()
697 }
698
699 fn view(&self) -> Mat4 {
700 self.0.view()
701 }
702
703 fn projection(&self) -> Mat4 {
704 self.0.projection()
705 }
706
707 fn viewport(&self) -> Viewport {
708 Viewport::new_at_origo(self.0.viewport().width, self.0.viewport().height)
709 }
710
711 fn z_near(&self) -> f32 {
712 self.0.z_near()
713 }
714
715 fn z_far(&self) -> f32 {
716 self.0.z_far()
717 }
718
719 fn color_mapping(&self) -> ColorMapping {
720 self.0.color_mapping()
721 }
722
723 fn tone_mapping(&self) -> ToneMapping {
724 self.0.tone_mapping()
725 }
726}