Created
August 14, 2010 05:43
-
-
Save Arbow/524032 to your computer and use it in GitHub Desktop.
Scala parallel version smallpt of http://www.cnblogs.com/miloyip/archive/2010/07/07/languages_brawl_GI.html
This file contains hidden or 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
//Scala parallel version smallpt of http://www.cnblogs.com/miloyip/archive/2010/07/07/languages_brawl_GI.html | |
package parallel | |
class RandomLCG(var seed:Long) { | |
def nextDouble(): Double = { | |
seed = (214013L * seed + 2531011L) % 0x100000000L | |
seed * (1.0 / 4294967296.0) | |
} | |
} | |
object Vec { | |
val Zero = Vec(0,0,0) | |
val XAxis = Vec(1,0,0) | |
val YAxis = Vec(0,1,0) | |
val ZAxis = Vec(0,0,1) | |
def apply(x:Double, y:Double, z:Double) = new Vec(x,y,z) | |
} | |
class Vec(val x:Double, val y:Double, val z:Double) { | |
def +(b:Vec) = Vec(x+b.x, y+b.y, z+b.z) | |
def -(b:Vec) = Vec(x-b.x, y-b.y, z-b.z) | |
def *(b:Vec) = Vec(x*b.x, y*b.y, z*b.z) | |
def *(b:Double) = Vec(x*b, y*b, z*b) | |
def norm() = this * (1/Math.sqrt(x*x+y*y+z*z)) | |
def %(b:Vec) = x*b.x + y*b.y + z*b.z | |
def ^(b:Vec) = Vec(y*b.z - z*b.y, z*b.x - x*b.z, x*b.y - y*b.x) | |
} | |
case class Ray(val o:Vec, val d:Vec) | |
object ReflectType extends Enumeration { | |
type ReflectType = Value | |
val DIFF,SPEC,REFR = Value | |
} | |
import ReflectType._ | |
case class Sphere(var rad:Double, var p:Vec, var e:Vec, var c:Vec, refl:ReflectType) { | |
val sqRad = rad * rad | |
val maxC = Math.max(Math.max(c.x, c.y), c.z) | |
val cc = c * (1.0 / maxC) | |
def intersect(r:Ray) = { | |
val op = p - r.o | |
val b = op % r.d | |
val det = b * b - op % op + sqRad | |
val eps = 1e-4 | |
if (det < 0) 1e20 | |
val dets = Math.sqrt(det) | |
if (b - dets > eps) b - dets | |
else if (b + dets > eps) b + dets | |
else 0 | |
} | |
} | |
class IntersectResult(var t:Double, var s:Sphere) { | |
def this() = this(1e20, null) | |
} | |
import scala.actors.Future | |
import scala.actors.Futures._ | |
object SmallPt { | |
val processorCount = Runtime.getRuntime.availableProcessors | |
val random = new ThreadLocal[RandomLCG]() { | |
override def initialValue(): RandomLCG = new RandomLCG(0L) | |
} | |
def rand = random.get.nextDouble | |
val spheres = List( | |
Sphere(1e5, Vec( 1e5+1,40.8,81.6), Vec.Zero, Vec(.75,.25,.25), DIFF),//Left | |
Sphere(1e5, Vec(-1e5+99,40.8,81.6), Vec.Zero, Vec(.25,.25,.75), DIFF),//Rght | |
Sphere(1e5, Vec(50,40.8, 1e5), Vec.Zero, Vec(.75,.75,.75), DIFF),//Back | |
Sphere(1e5, Vec(50,40.8,-1e5+170), Vec.Zero, Vec.Zero, DIFF),//Frnt | |
Sphere(1e5, Vec(50, 1e5, 81.6), Vec.Zero, Vec(.75,.75,.75), DIFF),//Botm | |
Sphere(1e5, Vec(50,-1e5+81.6,81.6), Vec.Zero, Vec(.75,.75,.75), DIFF),//Top | |
Sphere(16.5, Vec(27,16.5,47), Vec.Zero, Vec(1,1,1)*.999, SPEC),//Mirr | |
Sphere(16.5, Vec(73,16.5,78), Vec.Zero, Vec(1,1,1)*.999, REFR),//Glas | |
Sphere(600, Vec(50,681.6-.27,81.6), Vec(12,12,12), Vec.Zero, DIFF) //Lite | |
) | |
def clamp(x:Double) = { | |
if (x<0) 0 | |
else if (x>1) 1 | |
else x | |
} | |
def toInt(x:Double) = (Math.pow(clamp(x), 1 / 2.2) * 255 + .5).toInt | |
def intersect(r:Ray) = { | |
val result = new IntersectResult() | |
spheres.foreach { sphere => | |
val d = sphere.intersect(r) | |
if (d != 0 && d < result.t) { | |
result.t = d | |
result.s = sphere | |
} | |
} | |
result | |
} | |
def radiance(r:Ray, depth:Int): Vec = { | |
val ires = intersect(r) | |
if (ires.s == null) Vec.Zero | |
val obj = ires.s | |
var t = ires.t | |
var newDepth = depth + 1 | |
val isMaxDepth = newDepth > 100 | |
val isUseRR = newDepth > 5; | |
val isRR = isUseRR && rand < obj.maxC; | |
if (isMaxDepth || (isUseRR && !isRR)) obj.e | |
else { | |
val f = if (isUseRR && isRR) obj.cc else obj.c | |
val x = r.o + r.d * t | |
val n = (x - obj.p).norm | |
val nl = if (n % r.d < 0) n else n * -1 | |
obj.refl match { | |
case DIFF => | |
val r1 = 2 * Math.Pi * rand | |
val r2 = rand | |
val r2s = Math.sqrt(r2) | |
val w = nl | |
val wo = if (Math.abs(w.x) > .1) Vec(0, 1, 0) else Vec(1, 0, 0) | |
val u = (wo ^ w).norm | |
val v = w ^ u | |
val d = (u * Math.cos(r1) * r2s + v * Math.sin(r1) * r2s + w * Math.sqrt(1 - r2)).norm | |
obj.e + f * radiance(Ray(x, d), newDepth) | |
case SPEC => | |
obj.e + f * radiance(Ray(x, r.d - n * (2 * (n % r.d))), newDepth) | |
case _ => | |
val reflRay = Ray(x, r.d - n * (2 * (n % r.d))) | |
val into = n % nl > 0 | |
val nc = 1.0 | |
val nt = 1.5 | |
val nnt = if (into) nc / nt else nt / nc | |
val ddn = r.d % nl | |
val cos2t = 1 - nnt * nnt * (1 - ddn * ddn) | |
if (cos2t < 0) | |
obj.e + f * radiance(reflRay, newDepth) | |
else { | |
val tdir = (r.d*nnt - n*((if (into) 1 else -1) * (ddn*nnt + Math.sqrt(cos2t)))).norm | |
val a = nt - nc | |
val b = nt + nc | |
val R0 = a * a / (b * b) | |
val c = 1 - (if(into) -ddn else tdir % n) | |
val Re = R0 + (1 - R0) * c * c * c * c * c | |
val Tr = 1 - Re | |
val P = .25 + .5 * Re | |
val RP = Re / P | |
val TP = Tr / (1 - P) | |
var result:Vec = null | |
if (newDepth > 2) { | |
if (rand < P) result = radiance(reflRay, newDepth) * RP | |
else result = radiance(Ray(x, tdir), newDepth) * TP | |
} else { | |
result = radiance(reflRay, newDepth) * Re + radiance(Ray(x, tdir), newDepth) * Tr | |
} | |
obj.e + f * result | |
} | |
} | |
} | |
} | |
def parallelFor(range:Range)(body: Int => Unit) = { | |
range.grouped(range.size / processorCount).toList.map( { subRange => | |
future {subRange.foreach { body(_) }} | |
} ).foreach(_()) | |
} | |
def main(args : Array[String]) : Unit = { | |
val start = System.currentTimeMillis | |
val w, h = 256 | |
val samps = if (args.length == 1) args(0).toInt / 4 else 25 | |
val cam = Ray(Vec(50, 52, 295.6), Vec(0, -0.042612, -1).norm) | |
val cx = Vec(w * .5135 / h, 0, 0) | |
val cy = (cx ^ cam.d).norm * .5135 | |
val c:Array[Vec] = Array.ofDim(w*h) | |
val progress = new java.util.concurrent.atomic.AtomicInteger() | |
parallelFor (0 to h-1) {y=> | |
// for (y <- 0 to h-1) { | |
printf("\rRendering (%d spp) %5.2f%%", samps * 4, 100.0 * progress.incrementAndGet / h) | |
for (x <- 0 to w-1) { | |
for (sy <- 0 to 1) { | |
val i = (h - y - 1) * w + x | |
c(i) = Vec.Zero | |
for (sx <- 0 to 1) { | |
var r = Vec.Zero | |
for (s <- 0 to samps) { | |
val r1 = 2 * rand | |
val r2 = 2 * rand | |
val dx = if (r1 < 1) Math.sqrt(r1) - 1 else 1 - Math.sqrt(2 - r1) | |
val dy = if (r2 < 1) Math.sqrt(r2) - 1 else 1 - Math.sqrt(2 - r2) | |
val d = cx * (((sx + .5 + dx) / 2 + x) / w - .5) + | |
cy * (((sy + .5 + dy) / 2 + y) / h - .5) + cam.d | |
r = r + radiance(Ray(cam.o + d * 140, d.norm), 0) * (1.0 / samps) | |
} | |
c(i) = c(i) + Vec(clamp(r.x), clamp(r.y), clamp(r.z)) * .25 | |
} | |
} | |
} | |
} | |
printf("\n%f sec\n", (System.currentTimeMillis - start) / 1000.0) | |
import java.io._ | |
val fw = new FileWriter("image.ppm") | |
fw.write("P3\r\n" + w + " " + h + "\r\n255\r\n") | |
for (i <- 0 to w*h-1) { | |
fw.write(toInt(c(i).x) + " " + toInt(c(i).y) + " " + toInt(c(i).z) + "\r\n") | |
} | |
fw.close | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Lines 198 & 199:
Need to be moved outside of the sy loop, i.e. to line 197. As it stands c(i) is being reset to zero half-way through the calc for each pixel, leading to a darker than expected image, and slower convergence.