Added gpu config flag, completed light sampling methods

This commit is contained in:
Wito Wiala 2026-09-02 09:41:14 +01:00
parent 2448ab890e
commit 0fcfcbd467
18 changed files with 110 additions and 60 deletions

View file

@ -2,4 +2,13 @@ fn main() {
// This allows "spirv" to be used in #[cfg(target_arch = "...")] // This allows "spirv" to be used in #[cfg(target_arch = "...")]
// without triggering a warning. // without triggering a warning.
println!("cargo:rustc-check-cfg=cfg(target_arch, values(\"spirv\"))"); println!("cargo:rustc-check-cfg=cfg(target_arch, values(\"spirv\"))");
// `gpu` is set for every device backend, so host-only code can be gated once
// as #[cfg(not(gpu))] instead of naming each target. Adding a backend means
// editing this line, not 30-odd cfg attributes.
println!("cargo::rustc-check-cfg=cfg(gpu)");
let target = std::env::var("TARGET").unwrap_or_default();
if target.contains("spirv") || target.contains("cuda") {
println!("cargo::rustc-cfg=gpu");
}
} }

View file

@ -24,7 +24,7 @@ pub struct OrthographicCamera {
pub dy_camera: Vector3f, pub dy_camera: Vector3f,
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
impl OrthographicCamera { impl OrthographicCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,

View file

@ -26,7 +26,7 @@ pub struct PerspectiveCamera {
pub cos_total_width: Float, pub cos_total_width: Float,
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
impl PerspectiveCamera { impl PerspectiveCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,

View file

@ -137,7 +137,7 @@ pub trait CameraTrait {
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>; fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
fn get_film(&self) -> &Film { fn get_film(&self) -> &Film {
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
{ {
if self.base().film.is_null() { if self.base().film.is_null() {
panic!( panic!(

View file

@ -96,7 +96,7 @@ impl RGBFilm {
} }
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
{ {
if self.base.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
@ -203,7 +203,7 @@ impl RGBFilm {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(target_os = "cuda", derive(Copy))] #[cfg_attr(gpu, derive(Copy))]
pub struct GBufferPixel { pub struct GBufferPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub weight_sum: AtomicFloat, pub weight_sum: AtomicFloat,
@ -240,7 +240,7 @@ impl Default for GBufferPixel {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(target_os = "cuda", derive(Copy))] #[cfg_attr(gpu, derive(Copy))]
pub struct GBufferFilm { pub struct GBufferFilm {
pub base: FilmBase, pub base: FilmBase,
pub output_from_render: AnimatedTransform, pub output_from_render: AnimatedTransform,
@ -294,7 +294,7 @@ impl GBufferFilm {
} }
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
{ {
if self.base.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
@ -387,7 +387,7 @@ impl GBufferFilm {
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(target_os = "cuda", derive(Copy))] #[cfg_attr(gpu, derive(Copy))]
pub struct SpectralPixel { pub struct SpectralPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub rgb_weight_sum: AtomicFloat, pub rgb_weight_sum: AtomicFloat,
@ -419,7 +419,7 @@ impl Default for SpectralPixel {
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))] #[cfg_attr(gpu, derive(Copy, Clone))]
pub struct SpectralFilm { pub struct SpectralFilm {
pub base: FilmBase, pub base: FilmBase,
pub lambda_min: Float, pub lambda_min: Float,
@ -609,7 +609,7 @@ pub struct FilmBase {
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))] #[cfg_attr(gpu, derive(Copy, Clone))]
pub enum Film { pub enum Film {
RGB(RGBFilm), RGB(RGBFilm),
GBuffer(GBufferFilm), GBuffer(GBufferFilm),

View file

@ -590,7 +590,7 @@ impl SurfaceInteraction {
} }
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
pub fn set_intersection_properties( pub fn set_intersection_properties(
&mut self, &mut self,
mtl: MaterialIdx, mtl: MaterialIdx,

View file

@ -61,7 +61,7 @@ pub struct LightLiSample {
pub p_light: Interaction, pub p_light: Interaction,
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
impl LightLiSample { impl LightLiSample {
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self { pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
Self { Self {
@ -188,7 +188,7 @@ pub struct LightBounds {
pub two_sided: bool, pub two_sided: bool,
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
impl LightBounds { impl LightBounds {
pub fn new( pub fn new(
bounds: &Bounds3f, bounds: &Bounds3f,
@ -327,13 +327,13 @@ pub trait LightTrait {
self.base().light_type self.base().light_type
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn bounds(&self) -> Option<LightBounds>; fn bounds(&self) -> Option<LightBounds>;
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn preprocess(&mut self, scene_bounds: &Bounds3f); fn preprocess(&mut self, scene_bounds: &Bounds3f);
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum; fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
} }

View file

@ -99,7 +99,7 @@ pub struct MajorantGrid {
impl MajorantGrid { impl MajorantGrid {
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
pub fn new(bounds: Bounds3f, res: Point3i) -> Self { pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
let n_voxels = (res.x() * res.y() * res.z()) as usize; let n_voxels = (res.x() * res.y() * res.z()) as usize;
let voxels = gvec_with_capacity(n_voxels); let voxels = gvec_with_capacity(n_voxels);

View file

@ -245,7 +245,7 @@ pub struct PointTransformMapping {
} }
impl PointTransformMapping { impl PointTransformMapping {
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
pub fn new(texture_from_render: Transform) -> Self { pub fn new(texture_from_render: Transform) -> Self {
Self { Self {
texture_from_render, texture_from_render,

View file

@ -130,7 +130,7 @@ impl LightTrait for DiffuseAreaLight {
} }
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut l = SampledSpectrum::new(0.); let mut l = SampledSpectrum::new(0.);
if !self.image.is_null() { if !self.image.is_null() {
@ -153,10 +153,10 @@ impl LightTrait for DiffuseAreaLight {
PI * two_side * self.area * l PI * two_side * self.area * l
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} fn preprocess(&mut self, _scene_bounds: &Bounds3f) {}
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let mut phi = 0.; let mut phi = 0.;
if !self.image.is_null() { if !self.image.is_null() {

View file

@ -1,5 +1,8 @@
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f}; use crate::core::geometry::{
Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike,
};
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
@ -34,12 +37,20 @@ impl LightTrait for GoniometricLight {
fn sample_li( fn sample_li(
&self, &self,
_ctx: &LightSampleContext, ctx: &LightSampleContext,
_u: Point2f, _u: Point2f,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
todo!() let render_from_light = self.base().render_from_light;
let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.));
let wi = (p - ctx.p()).normalize();
let wl = render_from_light.apply_inverse_vector(-wi);
let li = self.i(wl, lambda) / p.distance_squared(ctx.p());
let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }
fn pdf_li( fn pdf_li(
@ -76,7 +87,7 @@ impl LightTrait for GoniometricLight {
)) ))
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let resolution = self.image.resolution(); let resolution = self.image.resolution();
let mut sum_y = 0.; let mut sum_y = 0.;

View file

@ -109,7 +109,7 @@ impl LightTrait for UniformInfiniteLight {
None None
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda) 4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
} }
@ -227,21 +227,17 @@ impl LightTrait for ImageInfiniteLight {
self.image_le(uv, lambda) self.image_le(uv, lambda)
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum_l = SampledSpectrum::new(0.); let mut sum_l = SampledSpectrum::new(0.);
let width = self.image.resolution().x(); let width = self.image.resolution().x();
let height = self.image.resolution().y(); let height = self.image.resolution().y();
for v in 0..height { for v in 0..height {
for u in 0..width { for u in 0..width {
let mut rgb = RGB::default(); let rgb = RGB::from(self.image.get_channels_with_wrap::<3>(
for c in 0..3 {
rgb[c] = self.image.get_channel_with_wrap(
Point2i::new(u, v), Point2i::new(u, v),
c,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); ));
}
sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()) sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero())
.sample(&lambda); .sample(&lambda);
} }
@ -341,7 +337,7 @@ impl PortalInfiniteLight {
(self.portal[1] - self.portal[0]).norm() * (self.portal[3] - self.portal[0]).norm() (self.portal[1] - self.portal[0]).norm() * (self.portal[3] - self.portal[0]).norm()
} }
pub fn render_from_image(portal_frame: Frame, uv: Point2f) -> (Vector3f, Float) { pub fn render_from_image_with(portal_frame: Frame, uv: Point2f) -> (Vector3f, Float) {
let alpha = -PI / 2.0 + uv.x() * PI; let alpha = -PI / 2.0 + uv.x() * PI;
let beta = -PI / 2.0 + uv.y() * PI; let beta = -PI / 2.0 + uv.y() * PI;
@ -354,6 +350,11 @@ impl PortalInfiniteLight {
(portal_frame.from_local(w), duv_dw) (portal_frame.from_local(w), duv_dw)
} }
#[inline]
pub fn render_from_image(&self, uv: Point2f) -> (Vector3f, Float) {
Self::render_from_image_with(self.portal_frame, uv)
}
} }
impl LightTrait for PortalInfiniteLight { impl LightTrait for PortalInfiniteLight {
@ -370,7 +371,7 @@ impl LightTrait for PortalInfiniteLight {
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let b = self.image_bounds(ctx.p())?; let b = self.image_bounds(ctx.p())?;
let (uv, map_pdf) = self.distribution.sample(u, b)?; let (uv, map_pdf) = self.distribution.sample(u, b)?;
let (wi, duv_dw) = Self::render_from_image(self.portal_frame, uv); let (wi, duv_dw) = self.render_from_image(uv);
if duv_dw == 0. { if duv_dw == 0. {
return None; return None;
} }
@ -403,17 +404,34 @@ impl LightTrait for PortalInfiniteLight {
} }
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
todo!() let mut sum_l = SampledSpectrum::new(0.);
let width = self.image.resolution().x();
let height = self.image.resolution().y();
for y in 0..height {
for x in 0..width {
let rgb = RGB::from(self.image.get_channels::<3>(Point2i::new(x, y)));
let st = Point2f::new(
(x as Float + 0.5) / width as Float,
(y as Float + 0.5) / height as Float,
);
let (_, duv_dw) = self.render_from_image(st);
sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero())
.sample(&lambda)
/ duv_dw;
}
} }
#[cfg(not(target_os = "cuda"))] self.scale * self.area() * sum_l / (width * height) as Float
}
#[cfg(not(gpu))]
fn preprocess(&mut self, scene_bounds: &Bounds3f) { fn preprocess(&mut self, scene_bounds: &Bounds3f) {
(self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere(); (self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere();
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
None None
} }

View file

@ -51,14 +51,14 @@ impl LightTrait for PointLight {
0. 0.
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * self.scale * self.i.sample(&lambda) 4. * PI * self.scale * self.i.sample(&lambda)
} }
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} fn preprocess(&mut self, _scene_bounds: &Bounds3f) {}
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let p = self let p = self
.base .base

View file

@ -4,6 +4,7 @@ use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta, Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
@ -33,7 +34,7 @@ pub struct ProjectionLight {
} }
impl ProjectionLight { impl ProjectionLight {
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if w.z() < self.hither { if w.z() < self.hither {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
@ -44,10 +45,10 @@ impl ProjectionLight {
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y()))); let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c as i32); rgb[c] = self.image.lookup_nearest_channel(uv, c);
} }
let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
self.scale * s.sample(&lambda) self.scale * s.sample(lambda)
} }
} }
@ -58,12 +59,23 @@ impl LightTrait for ProjectionLight {
fn sample_li( fn sample_li(
&self, &self,
_ctx: &LightSampleContext, ctx: &LightSampleContext,
_u: Point2f, _u: Point2f,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
todo!() let render_from_light = self.base().render_from_light;
let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.));
let wi = (p - ctx.p()).normalize();
let wl = render_from_light.apply_inverse_vector(-wi);
let li = self.i(wl, lambda) / p.distance_squared(ctx.p());
if li.is_black() {
return None;
}
let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }
fn pdf_li( fn pdf_li(
@ -72,7 +84,7 @@ impl LightTrait for ProjectionLight {
_wi: Vector3f, _wi: Vector3f,
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Float { ) -> Float {
todo!() 0.
} }
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
@ -93,10 +105,10 @@ impl LightTrait for ProjectionLight {
let dwda = cos_theta(w).powi(3); let dwda = cos_theta(w).powi(3);
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32); rgb[c] = self.image.get_channel(Point2i::new(x, y), c);
} }
let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
sum += s.sample(&lambda) * dwda; sum += s.sample(&lambda) * dwda;
} }
} }

View file

@ -65,7 +65,7 @@ impl LightTrait for SpotLight {
0. 0.
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
self.scale self.scale
* self.iemit.sample(&lambda) * self.iemit.sample(&lambda)

View file

@ -30,7 +30,7 @@ pub struct HairMaterial {
} }
impl HairMaterial { impl HairMaterial {
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
sigma_a: Ptr<SpectrumTexture>, sigma_a: Ptr<SpectrumTexture>,

View file

@ -98,7 +98,7 @@ impl BilinearPatchShape {
Some([mesh.n[v0], mesh.n[v1], mesh.n[v2], mesh.n[v3]]) Some([mesh.n[v0], mesh.n[v1], mesh.n[v2], mesh.n[v3]])
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(gpu))]
pub fn new(mesh: Ptr<BilinearPatchMesh>, blp_index: i32) -> Self { pub fn new(mesh: Ptr<BilinearPatchMesh>, blp_index: i32) -> Self {
let mut bp = BilinearPatchShape { let mut bp = BilinearPatchShape {
mesh, mesh,

View file

@ -184,7 +184,7 @@ fn create_portal_light(
// Build distribution // Build distribution
let duv_dw = |p: Point2f| -> Float { let duv_dw = |p: Point2f| -> Float {
let (_, jacobian) = PortalInfiniteLight::render_from_image(portal_frame, p); let (_, jacobian) = PortalInfiniteLight::render_from_image_with(portal_frame, p);
jacobian jacobian
}; };
let d = remapped.get_sampling_distribution( let d = remapped.get_sampling_distribution(
@ -247,7 +247,7 @@ fn remap_image_through_portal(
(y as Float + 0.5) / height as Float, (y as Float + 0.5) / height as Float,
); );
let (w_world, _) = PortalInfiniteLight::render_from_image(*portal_frame, uv); let (w_world, _) = PortalInfiniteLight::render_from_image_with(*portal_frame, uv);
let w_local = render_from_light.apply_inverse_vector(w_world).normalize(); let w_local = render_from_light.apply_inverse_vector(w_world).normalize();
let uv_equi = equal_area_sphere_to_square(w_local); let uv_equi = equal_area_sphere_to_square(w_local);