Last active
May 17, 2024 10:53
-
-
Save Stagyrite/3d99f58594527572a95560dabe92059e to your computer and use it in GitHub Desktop.
a raytracer inspired by small-raytracer
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
/* | |
* a raytracer inspired by small-raytracer | |
* https://github.com/FrontierPsychiatrist/small-raytracer/ | |
*/ | |
import javax.imageio.ImageIO; | |
import java.awt.image.BufferedImage; | |
import java.io.File; | |
import java.io.IOException; | |
/** | |
* A very small and very incapable raytracer. | |
* It contains a hard-coded scene (Cornell box without spheres) and outputs to a file called "test.bmp". | |
*/ | |
class SmallRayTracer { | |
private static final double EPSILON = 0.000000001; | |
public static void main(String[] args) throws IOException { | |
var rayTracing = new RayTracing(); | |
var img = new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB); | |
for (var x = 0; x < img.getWidth(); x++) { | |
for (var y = 0; y < img.getHeight(); y++) { | |
img.setRGB(x, y, rayTracing.render(x, y)); | |
} | |
} | |
File file = new File("test.bmp"); | |
ImageIO.write(img, "bmp", file); | |
} | |
private static class Color { | |
double red; | |
double green; | |
double blue; | |
public Color(Color other) { | |
super(); | |
this.red = other.red; | |
this.green = other.green; | |
this.blue = other.blue; | |
} | |
public Color(double red, double green, double blue) { | |
super(); | |
this.red = red; | |
this.green = green; | |
this.blue = blue; | |
} | |
void normalizeColor() { | |
var factor = 255 / Math.max(Math.max(red, green), blue); | |
if (factor < 1) { | |
red *= factor; | |
green *= factor; | |
blue *= factor; | |
} | |
} | |
int getRGB() { | |
var redChar = (char) red; | |
var greenChar = (char) green; | |
var blueChar = (char) blue; | |
return (0xFF << 24) | | |
((redChar & 0xFF) << 16) | | |
((greenChar & 0xFF) << 8) | | |
(blueChar & 0xFF); | |
} | |
void scale(double scale) { | |
red *= scale; | |
green *= scale; | |
blue *= scale; | |
} | |
} | |
private static class Intersection { | |
Point point; | |
double lambda; | |
final Plane plane; | |
Intersection(Point point, Plane plane) { | |
super(); | |
this.point = point; | |
this.plane = plane; | |
} | |
} | |
private static class Plane { | |
final Vector normal; | |
final double distance = 5; | |
final Color color; | |
Plane(Vector normal, Color color) { | |
super(); | |
this.normal = normal; | |
this.color = color; | |
} | |
} | |
private static class Point extends Vector { | |
Point(double x, double y, double z) { | |
super(x, y, z); | |
} | |
} | |
private static class Ray { | |
final Point origin; | |
final Vector direction; | |
Ray(int x, int y) { | |
this(new Point(0, 0, 0), | |
new Vector(-320 + x + 0.5, 240 - y + 0.5, -120)); | |
} | |
Ray(Point origin, Vector direction) { | |
super(); | |
this.origin = origin; | |
this.direction = direction; | |
} | |
Intersection getFirstIntersection(Plane[] box) { | |
Intersection nearestIntersect = null; | |
for (var plane : box) { | |
var intersect = rayHitsPlane(plane); | |
var found = nearestIntersect == null || nearestIntersect.lambda > intersect.lambda; | |
if (intersect.lambda > EPSILON && found) { | |
nearestIntersect = intersect; | |
} | |
} | |
return nearestIntersect; | |
} | |
Intersection rayHitsPlane(Plane p) { | |
var gamma = direction.scalarProduct(p.normal); | |
var side = p.distance - p.normal.scalarProduct(origin); | |
var intersect = new Intersection(new Point(0, 0, 0), p); | |
if (gamma * side > EPSILON) { | |
var lambda = side / gamma; | |
intersect.point = new Point( | |
origin.x + direction.x * lambda, | |
origin.y + direction.y * lambda, | |
origin.z + direction.z * lambda | |
); | |
intersect.lambda = lambda; | |
} | |
return intersect; | |
} | |
Color traceRay(Plane[] box) { | |
var color = new Color(0, 0, 0); | |
var light = new Point(0.5, 2, -3.5); | |
var intersect = getFirstIntersection(box); | |
if (intersect.lambda > EPSILON) { | |
var directionToLight = light.pointDifference(intersect.point); | |
var rayToLight = new Ray(intersect.point, directionToLight); | |
var tempIntersect = rayToLight.getFirstIntersection(box); | |
if (tempIntersect.lambda >= 1 || tempIntersect.lambda == 0) { | |
// This condition is always true. | |
var length = directionToLight.vectorLength(); | |
var scale = 5 / (length * length); | |
color = new Color(intersect.plane.color); | |
color.scale(scale); | |
} | |
} | |
return color; | |
} | |
} | |
private static class RayTracing { | |
/** | |
* a Cornell box | |
*/ | |
final Plane[] box = { | |
new Plane(new Vector(-1, 0, 0), new Color(255, 0, 100)), // links | |
new Plane(new Vector(1, 0, 0), new Color(0, 255, 0)), // rechts | |
new Plane(new Vector(0, 0, -1), new Color(255, 255, 255)), // hinten | |
new Plane(new Vector(0, 1, 0), new Color(255, 255, 255)), // oben | |
new Plane(new Vector(0, -1, 0), new Color(255, 255, 255)) // unten | |
}; | |
/** | |
* Renders an sRGB color. | |
* | |
* @param x the x position | |
* @param y the y position | |
* @return the sRGB color | |
*/ | |
int render(int x, int y) { | |
var r = new Ray(x, y); | |
var c = r.traceRay(box); | |
c.normalizeColor(); | |
return c.getRGB(); | |
} | |
} | |
private static class Vector { | |
final double x; | |
final double y; | |
final double z; | |
Vector(double x, double y, double z) { | |
super(); | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
Vector pointDifference(Point p2) { | |
return new Vector(x - p2.x, y - p2.y, z - p2.z); | |
} | |
double scalarProduct(Vector v2) { | |
return x * v2.x + y * v2.y + z * v2.z; | |
} | |
double vectorLength() { | |
return Math.sqrt(x * x + y * y + z * z); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's an output image converted to PNG.
![test](https://private-user-images.githubusercontent.com/55529808/325635807-0a1c7286-32c8-41b5-b098-e06bd6ab9b0f.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjAzNDgzMjksIm5iZiI6MTcyMDM0ODAyOSwicGF0aCI6Ii81NTUyOTgwOC8zMjU2MzU4MDctMGExYzcyODYtMzJjOC00MWI1LWIwOTgtZTA2YmQ2YWI5YjBmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA3MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNzA3VDEwMjcwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWVmYjQ3NTE4ZGJmMWQ0OWIwMDEyMDQzYWY1MzQ3OGU1MDEyZDgwYzUzZGY4MzU0MzMyNDc5ZjdkMTcxMjliZGMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.thiG8m-uW6HPO8nj-U93WT9w6g9fjsKFwVzbP9uSads)
Check out a GitHub repository for the source code in C.