Last active
September 16, 2024 08:25
-
-
Save chriswhocodes/7009568 to your computer and use it in GitHub Desktop.
Andrew Kensler's business card raytracer converted to Java
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
package com.chrisnewland.raytrace; | |
import java.io.FileOutputStream; | |
/* | |
* Standing on the shoulders of giants. | |
* I didn't invent this raytracer, just converted it from C to Java with the help of this web page by Fabien Sanglard: | |
* http://fabiensanglard.net/rayTracing_back_of_business_card/index.php | |
* The original code is by Andrew Kensler. Comments copied from Fabien Sanglard's notes. | |
*/ | |
public class JavaCard | |
{ | |
// Define a vector class | |
class V3f | |
{ | |
float x, y, z; // Vector has three float attributes. | |
// Empty constructor | |
V3f() | |
{ | |
} | |
// Constructor | |
V3f(float a, float b, float c) | |
{ | |
x = a; | |
y = b; | |
z = c; | |
} | |
// Vector add | |
V3f add(V3f r) | |
{ | |
return new V3f(x + r.x, y + r.y, z + r.z); | |
} | |
// Vector scaling | |
V3f scale(float r) | |
{ | |
return new V3f(x * r, y * r, z * r); | |
} | |
// Vector dot product | |
float dot(V3f r) | |
{ | |
return x * r.x + y * r.y + z * r.z; | |
} | |
// Cross-product | |
V3f cross(V3f r) | |
{ | |
return new V3f(y * r.z - z * r.y, z * r.x - x * r.z, x * r.y - y * r.x); | |
} | |
// Used later for normalizing the vector | |
V3f normalise() | |
{ | |
float factor = (float) (1f / (float) Math.sqrt((float) dot(this))); | |
return scale(factor); | |
} | |
public String toString() | |
{ | |
return x + " " + y + " " + z; | |
} | |
}; | |
int G[] = { 247570, 280596, 280600, 249748, 18578, 18577, 231184, 16, 16 }; | |
// Random generator, return a float within range [0-1] | |
float randomFloat() | |
{ | |
return (float) Math.random(); | |
} | |
// The intersection test for line [o,v]. | |
// Return 2 if a hit was found (and also return distance t and bouncing ray | |
// n). | |
// Return 0 if no hit was found but ray goes upward | |
// Return 1 if no hit was found but ray goes downward | |
// Returns object[] 0 = int (m), 1 = float (t), 2 = Vector3f n | |
Object[] test(V3f o, V3f d, V3f n) | |
{ | |
float t = 1e9f; | |
int m = 0; | |
float p2 = -o.z / d.z; | |
if (.01 < p2) | |
{ | |
t = p2; | |
n = new V3f(0, 0, 1); | |
m = 1; | |
} | |
for (int k = 18; k >= 0; k--) | |
{ | |
for (int j = 8; j >= 0; j--) | |
{ | |
if ((G[j] & (1 << k)) > 0) | |
{ | |
// There is a sphere but does the ray hit it ? | |
V3f p = o.add(new V3f(-k, 0, -j - 4)); | |
float b = p.dot(d); | |
float c = p.dot(p) - 1; | |
float q = b * b - c; | |
// Does the ray hit the sphere ? | |
if (q > 0) | |
{ | |
float s = -b - (float) Math.sqrt(q); | |
if (s < t && s > .01) | |
{ // So far this is the minimum distance, save | |
// it. And // also // compute the bouncing ray | |
// vector into 'n' | |
t = s; | |
n = (p.add(d.scale(t))).normalise(); | |
m = 2; | |
} | |
} | |
} | |
} | |
} | |
return new Object[] { m, t, n }; | |
} | |
// sample the world and return the pixel color for | |
// a ray passing by point o (Origin) and d (Direction) | |
V3f sample(V3f origin, V3f direction) | |
{ | |
V3f n = new V3f(); | |
// Search for an intersection ray Vs World. | |
Object[] result = test(origin, direction, n); | |
int m = (int) result[0]; | |
float t = (float) result[1]; | |
n = (V3f) result[2]; | |
if (m == 0) | |
{ | |
// No sphere found and the ray goes upward: Generate a sky color | |
return new V3f(.7f, .6f, 1f).scale((float) Math.pow(1 - direction.z, 4)); | |
} | |
// A sphere was maybe hit. | |
// h = intersection coordinate | |
V3f h = origin.add(direction.scale(t)); | |
// 'l' = direction to light (with random delta for soft-shadows). | |
V3f l = new V3f(9 + randomFloat(), 9 + randomFloat(), 16); | |
l = l.add(h.scale(-1)); | |
l = l.normalise(); | |
// r = The half-vector | |
V3f r = direction.add(n.scale(n.dot(direction.scale(-2f)))); | |
// Calculated the lambertian factor | |
float b = l.dot(n); | |
// Calculate illumination factor (lambertian coefficient > 0 or in | |
// shadow)? | |
if (b < 0) | |
{ | |
b = 0; | |
} | |
else | |
{ | |
result = test(h, l, n); | |
int res = (int) result[0]; | |
t = (float) result[1]; | |
n = (V3f) result[2]; | |
if (res > 0) | |
{ | |
b = 0; | |
} | |
} | |
// Calculate the color 'p' with diffuse and specular component | |
V3f rdash = r.scale(b > 0 ? 1 : 0); | |
float p = (float) Math.pow(l.dot(rdash), 99); | |
if ((m & 1) == 1) | |
{ | |
// No sphere was hit and the ray was going downward: | |
h = h.scale(0.2f); | |
// Generate a floor color | |
int ceil = (int) (Math.ceil(h.x) + Math.ceil(h.y)); | |
if ((ceil & 1) == 1) | |
{ | |
return new V3f(3, 1, 1).scale(b * .2f + .1f); | |
} | |
else | |
{ | |
return new V3f(3, 3, 3).scale(b * .2f + .1f); | |
} | |
} | |
// m == 2 A sphere was hit. | |
// Cast an ray bouncing from the sphere surface. | |
// Attenuate color by 50% since it is bouncing (* .5) | |
return new V3f(p, p, p).add(sample(h, r).scale(0.5f)); | |
} | |
public JavaCard() throws Exception | |
{ | |
// Camera direction | |
V3f g = new V3f(-6, -16, 0).normalise(); | |
// Camera up vector...Seem Z is pointing up :/ WTF ! | |
V3f a = new V3f(0, 0, 1).cross(g).normalise().scale(.002f); | |
// The right vector, obtained via traditional cross-product | |
V3f b = g.cross(a).normalise().scale(.002f); | |
// WTF ? See https://news.ycombinator.com/item?id=6425965 for more. | |
V3f c = a.add(b).scale(-256).add(g); | |
FileOutputStream fos = new FileOutputStream("java.ppm"); | |
fos.write(new String("P6 512 512 255 ").getBytes()); | |
for (int y = 511; y >= 0; y--) | |
{ | |
// For each column | |
for (int x = 511; x >= 0; x--) | |
{ // For each pixel in a line | |
// Reuse the vector class to store not XYZ but a RGB pixel color | |
// Default pixel color is almost pitch black | |
V3f p = new V3f(13, 13, 13); | |
// Cast 64 rays per pixel (For blur (stochastic sampling) and | |
// soft-shadows. | |
for (int r = 63; r >= 0; r--) | |
{ | |
// The delta to apply to the origin of the view (For Depth | |
// of View blur). | |
// v t = a * (R() - .5) * 99 + b * (R() - .5) * 99; | |
// A little bit of delta up/down and left/right | |
V3f t = a.scale(randomFloat() - 0.5f); | |
t = t.scale(99); | |
V3f t2 = b.scale(randomFloat() - 0.5f); | |
t2 = t2.scale(99); | |
t = t.add(t2); | |
// Set the camera focal point v(17,16,8) and Cast the ray | |
// Accumulate the color returned in the p variable | |
// Ray Direction with random deltas for stochastic sampling | |
V3f dirA = a.scale(randomFloat() + x); | |
V3f dirB = b.scale(randomFloat() + y); | |
V3f dirC = dirA.add(dirB).add(c); | |
V3f dir = t.scale(-1f).add(dirC.scale(16f)).normalise(); | |
// Ray Origin +p for color accumulation | |
p = sample(new V3f(17f, 16f, 8f).add(t), dir).scale(3.5f).add(p); | |
} | |
fos.write((int) p.x); | |
fos.write((int) p.y); | |
fos.write((int) p.z); | |
} | |
} | |
fos.close(); | |
} | |
public static void main(String[] args) throws Exception | |
{ | |
new JavaCard(); | |
} | |
} |
NB - This is my first attempt at conversion. Completely unoptimised and runs about 3x slower than the -O3 C version. Generates way too many new V3f objects so GC pauses may explain the slowness.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looks like Andrew Kensler's raytracer was inspired by one from Paul Heckbert http://www.cs.cmu.edu/~ph/