Last active
April 16, 2020 15:42
-
-
Save fritschy/a1171ca5e4a1cd0791f8ed37d6a4b6a8 to your computer and use it in GitHub Desktop.
Not so smallpt rust port...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![allow(non_snake_case)] use std::{collections::HashMap as HM,f64::consts::*,io | |
::Write,ops::*, sync::mpsc::channel, thread::spawn,time::Instant as I};enum S{U( | |
f64,usize,Vec<V3>),F}#[derive(Copy, Clone)]#[repr(C)]struct V3(f64,f64,f64);impl | |
Add for V3{type Output=Self; fn add(self,o:Self)->Self{V3(self.0+o.0,self.1+o.1, | |
self.2+o.2)}}impl Sub for V3{type Output=Self;fn sub(self,o:Self)->Self{V3(self. | |
0-o.0,self.1-o.1,self.2-o.2)}}impl Mul<f64>for V3{type Output=Self;fn mul(self,o | |
:f64)->Self{V3(self.0*o,self.1*o,self.2*o)}} impl Mul for V3{type Output=Self;fn | |
mul(self,o:Self) -> Self{V3(self.0*o.0,self.1*o.1,self.2*o.2)}} impl Rem for V3{ | |
type Output=Self; fn rem(self,o:Self)->Self{V3(self.1*o.2-self.2*o.1,self.2*o.0- | |
self.0*o.2,self.0*o.1-self.1*o.0,)}} impl V3{ fn norm(self)->Self{self*(1./self. | |
dot(self).sqrt())}fn dot(self,o:V3)->f64{self.0*o.0+self.1*o.1+self.2*o.2}fn map | |
(self,f:impl Fn(f64)->f64) -> Self{V3(f(self.0), f(self.1),f(self.2))}}#[derive( | |
Copy,Clone)]struct Ray(V3,V3);enum R{D,S,R} struct Sph{R:f64,p:V3,e:V3,c:V3,r:R} | |
fn rng(x:&mut u32)->f64{*x=*x^*x<<13;*x=*x^*x>>17;*x=*x^*x<<5;*x as f64/std::u32 | |
::MAX as f64}const END:f64=1e20; const E:f64=1e-4;const V3Z:V3=V3(0.,0.,0.);impl | |
Sph{fn intersect(&self,r:&Ray)->f64{let op=self.p-r.0;let b=op.dot(r.1);let det= | |
b*b-op.dot(op)+self.R*self.R;let det=if det<0.{return 0.}else{det.sqrt()};let t= | |
b-det;if t>E{t}else{let t=b+det;if t>E{t}else{0.}}}}const SPHERES:[Sph;9]=[Sph{R | |
:1e5,p:V3(1e5+1.,40.8,81.6),e:V3Z,c: V3(0.75,0.25,0.25),r:R::D},Sph{R:1e5,p:V3(- | |
1e5+99.,40.8,81.6),e:V3Z,c: V3(0.25,0.25,0.75),r: R::D},Sph{R:1e5,p:V3(50.,40.8, | |
1e5),e:V3Z,c:V3(0.75,0.75,0.75),r:R::D},Sph{R:1e5,p:V3(50.,40.8,-1e5+170.),e:V3Z | |
,c:V3Z,r:R::D},Sph{R:1e5,p: V3(50.,1e5,81.6),e:V3Z,c:V3(0.75,0.75,0.75),r:R::D}, | |
Sph{R:1e5,p:V3(50.,-1e5+81.6,81.6),e:V3Z,c:V3(0.75,0.75,0.75),r:R::D},Sph{R:16.5 | |
,p:V3(27.,16.5,47.),e:V3Z,c: V3(0.999,0.999,0.999),r: R::S},Sph{R:16.5,p:V3(73., | |
16.5,78.),e:V3Z,c:V3(0.999,0.999,0.999),r: R::R},Sph{R:600.,p:V3(50.,681.6-0.27, | |
81.6),e:V3(12.,12.,12.),c:V3Z,r:R::D},]; fn clamp(x:f64)->f64{x.max(0.).min(1.)} | |
fn toint(x:f64)->u8{(clamp(x).powf(1./2.2)*255.+0.5)as u8}fn intersect(r:&Ray,t: | |
&mut f64,sph:&mut &Sph)->bool {let x=SPHERES.iter().fold((&SPHERES[0],END),|acc, | |
i|{let nt=i.intersect(r);if nt>0.&&nt<acc.1{(i,nt)}else{acc}});*sph=&x.0;*t=x.1; | |
*t<END}fn rad(r:&Ray,d:i32,Xi:&mut u32)->V3{let(mut t,mut obj)=(0.,&SPHERES[0]); | |
if d>100||!intersect(r,&mut t,&mut obj){return V3Z}let x=r.0+r.1*t;let n=(x-obj. | |
p).norm();let nl=if n.dot(r.1)<0.{n}else{n*-1.};let mut f=obj.c;let(d,p)=(d+1,f. | |
0.max(f.1).max(f.2));if d>5{if rng(Xi)<p{f=f*(1./p)} else{return obj.e}}if let R | |
::D=obj.r{let(r1,r2)=(2.*PI*rng(Xi),rng(Xi));let r2s=r2.sqrt();let w=nl;let u=(( | |
if w.0.abs()>0.1{V3(0., 1., 0.)} else {V3(1.,0.,0.)})%w).norm();let v=w%u;return | |
obj.e+f*rad(&Ray(x,(u*r1.cos()*r2s+v*r1.sin()*r2s+w*(1.-r2).sqrt()).norm()),d,Xi | |
)} else if let R::S=obj.r{ return obj.e+f*rad(&Ray(x,r.1-n*2.*n.dot(r.1)),d,Xi)} | |
let reflRay=Ray(x,r.1-n*2.*n.dot(r.1));let into=n.dot(nl)>0.;let(nc,nt)=(1.,1.5) | |
;let (nnt,ddn)=(if into{nc/nt}else{nt/nc},r.1.dot(nl));let cos2t=1.-nnt*nnt*(1.- | |
ddn*ddn); if cos2t<0.{return obj.e+f*rad(&reflRay,d,Xi)}let tdir=(r.1*nnt-n*((if | |
into{1.}else{-1.})*(ddn*nnt+cos2t.sqrt()))).norm();let(a,b)=(nt-nc,nt+nc);let R0 | |
=a*a/(b*b); let c=1.-if into{-ddn}else{tdir.dot(n)};let Re=R0+(1.-R0)*c.powi(5); | |
let(Tr,P)=(1.-Re,0.25+0.5*Re);let(RP,TP)=(Re/P,Tr/(1.-P));obj.e+f*(if d>2{if rng | |
(Xi)<P{rad(&reflRay,d,Xi)*RP}else{rad(&Ray(x,tdir),d,Xi)*TP}}else{rad(&reflRay,d | |
,Xi)*Re + rad(&Ray(x,tdir), d,Xi)*Tr})}fn main()->std::io::Result<()>{let(w,h)=( | |
1024,768); let mut args = std::env::args(); let ns = args.nth(1). unwrap_or("4". | |
to_string()).parse().unwrap_or(4);let ns=(if ns<4{4}else{ns})/4;let nt=args.next | |
(). unwrap_or("4".to_string()). parse(). unwrap_or(4); let cam=Ray(V3(50.,51.55, | |
295.6), V3(0.,-0.042612,-1.).norm());let cx=V3(w as f64*0.5135/h as f64, 0., 0.) | |
;let cy=(cx%cam.1).norm()*0.5135;let mut Ts=Vec::with_capacity(nt);let mut ts=(0 | |
..nt).map(|x|(x,0.)).collect::<HM<_,_>>();let(stx,srx)=channel();for t in 0..nt{ | |
let stx=stx.clone();Ts.push(spawn(move||{let(mut Xi,rs)=(t as u32+1,|Xi:&mut _|{ | |
let r1=2.*rng(Xi);if r1<1.{r1.sqrt()-1.}else{1.-(2.-r1).sqrt()}});for y in(0..(h | |
+nt-1)/nt).map(|x|x*nt+t).take_while(|x| *x<h){ let mut c=Vec::with_capacity(w); | |
for x in 0..w{let C=[(0,0),(0,1),(1,1),(1,0)].iter().fold(V3Z,|acc,(sx,sy)|{acc+ | |
(0..ns).fold(V3Z,|acc,_|{let(dx,dy)=(rs(&mut Xi),rs(&mut Xi));let d=cx*(((*sx as | |
f64+0.5+dx)/2.+ x as f64)/w as f64-0.5)+ cy*(((*sy as f64+0.5+dy)/2.+y as f64)/h | |
as f64-0.5)+cam.1;acc+rad(&Ray(cam.0+d*140.,d.norm()),0,&mut Xi)*(1./ns as f64)} | |
).map(clamp)});c.push(C*0.25)}stx.send((t,S::U(y as f64/(h-1) as f64,h-y-1,c))). | |
unwrap()}stx.send((t,S::F)).unwrap()}));ts.insert(t, 0.);} let(mut li,mut so,mut | |
ib)=(I::now(),0.,Vec::new());ib.resize(w*h,V3Z); while !ts.is_empty(){match srx. | |
recv().unwrap(){ (t, S::U(pct, sli, sl)) => {(&mut ib [sli * w .. (sli+1) * w]). | |
copy_from_slice(sl.as_slice());ts.entry(t).and_modify(|v|*v=pct);}(t,S::F)=> {so | |
+=1.; ts.remove(&t);}}let now=I::now();if now.duration_since(li).as_millis()>=50 | |
{eprint!("\rRendering ({} spp) {:5.2}%",ns*4,(ts.iter().map(|(_,v)|*v).sum::<f64 | |
>()+so)/nt as f64*100.); li=now}}eprintln!(); for i in Ts{i.join().unwrap ()}let | |
mut f=std::fs::File::create ("image.ppm")?;let mut fb=Vec::with_capacity(w*h*3); | |
f.write_all(format!("P6\n{} {}\n255\n", w, h).as_bytes())?; for i in ib{fb.push( | |
toint(i.0));fb.push(toint(i.1));fb.push(toint(i.2))}f.write_all(fb.as_slice())?; | |
Ok(())} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Based on smallpt by Kevin Beason: http://www.kevinbeason.com/smallpt/ | |
#![allow(non_snake_case, non_camel_case_types)] | |
use std::{ | |
collections::HashMap as Map, f64::consts::*, io::Write, ops::*, sync::mpsc::channel, | |
thread::spawn, time::Instant, | |
}; | |
enum State { | |
Update(f64, usize, Vec<V3>), | |
Finished, | |
} | |
#[derive(Copy, Clone)] | |
#[repr(C)] | |
struct V3(f64, f64, f64); | |
impl Add for V3 { | |
type Output = Self; | |
fn add(self, o: Self) -> Self { V3(self.0 + o.0, self.1 + o.1, self.2 + o.2) } | |
} | |
impl Sub for V3 { | |
type Output = Self; | |
fn sub(self, o: Self) -> Self { V3(self.0 - o.0, self.1 - o.1, self.2 - o.2) } | |
} | |
impl Mul<f64> for V3 { | |
type Output = Self; | |
fn mul(self, o: f64) -> Self { V3(self.0 * o, self.1 * o, self.2 * o) } | |
} | |
impl Mul for V3 { | |
type Output = Self; | |
fn mul(self, o: Self) -> Self { V3(self.0 * o.0, self.1 * o.1, self.2 * o.2) } | |
} | |
impl Rem for V3 { | |
type Output = Self; | |
fn rem(self, o: Self) -> Self { | |
V3( | |
self.1 * o.2 - self.2 * o.1, | |
self.2 * o.0 - self.0 * o.2, | |
self.0 * o.1 - self.1 * o.0, | |
) | |
} | |
} | |
impl V3 { | |
fn norm(self) -> Self { self * (1. / self.dot(self).sqrt()) } | |
fn dot(self, o: V3) -> f64 { self.0 * o.0 + self.1 * o.1 + self.2 * o.2 } | |
fn map(self, f: impl Fn(f64) -> f64) -> Self { V3(f(self.0), f(self.1), f(self.2)) } | |
} | |
#[derive(Copy, Clone)] | |
struct Ray(V3, V3); | |
enum Refl_t { | |
DIFF, | |
SPEC, | |
REFR, | |
} | |
struct Sphere { | |
rad: f64, | |
p: V3, | |
e: V3, | |
c: V3, | |
refl: Refl_t, | |
} | |
fn rng(x: &mut u32) -> f64 { | |
// 32bit xorshift | |
*x = *x ^ *x << 13; | |
*x = *x ^ *x >> 17; | |
*x = *x ^ *x << 5; | |
*x as f64 / std::u32::MAX as f64 | |
} | |
const END: f64 = 1e20; | |
const EPS: f64 = 1e-4; | |
const V3Z: V3 = V3(0., 0., 0.); | |
impl Sphere { | |
fn intersect(&self, r: &Ray) -> f64 { | |
let op = self.p - r.0; | |
let b = op.dot(r.1); | |
let det = b * b - op.dot(op) + self.rad * self.rad; | |
let det = if det < 0. { return 0. } else { det.sqrt() }; | |
let t = b - det; | |
if t > EPS { | |
t | |
} else { | |
let t = b + det; | |
if t > EPS { | |
t | |
} else { | |
0. | |
} | |
} | |
} | |
} | |
const WHITE: V3 = V3(0.75, 0.75, 0.75); | |
const BLUE: V3 = V3(0.25, 0.25, 0.75); | |
const RED: V3 = V3(0.75, 0.25, 0.25); | |
const LIGHT: V3 = V3(0.999, 0.999, 0.999); | |
const BRIGHT: V3= V3(12., 12., 12.); | |
const SPHERES: [Sphere; 9] = [ | |
Sphere { rad: 1e5, p: V3(1e5 + 1., 40.8, 81.6), e: V3Z, c: RED, refl: Refl_t::DIFF, }, | |
Sphere { rad: 1e5, p: V3(-1e5 + 99., 40.8, 81.6), e: V3Z, c: BLUE, refl: Refl_t::DIFF, }, | |
Sphere { rad: 1e5, p: V3(50., 40.8, 1e5), e: V3Z, c: WHITE, refl: Refl_t::DIFF, }, | |
Sphere { rad: 1e5, p: V3(50., 40.8, -1e5 + 170.), e: V3Z, c: V3Z, refl: Refl_t::DIFF, }, | |
Sphere { rad: 1e5, p: V3(50., 1e5, 81.6), e: V3Z, c: WHITE, refl: Refl_t::DIFF, }, | |
Sphere { rad: 1e5, p: V3(50., -1e5 + 81.6, 81.6), e: V3Z, c: WHITE, refl: Refl_t::DIFF, }, | |
Sphere { rad: 16.5, p: V3(27., 16.5, 47.), e: V3Z, c: LIGHT, refl: Refl_t::SPEC, }, | |
Sphere { rad: 16.5, p: V3(73., 16.5, 78.), e: V3Z, c: LIGHT, refl: Refl_t::REFR, }, | |
Sphere { rad: 600., p: V3(50., 681.6 - 0.27, 81.6), e: BRIGHT, c: V3Z, refl: Refl_t::DIFF, }, | |
]; | |
fn clamp(x: f64) -> f64 { x.max(0.0).min(1.0) } | |
fn toint(x: f64) -> u8 { (clamp(x).powf(1. / 2.2) * 255. + 0.5) as u8 } | |
fn intersect(r: &Ray, t: &mut f64, sph: &mut &Sphere) -> bool { | |
let x = SPHERES.iter().fold((&SPHERES[0], END), |acc, i| { | |
let nt = i.intersect(r); | |
if nt > 0. && nt < acc.1 { (i, nt) } else { acc } }); | |
*sph = &x.0; | |
*t = x.1; | |
*t < END | |
} | |
fn radiance(r: &Ray, depth: i32, Xi: &mut u32) -> V3 { | |
let (mut t, mut obj) = (0., &SPHERES[0]); | |
if depth > 100 || !intersect(r, &mut t, &mut obj) { | |
return V3Z; | |
} | |
let x = r.0 + r.1 * t; | |
let n = (x - obj.p).norm(); | |
let nl = if n.dot(r.1) < 0. { n } else { n * -1. }; | |
let mut f = obj.c; | |
let (depth, p) = (depth + 1, f.0.max(f.1).max(f.2)); | |
if depth > 5 { if rng(Xi) < p { f = f * (1. / p) } else { return obj.e } } | |
if let Refl_t::DIFF = obj.refl { | |
let (r1, r2) = (2. * PI * rng(Xi), rng(Xi)); | |
let r2s = r2.sqrt(); | |
let w = nl; | |
let u = ((if w.0.abs() > 0.1 { | |
V3(0., 1., 0.) | |
} else { | |
V3(1., 0., 0.) | |
}) % w) | |
.norm(); | |
let v = w % u; | |
return obj.e + f * radiance(&Ray(x, (u * r1.cos() * r2s | |
+ v * r1.sin() * r2s | |
+ w * (1. - r2).sqrt()).norm()), depth, Xi) | |
} else if let Refl_t::SPEC = obj.refl { | |
return obj.e + f * radiance(&Ray(x, r.1 - n * 2. * n.dot(r.1)), depth, Xi) | |
} | |
let reflRay = Ray(x, r.1 - n * 2. * n.dot(r.1)); | |
let into = n.dot(nl) > 0.; | |
let (nc, nt) = (1., 1.5); | |
let (nnt, ddn) = (if into { nc / nt } else { nt / nc }, r.1.dot(nl)); | |
let cos2t = 1. - nnt * nnt * (1. - ddn * ddn); | |
if cos2t < 0. { return obj.e + f * radiance(&reflRay, depth, Xi); } | |
let tdir = (r.1 * nnt - n * ((if into { 1. } else { -1. }) * (ddn * nnt + cos2t.sqrt()))).norm(); | |
let (a, b) = (nt - nc, nt + nc); | |
let R0 = a * a / (b * b); | |
let c = 1. - if into { -ddn } else { tdir.dot(n) }; | |
let Re = R0 + (1. - R0) * c.powi(5); | |
let (Tr, P) = (1. - Re, 0.25 + 0.5 * Re); | |
let (RP, TP) = (Re / P, Tr / (1. - P)); | |
obj.e + f * (if depth > 2 { if rng(Xi) < P { radiance(&reflRay, depth, Xi) * RP } | |
else { radiance(&Ray(x, tdir), depth, Xi) * TP } } | |
else { radiance(&reflRay, depth, Xi) * Re + radiance(&Ray(x, tdir), depth, Xi) * Tr }) | |
} | |
fn main() -> std::io::Result<()> { | |
let (w, h) = (1024, 768); | |
let mut args = std::env::args(); | |
let num_samps = args.nth(1).unwrap_or("4".to_string()).parse().unwrap_or(4); | |
let num_samps = (if num_samps < 4 { 4 } else { num_samps }) / 4; | |
let num_threads = args.next().unwrap_or("4".to_string()).parse().unwrap_or(4); | |
let cam = Ray(V3(50., 51.55 /*XXX 52*/, 295.6), V3(0., -0.042612, -1.).norm()); | |
let cx = V3(w as f64 * 0.5135 / h as f64, 0., 0.); | |
let cy = (cx % cam.1).norm() * 0.5135; | |
let mut threads = Vec::with_capacity(num_threads); | |
let mut thread_states = (0..num_threads).map(|x| (x, 0.)).collect::<Map<_, _>>(); | |
let (status_tx, status_rx) = channel(); | |
for t in 0..num_threads { | |
let status_tx = status_tx.clone(); | |
threads.push(spawn(move || { | |
let (mut Xi, rs) = (t as u32 + 1, |Xi: &mut _| { | |
let r1 = 2. * rng(Xi); | |
if r1 < 1. { r1.sqrt() - 1. } else { 1. - (2. - r1).sqrt()}}); | |
for y in (0..(h + num_threads - 1) / num_threads) // render only h/num_threads rows... | |
.map(|x| x * num_threads + t) // render ony every num_threads'th row | |
.take_while(|x| *x < h) // fix up larger then allowed... | |
{ | |
let mut row = Vec::with_capacity(w); | |
for x in 0..w { | |
row.push( | |
[(0, 0), (0, 1), (1, 1), (1, 0)] | |
.iter() | |
.fold(V3Z, |acc, (sx, sy)| { | |
acc + (0..num_samps) | |
.fold(V3Z, |acc, _| { | |
let (dx, dy) = (rs(&mut Xi), rs(&mut Xi)); | |
let d = cx * (((*sx as f64 + 0.5 + dx) / 2. + x as f64) / w as f64 - 0.5) | |
+ cy * (((*sy as f64 + 0.5 + dy) / 2. + y as f64) / h as f64 - 0.5) | |
+ cam.1; | |
acc + radiance(&Ray(cam.0 + d * 140., d.norm()), 0, &mut Xi) | |
* (1. / num_samps as f64) | |
}).map(clamp) // clamp here, to not blow out large-valued samples. | |
}) * 0.25); | |
} | |
status_tx.send((t, State::Update(y as f64 / (h - 1) as f64, h - y - 1, row))).unwrap(); | |
} | |
status_tx.send((t, State::Finished)).unwrap(); | |
})); | |
thread_states.insert(t, 0.); | |
} | |
let (mut last_instant, mut sum_offset, mut imgbuf) = (Instant::now(), 0., Vec::new()); | |
imgbuf.resize(w * h, V3Z); | |
while !thread_states.is_empty() { | |
match status_rx.recv().unwrap() { | |
(t, State::Update(pct, sli, sl)) => { | |
(&mut imgbuf[sli * w..(sli + 1) * w]).copy_from_slice(sl.as_slice()); | |
thread_states.entry(t).and_modify(|v| *v = pct); | |
} | |
(t, State::Finished) => { | |
sum_offset += 1.; | |
thread_states.remove(&t); | |
} | |
} | |
let now = Instant::now(); | |
if now.duration_since(last_instant).as_millis() >= 50 { | |
eprint!("\rRendering ({} spp) {:5.2}%", num_samps * 4, | |
(thread_states.iter().map(|(_, v)| *v).sum::<f64>() + sum_offset) / num_threads as f64 * 100.); | |
last_instant = now; | |
} | |
} | |
eprintln!(); | |
for i in threads { i.join().unwrap() } | |
let mut f = std::fs::File::create("image.ppm")?; | |
let mut fb = Vec::with_capacity(w * h * 3); | |
f.write_all(format!("P6\n{} {}\n255\n", w, h).as_bytes())?; | |
for i in imgbuf { fb.push(toint(i.0)); fb.push(toint(i.1)); fb.push(toint(i.2)) } | |
f.write_all(fb.as_slice())?; | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment