Ray tracing with Groovy and Java

July 08, 2015

While I was reading the excellent book by Kevin Suffern "Ray tracing from the ground up", I implemented a ray tracer in Java and wrote a DSL in Groovy, to describe scenes more easily and dynamically.

  • migrated the code from C++ to Java
  • made the code "more" object oriented
  • made the code thread-safe for parallel execution
  • wrote a DSL for easy scene creation and manipulation

The following image ...

... is defined by the following DSL:

import net.dinkla.raytracer.colors.RGBColor
import net.dinkla.raytracer.utilities.Resolution

builder.world(id: "World48") {

    viewPlane(resolution: Resolution.RESOLUTION_1080, maxDepth: 2)

    camera(d: 1250, eye: p(0, 0.1, 10), lookAt: p(0, -1, 0))

    ambientLight(color: RGBColor.WHITE, ls: 0.5f)

    lights {
        pointLight(location: p(0, 5, 0), color: c(1, 1, 1), ls: 1)
    }

    materials {
        phong(id: "grey", ks: 1.0, cd: c(0.1, 0.1, 0.1), ka: 0.5, kd: 1.0, exp: 10)
        phong(id: "sky", cd: c(0.1, 0.7, 1.0), ka: 0.75, kd: 1.0)
        reflective(id: "white", ks: 0.7, cd: c(1.0, 1.0, 1.0), ka: 0.5, kd: 0.75, exp: 2)
        phong(id: "red", ks: 0.9, cd: c(0.9, 0.4, 0.1), ka: 0.5, kd: 0.75, exp: 10)
        phong(id: "orange", ks: 0.9, cd: c(0.9, 0.7, 0.1), ka: 0.5, kd: 0.75, exp: 10)
    }

    objects {
        plane(point: p(0,-1.1,0), normal: n(0, 1, 0), material: "white")
        sphere(center: p(2.5, 0.5, 0.5), radius: 0.5, material: "orange")
        triangle(a: p(-3, 0, -1), b: p(-3, -1, 1), c: p(-1, 0, 1), material: "orange")
        smoothTriangle(a: p(-5, 0, -1), b: p(-5, -1, 1), c: p(-3, 0, 1), material: "orange")
        ply(file: "resources/TwoTriangles.ply", material: "red")
        grid {
            triangle(a: p(3, 0, -1), b: p(3, -1, 1), c: p(5, 0, 1), material: "orange")
            sphere(center: p(1.5, 1.5, 1.5), radius: 0.5, material: "sky")
        }
    }
}

There is a white plane, an orange sphere, some triangles, etc. grid is used to store objects in a grid-like data structure to speed up look ups. Behind the scenes the DSL is translated into Java calls. A grid is used also in the following image:

I also implemented ambient occuclusion:

In this image there are three light sources in red, green and blue and the white ambient light. The DSL is:

package lights.ambient

import net.dinkla.raytracer.math.Point3D
import net.dinkla.raytracer.colors.RGBColor
import net.dinkla.raytracer.math.Normal
import net.dinkla.raytracer.objects.acceleration.Grid
import net.dinkla.raytracer.samplers.Sampler
import net.dinkla.raytracer.samplers.MultiJittered
import net.dinkla.raytracer.utilities.Resolution;

def NUM_AMBIENT_SAMPLES = 64

String path = '/opt/rendering/ply'
Grid bunny = builder.ply(file: "\${path}/bunny/bunny16K.ply", multiplier: 2.0, smooth: true)

def sampler = new Sampler(new MultiJittered(), 2500, 100)
sampler.mapSamplesToHemiSphere(1.0)

builder.world(id: "World54") {

    viewPlane(resolution: Resolution.RESOLUTION_1440)

    camera(d: 2000, eye: p(0, 1, 10), lookAt: p(0, 0.75, 0), numThreads: 30 )

    ambientOccluder(minAmount: RGBColor.WHITE, sampler: sampler, numSamples: NUM_AMBIENT_SAMPLES)

    lights {
        pointLight(location: p(-5, 5, 0), color: c(1, 0, 0), ls: 1)
        pointLight(location: p(5, 5, 0), color: c(0, 0, 1), ls: 1)
        pointLight(location: p(5, 5, -15), color: c(0, 1, 0), ls: 1)
    }

    materials {
        phong(id: "yellow", cd: c(1, 1, 0), ka: 0.5, kd: 0.5, ks: 0.25, exp: 4)
        matte(id: "gray", cd: c(1), ka: 0.5, kd: 0.5)
        phong(id: "orange", cd: c(1, 0.5, 0), ka: 0.5, kd: 0.25, ks: 0.25, exp: 20)
        phong(id: "chocolate", cd: c(0.5647, 0.1294, 0), ka: 0.5, kd: 0.25, ks: 0.55, exp: 2)
    }

    objects {
        sphere(material: "yellow", center: p(0, 2, 0), radius: 2)
        sphere(material: "orange", center: p(1, 0.75, 4), radius: 0.75)
        plane(material: "gray", point: Point3D.ORIGIN, normal: Normal.UP)
        instance(object: bunny, material: "chocolate") {
            scale(v(5, 5, 5,))
            translate(v(-1.5, 0, 6))
        }
    }
}

Here an "instantiation" with translation and scaling is used.

One of the big advanges of using a Groovy DSL is, that it is interpreted and runtime and so you can change the source and run it again without recompiling or restarting the program.

In the next example you can see the reflections for some geometric objects.

Examples of rendered images

Download

The code is available at GitHub.