oxicuda_nerf/rendering/
volume_render.rs1use crate::error::{NerfError, NerfResult};
11
12#[derive(Debug, Clone, Copy, Default)]
16pub struct RenderResult {
17 pub rgb: [f32; 3],
19 pub depth: f32,
21 pub opacity: f32,
23}
24
25pub fn volume_render(sigma: &[f32], color: &[f32], t_vals: &[f32]) -> NerfResult<RenderResult> {
41 let n = sigma.len();
42 if n == 0 {
43 return Err(NerfError::EmptyInput);
44 }
45 if color.len() != n * 3 {
46 return Err(NerfError::DimensionMismatch {
47 expected: n * 3,
48 got: color.len(),
49 });
50 }
51 if t_vals.len() != n {
52 return Err(NerfError::DimensionMismatch {
53 expected: n,
54 got: t_vals.len(),
55 });
56 }
57
58 let mut rgb = [0.0_f32; 3];
59 let mut depth = 0.0_f32;
60 let mut opacity = 0.0_f32;
61 let mut transmittance = 1.0_f32;
62
63 for i in 0..n {
64 let delta = if i + 1 < n {
66 (t_vals[i + 1] - t_vals[i]).max(0.0)
67 } else {
68 1e10_f32
69 };
70
71 let sigma_i = sigma[i].max(0.0);
72 let alpha = 1.0 - (-sigma_i * delta).exp();
73 let weight = transmittance * alpha;
74
75 rgb[0] += weight * color[i * 3];
76 rgb[1] += weight * color[i * 3 + 1];
77 rgb[2] += weight * color[i * 3 + 2];
78 depth += weight * t_vals[i];
79 opacity += weight;
80
81 transmittance *= 1.0 - alpha;
82
83 if transmittance < 1e-4 {
85 break;
86 }
87 }
88
89 Ok(RenderResult {
90 rgb,
91 depth,
92 opacity,
93 })
94}
95
96pub fn volume_render_batch(
112 sigma: &[f32],
113 color: &[f32],
114 t_vals: &[f32],
115 n_rays: usize,
116 n_samples: usize,
117) -> NerfResult<(Vec<f32>, Vec<f32>, Vec<f32>)> {
118 if n_rays == 0 || n_samples == 0 {
119 return Err(NerfError::EmptyInput);
120 }
121 if sigma.len() != n_rays * n_samples {
122 return Err(NerfError::DimensionMismatch {
123 expected: n_rays * n_samples,
124 got: sigma.len(),
125 });
126 }
127 if color.len() != n_rays * n_samples * 3 {
128 return Err(NerfError::DimensionMismatch {
129 expected: n_rays * n_samples * 3,
130 got: color.len(),
131 });
132 }
133 if t_vals.len() != n_rays * n_samples {
134 return Err(NerfError::DimensionMismatch {
135 expected: n_rays * n_samples,
136 got: t_vals.len(),
137 });
138 }
139
140 let mut rgb_out = vec![0.0_f32; n_rays * 3];
141 let mut depth_out = vec![0.0_f32; n_rays];
142 let mut opacity_out = vec![0.0_f32; n_rays];
143
144 for ray_idx in 0..n_rays {
145 let s_off = ray_idx * n_samples;
146 let c_off = ray_idx * n_samples * 3;
147
148 let ray_sigma = &sigma[s_off..s_off + n_samples];
149 let ray_color = &color[c_off..c_off + n_samples * 3];
150 let ray_t = &t_vals[s_off..s_off + n_samples];
151
152 let res = volume_render(ray_sigma, ray_color, ray_t)?;
153
154 rgb_out[ray_idx * 3] = res.rgb[0];
155 rgb_out[ray_idx * 3 + 1] = res.rgb[1];
156 rgb_out[ray_idx * 3 + 2] = res.rgb[2];
157 depth_out[ray_idx] = res.depth;
158 opacity_out[ray_idx] = res.opacity;
159 }
160
161 Ok((rgb_out, depth_out, opacity_out))
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn empty_scene_zero_opacity() {
170 let sigma = vec![0.0_f32; 8];
171 let color = vec![1.0_f32; 24]; let t = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
173 let res = volume_render(&sigma, &color, &t).unwrap();
174 assert!(res.opacity < 1e-6, "zero density → zero opacity");
175 }
176
177 #[test]
178 fn opaque_first_sample() {
179 let mut sigma = vec![0.0_f32; 8];
180 sigma[0] = 1e6_f32; let mut color = vec![0.0_f32; 24];
182 color[0] = 1.0;
184 let t = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
185 let res = volume_render(&sigma, &color, &t).unwrap();
186 assert!(
187 res.rgb[0] > 0.99,
188 "red first sample dominant, got {}",
189 res.rgb[0]
190 );
191 assert!(res.opacity > 0.99, "high opacity, got {}", res.opacity);
192 }
193
194 #[test]
195 fn batch_render_shape() {
196 let n_rays = 4;
197 let n_samp = 8;
198 let sigma = vec![0.1_f32; n_rays * n_samp];
199 let color = vec![0.5_f32; n_rays * n_samp * 3];
200 let t: Vec<f32> = (0..n_rays * n_samp).map(|i| i as f32 * 0.1 + 0.1).collect();
201 let (rgb, depth, opacity) =
202 volume_render_batch(&sigma, &color, &t, n_rays, n_samp).unwrap();
203 assert_eq!(rgb.len(), n_rays * 3);
204 assert_eq!(depth.len(), n_rays);
205 assert_eq!(opacity.len(), n_rays);
206 }
207}