Skip to content

Instantly share code, notes, and snippets.

@chriswhocodes
Last active September 16, 2024 08:25
Show Gist options
  • Save chriswhocodes/7009568 to your computer and use it in GitHub Desktop.
Save chriswhocodes/7009568 to your computer and use it in GitHub Desktop.
Andrew Kensler's business card raytracer converted to Java
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();
}
}
@chriswhocodes
Copy link
Author

Looks like Andrew Kensler's raytracer was inspired by one from Paul Heckbert http://www.cs.cmu.edu/~ph/

@chriswhocodes
Copy link
Author

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