Skip to content

Instantly share code, notes, and snippets.

@Arbow
Created August 14, 2010 05:43
Show Gist options
  • Save Arbow/524032 to your computer and use it in GitHub Desktop.
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
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
}
}
@jon-hanson
Copy link

Lines 198 & 199:

                val i = (h - y - 1) * w + x
                c(i) = Vec.Zero 

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment