diff --git a/README.md b/README.md
index 86a921b..ddad3a0 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,20 @@
-# processing
\ No newline at end of file
+# Path tracer
+
+## Render Gallery
+
+**All render frames converged within 0.5s on a gtx 1080**
+**NOTE: if the fps is abysmal on your machine, you may need to lower the SPF located in the fragment shader**
+
+
+
+
+
+
+
+## Some earlier renders (images are very noisy)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Runner/Buffer.pde b/Runner/Buffer.pde
new file mode 100644
index 0000000..ac6f84e
--- /dev/null
+++ b/Runner/Buffer.pde
@@ -0,0 +1,38 @@
+import java.nio.*;
+import com.jogamp.opengl.GL4;
+
+public class Buffer {
+ int id;
+
+ Buffer() {}
+
+ void create() {
+ int ids[] = new int[1];
+ gl.glCreateBuffers(1, ids, 0);
+ id = ids[0];
+ }
+
+ void delete() {
+ int ids[] = new int[1];
+ ids[0] = id;
+ gl.glDeleteBuffers(1, ids, 0);
+ }
+
+ void init(long size, java.nio.Buffer data, int target, int usage) {
+ bind(target);
+ gl.glBufferData(target, size, data, usage);
+ unbind(target);
+ }
+
+ void update(long offset, long size, java.nio.Buffer data) { // not supported by PJOGL
+ gl.glNamedBufferSubData(id, offset, size, data);
+ }
+
+ void bind(int target) {
+ gl.glBindBuffer(target, id);
+ }
+
+ void unbind(int target){
+ gl.glBindBuffer(target, 0);
+ }
+}
diff --git a/Runner/Camera.pde b/Runner/Camera.pde
new file mode 100644
index 0000000..c34eb12
--- /dev/null
+++ b/Runner/Camera.pde
@@ -0,0 +1,105 @@
+public class Camera {
+ float yaw = 0;
+ float pitch = 0;
+ float prevX = screenSize[0]/2;
+ float prevY = screenSize[1]/2;
+
+ vec3 pos;
+ vec3 dir;
+ vec3 up;
+
+ mat4 view, invView;
+ mat4 proj, invProj;
+ final float rotSpeed = 0.05f;
+ final float moveSpeed = 0.010f;
+
+ int shaderProjLoc, shaderViewLoc, invProjLoc, invViewLoc;
+ boolean wKey = false, sKey = false, aKey = false, dKey = false, spaceKey = false, shiftKey = false, isActive = true;
+
+ public Camera(float posx, float posy, float posz, float dirx, float diry, float dirz) {
+ proj = MMath.perspective(MMath.radians(45.0f), (float)screenSize[0] / (float)screenSize[1], 0.1, 2000.0f);
+ invProj = MMath.inverse(proj);
+ pos = new vec3(posx, posy, posz);
+ dir = new vec3(dirx, diry, dirz);
+ up = new vec3(0, 1, 0);
+ view = MMath.lookAt(pos, dir, up);
+ win = (GLWindow)surface.getNative();
+ }
+
+ public void mouseCallback() {
+ if(!isActive) return;
+
+ float dx = prevX-mouseX;
+ float dy = prevY-mouseY;
+ prevX = mouseX;
+ prevY = mouseY;
+ yaw += dx * rotSpeed;
+ pitch += dy * rotSpeed;
+ if(pitch > 89.0)
+ pitch = 89.0;
+ if (pitch < -89.0)
+ pitch = -89.0;
+
+ dir = MMath.normalize(new vec3(sin(MMath.radians(yaw)) * cos(MMath.radians(pitch)),
+ sin(MMath.radians(pitch)),
+ cos(MMath.radians(yaw)) * cos(MMath.radians(pitch))));
+
+ prevX = screenSize[0]/2.0; // recenter mouse
+ prevY = screenSize[1]/2.0;
+ win.warpPointer((int) prevX, (int) prevY);
+ }
+
+ public boolean setKbState(final int _keyCode, final char _key, final boolean state) {
+ if(!isActive) return state;
+ switch(_key) {
+ case 'w':
+ case 'W':
+ return wKey = state; // forward
+ case 's':
+ case 'S':
+ return sKey = state; // backward
+ case 'a':
+ case 'A':
+ return aKey = state; // left
+ case 'd':
+ case 'D':
+ return dKey = state; // right
+ case ' ':
+ return spaceKey = state; // up
+ default:
+ switch(_keyCode) {
+ case SHIFT:
+ return shiftKey = state;
+ default:
+ return state;
+ }
+ }
+ }
+
+ public void updatePos(float dt) {
+ if(!isActive) return;
+ if (wKey)
+ pos.add(MMath.mul(dir, moveSpeed * dt));
+ if (sKey)
+ pos.add(MMath.mul(dir, moveSpeed * dt * -1));
+ if (aKey)
+ pos.add(MMath.mul(MMath.normalize(MMath.cross(up, dir)), moveSpeed * dt));
+ if (dKey)
+ pos.add(MMath.mul(MMath.normalize(MMath.cross(up, dir)), moveSpeed * dt * -1));
+ if (spaceKey)
+ pos.add(MMath.mul(MMath.normalize(up), moveSpeed * dt));
+ if (shiftKey)
+ pos.add(MMath.mul(MMath.normalize(up), moveSpeed * dt * -1));
+ }
+
+ void updateUniforms() {
+ mat4 currView = MMath.lookAt(pos, MMath.add(pos, dir), up);
+ if(!view.equals(currView)) { // camera view or position changed
+ screenSize[2] = 0;
+ view.m = currView.m;
+ invView = MMath.inverse(view);
+ gl.glUniformMatrix4fv(shaderViewLoc, 1, false, view.m, 0);
+ gl.glUniformMatrix4fv(invViewLoc, 1, false, invView.m, 0);
+ }
+ }
+}
diff --git a/Runner/Math.pde b/Runner/Math.pde
new file mode 100644
index 0000000..aa7e245
--- /dev/null
+++ b/Runner/Math.pde
@@ -0,0 +1,180 @@
+public static class vec3 {
+ public float x;
+ public float y;
+ public float z;
+
+ public vec3(float _x, float _y, float _z) {
+ x = _x;
+ y = _y;
+ z = _z;
+ }
+
+ public void add(vec3 other) {
+ x += other.x;
+ y += other.y;
+ z += other.z;
+ }
+
+ public String toString() {
+ return "vec3(" + x + ", " + y + ", " + z + ")";
+ }
+}
+
+public static class mat4 {
+ // row-major
+ public float m[];
+
+ public mat4() {
+ //defaults to identity matrix
+ m = new float[16];
+ set(0,0,1);
+ set(1,1,1);
+ set(2,2,1);
+ set(3,3,1);
+ }
+
+ public void zero() {
+ for(int i = 0; i < 16; ++i) {
+ m[i] = 0;
+ }
+ }
+
+ public void set(int row, int col, float val) {
+ m[row*4+col] = val;
+ }
+
+ public float get(int row, int col) {
+ return m[row*4+col];
+ }
+
+ public boolean equals(mat4 other) {
+ for(int i = 0; i < 16; ++i) {
+ if(m[i] != other.m[i]) return false;
+ }
+ return true;
+ }
+}
+
+public static class MMath {
+ public static final float PI = 3.141592;
+ private MMath() {}
+
+ public static vec3 normalize(vec3 vec) {
+ float len = sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);
+ return new vec3(vec.x/len, vec.y/len, vec.z/len);
+ }
+
+ public static vec3 add(vec3 a, vec3 b) { // no operator overloading in java :(
+ return new vec3(a.x+b.x, a.y+b.y, a.z+b.z);
+ }
+
+ public static vec3 sub(vec3 a, vec3 b) {
+ return new vec3(a.x-b.x, a.y-b.y, a.z-b.z);
+ }
+
+ public static vec3 mul(vec3 a, float scalar) {
+ return new vec3(a.x*scalar, a.y*scalar, a.z*scalar);
+ }
+
+ public static float dot(vec3 a, vec3 b) {
+ return a.x*b.x + a.y*b.y + a.z*b.z;
+ }
+
+ public static vec3 cross(vec3 a, vec3 b) {
+ float x = a.y*b.z - a.z*b.y;
+ float y = a.z*b.x - a.x*b.z;
+ float z = a.x*b.y - a.y*b.x;
+ return new vec3(x, y, z);
+ }
+
+ public static float radians(float deg) {
+ return deg * MMath.PI / 180.0f;
+ }
+
+ public static mat4 perspective(float fov, float ratio, float near, float far) {
+ // impl identical to glm's perspective
+ // credit: https://github.com/g-truc/glm/blob/0.9.5/glm/gtc/matrix_transform.inl#L208
+ float halfTanFov = tan(fov/2);
+ mat4 result = new mat4();
+ result.zero();
+ result.set(0,0,1 / (ratio * halfTanFov));
+ result.set(1,1,1 / halfTanFov);
+ result.set(2,2,-(far + near) / (far - near));
+ result.set(2,3,-1);
+ result.set(3,2,-(2 * far * near) / (far - near));
+ return result;
+ }
+
+ public static mat4 lookAt(vec3 pos, vec3 center, vec3 up) {
+ // impl identical to glm's lookAt
+ // credit: https://github.com/g-truc/glm/blob/0.9.5/glm/gtc/matrix_transform.inl#L385
+ vec3 f = normalize(sub(center, pos));
+ vec3 s = normalize(cross(f, up));
+ vec3 u = cross(s, f);
+ mat4 result = new mat4();
+
+ result.set(0,0,s.x);
+ result.set(1,0,s.y);
+ result.set(2,0,s.z);
+ result.set(0,1,u.x);
+ result.set(1,1,u.y);
+ result.set(2,1,u.z);
+ result.set(0,2, -f.x);
+ result.set(1,2, -f.y);
+ result.set(2,2, -f.z);
+ result.set(3,0, -dot(s, pos));
+ result.set(3,1, -dot(u, pos));
+ result.set(3,2,dot(f, pos));
+ return result;
+ }
+
+ public static mat4 inverse(mat4 mat) {
+ // credit to David Moore, https://cgit.freedesktop.org/mesa/glu/tree/src/libutil/project.c
+ float det;
+ mat4 result = new mat4();
+
+ result.m[0] = mat.m[5]*mat.m[10]*mat.m[15] - mat.m[5]*mat.m[11]*mat.m[14] - mat.m[9]*mat.m[6]*mat.m[15]
+ + mat.m[9]*mat.m[7]*mat.m[14] + mat.m[13]*mat.m[6]*mat.m[11] - mat.m[13]*mat.m[7]*mat.m[10];
+ result.m[4] = -mat.m[4]*mat.m[10]*mat.m[15] + mat.m[4]*mat.m[11]*mat.m[14] + mat.m[8]*mat.m[6]*mat.m[15]
+ - mat.m[8]*mat.m[7]*mat.m[14] - mat.m[12]*mat.m[6]*mat.m[11] + mat.m[12]*mat.m[7]*mat.m[10];
+ result.m[8] = mat.m[4]*mat.m[9]*mat.m[15] - mat.m[4]*mat.m[11]*mat.m[13] - mat.m[8]*mat.m[5]*mat.m[15]
+ + mat.m[8]*mat.m[7]*mat.m[13] + mat.m[12]*mat.m[5]*mat.m[11] - mat.m[12]*mat.m[7]*mat.m[9];
+ result.m[12] = -mat.m[4]*mat.m[9]*mat.m[14] + mat.m[4]*mat.m[10]*mat.m[13] + mat.m[8]*mat.m[5]*mat.m[14]
+ - mat.m[8]*mat.m[6]*mat.m[13] - mat.m[12]*mat.m[5]*mat.m[10] + mat.m[12]*mat.m[6]*mat.m[9];
+ result.m[1] = -mat.m[1]*mat.m[10]*mat.m[15] + mat.m[1]*mat.m[11]*mat.m[14] + mat.m[9]*mat.m[2]*mat.m[15]
+ - mat.m[9]*mat.m[3]*mat.m[14] - mat.m[13]*mat.m[2]*mat.m[11] + mat.m[13]*mat.m[3]*mat.m[10];
+ result.m[5] = mat.m[0]*mat.m[10]*mat.m[15] - mat.m[0]*mat.m[11]*mat.m[14] - mat.m[8]*mat.m[2]*mat.m[15]
+ + mat.m[8]*mat.m[3]*mat.m[14] + mat.m[12]*mat.m[2]*mat.m[11] - mat.m[12]*mat.m[3]*mat.m[10];
+ result.m[9] = -mat.m[0]*mat.m[9]*mat.m[15] + mat.m[0]*mat.m[11]*mat.m[13] + mat.m[8]*mat.m[1]*mat.m[15]
+ - mat.m[8]*mat.m[3]*mat.m[13] - mat.m[12]*mat.m[1]*mat.m[11] + mat.m[12]*mat.m[3]*mat.m[9];
+ result.m[13] = mat.m[0]*mat.m[9]*mat.m[14] - mat.m[0]*mat.m[10]*mat.m[13] - mat.m[8]*mat.m[1]*mat.m[14]
+ + mat.m[8]*mat.m[2]*mat.m[13] + mat.m[12]*mat.m[1]*mat.m[10] - mat.m[12]*mat.m[2]*mat.m[9];
+ result.m[2] = mat.m[1]*mat.m[6]*mat.m[15] - mat.m[1]*mat.m[7]*mat.m[14] - mat.m[5]*mat.m[2]*mat.m[15]
+ + mat.m[5]*mat.m[3]*mat.m[14] + mat.m[13]*mat.m[2]*mat.m[7] - mat.m[13]*mat.m[3]*mat.m[6];
+ result.m[6] = -mat.m[0]*mat.m[6]*mat.m[15] + mat.m[0]*mat.m[7]*mat.m[14] + mat.m[4]*mat.m[2]*mat.m[15]
+ - mat.m[4]*mat.m[3]*mat.m[14] - mat.m[12]*mat.m[2]*mat.m[7] + mat.m[12]*mat.m[3]*mat.m[6];
+ result.m[10] = mat.m[0]*mat.m[5]*mat.m[15] - mat.m[0]*mat.m[7]*mat.m[13] - mat.m[4]*mat.m[1]*mat.m[15]
+ + mat.m[4]*mat.m[3]*mat.m[13] + mat.m[12]*mat.m[1]*mat.m[7] - mat.m[12]*mat.m[3]*mat.m[5];
+ result.m[14] = -mat.m[0]*mat.m[5]*mat.m[14] + mat.m[0]*mat.m[6]*mat.m[13] + mat.m[4]*mat.m[1]*mat.m[14]
+ - mat.m[4]*mat.m[2]*mat.m[13] - mat.m[12]*mat.m[1]*mat.m[6] + mat.m[12]*mat.m[2]*mat.m[5];
+ result.m[3] = -mat.m[1]*mat.m[6]*mat.m[11] + mat.m[1]*mat.m[7]*mat.m[10] + mat.m[5]*mat.m[2]*mat.m[11]
+ - mat.m[5]*mat.m[3]*mat.m[10] - mat.m[9]*mat.m[2]*mat.m[7] + mat.m[9]*mat.m[3]*mat.m[6];
+ result.m[7] = mat.m[0]*mat.m[6]*mat.m[11] - mat.m[0]*mat.m[7]*mat.m[10] - mat.m[4]*mat.m[2]*mat.m[11]
+ + mat.m[4]*mat.m[3]*mat.m[10] + mat.m[8]*mat.m[2]*mat.m[7] - mat.m[8]*mat.m[3]*mat.m[6];
+ result.m[11] = -mat.m[0]*mat.m[5]*mat.m[11] + mat.m[0]*mat.m[7]*mat.m[9] + mat.m[4]*mat.m[1]*mat.m[11]
+ - mat.m[4]*mat.m[3]*mat.m[9] - mat.m[8]*mat.m[1]*mat.m[7] + mat.m[8]*mat.m[3]*mat.m[5];
+ result.m[15] = mat.m[0]*mat.m[5]*mat.m[10] - mat.m[0]*mat.m[6]*mat.m[9] - mat.m[4]*mat.m[1]*mat.m[10]
+ + mat.m[4]*mat.m[2]*mat.m[9] + mat.m[8]*mat.m[1]*mat.m[6] - mat.m[8]*mat.m[2]*mat.m[5];
+
+ det = mat.m[0]*result.m[0] + mat.m[1]*result.m[4] + mat.m[2]*result.m[8] + mat.m[3]*result.m[12];
+ det = 1.0 / det;
+
+ if(det == 0) // no inv exists, just crash
+ throw new ArithmeticException("Inverse doesn't exist");
+
+ for (int i = 0; i < 16; i++)
+ result.m[i] *= det;
+
+ return result;
+ }
+}
diff --git a/Runner/Object.pde b/Runner/Object.pde
deleted file mode 100644
index fe8bd18..0000000
--- a/Runner/Object.pde
+++ /dev/null
@@ -1,35 +0,0 @@
-public class MRect
-{
- private int w; // single bar width
- private float xpos; // rect xposition
- private float h; // rect height
- private float ypos ; // rect yposition
- private float d; // single bar distance
- private float t; // number of bars
-
- public MRect(int iw, float ixp, float ih, float iyp, float id, float it) {
- w = iw;
- xpos = ixp;
- h = ih;
- ypos = iyp;
- d = id;
- t = it;
- }
-
- public void move (float posX, float posY, float damping) {
- float dif = ypos - posY;
- if (abs(dif) > 1) {
- ypos -= dif/damping;
- }
- dif = xpos - posX;
- if (abs(dif) > 1) {
- xpos -= dif/damping;
- }
- }
-
- public void display() {
- for (int i=0; i list = new ArrayList();
+ list.add(5);
+ list.add(10);
+ for(int a : list) {
+ System.out.println(a);
+ }
+
+ frameRate(120); // cap to 60fps
+
+ shader = loadShader("frag.glsl", "vert.glsl");
+ quadBuffer = allocateDirectFloatBuffer(quadVerts.length);
+ quadBuffer.rewind();
+ quadBuffer.put(quadVerts);
+ quadBuffer.rewind();
+ win = (GLWindow)surface.getNative();
+ win.confinePointer(true);
+ win.setPointerVisible(false);
+ win.warpPointer((int)screenSize[0]/2, (int)screenSize[1]/2);
+ win.setResizable(false);
+
+ cam = new Camera(0, 0, -4, 0, 0, 1);
+ cam.prevX = mouseX;
+ cam.prevY = mouseY;
+
+ pgl = (PJOGL)beginPGL();
+ gl = pgl.gl.getGL4();
+
+ vao.create();
+ vbo.create();
+ vao.addAttrib(0, 0, 3);
+ vao.addBuffer(vbo, BufferType.VERTEX, Float.BYTES*3);
+ vbo.init(quadVerts.length*Float.BYTES, quadBuffer, GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW);
+ shader.bind();
+
+ screenSizeLoc = gl.glGetUniformLocation(shader.glProgram, "screen_size");
+ cam.shaderProjLoc = gl.glGetUniformLocation(shader.glProgram, "cam_proj");
+ cam.shaderViewLoc = gl.glGetUniformLocation(shader.glProgram, "cam_view");
+ cam.invProjLoc = gl.glGetUniformLocation(shader.glProgram, "inv_cam_proj");
+ cam.invViewLoc = gl.glGetUniformLocation(shader.glProgram, "inv_cam_view");
+ int outImgLoc = gl.glGetUniformLocation(shader.glProgram, "out_img");
+ gl.glUniform3fv(screenSizeLoc, 1, screenSize, 0);
+ gl.glUniformMatrix4fv(cam.shaderProjLoc, 1, false, cam.proj.m, 0);
+ gl.glUniformMatrix4fv(cam.invProjLoc, 1, false, cam.invProj.m, 0);
+
+ int texs[]= new int[1];
+ gl.glCreateTextures(GL.GL_TEXTURE_2D, 1, texs, 0);
+ tex = texs[0];
+ gl.glTextureStorage2D(tex, 1, GL.GL_RGBA32F, (int)screenSize[0], (int)screenSize[1]);
+ gl.glUniform1i(outImgLoc, 0);
+
+ endPGL();
+}
+
+long prev = 0;
+
+void draw() {
+ cam.mouseCallback();
+ double dt = (-prev + (prev = frameRateLastNanos))/1e6d;
+ cam.updatePos((float) dt);
+ cam.updateUniforms();
+ gl.glUniform3fv(screenSizeLoc, 1, screenSize, 0);
+ background(0);
+ shader.bind();
+ gl.glBindTextureUnit(0, tex);
+ gl.glBindImageTexture(0, tex, 0, false, 0, GL4.GL_READ_WRITE, GL.GL_RGBA32F);
+ vao.bind();
+ vbo.bind(GL.GL_ARRAY_BUFFER);
+ gl.glDrawArrays(GL.GL_TRIANGLES, 0, quadVerts.length);
+ surface.setTitle("FPS: " + frameRate);
+ if(screenSize[2] < 1e6) ++screenSize[2];
}
diff --git a/Runner/VertexArray.pde b/Runner/VertexArray.pde
new file mode 100644
index 0000000..8a29f02
--- /dev/null
+++ b/Runner/VertexArray.pde
@@ -0,0 +1,35 @@
+import com.jogamp.opengl.GL4;
+
+public enum BufferType {
+ VERTEX,
+ INDEX,
+}
+
+class VertexArray {
+ int id;
+
+ void create() {
+ int ids[] = new int[1];
+ ids[0] = id;
+ gl.glCreateVertexArrays(1, ids, 0);
+ }
+
+ void addBuffer(Buffer buffer, BufferType type, int stride) {
+ switch(type) {
+ case VERTEX:
+ gl.glVertexArrayVertexBuffer(id, 0, buffer.id, 0, stride);
+ case INDEX:
+ gl.glVertexArrayElementBuffer(id, buffer.id);
+ }
+ }
+
+ void addAttrib(int index, int offset, int count) {
+ gl.glEnableVertexArrayAttrib(id, index);
+ gl.glVertexArrayAttribFormat(id, index, count, GL.GL_FLOAT, false, offset);
+ gl.glVertexArrayAttribBinding(id, index, 0);
+ }
+
+ void bind(){
+ gl.glBindVertexArray(id);
+ }
+}
diff --git a/Runner/code/sketch.properties b/Runner/code/sketch.properties
new file mode 100644
index 0000000..5d0c318
--- /dev/null
+++ b/Runner/code/sketch.properties
@@ -0,0 +1,2 @@
+mode=Java
+mode.id=processing.mode.java.JavaMode
diff --git a/Runner/frag.glsl b/Runner/frag.glsl
new file mode 100644
index 0000000..0f4f43f
--- /dev/null
+++ b/Runner/frag.glsl
@@ -0,0 +1,581 @@
+#version 420 core
+//#extension GL_EXT_control_flow_attributes : require
+#extension GL_EXT_shader_image_load_store : require
+
+out vec4 frag_color;
+
+uniform vec3 screen_size; // z is internal counter
+uniform mat4 inv_cam_proj;
+uniform mat4 inv_cam_view;
+layout(rgba32f) uniform image2D out_img;
+
+#define PI 3.1415926
+#define EPS 1e-3
+#define T_MAX 1e6
+
+struct Material {
+ vec3 albedo;
+ vec3 specular;
+ float spec_chance;
+ float roughness;
+ float emmitence;
+ float ior;
+ uint effect;
+};
+
+struct Ray {
+ vec3 origin;
+ vec3 dir;
+};
+
+struct Sphere {
+ vec3 pos;
+ float r;
+ uint mat;
+};
+
+vec3 LessThan(vec3 f, float value)
+{
+ return vec3(
+ (f.x < value) ? 1.0f : 0.0f,
+ (f.y < value) ? 1.0f : 0.0f,
+ (f.z < value) ? 1.0f : 0.0f);
+}
+
+vec3 LinearToSRGB(vec3 rgb)
+{
+ rgb = clamp(rgb, 0.0f, 1.0f);
+
+ return mix(
+ pow(rgb, vec3(1.0f / 2.4f)) * 1.055f - 0.055f,
+ rgb * 12.92f,
+ LessThan(rgb, 0.0031308f)
+ );
+}
+
+vec3 SRGBToLinear(vec3 rgb)
+{
+ rgb = clamp(rgb, 0.0f, 1.0f);
+
+ return mix(
+ pow(((rgb + 0.055f) / 1.055f), vec3(2.4f)),
+ rgb / 12.92f,
+ LessThan(rgb, 0.04045f)
+ );
+}
+
+uint wang_hash(inout uint seed) {
+ seed = uint(seed ^ uint(61)) ^ uint(seed >> uint(16));
+ seed *= uint(9);
+ seed = seed ^ (seed >> 4);
+ seed *= uint(0x27d4eb2d);
+ seed = seed ^ (seed >> 15);
+ return seed;
+}
+
+// ACES tone mapping curve fit to go from HDR to LDR
+//https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
+vec3 ACESFilm(vec3 x)
+{
+ float a = 2.51f;
+ float b = 0.03f;
+ float c = 2.43f;
+ float d = 0.59f;
+ float e = 0.14f;
+ return clamp((x*(a*x + b)) / (x*(c*x + d) + e), 0.0f, 1.0f);
+}
+
+float rand3D(in vec3 co) {
+ return fract(sin(dot(co.xyz ,vec3(12.9898,78.233,144.7272))) * 43758.5453);
+}
+
+// Generates a seed for a random number generator from 2 inputs plus a backoff
+// https://github.com/nvpro-samples/optix_prime_baking/blob/332a886f1ac46c0b3eea9e89a59593470c755a0e/random.h
+// https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_jitter_cam
+// https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
+uint init_rand_seed(uint val0, uint val1) {
+ uint v0 = val0, v1 = val1, s0 = 0;
+ //[[unroll]] hopefully unrolls automatically
+ for (uint n = 0; n < 16; n++){
+ s0 += 0x9e3779b9;
+ v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4);
+ v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e);
+ }
+
+ return v0;
+}
+
+float rand(inout uint state) {
+ return float(wang_hash(state)) / 4294967296.0;
+}
+
+vec3 rand_vec(inout uint state) {
+ float z = rand(state) * 2.0f - 1.0f;
+ float a = rand(state) * 2 * PI;
+ float r = sqrt(1.0f - z * z);
+ float x = r * cos(a);
+ float y = r * sin(a);
+ return vec3(x, y, z);
+}
+
+// schlick's approximation of the fresnel effect
+float fresnel_coe(float n1, float n2, vec3 normal, vec3 incident, float f0, float f90){
+ float r0 = (n1-n2) / (n1+n2);
+ r0 *= r0;
+ float cosX = -dot(normal, incident);
+ if (n1 > n2) {
+ float n = n1/n2;
+ float sinT2 = n*n*(1.0-cosX*cosX);
+ // Total internal reflection
+ if (sinT2 > 1.0)
+ return f90;
+ cosX = sqrt(1.0-sinT2);
+ }
+ float x = 1.0-cosX;
+ float ret = r0+(1.0-r0)*x*x*x*x*x;
+
+ return mix(f0, f90, ret);
+}
+
+#define NUM_SPHERES 13 // max no.
+#define NUM_MATERIALS 11
+
+Sphere spheres[NUM_SPHERES];
+Material mats[NUM_MATERIALS];
+
+uint sphere_count = NUM_SPHERES;
+float hit_sphere(in Sphere sphere, Ray ray) {
+ float t = -1.0;
+ vec3 rc = ray.origin - sphere.pos;
+ float c = dot(rc, rc) - (sphere.r * sphere.r);
+ float b = dot(ray.dir, rc);
+ float d = b*b - c;
+ if(d > 0.0) {
+ t = -b - sqrt(abs(d));
+ return t;
+ } else {
+ return -1.0;
+ }
+}
+
+float intersect_scene(inout uint sphere, Ray ray) {
+ float mint = T_MAX;
+ sphere = 0;
+ for(int i = 0; i < sphere_count; ++i) {
+ float t = hit_sphere(spheres[i], ray);
+ if(t > 0 && t < mint) {
+ mint = t;
+ sphere = i;
+ }
+ }
+ return (mint >= T_MAX) ? -1 : mint;
+}
+
+Ray gen_ray(inout uint rng_state) {
+ float focal_len = sqrt(screen_size.x*screen_size.x + screen_size.y*screen_size.y) / ( 2*tan( radians(45/2) ) );
+ vec2 jitter = vec2(rand(rng_state), rand(rng_state));
+ vec2 pixel_center = gl_FragCoord.xy + jitter;
+ vec2 uv = pixel_center / screen_size.xy;
+ uv = uv * 2.0 - 1.0;
+ vec4 origin = inv_cam_view * vec4(0,0,0,1);
+ vec4 target = inv_cam_proj * vec4(uv.x, uv.y, focal_len, 1);
+ vec4 dir = inv_cam_view * vec4(normalize(target.xyz), 0);
+ Ray ray;
+ ray.dir = dir.xyz;
+ ray.origin = origin.xyz;
+ return ray;
+}
+
+void get_effect(inout Material mat, in vec3 inter_p) {
+ switch(mat.effect) {
+ case 0:
+ break;
+ case 1:
+ if(rand3D(round(inter_p)) > 0.70) { // pixel light effect
+ mat.emmitence = 0.5;
+ } else {
+ mat.emmitence = 0.075;
+ }
+ break;
+ case 2:
+ if(inter_p.y - floor(inter_p.y) < 0.5) { // stripe light effect
+ mat.emmitence = 0.5;
+ } else {
+ mat.emmitence = 0.2;
+ }
+ default:
+ break;
+ }
+}
+
+// not physically accurate, but still looks good
+vec3 brdf(inout Ray in_ray, Material mat, vec3 normal, inout uint rng_state ){
+ float spec_prob = mat.spec_chance;
+ if(spec_prob > 0) {
+ spec_prob = fresnel_coe(1.0, mat.ior, in_ray.dir, normal, mat.spec_chance, 1.0);
+ }
+ spec_prob = (rand(rng_state) < spec_prob) ? 1.0f : 0.0f;
+ vec3 diff_ray = normalize(normal + rand_vec(rng_state));
+ vec3 spec_ray = reflect(in_ray.dir, normal);
+ spec_ray = normalize(mix(spec_ray, diff_ray, mat.roughness*mat.roughness));
+ in_ray.dir = mix(diff_ray, spec_ray, spec_prob); // glossy outgoing ray
+ return mix(mat.albedo, mat.specular, spec_prob);
+}
+
+vec3 calc_sample(uint max_bounces, inout uint rng_state) {
+ vec3 accumulator = vec3(0);
+ vec3 mask = vec3(1);
+
+ Ray ray = gen_ray(rng_state);
+ uint id;
+ for(int b = 0; b < max_bounces; ++b) {
+ float t = intersect_scene(id, ray);
+ if(t < EPS) break;
+ vec3 inter_p = ray.origin + ray.dir*t;
+
+ Material mat = mats[spheres[id].mat];
+
+ get_effect(mat, inter_p);
+
+ vec3 normal = normalize(inter_p - spheres[id].pos);
+ ray.origin = inter_p;
+
+ vec3 throughput = brdf(ray, mat, normal, rng_state);
+ accumulator += (mask * mat.emmitence * mat.albedo);
+ mask *= throughput;
+
+ // russian roulette
+ float p = max(mask.r, max(mask.g, mask.b));
+ if(rand(rng_state) > p)
+ break;
+ mask *= 1.0/p; // weight samples based on chance of terminating
+ }
+ return accumulator;
+}
+
+#define WHITE_DIFF 0
+#define MIRROR 1
+#define PIXEL_LIGHT 2
+#define RED_DIFF 3
+#define GREEN_DIFF 4
+#define GOLD_SPEC 5
+#define WHITE_SPEC 6
+#define EMITTING_SPEC 7
+#define SILVER_SPEC 8
+#define STRIPED_LIGHT 9
+#define BLUE_DIFF 10
+
+void load_mats() {
+ { // init scene
+ mats[0].albedo = vec3(1);
+ mats[0].emmitence = 0;
+ mats[0].effect = 0;
+ mats[0].specular = vec3(0);
+ mats[0].spec_chance = 0;
+ mats[0].roughness = 0;
+ mats[0].ior = 1;
+
+ mats[1].albedo = vec3(1);
+ mats[1].emmitence = 0;
+ mats[1].effect = 0;
+ mats[1].specular = vec3(1);
+ mats[1].spec_chance = 1.0;
+ mats[1].roughness = 0.05;
+ mats[1].ior = 1;
+
+ mats[2].albedo = vec3(1);
+ mats[2].emmitence = 0.25;
+ mats[2].effect = 1;
+ mats[2].specular = vec3(0);
+ mats[2].spec_chance = 0;
+ mats[2].roughness = 0;
+ mats[2].ior = 1;
+
+ mats[3].albedo = vec3(0.7,0,0);
+ mats[3].emmitence = 0;
+ mats[3].effect = 0;
+ mats[3].specular = vec3(0);
+ mats[3].spec_chance = 0;
+ mats[3].roughness = 0;
+ mats[3].ior = 1;
+
+ mats[4].albedo = vec3(0,0.7,0);
+ mats[4].emmitence = 0;
+ mats[4].effect = 0;
+ mats[4].specular = vec3(0);
+ mats[4].spec_chance = 0;
+ mats[4].roughness = 0;
+ mats[4].ior = 1;
+
+ mats[5].albedo = vec3(212.0/255.0,175.0/255.0,55.0/255.0);
+ mats[5].emmitence = 0;
+ mats[5].effect = 0;
+ mats[5].specular = mats[5].albedo;
+ mats[5].spec_chance = 0.7;
+ mats[5].roughness = 0.5;
+ mats[5].ior = 1;
+
+ mats[6].albedo = vec3(0.7,0.7,0.7);
+ mats[6].emmitence = 0;
+ mats[6].effect = 1;
+ mats[6].specular = vec3(1);
+ mats[6].spec_chance = 0.5;
+ mats[6].roughness = 0.2;
+ mats[6].ior = 1;
+
+ mats[7].albedo = vec3(1);
+ mats[7].emmitence = 0.2;
+ mats[7].effect = 0;
+ mats[7].specular = vec3(1);
+ mats[7].spec_chance = 0.5;
+ mats[7].roughness = 0.2;
+ mats[7].ior = 1;
+
+ mats[8].albedo = vec3(192.0/255.0,192.0/255.0,192.0/255.0);
+ mats[8].emmitence = 0;
+ mats[8].effect = 0;
+ mats[8].specular = mats[8].albedo;
+ mats[8].spec_chance = 0.7;
+ mats[8].roughness = 0.5;
+ mats[8].ior = 1;
+
+ mats[9].albedo = vec3(1);
+ mats[9].emmitence = 0.3;
+ mats[9].effect = 2;
+ mats[9].specular = vec3(0);
+ mats[9].spec_chance = 0;
+ mats[9].roughness = 0;
+ mats[9].ior = 1;
+
+ mats[10].albedo = vec3(0,0,0.7);
+ mats[10].emmitence = 0;
+ mats[10].effect = 0;
+ mats[10].specular = vec3(0);
+ mats[10].spec_chance = 0;
+ mats[10].roughness = 0;
+ mats[10].ior = 1;
+ }
+}
+
+void load_scene1() {
+ const float room_height = 18.0f;
+ const float room_width = 18.0f;
+ const float wall_r = 1000;
+
+ spheres[0].pos = vec3(0, -wall_r, 0); // bottom floor
+ spheres[0].r = wall_r;
+ spheres[0].mat = PIXEL_LIGHT;
+
+ spheres[1].pos = vec3(0, room_height+wall_r, 0); // top ceiling
+ spheres[1].r = wall_r;
+ spheres[1].mat = MIRROR;
+
+ spheres[2].pos = vec3(room_width/2 + wall_r, 0, 0); // side wall
+ spheres[2].r = wall_r;
+ spheres[2].mat = MIRROR;
+
+ spheres[3].pos = vec3(-room_width/2 - wall_r, 0, 0); // side wall
+ spheres[3].r = wall_r;
+ spheres[3].mat = MIRROR;
+
+ spheres[4].pos = vec3(0, 0, room_width/2 + wall_r); // front wall
+ spheres[4].r = wall_r;
+ spheres[4].mat = MIRROR;
+
+ spheres[5].pos = vec3(0, 0, -room_width/2 - wall_r); // back wall
+ spheres[5].r = wall_r;
+ spheres[5].mat = MIRROR;
+
+ spheres[6].pos = vec3(0, 1, 0);
+ spheres[6].r = 1;
+ spheres[6].mat = MIRROR;
+
+ spheres[7].pos = vec3(0, 5, 0);
+ spheres[7].r = 2;
+ spheres[7].mat = MIRROR;
+
+ spheres[8].pos = vec3(0, 9, 0);
+ spheres[8].r = 1;
+ spheres[8].mat = MIRROR;
+
+ spheres[9].pos = vec3(4, 5, 0);
+ spheres[9].r = 1;
+ spheres[9].mat = MIRROR;
+
+ spheres[10].pos = vec3(-4, 5, 0);
+ spheres[10].r = 1;
+ spheres[10].mat = MIRROR;
+
+ spheres[11].pos = vec3(0, 5, 4);
+ spheres[11].r = 1;
+ spheres[11].mat = MIRROR;
+
+ spheres[12].pos = vec3(0, 5, -4);
+ spheres[12].r = 1;
+ spheres[12].mat = MIRROR;
+
+ sphere_count = 13;
+}
+
+void load_scene2() {
+ const float room_height = 15.0f;
+ const float room_width = 15.0f;
+ const float wall_r = 1000;
+
+ mats[2].albedo = vec3(0.5, 0.05, 0.05); // for red light
+
+ spheres[0].pos = vec3(0, -wall_r, 0); // bottom floor
+ spheres[0].r = wall_r;
+ spheres[0].mat = WHITE_DIFF;
+
+ spheres[1].pos = vec3(0, room_height+wall_r, 0); // top ceiling
+ spheres[1].r = wall_r;
+ spheres[1].mat = WHITE_DIFF;
+
+ spheres[2].pos = vec3(room_width/2 + wall_r, 0, 0); // side wall
+ spheres[2].r = wall_r;
+ spheres[2].mat = PIXEL_LIGHT;
+
+ spheres[3].pos = vec3(-room_width/2 - wall_r, 0, 0); // side wall
+ spheres[3].r = wall_r;
+ spheres[3].mat = EMITTING_SPEC;
+
+ spheres[4].pos = vec3(0, 0, room_width/2 + wall_r); // front wall
+ spheres[4].r = wall_r;
+ spheres[4].mat = WHITE_DIFF;
+
+ spheres[5].pos = vec3(0, 0, -room_width/2 - wall_r); // back wall
+ spheres[5].r = wall_r;
+ spheres[5].mat = WHITE_DIFF;
+
+ spheres[6].pos = vec3(0, 1, 4);
+ spheres[6].r = 1;
+ spheres[6].mat = WHITE_DIFF;
+
+ spheres[7].pos = vec3(0, 2, -4);
+ spheres[7].r = 2;
+ spheres[7].mat = WHITE_DIFF;
+
+ sphere_count = 8;
+}
+
+void load_scene3() {
+ const float room_height = 20.0f;
+ const float room_width = 30.0f;
+ const float wall_r = 1000;
+
+ spheres[0].pos = vec3(0, -wall_r, 0); // bottom floor
+ spheres[0].r = wall_r;
+ spheres[0].mat = WHITE_DIFF;
+
+ spheres[1].pos = vec3(0, room_height+wall_r, 0); // top ceiling
+ spheres[1].r = wall_r;
+ spheres[1].mat = WHITE_DIFF;
+
+ spheres[2].pos = vec3(room_width/2 + wall_r, 0, 0); // side wall
+ spheres[2].r = wall_r;
+ spheres[2].mat = PIXEL_LIGHT;
+
+ spheres[3].pos = vec3(-room_width/2 - wall_r, 0, 0); // side wall
+ spheres[3].r = wall_r;
+ spheres[3].mat = GOLD_SPEC;
+
+ spheres[4].pos = vec3(0, 0, room_width/2 + wall_r); // front wall
+ spheres[4].r = wall_r;
+ spheres[4].mat = SILVER_SPEC;
+
+ spheres[5].pos = vec3(0, 0, -room_width/2 - wall_r); // back wall
+ spheres[5].r = wall_r;
+ spheres[5].mat = SILVER_SPEC;
+
+ spheres[6].pos = vec3(0, 2, -8);
+ spheres[6].r = 2;
+ spheres[6].mat = SILVER_SPEC;
+
+ spheres[7].pos = vec3(0, 3, -1);
+ spheres[7].r = 3;
+ spheres[7].mat = GOLD_SPEC;
+
+ spheres[8].pos = vec3(0, 4, 8);
+ spheres[8].r = 4;
+ spheres[8].mat = MIRROR;
+
+ sphere_count = 9;
+}
+
+void load_scene4() {
+ const float room_height = 30.0f;
+ const float room_width = 30.0f;
+ const float wall_r = 1000;
+
+ spheres[0].pos = vec3(0, -wall_r, 0); // bottom floor
+ spheres[0].r = wall_r;
+ spheres[0].mat = RED_DIFF;
+
+ spheres[1].pos = vec3(0, room_height+wall_r, 0); // top ceiling
+ spheres[1].r = wall_r;
+ spheres[1].mat = BLUE_DIFF;
+
+ spheres[2].pos = vec3(room_width/2 + wall_r, 0, 0); // side wall
+ spheres[2].r = wall_r;
+ spheres[2].mat = MIRROR;
+
+ spheres[3].pos = vec3(-room_width/2 - wall_r, 0, 0); // side wall
+ spheres[3].r = wall_r;
+ spheres[3].mat = MIRROR;
+
+ spheres[4].pos = vec3(0, 0, room_width/2 + wall_r); // front wall
+ spheres[4].r = wall_r;
+ spheres[4].mat = STRIPED_LIGHT;
+
+ spheres[5].pos = vec3(0, 0, -room_width/2 - wall_r); // back wall
+ spheres[5].r = wall_r;
+ spheres[5].mat = WHITE_DIFF;
+
+ spheres[6].pos = vec3(0, 4, 0);
+ spheres[6].r = 4;
+ spheres[6].mat = MIRROR;
+
+ spheres[7].pos = vec3(0, 12, 0);
+ spheres[7].r = 4;
+ spheres[7].mat = MIRROR;
+
+ spheres[8].pos = vec3(0, 20, 0);
+ spheres[8].r = 4;
+ spheres[8].mat = MIRROR;
+
+ sphere_count = 9;
+}
+
+void main() {
+ const uint SPF = 15; // sample per frame
+ const uint MAX_DEPTH = 7;
+ const float gamma = 2.2;
+
+ load_mats();
+
+ load_scene1();
+
+ //load_scene2();
+
+ //load_scene3();
+
+ //load_scene4();
+
+ vec3 pixel_color = vec3(0);
+ uint seed = uint(screen_size.z+1)*init_rand_seed(uint(dot(gl_FragCoord.xz, vec2(gl_FragCoord.y))), uint(dot(gl_FragCoord.zy, vec2(gl_FragCoord.x))));
+ for(int s = 0; s < SPF; ++s) {
+ pixel_color += calc_sample(MAX_DEPTH, seed);
+ }
+ pixel_color /= float(SPF);
+
+ vec4 prev_color = imageLoad(out_img, ivec2(gl_FragCoord.xy));
+ vec3 last_frame_color = prev_color.xyz * float(screen_size.z);
+ pixel_color += last_frame_color;
+ pixel_color /= float(screen_size.z+1);
+ imageStore(out_img, ivec2(gl_FragCoord.xy), vec4(pixel_color, 1));
+
+ pixel_color = ACESFilm(pixel_color);
+ pixel_color = LinearToSRGB(pixel_color);
+ frag_color = vec4(pixel_color, 1);
+}
diff --git a/Runner/vert.glsl b/Runner/vert.glsl
new file mode 100644
index 0000000..e9504d9
--- /dev/null
+++ b/Runner/vert.glsl
@@ -0,0 +1,12 @@
+#version 420 core
+
+#extension GL_EXT_shader_image_load_store : require
+
+layout(location = 0) in vec3 pos;
+
+uniform mat4 cam_proj;
+uniform mat4 cam_view;
+
+void main() {
+ gl_Position = vec4(pos.xyz, 1.0);
+}
diff --git a/index.html b/index.html
index 432c007..6aa9991 100644
--- a/index.html
+++ b/index.html
@@ -4,6 +4,6 @@
-
+