1use std::collections::HashMap;
14
15use super::{
16 Viewport, Mat4,
17 vec3_sub, vec3_dot, vec3_length, clampf, lerpf, saturate,
18};
19use super::gbuffer::GBuffer;
20use super::materials::MaterialSortKey;
21
22#[derive(Debug, Clone)]
28pub enum LightType {
29 Directional {
31 direction: [f32; 3],
32 color: [f32; 3],
33 intensity: f32,
34 cast_shadows: bool,
35 },
36 Point {
38 position: [f32; 3],
39 color: [f32; 3],
40 intensity: f32,
41 range: f32,
42 cast_shadows: bool,
43 },
44 Spot {
46 position: [f32; 3],
47 direction: [f32; 3],
48 color: [f32; 3],
49 intensity: f32,
50 range: f32,
51 inner_angle: f32,
52 outer_angle: f32,
53 cast_shadows: bool,
54 },
55 Area {
57 position: [f32; 3],
58 normal: [f32; 3],
59 up: [f32; 3],
60 width: f32,
61 height: f32,
62 color: [f32; 3],
63 intensity: f32,
64 },
65 Ambient {
67 color: [f32; 3],
68 intensity: f32,
69 },
70}
71
72impl LightType {
73 pub fn position(&self) -> Option<[f32; 3]> {
75 match self {
76 Self::Directional { .. } | Self::Ambient { .. } => None,
77 Self::Point { position, .. }
78 | Self::Spot { position, .. }
79 | Self::Area { position, .. } => Some(*position),
80 }
81 }
82
83 pub fn range(&self) -> f32 {
85 match self {
86 Self::Directional { .. } | Self::Ambient { .. } => f32::MAX,
87 Self::Point { range, .. } | Self::Spot { range, .. } => *range,
88 Self::Area { width, height, .. } => (*width + *height) * 2.0,
89 }
90 }
91
92 pub fn color(&self) -> [f32; 3] {
94 match self {
95 Self::Directional { color, .. }
96 | Self::Point { color, .. }
97 | Self::Spot { color, .. }
98 | Self::Area { color, .. }
99 | Self::Ambient { color, .. } => *color,
100 }
101 }
102
103 pub fn intensity(&self) -> f32 {
105 match self {
106 Self::Directional { intensity, .. }
107 | Self::Point { intensity, .. }
108 | Self::Spot { intensity, .. }
109 | Self::Area { intensity, .. }
110 | Self::Ambient { intensity, .. } => *intensity,
111 }
112 }
113
114 pub fn casts_shadows(&self) -> bool {
116 match self {
117 Self::Directional { cast_shadows, .. }
118 | Self::Point { cast_shadows, .. }
119 | Self::Spot { cast_shadows, .. } => *cast_shadows,
120 _ => false,
121 }
122 }
123
124 pub fn evaluate(&self, surface_pos: [f32; 3]) -> ([f32; 3], f32, [f32; 3]) {
127 match self {
128 Self::Directional { direction, color, intensity, .. } => {
129 let dir = [-direction[0], -direction[1], -direction[2]];
130 (dir, *intensity, *color)
131 }
132 Self::Point { position, color, intensity, range, .. } => {
133 let to_light = vec3_sub(*position, surface_pos);
134 let dist = vec3_length(to_light);
135 if dist > *range || dist < 1e-6 {
136 return ([0.0, 0.0, 0.0], 0.0, *color);
137 }
138 let dir = [to_light[0] / dist, to_light[1] / dist, to_light[2] / dist];
139 let att = point_attenuation(dist, *range) * *intensity;
140 (dir, att, *color)
141 }
142 Self::Spot {
143 position, direction, color, intensity, range,
144 inner_angle, outer_angle, ..
145 } => {
146 let to_light = vec3_sub(*position, surface_pos);
147 let dist = vec3_length(to_light);
148 if dist > *range || dist < 1e-6 {
149 return ([0.0, 0.0, 0.0], 0.0, *color);
150 }
151 let dir = [to_light[0] / dist, to_light[1] / dist, to_light[2] / dist];
152 let cos_angle = -vec3_dot(dir, *direction);
153 let cos_inner = inner_angle.cos();
154 let cos_outer = outer_angle.cos();
155 let spot_att = saturate((cos_angle - cos_outer) / (cos_inner - cos_outer).max(1e-6));
156 let att = point_attenuation(dist, *range) * spot_att * *intensity;
157 (dir, att, *color)
158 }
159 Self::Area { position, color, intensity, .. } => {
160 let to_light = vec3_sub(*position, surface_pos);
161 let dist = vec3_length(to_light);
162 if dist < 1e-6 {
163 return ([0.0, 0.0, 1.0], *intensity, *color);
164 }
165 let dir = [to_light[0] / dist, to_light[1] / dist, to_light[2] / dist];
166 let att = *intensity / (dist * dist + 1.0);
167 (dir, att, *color)
168 }
169 Self::Ambient { color, intensity } => {
170 ([0.0, 1.0, 0.0], *intensity, *color)
171 }
172 }
173 }
174}
175
176fn point_attenuation(distance: f32, range: f32) -> f32 {
178 let ratio = clampf(distance / range, 0.0, 1.0);
179 let att_factor = 1.0 - ratio * ratio;
180 (att_factor * att_factor).max(0.0) / (distance * distance + 1.0)
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum SortMode {
190 FrontToBack,
192 BackToFront,
194 ByMaterial,
196 None,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
202pub enum RenderBucket {
203 Opaque,
205 Transparent,
207 Overlay,
209 Debug,
211 Sky,
213}
214
215impl RenderBucket {
216 pub fn default_sort_mode(&self) -> SortMode {
217 match self {
218 Self::Opaque => SortMode::FrontToBack,
219 Self::Transparent => SortMode::BackToFront,
220 Self::Overlay => SortMode::None,
221 Self::Debug => SortMode::None,
222 Self::Sky => SortMode::None,
223 }
224 }
225}
226
227#[derive(Debug, Clone)]
229pub struct RenderItem {
230 pub id: u64,
232 pub transform: Mat4,
234 pub mesh_handle: u64,
236 pub material_index: u32,
238 pub sort_key: MaterialSortKey,
240 pub camera_distance: f32,
242 pub bucket: RenderBucket,
244 pub visible: bool,
246 pub bounds_center: [f32; 3],
248 pub bounds_radius: f32,
250 pub instance_count: u32,
252 pub instance_buffer: u64,
254 pub vertex_count: u32,
256 pub index_count: u32,
258 pub alpha_test: bool,
260 pub two_sided: bool,
262}
263
264impl RenderItem {
265 pub fn new(id: u64, mesh_handle: u64, material_index: u32) -> Self {
266 Self {
267 id,
268 transform: Mat4::IDENTITY,
269 mesh_handle,
270 material_index,
271 sort_key: MaterialSortKey::default(),
272 camera_distance: 0.0,
273 bucket: RenderBucket::Opaque,
274 visible: true,
275 bounds_center: [0.0; 3],
276 bounds_radius: 1.0,
277 instance_count: 1,
278 instance_buffer: 0,
279 vertex_count: 0,
280 index_count: 0,
281 alpha_test: false,
282 two_sided: false,
283 }
284 }
285
286 pub fn with_transform(mut self, t: Mat4) -> Self {
287 self.transform = t;
288 self.bounds_center = [t.cols[3][0], t.cols[3][1], t.cols[3][2]];
290 self
291 }
292
293 pub fn with_bucket(mut self, b: RenderBucket) -> Self {
294 self.bucket = b;
295 self
296 }
297
298 pub fn with_bounds(mut self, center: [f32; 3], radius: f32) -> Self {
299 self.bounds_center = center;
300 self.bounds_radius = radius;
301 self
302 }
303
304 pub fn compute_camera_distance(&mut self, camera_pos: [f32; 3]) {
306 let dx = self.bounds_center[0] - camera_pos[0];
307 let dy = self.bounds_center[1] - camera_pos[1];
308 let dz = self.bounds_center[2] - camera_pos[2];
309 self.camera_distance = dx * dx + dy * dy + dz * dz;
310 }
311
312 pub fn frustum_cull(&mut self, frustum_planes: &[[f32; 4]; 6]) -> bool {
314 for plane in frustum_planes {
315 let dist = plane[0] * self.bounds_center[0]
316 + plane[1] * self.bounds_center[1]
317 + plane[2] * self.bounds_center[2]
318 + plane[3];
319 if dist < -self.bounds_radius {
320 self.visible = false;
321 return false;
322 }
323 }
324 self.visible = true;
325 true
326 }
327}
328
329#[derive(Debug)]
335pub struct RenderQueue {
336 pub buckets: HashMap<RenderBucket, Vec<RenderItem>>,
338 pub sort_modes: HashMap<RenderBucket, SortMode>,
340 pub camera_position: [f32; 3],
342 pub frustum_planes: [[f32; 4]; 6],
344 pub total_submitted: u32,
346 pub total_visible: u32,
348 pub total_culled: u32,
350 next_id: u64,
352}
353
354impl RenderQueue {
355 pub fn new() -> Self {
356 let mut sort_modes = HashMap::new();
357 sort_modes.insert(RenderBucket::Opaque, SortMode::FrontToBack);
358 sort_modes.insert(RenderBucket::Transparent, SortMode::BackToFront);
359 sort_modes.insert(RenderBucket::Overlay, SortMode::None);
360 sort_modes.insert(RenderBucket::Debug, SortMode::None);
361 sort_modes.insert(RenderBucket::Sky, SortMode::None);
362
363 Self {
364 buckets: HashMap::new(),
365 sort_modes,
366 camera_position: [0.0; 3],
367 frustum_planes: [[0.0; 4]; 6],
368 total_submitted: 0,
369 total_visible: 0,
370 total_culled: 0,
371 next_id: 1,
372 }
373 }
374
375 pub fn clear(&mut self) {
377 for bucket in self.buckets.values_mut() {
378 bucket.clear();
379 }
380 self.total_submitted = 0;
381 self.total_visible = 0;
382 self.total_culled = 0;
383 }
384
385 pub fn set_camera(&mut self, position: [f32; 3], frustum_planes: [[f32; 4]; 6]) {
387 self.camera_position = position;
388 self.frustum_planes = frustum_planes;
389 }
390
391 pub fn submit(&mut self, mut item: RenderItem) {
393 item.id = self.next_id;
394 self.next_id += 1;
395 self.total_submitted += 1;
396
397 item.compute_camera_distance(self.camera_position);
399
400 let visible = item.frustum_cull(&self.frustum_planes);
402 if visible {
403 self.total_visible += 1;
404 } else {
405 self.total_culled += 1;
406 }
407
408 let bucket = item.bucket;
409 self.buckets.entry(bucket).or_default().push(item);
410 }
411
412 pub fn submit_batch(&mut self, items: Vec<RenderItem>) {
414 for item in items {
415 self.submit(item);
416 }
417 }
418
419 pub fn sort(&mut self) {
421 for (bucket, items) in &mut self.buckets {
422 items.retain(|item| item.visible);
424
425 let mode = self.sort_modes.get(bucket)
426 .copied()
427 .unwrap_or(bucket.default_sort_mode());
428
429 match mode {
430 SortMode::FrontToBack => {
431 items.sort_by(|a, b| {
432 a.camera_distance.partial_cmp(&b.camera_distance)
433 .unwrap_or(std::cmp::Ordering::Equal)
434 });
435 }
436 SortMode::BackToFront => {
437 items.sort_by(|a, b| {
438 b.camera_distance.partial_cmp(&a.camera_distance)
439 .unwrap_or(std::cmp::Ordering::Equal)
440 });
441 }
442 SortMode::ByMaterial => {
443 items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
444 }
445 SortMode::None => {}
446 }
447 }
448 }
449
450 pub fn get_bucket(&self, bucket: RenderBucket) -> &[RenderItem] {
452 self.buckets.get(&bucket).map(|v| v.as_slice()).unwrap_or(&[])
453 }
454
455 pub fn opaque_items(&self) -> &[RenderItem] {
457 self.get_bucket(RenderBucket::Opaque)
458 }
459
460 pub fn transparent_items(&self) -> &[RenderItem] {
462 self.get_bucket(RenderBucket::Transparent)
463 }
464
465 pub fn overlay_items(&self) -> &[RenderItem] {
467 self.get_bucket(RenderBucket::Overlay)
468 }
469
470 pub fn total_triangles(&self) -> u64 {
472 self.buckets.values()
473 .flat_map(|items| items.iter())
474 .filter(|i| i.visible)
475 .map(|i| {
476 let tris = if i.index_count > 0 {
477 i.index_count / 3
478 } else {
479 i.vertex_count / 3
480 };
481 tris as u64 * i.instance_count as u64
482 })
483 .sum()
484 }
485
486 pub fn total_draw_calls(&self) -> u32 {
488 self.buckets.values()
489 .flat_map(|items| items.iter())
490 .filter(|i| i.visible)
491 .count() as u32
492 }
493}
494
495impl Default for RenderQueue {
496 fn default() -> Self {
497 Self::new()
498 }
499}
500
501#[derive(Debug)]
508pub struct HdrFramebuffer {
509 pub fbo_handle: u64,
511 pub color_handle: u64,
513 pub depth_handle: u64,
515 pub width: u32,
517 pub height: u32,
518 pub allocated: bool,
520 pub generation: u32,
522 next_handle: u64,
524 pub bloom_handle: u64,
526 pub bloom_threshold: f32,
528 pub bloom_mip_levels: u32,
530 pub bloom_mips: Vec<u64>,
532}
533
534impl HdrFramebuffer {
535 pub fn new(width: u32, height: u32) -> Self {
536 Self {
537 fbo_handle: 0,
538 color_handle: 0,
539 depth_handle: 0,
540 width,
541 height,
542 allocated: false,
543 generation: 0,
544 next_handle: 1000,
545 bloom_handle: 0,
546 bloom_threshold: 1.0,
547 bloom_mip_levels: 5,
548 bloom_mips: Vec::new(),
549 }
550 }
551
552 pub fn create(&mut self) -> Result<(), String> {
554 self.fbo_handle = self.alloc_handle();
555 self.color_handle = self.alloc_handle();
556 self.depth_handle = self.alloc_handle();
557 self.bloom_handle = self.alloc_handle();
558
559 self.bloom_mips.clear();
561 for _ in 0..self.bloom_mip_levels {
562 let handle = self.alloc_handle();
563 self.bloom_mips.push(handle);
564 }
565
566 self.allocated = true;
567 self.generation += 1;
568 Ok(())
569 }
570
571 pub fn destroy(&mut self) {
573 self.fbo_handle = 0;
574 self.color_handle = 0;
575 self.depth_handle = 0;
576 self.bloom_handle = 0;
577 self.bloom_mips.clear();
578 self.allocated = false;
579 }
580
581 pub fn resize(&mut self, width: u32, height: u32) {
583 if self.width == width && self.height == height {
584 return;
585 }
586 self.width = width;
587 self.height = height;
588 if self.allocated {
589 self.generation += 1;
590 }
592 }
593
594 pub fn bind(&self) {
596 }
598
599 pub fn unbind(&self) {
601 }
603
604 pub fn memory_bytes(&self) -> u64 {
606 let base = self.width as u64 * self.height as u64;
607 let color = base * 8; let depth = base * 4; let bloom = base * 8; let bloom_mips = (bloom as f64 * 0.334) as u64; color + depth + bloom + bloom_mips
612 }
613
614 fn alloc_handle(&mut self) -> u64 {
615 let h = self.next_handle;
616 self.next_handle += 1;
617 h
618 }
619}
620
621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
627pub enum ExposureMode {
628 Manual,
630 AverageLuminance,
632 Histogram,
634 SpotMetering,
636}
637
638#[derive(Debug, Clone)]
640pub struct ExposureController {
641 pub mode: ExposureMode,
643 pub exposure: f32,
645 pub target_exposure: f32,
647 pub manual_exposure: f32,
649 pub adaptation_speed_up: f32,
651 pub adaptation_speed_down: f32,
652 pub min_exposure: f32,
654 pub max_exposure: f32,
656 pub ev_compensation: f32,
658 pub key_value: f32,
660 pub histogram_low_percentile: f32,
662 pub histogram_high_percentile: f32,
664 pub histogram_bins: u32,
666 pub histogram: Vec<u32>,
668 pub average_luminance: f32,
670 pub spot_radius: f32,
672}
673
674impl ExposureController {
675 pub fn new() -> Self {
676 Self {
677 mode: ExposureMode::AverageLuminance,
678 exposure: 1.0,
679 target_exposure: 1.0,
680 manual_exposure: 1.0,
681 adaptation_speed_up: 2.0,
682 adaptation_speed_down: 1.0,
683 min_exposure: 0.001,
684 max_exposure: 100.0,
685 ev_compensation: 0.0,
686 key_value: 0.18,
687 histogram_low_percentile: 0.1,
688 histogram_high_percentile: 0.9,
689 histogram_bins: 256,
690 histogram: vec![0; 256],
691 average_luminance: 0.18,
692 spot_radius: 0.1,
693 }
694 }
695
696 pub fn update(&mut self, dt: f32) {
698 match self.mode {
699 ExposureMode::Manual => {
700 self.exposure = self.manual_exposure;
701 return;
702 }
703 ExposureMode::AverageLuminance => {
704 self.target_exposure = self.compute_exposure_from_luminance(self.average_luminance);
705 }
706 ExposureMode::Histogram => {
707 let avg = self.compute_histogram_average();
708 self.target_exposure = self.compute_exposure_from_luminance(avg);
709 }
710 ExposureMode::SpotMetering => {
711 self.target_exposure = self.compute_exposure_from_luminance(self.average_luminance);
712 }
713 }
714
715 self.target_exposure *= (2.0f32).powf(self.ev_compensation);
717
718 self.target_exposure = clampf(self.target_exposure, self.min_exposure, self.max_exposure);
720
721 let speed = if self.target_exposure > self.exposure {
723 self.adaptation_speed_up
724 } else {
725 self.adaptation_speed_down
726 };
727
728 let factor = 1.0 - (-speed * dt).exp();
729 self.exposure = lerpf(self.exposure, self.target_exposure, factor);
730 self.exposure = clampf(self.exposure, self.min_exposure, self.max_exposure);
731 }
732
733 fn compute_exposure_from_luminance(&self, luminance: f32) -> f32 {
735 if luminance < 1e-6 {
736 return self.max_exposure;
737 }
738 self.key_value / luminance
739 }
740
741 fn compute_histogram_average(&self) -> f32 {
743 let total: u32 = self.histogram.iter().sum();
744 if total == 0 {
745 return 0.18;
746 }
747
748 let low_count = (total as f32 * self.histogram_low_percentile) as u32;
749 let high_count = (total as f32 * self.histogram_high_percentile) as u32;
750
751 let mut running = 0u32;
752 let mut weighted_sum = 0.0f64;
753 let mut valid_count = 0u32;
754
755 for (i, &count) in self.histogram.iter().enumerate() {
756 let prev_running = running;
757 running += count;
758
759 if running <= low_count {
761 continue;
762 }
763 if prev_running >= high_count {
765 break;
766 }
767
768 let contributing = if prev_running < low_count {
769 count - (low_count - prev_running)
770 } else if running > high_count {
771 high_count - prev_running
772 } else {
773 count
774 };
775
776 let t = i as f32 / self.histogram_bins as f32;
778 let log_lum = t * 20.0 - 10.0; let lum = log_lum.exp();
780
781 weighted_sum += lum as f64 * contributing as f64;
782 valid_count += contributing;
783 }
784
785 if valid_count == 0 {
786 return 0.18;
787 }
788
789 (weighted_sum / valid_count as f64) as f32
790 }
791
792 pub fn feed_luminance(&mut self, luminance: f32) {
795 self.average_luminance = luminance.max(1e-6);
796 }
797
798 pub fn feed_histogram(&mut self, histogram: Vec<u32>) {
800 self.histogram = histogram;
801 }
802
803 pub fn exposure_multiplier(&self) -> f32 {
805 self.exposure
806 }
807
808 pub fn reset(&mut self) {
810 *self = Self::new();
811 }
812}
813
814impl Default for ExposureController {
815 fn default() -> Self {
816 Self::new()
817 }
818}
819
820#[derive(Debug, Clone, Copy, PartialEq, Eq)]
826pub enum ToneMappingOperator {
827 Reinhard,
829 ReinhardExtended,
831 AcesFilmic,
833 Hable,
835 Linear,
837 AgX,
839}
840
841impl ToneMappingOperator {
842 pub fn apply(&self, color: [f32; 3], exposure: f32) -> [f32; 3] {
844 let c = [
845 color[0] * exposure,
846 color[1] * exposure,
847 color[2] * exposure,
848 ];
849 match self {
850 Self::Reinhard => Self::reinhard(c),
851 Self::ReinhardExtended => Self::reinhard_extended(c, 4.0),
852 Self::AcesFilmic => Self::aces_filmic(c),
853 Self::Hable => Self::hable(c),
854 Self::Linear => [
855 saturate(c[0]),
856 saturate(c[1]),
857 saturate(c[2]),
858 ],
859 Self::AgX => Self::agx(c),
860 }
861 }
862
863 fn reinhard(c: [f32; 3]) -> [f32; 3] {
864 [
865 c[0] / (1.0 + c[0]),
866 c[1] / (1.0 + c[1]),
867 c[2] / (1.0 + c[2]),
868 ]
869 }
870
871 fn reinhard_extended(c: [f32; 3], white_point: f32) -> [f32; 3] {
872 let wp2 = white_point * white_point;
873 [
874 c[0] * (1.0 + c[0] / wp2) / (1.0 + c[0]),
875 c[1] * (1.0 + c[1] / wp2) / (1.0 + c[1]),
876 c[2] * (1.0 + c[2] / wp2) / (1.0 + c[2]),
877 ]
878 }
879
880 fn aces_filmic(c: [f32; 3]) -> [f32; 3] {
881 fn aces_channel(x: f32) -> f32 {
883 let a = 2.51;
884 let b = 0.03;
885 let c = 2.43;
886 let d = 0.59;
887 let e = 0.14;
888 saturate((x * (a * x + b)) / (x * (c * x + d) + e))
889 }
890 [aces_channel(c[0]), aces_channel(c[1]), aces_channel(c[2])]
891 }
892
893 fn hable(c: [f32; 3]) -> [f32; 3] {
894 fn hable_partial(x: f32) -> f32 {
895 let a = 0.15;
896 let b = 0.50;
897 let cc = 0.10;
898 let d = 0.20;
899 let e = 0.02;
900 let f = 0.30;
901 ((x * (a * x + cc * b) + d * e) / (x * (a * x + b) + d * f)) - e / f
902 }
903 let exposure_bias = 2.0;
904 let white_scale = 1.0 / hable_partial(11.2);
905 [
906 hable_partial(c[0] * exposure_bias) * white_scale,
907 hable_partial(c[1] * exposure_bias) * white_scale,
908 hable_partial(c[2] * exposure_bias) * white_scale,
909 ]
910 }
911
912 fn agx(c: [f32; 3]) -> [f32; 3] {
913 fn agx_channel(x: f32) -> f32 {
915 let x = x.max(0.0);
916 let a = x.ln().max(-10.0).min(10.0);
917 let mapped = 0.5 + 0.5 * (a * 0.3).tanh();
918 mapped
919 }
920 [agx_channel(c[0]), agx_channel(c[1]), agx_channel(c[2])]
921 }
922
923 pub fn glsl_function(&self) -> &'static str {
925 match self {
926 Self::Reinhard => {
927 r#"vec3 tonemap(vec3 c) { return c / (1.0 + c); }"#
928 }
929 Self::ReinhardExtended => {
930 r#"vec3 tonemap(vec3 c) {
931 float wp2 = 16.0;
932 return c * (1.0 + c / wp2) / (1.0 + c);
933}"#
934 }
935 Self::AcesFilmic => {
936 r#"vec3 tonemap(vec3 x) {
937 float a = 2.51; float b = 0.03;
938 float c = 2.43; float d = 0.59; float e = 0.14;
939 return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0);
940}"#
941 }
942 Self::Hable => {
943 r#"float hable(float x) {
944 float A=0.15,B=0.50,C=0.10,D=0.20,E=0.02,F=0.30;
945 return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
946}
947vec3 tonemap(vec3 c) {
948 float w = 1.0/hable(11.2);
949 return vec3(hable(c.x*2.0)*w, hable(c.y*2.0)*w, hable(c.z*2.0)*w);
950}"#
951 }
952 Self::Linear => {
953 r#"vec3 tonemap(vec3 c) { return clamp(c, 0.0, 1.0); }"#
954 }
955 Self::AgX => {
956 r#"vec3 tonemap(vec3 c) {
957 vec3 a = log(max(c, vec3(0.0001)));
958 return 0.5 + 0.5 * tanh(a * 0.3);
959}"#
960 }
961 }
962 }
963}
964
965#[derive(Debug)]
972pub struct DepthPrePass {
973 pub enabled: bool,
975 pub shader_handle: u64,
977 pub items: Vec<u64>,
979 pub use_gbuffer_depth: bool,
981 pub rendered_count: u32,
983 pub time_us: u64,
985 pub depth_func: DepthFunction,
987 pub depth_write: bool,
989 pub alpha_test_in_prepass: bool,
991 pub alpha_threshold: f32,
993}
994
995#[derive(Debug, Clone, Copy, PartialEq, Eq)]
997pub enum DepthFunction {
998 Less,
999 LessEqual,
1000 Greater,
1001 GreaterEqual,
1002 Equal,
1003 NotEqual,
1004 Always,
1005 Never,
1006}
1007
1008impl DepthPrePass {
1009 pub fn new() -> Self {
1010 Self {
1011 enabled: true,
1012 shader_handle: 0,
1013 items: Vec::new(),
1014 use_gbuffer_depth: true,
1015 rendered_count: 0,
1016 time_us: 0,
1017 depth_func: DepthFunction::Less,
1018 depth_write: true,
1019 alpha_test_in_prepass: false,
1020 alpha_threshold: 0.5,
1021 }
1022 }
1023
1024 pub fn execute(&mut self, queue: &RenderQueue, _gbuffer: &mut GBuffer) {
1026 let start = std::time::Instant::now();
1027
1028 self.items.clear();
1029 self.rendered_count = 0;
1030
1031 if !self.enabled {
1032 return;
1033 }
1034
1035 let opaque = queue.opaque_items();
1036 for item in opaque {
1037 if !item.visible {
1038 continue;
1039 }
1040 if item.alpha_test && !self.alpha_test_in_prepass {
1042 continue;
1043 }
1044 self.items.push(item.id);
1045 self.rendered_count += 1;
1046 }
1048
1049 self.time_us = start.elapsed().as_micros() as u64;
1050 }
1051
1052 pub fn vertex_shader() -> &'static str {
1054 r#"#version 330 core
1055layout(location = 0) in vec3 a_position;
1056uniform mat4 u_model;
1057uniform mat4 u_view_projection;
1058void main() {
1059 gl_Position = u_view_projection * u_model * vec4(a_position, 1.0);
1060}
1061"#
1062 }
1063
1064 pub fn fragment_shader_alpha_test() -> &'static str {
1066 r#"#version 330 core
1067uniform sampler2D u_albedo_tex;
1068uniform float u_alpha_threshold;
1069in vec2 v_texcoord;
1070void main() {
1071 float alpha = texture(u_albedo_tex, v_texcoord).a;
1072 if (alpha < u_alpha_threshold) discard;
1073}
1074"#
1075 }
1076}
1077
1078impl Default for DepthPrePass {
1079 fn default() -> Self {
1080 Self::new()
1081 }
1082}
1083
1084#[derive(Debug)]
1090pub struct GeometryPass {
1091 pub enabled: bool,
1093 pub shader_handle: u64,
1095 pub draw_call_count: u32,
1097 pub triangle_count: u64,
1099 pub time_us: u64,
1101 pub depth_test: bool,
1103 pub depth_func: DepthFunction,
1105 pub depth_write: bool,
1107 pub backface_cull: bool,
1109 pub polygon_offset: Option<(f32, f32)>,
1111 pub use_instancing: bool,
1113 pub max_instances_per_draw: u32,
1115}
1116
1117impl GeometryPass {
1118 pub fn new() -> Self {
1119 Self {
1120 enabled: true,
1121 shader_handle: 0,
1122 draw_call_count: 0,
1123 triangle_count: 0,
1124 time_us: 0,
1125 depth_test: true,
1126 depth_func: DepthFunction::Equal,
1127 depth_write: false,
1128 backface_cull: true,
1129 polygon_offset: None,
1130 use_instancing: true,
1131 max_instances_per_draw: 1024,
1132 }
1133 }
1134
1135 pub fn execute(&mut self, queue: &RenderQueue, gbuffer: &mut GBuffer) {
1137 let start = std::time::Instant::now();
1138
1139 self.draw_call_count = 0;
1140 self.triangle_count = 0;
1141
1142 if !self.enabled {
1143 return;
1144 }
1145
1146 let _ = gbuffer.bind();
1148
1149 gbuffer.clear_all();
1151
1152 let opaque = queue.opaque_items();
1153 for item in opaque {
1154 if !item.visible {
1155 continue;
1156 }
1157
1158 self.draw_call_count += 1;
1159 let tris = if item.index_count > 0 {
1160 item.index_count / 3
1161 } else {
1162 item.vertex_count / 3
1163 };
1164 self.triangle_count += tris as u64 * item.instance_count as u64;
1165
1166 }
1172
1173 gbuffer.unbind();
1174 gbuffer.stats.geometry_draw_calls = self.draw_call_count;
1175
1176 self.time_us = start.elapsed().as_micros() as u64;
1177 }
1178
1179 pub fn vertex_shader() -> &'static str {
1181 r#"#version 330 core
1182layout(location = 0) in vec3 a_position;
1183layout(location = 1) in vec3 a_normal;
1184layout(location = 2) in vec2 a_texcoord;
1185layout(location = 3) in vec3 a_tangent;
1186
1187uniform mat4 u_model;
1188uniform mat4 u_view;
1189uniform mat4 u_projection;
1190uniform mat3 u_normal_matrix;
1191
1192out vec3 v_world_pos;
1193out vec3 v_normal;
1194out vec2 v_texcoord;
1195out vec3 v_tangent;
1196
1197void main() {
1198 vec4 world_pos = u_model * vec4(a_position, 1.0);
1199 v_world_pos = world_pos.xyz;
1200 v_normal = u_normal_matrix * a_normal;
1201 v_texcoord = a_texcoord;
1202 v_tangent = u_normal_matrix * a_tangent;
1203 gl_Position = u_projection * u_view * world_pos;
1204}
1205"#
1206 }
1207
1208 pub fn fragment_shader() -> &'static str {
1210 r#"#version 330 core
1211in vec3 v_world_pos;
1212in vec3 v_normal;
1213in vec2 v_texcoord;
1214in vec3 v_tangent;
1215
1216layout(location = 0) out vec4 out_position;
1217layout(location = 1) out vec2 out_normal;
1218layout(location = 2) out vec4 out_albedo;
1219layout(location = 3) out vec4 out_emission;
1220layout(location = 4) out float out_matid;
1221layout(location = 5) out float out_roughness;
1222layout(location = 6) out float out_metallic;
1223
1224uniform sampler2D u_albedo_map;
1225uniform sampler2D u_normal_map;
1226uniform sampler2D u_roughness_map;
1227uniform sampler2D u_metallic_map;
1228uniform sampler2D u_emission_map;
1229uniform vec4 u_albedo_color;
1230uniform float u_roughness;
1231uniform float u_metallic;
1232uniform vec3 u_emission;
1233uniform float u_material_id;
1234uniform bool u_has_normal_map;
1235
1236// Octahedral encoding
1237vec2 oct_encode(vec3 n) {
1238 float sum = abs(n.x) + abs(n.y) + abs(n.z);
1239 vec2 o = n.xy / sum;
1240 if (n.z < 0.0) {
1241 o = (1.0 - abs(o.yx)) * vec2(o.x >= 0.0 ? 1.0 : -1.0, o.y >= 0.0 ? 1.0 : -1.0);
1242 }
1243 return o;
1244}
1245
1246void main() {
1247 out_position = vec4(v_world_pos, 1.0);
1248
1249 vec3 N = normalize(v_normal);
1250 if (u_has_normal_map) {
1251 vec3 T = normalize(v_tangent);
1252 vec3 B = cross(N, T);
1253 mat3 TBN = mat3(T, B, N);
1254 vec3 tangent_normal = texture(u_normal_map, v_texcoord).xyz * 2.0 - 1.0;
1255 N = normalize(TBN * tangent_normal);
1256 }
1257 out_normal = oct_encode(N);
1258
1259 out_albedo = texture(u_albedo_map, v_texcoord) * u_albedo_color;
1260 out_emission = vec4(u_emission + texture(u_emission_map, v_texcoord).rgb, 1.0);
1261 out_matid = u_material_id / 255.0;
1262 out_roughness = texture(u_roughness_map, v_texcoord).r * u_roughness;
1263 out_metallic = texture(u_metallic_map, v_texcoord).r * u_metallic;
1264}
1265"#
1266 }
1267}
1268
1269impl Default for GeometryPass {
1270 fn default() -> Self {
1271 Self::new()
1272 }
1273}
1274
1275#[derive(Debug)]
1282pub struct LightingPass {
1283 pub enabled: bool,
1285 pub shader_handle: u64,
1287 pub lights: Vec<LightType>,
1289 pub max_lights_per_pixel: u32,
1291 pub use_light_volumes: bool,
1293 pub time_us: u64,
1295 pub lights_evaluated: u32,
1297 pub ambient_color: [f32; 3],
1299 pub ambient_intensity: f32,
1301 pub ssao_enabled: bool,
1303 pub ssao_radius: f32,
1305 pub ssao_bias: f32,
1307 pub ssao_kernel_size: u32,
1309 pub environment_map: u64,
1311 pub ibl_enabled: bool,
1313 pub ibl_intensity: f32,
1315}
1316
1317impl LightingPass {
1318 pub fn new() -> Self {
1319 Self {
1320 enabled: true,
1321 shader_handle: 0,
1322 lights: Vec::new(),
1323 max_lights_per_pixel: 128,
1324 use_light_volumes: false,
1325 time_us: 0,
1326 lights_evaluated: 0,
1327 ambient_color: [0.03, 0.03, 0.05],
1328 ambient_intensity: 1.0,
1329 ssao_enabled: false,
1330 ssao_radius: 0.5,
1331 ssao_bias: 0.025,
1332 ssao_kernel_size: 64,
1333 environment_map: 0,
1334 ibl_enabled: false,
1335 ibl_intensity: 1.0,
1336 }
1337 }
1338
1339 pub fn add_light(&mut self, light: LightType) {
1341 self.lights.push(light);
1342 }
1343
1344 pub fn clear_lights(&mut self) {
1346 self.lights.clear();
1347 }
1348
1349 pub fn execute(
1351 &mut self,
1352 gbuffer: &GBuffer,
1353 hdr_fb: &HdrFramebuffer,
1354 view_matrix: &Mat4,
1355 projection_matrix: &Mat4,
1356 camera_pos: [f32; 3],
1357 ) {
1358 let start = std::time::Instant::now();
1359
1360 if !self.enabled {
1361 return;
1362 }
1363
1364 hdr_fb.bind();
1366
1367 let _bindings = gbuffer.bind_for_reading();
1369
1370 self.lights_evaluated = self.lights.len().min(self.max_lights_per_pixel as usize) as u32;
1378
1379 let _ = view_matrix;
1380 let _ = projection_matrix;
1381 let _ = camera_pos;
1382
1383 hdr_fb.unbind();
1384
1385 self.time_us = start.elapsed().as_micros() as u64;
1386 }
1387
1388 pub fn evaluate_pbr(
1390 &self,
1391 position: [f32; 3],
1392 normal: [f32; 3],
1393 albedo: [f32; 3],
1394 roughness: f32,
1395 metallic: f32,
1396 camera_pos: [f32; 3],
1397 ) -> [f32; 3] {
1398 let v = super::vec3_normalize(vec3_sub(camera_pos, position));
1399 let mut total = [
1400 self.ambient_color[0] * self.ambient_intensity * albedo[0],
1401 self.ambient_color[1] * self.ambient_intensity * albedo[1],
1402 self.ambient_color[2] * self.ambient_intensity * albedo[2],
1403 ];
1404
1405 for light in &self.lights {
1406 let (l, attenuation, light_color) = light.evaluate(position);
1407 if attenuation < 1e-6 {
1408 continue;
1409 }
1410
1411 let n_dot_l = vec3_dot(normal, l).max(0.0);
1412 if n_dot_l < 1e-6 {
1413 continue;
1414 }
1415
1416 let h = super::vec3_normalize(super::vec3_add(v, l));
1418 let n_dot_h = vec3_dot(normal, h).max(0.0);
1419 let n_dot_v = vec3_dot(normal, v).max(0.001);
1420
1421 let a = roughness * roughness;
1423 let a2 = a * a;
1424 let denom = n_dot_h * n_dot_h * (a2 - 1.0) + 1.0;
1425 let d = a2 / (std::f32::consts::PI * denom * denom).max(1e-6);
1426
1427 let k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
1429 let g1_v = n_dot_v / (n_dot_v * (1.0 - k) + k);
1430 let g1_l = n_dot_l / (n_dot_l * (1.0 - k) + k);
1431 let g = g1_v * g1_l;
1432
1433 let f0_base = lerpf(0.04, 1.0, metallic);
1435 let f0 = [
1436 lerpf(0.04, albedo[0], metallic),
1437 lerpf(0.04, albedo[1], metallic),
1438 lerpf(0.04, albedo[2], metallic),
1439 ];
1440 let _ = f0_base;
1441 let v_dot_h = vec3_dot(v, h).max(0.0);
1442 let fresnel_factor = (1.0 - v_dot_h).powf(5.0);
1443 let f = [
1444 f0[0] + (1.0 - f0[0]) * fresnel_factor,
1445 f0[1] + (1.0 - f0[1]) * fresnel_factor,
1446 f0[2] + (1.0 - f0[2]) * fresnel_factor,
1447 ];
1448
1449 let spec_denom = (4.0 * n_dot_v * n_dot_l).max(1e-6);
1451 let spec = [
1452 d * g * f[0] / spec_denom,
1453 d * g * f[1] / spec_denom,
1454 d * g * f[2] / spec_denom,
1455 ];
1456
1457 let kd = [
1459 (1.0 - f[0]) * (1.0 - metallic),
1460 (1.0 - f[1]) * (1.0 - metallic),
1461 (1.0 - f[2]) * (1.0 - metallic),
1462 ];
1463 let diffuse = [
1464 kd[0] * albedo[0] / std::f32::consts::PI,
1465 kd[1] * albedo[1] / std::f32::consts::PI,
1466 kd[2] * albedo[2] / std::f32::consts::PI,
1467 ];
1468
1469 for i in 0..3 {
1471 total[i] += (diffuse[i] + spec[i]) * light_color[i] * attenuation * n_dot_l;
1472 }
1473 }
1474
1475 total
1476 }
1477
1478 pub fn fragment_shader() -> &'static str {
1480 r#"#version 330 core
1481in vec2 v_texcoord;
1482out vec4 frag_color;
1483
1484uniform sampler2D g_position;
1485uniform sampler2D g_normal;
1486uniform sampler2D g_albedo;
1487uniform sampler2D g_emission;
1488uniform sampler2D g_roughness;
1489uniform sampler2D g_metallic;
1490uniform sampler2D g_depth;
1491
1492uniform vec3 u_camera_pos;
1493uniform mat4 u_inv_view_proj;
1494
1495struct Light {
1496 int type; // 0=dir, 1=point, 2=spot
1497 vec3 position;
1498 vec3 direction;
1499 vec3 color;
1500 float intensity;
1501 float range;
1502 float inner_angle;
1503 float outer_angle;
1504};
1505
1506#define MAX_LIGHTS 128
1507uniform Light u_lights[MAX_LIGHTS];
1508uniform int u_light_count;
1509uniform vec3 u_ambient;
1510
1511const float PI = 3.14159265359;
1512
1513vec3 oct_decode(vec2 o) {
1514 float z = 1.0 - abs(o.x) - abs(o.y);
1515 vec2 xy = z >= 0.0 ? o : (1.0 - abs(o.yx)) * vec2(o.x >= 0.0 ? 1.0 : -1.0, o.y >= 0.0 ? 1.0 : -1.0);
1516 return normalize(vec3(xy, z));
1517}
1518
1519float ggx_distribution(float NdotH, float roughness) {
1520 float a2 = roughness * roughness * roughness * roughness;
1521 float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
1522 return a2 / (PI * d * d);
1523}
1524
1525float geometry_schlick(float NdotV, float NdotL, float roughness) {
1526 float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
1527 float g1 = NdotV / (NdotV * (1.0 - k) + k);
1528 float g2 = NdotL / (NdotL * (1.0 - k) + k);
1529 return g1 * g2;
1530}
1531
1532vec3 fresnel_schlick(float cosTheta, vec3 F0) {
1533 return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
1534}
1535
1536void main() {
1537 vec3 pos = texture(g_position, v_texcoord).xyz;
1538 vec3 N = oct_decode(texture(g_normal, v_texcoord).xy);
1539 vec4 albedo_alpha = texture(g_albedo, v_texcoord);
1540 vec3 albedo = albedo_alpha.rgb;
1541 vec3 emission = texture(g_emission, v_texcoord).rgb;
1542 float roughness = texture(g_roughness, v_texcoord).r;
1543 float metallic = texture(g_metallic, v_texcoord).r;
1544
1545 vec3 V = normalize(u_camera_pos - pos);
1546 vec3 F0 = mix(vec3(0.04), albedo, metallic);
1547
1548 vec3 Lo = vec3(0.0);
1549 for (int i = 0; i < u_light_count && i < MAX_LIGHTS; i++) {
1550 vec3 L;
1551 float attenuation;
1552
1553 if (u_lights[i].type == 0) {
1554 L = -u_lights[i].direction;
1555 attenuation = u_lights[i].intensity;
1556 } else {
1557 vec3 toLight = u_lights[i].position - pos;
1558 float dist = length(toLight);
1559 L = toLight / dist;
1560 float r = dist / u_lights[i].range;
1561 attenuation = u_lights[i].intensity * max((1.0 - r*r), 0.0) / (dist*dist + 1.0);
1562
1563 if (u_lights[i].type == 2) {
1564 float cosAngle = dot(-L, u_lights[i].direction);
1565 float spot = clamp((cosAngle - cos(u_lights[i].outer_angle)) /
1566 (cos(u_lights[i].inner_angle) - cos(u_lights[i].outer_angle)), 0.0, 1.0);
1567 attenuation *= spot;
1568 }
1569 }
1570
1571 vec3 H = normalize(V + L);
1572 float NdotL = max(dot(N, L), 0.0);
1573 float NdotH = max(dot(N, H), 0.0);
1574 float NdotV = max(dot(N, V), 0.001);
1575
1576 float D = ggx_distribution(NdotH, roughness);
1577 float G = geometry_schlick(NdotV, NdotL, roughness);
1578 vec3 F = fresnel_schlick(max(dot(H, V), 0.0), F0);
1579
1580 vec3 spec = D * G * F / max(4.0 * NdotV * NdotL, 0.001);
1581 vec3 kD = (1.0 - F) * (1.0 - metallic);
1582 vec3 diffuse = kD * albedo / PI;
1583
1584 Lo += (diffuse + spec) * u_lights[i].color * attenuation * NdotL;
1585 }
1586
1587 vec3 ambient = u_ambient * albedo;
1588 vec3 color = ambient + Lo + emission;
1589 frag_color = vec4(color, 1.0);
1590}
1591"#
1592 }
1593}
1594
1595impl Default for LightingPass {
1596 fn default() -> Self {
1597 Self::new()
1598 }
1599}
1600
1601#[derive(Debug)]
1609pub struct ForwardPass {
1610 pub enabled: bool,
1612 pub shader_handle: u64,
1614 pub draw_call_count: u32,
1616 pub triangle_count: u64,
1618 pub time_us: u64,
1620 pub depth_test: bool,
1622 pub depth_write: bool,
1624 pub blend_mode: ForwardBlendMode,
1626 pub premultiplied_alpha: bool,
1628 pub render_particles: bool,
1630 pub render_glyphs: bool,
1632 pub oit_layers: u32,
1635}
1636
1637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1639pub enum ForwardBlendMode {
1640 AlphaBlend,
1642 Additive,
1644 PremultipliedAlpha,
1646 Multiply,
1648}
1649
1650impl ForwardPass {
1651 pub fn new() -> Self {
1652 Self {
1653 enabled: true,
1654 shader_handle: 0,
1655 draw_call_count: 0,
1656 triangle_count: 0,
1657 time_us: 0,
1658 depth_test: true,
1659 depth_write: false,
1660 blend_mode: ForwardBlendMode::AlphaBlend,
1661 premultiplied_alpha: false,
1662 render_particles: true,
1663 render_glyphs: true,
1664 oit_layers: 0,
1665 }
1666 }
1667
1668 pub fn execute(
1670 &mut self,
1671 queue: &RenderQueue,
1672 hdr_fb: &HdrFramebuffer,
1673 _gbuffer: &GBuffer,
1674 _lights: &[LightType],
1675 _view: &Mat4,
1676 _proj: &Mat4,
1677 _camera_pos: [f32; 3],
1678 ) {
1679 let start = std::time::Instant::now();
1680
1681 self.draw_call_count = 0;
1682 self.triangle_count = 0;
1683
1684 if !self.enabled {
1685 return;
1686 }
1687
1688 hdr_fb.bind();
1689
1690 let transparent = queue.transparent_items();
1692 for item in transparent {
1693 if !item.visible {
1694 continue;
1695 }
1696 self.draw_call_count += 1;
1697 let tris = if item.index_count > 0 {
1698 item.index_count / 3
1699 } else {
1700 item.vertex_count / 3
1701 };
1702 self.triangle_count += tris as u64 * item.instance_count as u64;
1703 }
1704
1705 let overlay = queue.overlay_items();
1707 for item in overlay {
1708 if !item.visible {
1709 continue;
1710 }
1711 self.draw_call_count += 1;
1712 }
1713
1714 hdr_fb.unbind();
1715
1716 self.time_us = start.elapsed().as_micros() as u64;
1717 }
1718}
1719
1720impl Default for ForwardPass {
1721 fn default() -> Self {
1722 Self::new()
1723 }
1724}
1725
1726#[derive(Debug)]
1732pub struct PostProcessPass {
1733 pub enabled: bool,
1735 pub shader_handle: u64,
1737 pub time_us: u64,
1739 pub bloom_enabled: bool,
1741 pub bloom_intensity: f32,
1742 pub bloom_threshold: f32,
1743 pub bloom_radius: f32,
1744 pub bloom_mip_count: u32,
1745 pub tone_mapping: ToneMappingOperator,
1747 pub exposure: ExposureController,
1749 pub gamma: f32,
1751 pub vignette_enabled: bool,
1753 pub vignette_intensity: f32,
1754 pub vignette_smoothness: f32,
1755 pub chromatic_aberration_enabled: bool,
1757 pub chromatic_aberration_intensity: f32,
1758 pub film_grain_enabled: bool,
1760 pub film_grain_intensity: f32,
1761 pub dithering_enabled: bool,
1763 pub color_lut_handle: u64,
1765 pub color_lut_enabled: bool,
1766 pub saturation: f32,
1768 pub contrast: f32,
1770 pub brightness: f32,
1772}
1773
1774impl PostProcessPass {
1775 pub fn new() -> Self {
1776 Self {
1777 enabled: true,
1778 shader_handle: 0,
1779 time_us: 0,
1780 bloom_enabled: true,
1781 bloom_intensity: 0.5,
1782 bloom_threshold: 1.0,
1783 bloom_radius: 5.0,
1784 bloom_mip_count: 5,
1785 tone_mapping: ToneMappingOperator::AcesFilmic,
1786 exposure: ExposureController::new(),
1787 gamma: 2.2,
1788 vignette_enabled: false,
1789 vignette_intensity: 0.3,
1790 vignette_smoothness: 2.0,
1791 chromatic_aberration_enabled: false,
1792 chromatic_aberration_intensity: 0.005,
1793 film_grain_enabled: false,
1794 film_grain_intensity: 0.05,
1795 dithering_enabled: true,
1796 color_lut_handle: 0,
1797 color_lut_enabled: false,
1798 saturation: 1.0,
1799 contrast: 1.0,
1800 brightness: 0.0,
1801 }
1802 }
1803
1804 pub fn execute(&mut self, hdr_fb: &HdrFramebuffer, _viewport: &Viewport, dt: f32) {
1806 let start = std::time::Instant::now();
1807
1808 if !self.enabled {
1809 return;
1810 }
1811
1812 self.exposure.update(dt);
1814
1815 let _ = hdr_fb;
1827
1828 self.time_us = start.elapsed().as_micros() as u64;
1829 }
1830
1831 pub fn bloom_extract(&self, color: [f32; 3]) -> [f32; 3] {
1833 let luminance = 0.2126 * color[0] + 0.7152 * color[1] + 0.0722 * color[2];
1834 if luminance > self.bloom_threshold {
1835 let excess = luminance - self.bloom_threshold;
1836 let factor = excess / luminance.max(1e-6);
1837 [
1838 color[0] * factor * self.bloom_intensity,
1839 color[1] * factor * self.bloom_intensity,
1840 color[2] * factor * self.bloom_intensity,
1841 ]
1842 } else {
1843 [0.0, 0.0, 0.0]
1844 }
1845 }
1846
1847 pub fn apply_vignette(&self, color: [f32; 3], uv: [f32; 2]) -> [f32; 3] {
1849 if !self.vignette_enabled {
1850 return color;
1851 }
1852 let center = [uv[0] - 0.5, uv[1] - 0.5];
1853 let dist = (center[0] * center[0] + center[1] * center[1]).sqrt() * 1.414;
1854 let vignette = 1.0 - self.vignette_intensity * dist.powf(self.vignette_smoothness);
1855 let v = vignette.max(0.0);
1856 [color[0] * v, color[1] * v, color[2] * v]
1857 }
1858
1859 pub fn color_adjust(&self, color: [f32; 3]) -> [f32; 3] {
1861 let c = [
1863 color[0] + self.brightness,
1864 color[1] + self.brightness,
1865 color[2] + self.brightness,
1866 ];
1867
1868 let c = [
1870 (c[0] - 0.5) * self.contrast + 0.5,
1871 (c[1] - 0.5) * self.contrast + 0.5,
1872 (c[2] - 0.5) * self.contrast + 0.5,
1873 ];
1874
1875 let lum = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
1877 [
1878 lerpf(lum, c[0], self.saturation),
1879 lerpf(lum, c[1], self.saturation),
1880 lerpf(lum, c[2], self.saturation),
1881 ]
1882 }
1883
1884 pub fn fragment_shader(&self) -> String {
1886 let mut s = String::from(r#"#version 330 core
1887in vec2 v_texcoord;
1888out vec4 frag_color;
1889
1890uniform sampler2D u_hdr_color;
1891uniform sampler2D u_bloom;
1892uniform float u_exposure;
1893uniform float u_gamma;
1894uniform float u_bloom_intensity;
1895uniform float u_time;
1896"#);
1897
1898 s.push_str(self.tone_mapping.glsl_function());
1899 s.push('\n');
1900
1901 s.push_str(r#"
1902void main() {
1903 vec3 hdr = texture(u_hdr_color, v_texcoord).rgb;
1904 vec3 bloom = texture(u_bloom, v_texcoord).rgb;
1905 hdr += bloom * u_bloom_intensity;
1906 hdr *= u_exposure;
1907 vec3 mapped = tonemap(hdr);
1908 mapped = pow(mapped, vec3(1.0 / u_gamma));
1909 frag_color = vec4(mapped, 1.0);
1910}
1911"#);
1912
1913 s
1914 }
1915}
1916
1917impl Default for PostProcessPass {
1918 fn default() -> Self {
1919 Self::new()
1920 }
1921}
1922
1923#[derive(Debug, Clone, Default)]
1929pub struct DeferredFrameStats {
1930 pub total_time_us: u64,
1932 pub depth_prepass_us: u64,
1934 pub geometry_pass_us: u64,
1935 pub lighting_pass_us: u64,
1936 pub forward_pass_us: u64,
1937 pub postprocess_pass_us: u64,
1938 pub aa_pass_us: u64,
1939 pub total_draw_calls: u32,
1941 pub opaque_draw_calls: u32,
1942 pub transparent_draw_calls: u32,
1943 pub total_triangles: u64,
1945 pub items_submitted: u32,
1947 pub items_visible: u32,
1948 pub items_culled: u32,
1949 pub gbuffer_memory_mb: f32,
1951 pub exposure: f32,
1953 pub frame_number: u64,
1955}
1956
1957#[derive(Debug)]
1959pub struct DeferredPipeline {
1960 pub gbuffer: GBuffer,
1962 pub hdr_framebuffer: HdrFramebuffer,
1964 pub depth_prepass: DepthPrePass,
1966 pub geometry_pass: GeometryPass,
1968 pub lighting_pass: LightingPass,
1970 pub forward_pass: ForwardPass,
1972 pub postprocess_pass: PostProcessPass,
1974 pub render_queue: RenderQueue,
1976 pub viewport: Viewport,
1978 pub view_matrix: Mat4,
1980 pub projection_matrix: Mat4,
1982 pub camera_position: [f32; 3],
1984 pub frame_stats: DeferredFrameStats,
1986 pub initialized: bool,
1988 pub frame_number: u64,
1990 pub dt: f32,
1992}
1993
1994impl DeferredPipeline {
1995 pub fn new(width: u32, height: u32) -> Self {
1997 let viewport = Viewport::new(width, height);
1998 Self {
1999 gbuffer: GBuffer::new(viewport),
2000 hdr_framebuffer: HdrFramebuffer::new(width, height),
2001 depth_prepass: DepthPrePass::new(),
2002 geometry_pass: GeometryPass::new(),
2003 lighting_pass: LightingPass::new(),
2004 forward_pass: ForwardPass::new(),
2005 postprocess_pass: PostProcessPass::new(),
2006 render_queue: RenderQueue::new(),
2007 viewport,
2008 view_matrix: Mat4::IDENTITY,
2009 projection_matrix: Mat4::IDENTITY,
2010 camera_position: [0.0; 3],
2011 frame_stats: DeferredFrameStats::default(),
2012 initialized: false,
2013 frame_number: 0,
2014 dt: 0.016,
2015 }
2016 }
2017
2018 pub fn initialize(&mut self) -> Result<(), String> {
2020 self.gbuffer.create().map_err(|e| e.to_string())?;
2021 self.hdr_framebuffer.create()?;
2022 self.initialized = true;
2023 Ok(())
2024 }
2025
2026 pub fn shutdown(&mut self) {
2028 self.gbuffer.destroy();
2029 self.hdr_framebuffer.destroy();
2030 self.initialized = false;
2031 }
2032
2033 pub fn resize(&mut self, width: u32, height: u32) {
2035 self.viewport = Viewport::new(width, height);
2036 let _ = self.gbuffer.resize(width, height);
2037 self.hdr_framebuffer.resize(width, height);
2038 }
2039
2040 pub fn set_camera(
2042 &mut self,
2043 position: [f32; 3],
2044 view: Mat4,
2045 projection: Mat4,
2046 frustum_planes: [[f32; 4]; 6],
2047 ) {
2048 self.camera_position = position;
2049 self.view_matrix = view;
2050 self.projection_matrix = projection;
2051 self.render_queue.set_camera(position, frustum_planes);
2052 }
2053
2054 pub fn submit(&mut self, item: RenderItem) {
2056 self.render_queue.submit(item);
2057 }
2058
2059 pub fn execute_frame(&mut self, dt: f32) {
2061 let frame_start = std::time::Instant::now();
2062 self.dt = dt;
2063 self.frame_number += 1;
2064
2065 self.render_queue.sort();
2067
2068 self.depth_prepass.execute(&self.render_queue, &mut self.gbuffer);
2070
2071 self.geometry_pass.execute(&self.render_queue, &mut self.gbuffer);
2073
2074 self.lighting_pass.execute(
2076 &self.gbuffer,
2077 &self.hdr_framebuffer,
2078 &self.view_matrix,
2079 &self.projection_matrix,
2080 self.camera_position,
2081 );
2082
2083 let lights_clone: Vec<LightType> = self.lighting_pass.lights.clone();
2085 self.forward_pass.execute(
2086 &self.render_queue,
2087 &self.hdr_framebuffer,
2088 &self.gbuffer,
2089 &lights_clone,
2090 &self.view_matrix,
2091 &self.projection_matrix,
2092 self.camera_position,
2093 );
2094
2095 self.postprocess_pass.execute(&self.hdr_framebuffer, &self.viewport, dt);
2097
2098 self.frame_stats = DeferredFrameStats {
2100 total_time_us: frame_start.elapsed().as_micros() as u64,
2101 depth_prepass_us: self.depth_prepass.time_us,
2102 geometry_pass_us: self.geometry_pass.time_us,
2103 lighting_pass_us: self.lighting_pass.time_us,
2104 forward_pass_us: self.forward_pass.time_us,
2105 postprocess_pass_us: self.postprocess_pass.time_us,
2106 aa_pass_us: 0,
2107 total_draw_calls: self.geometry_pass.draw_call_count + self.forward_pass.draw_call_count,
2108 opaque_draw_calls: self.geometry_pass.draw_call_count,
2109 transparent_draw_calls: self.forward_pass.draw_call_count,
2110 total_triangles: self.geometry_pass.triangle_count + self.forward_pass.triangle_count,
2111 items_submitted: self.render_queue.total_submitted,
2112 items_visible: self.render_queue.total_visible,
2113 items_culled: self.render_queue.total_culled,
2114 gbuffer_memory_mb: self.gbuffer.stats().total_memory_bytes as f32 / (1024.0 * 1024.0),
2115 exposure: self.postprocess_pass.exposure.exposure,
2116 frame_number: self.frame_number,
2117 };
2118
2119 self.render_queue.clear();
2121 }
2122
2123 pub fn stats_summary(&self) -> String {
2125 let s = &self.frame_stats;
2126 format!(
2127 "Frame {} | {:.1}ms total | Draws: {} | Tris: {} | Visible: {}/{} | Exposure: {:.2} | GBuf: {:.1}MB",
2128 s.frame_number,
2129 s.total_time_us as f64 / 1000.0,
2130 s.total_draw_calls,
2131 s.total_triangles,
2132 s.items_visible,
2133 s.items_submitted,
2134 s.exposure,
2135 s.gbuffer_memory_mb,
2136 )
2137 }
2138}
2139
2140impl Default for DeferredPipeline {
2141 fn default() -> Self {
2142 Self::new(1920, 1080)
2143 }
2144}
2145
2146#[cfg(test)]
2151mod tests {
2152 use super::*;
2153
2154 #[test]
2155 fn test_render_queue_sorting() {
2156 let mut queue = RenderQueue::new();
2157 queue.set_camera(
2159 [0.0, 0.0, 0.0],
2160 [[0.0, 0.0, 1.0, 1000.0]; 6],
2161 );
2162
2163 let item1 = RenderItem::new(0, 1, 0).with_bounds([0.0, 0.0, 10.0], 1.0);
2164 let item2 = RenderItem::new(0, 2, 0).with_bounds([0.0, 0.0, 5.0], 1.0);
2165 let item3 = RenderItem::new(0, 3, 0).with_bounds([0.0, 0.0, 20.0], 1.0);
2166
2167 queue.submit(item1);
2168 queue.submit(item2);
2169 queue.submit(item3);
2170 queue.sort();
2171
2172 let opaque = queue.opaque_items();
2173 assert_eq!(opaque.len(), 3);
2174 assert!(opaque[0].camera_distance <= opaque[1].camera_distance);
2176 assert!(opaque[1].camera_distance <= opaque[2].camera_distance);
2177 }
2178
2179 #[test]
2180 fn test_render_queue_transparent_sorting() {
2181 let mut queue = RenderQueue::new();
2182 queue.set_camera(
2183 [0.0, 0.0, 0.0],
2184 [[0.0, 0.0, 1.0, 1000.0]; 6],
2185 );
2186
2187 let item1 = RenderItem::new(0, 1, 0)
2188 .with_bucket(RenderBucket::Transparent)
2189 .with_bounds([0.0, 0.0, 10.0], 1.0);
2190 let item2 = RenderItem::new(0, 2, 0)
2191 .with_bucket(RenderBucket::Transparent)
2192 .with_bounds([0.0, 0.0, 5.0], 1.0);
2193
2194 queue.submit(item1);
2195 queue.submit(item2);
2196 queue.sort();
2197
2198 let transparent = queue.transparent_items();
2199 assert_eq!(transparent.len(), 2);
2200 assert!(transparent[0].camera_distance >= transparent[1].camera_distance);
2202 }
2203
2204 #[test]
2205 fn test_light_evaluation() {
2206 let light = LightType::Directional {
2207 direction: [0.0, -1.0, 0.0],
2208 color: [1.0, 1.0, 1.0],
2209 intensity: 1.0,
2210 cast_shadows: false,
2211 };
2212 let (dir, att, _color) = light.evaluate([0.0, 0.0, 0.0]);
2213 assert!((dir[1] - 1.0).abs() < 0.001); assert!((att - 1.0).abs() < 0.001);
2215 }
2216
2217 #[test]
2218 fn test_point_light_attenuation() {
2219 let light = LightType::Point {
2220 position: [0.0, 5.0, 0.0],
2221 color: [1.0, 1.0, 1.0],
2222 intensity: 10.0,
2223 range: 20.0,
2224 cast_shadows: false,
2225 };
2226 let (_, att_near, _) = light.evaluate([0.0, 4.0, 0.0]);
2227 let (_, att_far, _) = light.evaluate([0.0, -10.0, 0.0]);
2228 assert!(att_near > att_far, "Near attenuation should be greater");
2229 }
2230
2231 #[test]
2232 fn test_tone_mapping() {
2233 let color = [2.0, 1.0, 0.5];
2234 let reinhard = ToneMappingOperator::Reinhard.apply(color, 1.0);
2235 for c in &reinhard {
2236 assert!(*c >= 0.0 && *c <= 1.0);
2237 }
2238 let aces = ToneMappingOperator::AcesFilmic.apply(color, 1.0);
2239 for c in &aces {
2240 assert!(*c >= 0.0 && *c <= 1.0);
2241 }
2242 }
2243
2244 #[test]
2245 fn test_exposure_controller() {
2246 let mut ec = ExposureController::new();
2247 ec.mode = ExposureMode::AverageLuminance;
2248 ec.feed_luminance(0.5);
2249 ec.update(0.016);
2250 assert!(ec.exposure > 0.0);
2251 }
2252
2253 #[test]
2254 fn test_pipeline_creation() {
2255 let mut pipeline = DeferredPipeline::new(1920, 1080);
2256 assert!(!pipeline.initialized);
2257 pipeline.initialize().unwrap();
2258 assert!(pipeline.initialized);
2259 }
2260
2261 #[test]
2262 fn test_pipeline_frame() {
2263 let mut pipeline = DeferredPipeline::new(800, 600);
2264 pipeline.initialize().unwrap();
2265
2266 pipeline.lighting_pass.add_light(LightType::Directional {
2267 direction: [0.0, -1.0, 0.0],
2268 color: [1.0, 1.0, 1.0],
2269 intensity: 1.0,
2270 cast_shadows: false,
2271 });
2272
2273 pipeline.set_camera(
2274 [0.0, 5.0, 10.0],
2275 Mat4::IDENTITY,
2276 Mat4::IDENTITY,
2277 [[0.0, 0.0, 1.0, 1000.0]; 6],
2278 );
2279
2280 let item = RenderItem::new(0, 1, 0)
2281 .with_bounds([0.0, 0.0, 0.0], 5.0);
2282 pipeline.submit(item);
2283
2284 pipeline.execute_frame(0.016);
2285 assert_eq!(pipeline.frame_stats.frame_number, 1);
2286 }
2287
2288 #[test]
2289 fn test_hdr_framebuffer() {
2290 let mut fb = HdrFramebuffer::new(1920, 1080);
2291 fb.create().unwrap();
2292 assert!(fb.allocated);
2293 assert!(fb.memory_bytes() > 0);
2294 fb.resize(2560, 1440);
2295 assert_eq!(fb.width, 2560);
2296 }
2297
2298 #[test]
2299 fn test_bloom_extract() {
2300 let pp = PostProcessPass::new();
2301 let bright = pp.bloom_extract([2.0, 2.0, 2.0]);
2302 assert!(bright[0] > 0.0);
2303 let dark = pp.bloom_extract([0.1, 0.1, 0.1]);
2304 assert!((dark[0] - 0.0).abs() < 0.001);
2305 }
2306
2307 #[test]
2308 fn test_pbr_lighting() {
2309 let mut lp = LightingPass::new();
2310 lp.add_light(LightType::Directional {
2311 direction: [0.0, -1.0, 0.0],
2312 color: [1.0, 1.0, 1.0],
2313 intensity: 2.0,
2314 cast_shadows: false,
2315 });
2316
2317 let result = lp.evaluate_pbr(
2318 [0.0, 0.0, 0.0],
2319 [0.0, 1.0, 0.0],
2320 [0.8, 0.2, 0.2],
2321 0.5,
2322 0.0,
2323 [0.0, 5.0, 5.0],
2324 );
2325
2326 assert!(result[0] > 0.0);
2327 assert!(result[1] > 0.0);
2328 assert!(result[2] > 0.0);
2329 }
2330}