Ray-Tracing mit Groovy und Java

Als ich das exzellente Buch von Kevin Suffern "Ray tracing from the ground up" las, implementierte ich einen Ray-Tracer in Java und schrieb eine Groovy DSL, um Scenen einfach und dynamisch zu beschreiben.

Das folgende Bild

Basic Example

wird mit der folgenden Groovy DSL beschrieben:

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")
        }
    }
}

Es gibt eine weisse Ebene, eine orangene Kugel ("sphere"), verschiedene Dreiecke. Mit grid werden die Daten in einer Gitter-Datenstruktur gespeichert, um die Abfragezeiten zu beschleunigen. Hinter der Kulissen werden die Sprachelemente in Java-Code übersetzt und ausgeführt. In folgendem Bild wurden die Daten in einem Grid abgelegt.

ManySpheresWithReflections

Ich habe auch Ambient-Occlusion implementiert, wie im folgenden Bild.

AmbientOcclusionWithBunny

Hier gibt es drei Lichtquellen in blau, rot und grün, sowie das weiße Umgebungslicht. Die DSL sieht folgendermaßen aus:

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))
        }
    }
}

Hier wird auch eine "Instantierung" mit Translation und Skalierung eingesetzt.

Einer der großen Vorteile der DSL ist, das sie interpretiert wird, also pro Laufzeit geändert werden kann, ohne das Programm neu starten zu müssen.

In folgendem Beispiel sieht man die Reflektionen für verschiedene geometrische Objekte

AmbientOcclusionWithBunny

Weitere Beispiele

Download

Der Code ist auf GitHub verfügbar.