diff --git a/Compute/Compute.vcxproj b/Compute/Compute.vcxproj
new file mode 100644
index 0000000..3bf50d4
--- /dev/null
+++ b/Compute/Compute.vcxproj
@@ -0,0 +1,138 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 15.0
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}
+ Project2
+ 10.0.17763.0
+ GLPathtracing
+
+
+
+ Application
+ true
+ v141
+ MultiByte
+
+
+ Application
+ false
+ v141
+ true
+ MultiByte
+
+
+ Application
+ true
+ v141
+ MultiByte
+
+
+ Application
+ false
+ v141
+ true
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Level3
+ Disabled
+ true
+ true
+
+
+
+
+ Level3
+ Disabled
+ true
+ true
+ C:\glew\include;C:\glfw\include;C:\glm;
+
+
+ C:\glew\lib\Release\x64;C:\glfw\lib-vc2015
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ true
+
+
+ true
+ true
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ true
+ C:\glew\include;C:\glfw\include;C:\glm;
+
+
+ true
+ true
+ C:\glew\lib\Release\x64;C:\glfw\lib-vc2015
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Compute/Compute.vcxproj.filters b/Compute/Compute.vcxproj.filters
new file mode 100644
index 0000000..da04d75
--- /dev/null
+++ b/Compute/Compute.vcxproj.filters
@@ -0,0 +1,38 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+
+
+ Resource Files
+
+
+ Resource Files
+
+
+ Resource Files
+
+
+
\ No newline at end of file
diff --git a/Compute/ComputeShader.glsl b/Compute/ComputeShader.glsl
new file mode 100644
index 0000000..86ce3cd
--- /dev/null
+++ b/Compute/ComputeShader.glsl
@@ -0,0 +1,564 @@
+#version 430 core
+
+const int MULTISAMPLING_RAYS_EMITTED = 4;
+const int NUM_BOUNCES = 10;
+const bool ANTIALIASING = true;
+const bool DEPTH_OF_FIELD = true;
+const bool MOTION_BLUR = true;
+
+const float PI = 3.14159265359;
+const float MAX_SCENE_BOUNDS = 100.0;
+
+const int LAMBERTIAN = 0x00000001;
+const int METAL = 0x00000002;
+const int DIELECTRIC = 0x00000004;
+const int EMISSIVE = 0x00000008;
+
+layout (local_size_x = 16, local_size_y = 8) in;
+layout (rgba32f, binding = 0) uniform image2D framebuffer;
+layout (binding = 1) uniform atomic_uint raysTraced;
+
+uniform float globalTime;
+uniform int frameCount;
+uniform vec3 eye;
+uniform vec3 ray00;
+uniform vec3 ray01;
+uniform vec3 ray10;
+uniform vec3 ray11;
+uniform mat3 transposeInverseViewMatrix;
+
+float rand(vec2 co) {
+ return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
+}
+
+float noise(vec2 p) {
+ vec2 ip = floor(p);
+ vec2 u = fract(p);
+ u = u * u * (3.0 - 2.0 * u);
+
+ float res = mix(
+ mix(rand(ip), rand(ip + vec2(1.0, 0.0)), u.x),
+ mix(rand(ip + vec2(0.0, 1.0)), rand(ip + vec2(1.0, 1.0)), u.x), u.y);
+ return res * res;
+}
+
+vec3 sphereRand(vec2 co) {
+ vec3 x = vec3(rand(co + 10), rand(co + 20), rand(co + 30));
+ do {
+ x = vec3(rand(x.xy), rand(x.yz), rand(x.xz)) * 2.0 - 1.0;
+ } while (length(x) * length(x) >= 1);
+ return x;
+}
+
+float schlick(float cosine, float refractionIndex) {
+ float r = (1.0 - refractionIndex) / (1.0 + refractionIndex);
+ return mix(pow(1.0 - cosine, 5.0), 1.0, r * r);
+}
+
+struct box {
+ vec3 min;
+ vec3 max;
+ vec3 color;
+
+ int material;
+ float fuzz;
+ float refractionIndex;
+};
+
+struct sphere {
+ vec3 center;
+ float radius;
+ vec3 color;
+
+ int material;
+ float fuzz;
+ float refractionIndex;
+};
+
+struct hitinfo {
+ float intersection;
+ vec3 point;
+ vec3 color;
+ vec3 normal;
+
+ int material;
+ float fuzz;
+ float refractionIndex;
+};
+
+const box boxes[] = {
+ // FLOOR
+ {
+ vec3(-5.0, -0.1, -5.0),
+ vec3(5.0, 0.0, 5.0),
+ vec3(0.9),
+ LAMBERTIAN,
+ float(0.4),
+ float(0.0)
+ },
+ // AXIS INDICATORS
+ {
+ vec3(0.0, 0.0, 0.0),
+ vec3(0.2, 0.2, 0.2),
+ vec3(1.0),
+ LAMBERTIAN,
+ float(0.0),
+ float(0.0)
+ },
+ {
+ vec3(0.0, 0.2, 0.0),
+ vec3(0.2, 1.0, 0.2),
+ vec3(0.0, 1.0, 0.0),
+ LAMBERTIAN,
+ float(0.0),
+ float(0.0)
+ },
+ {
+ vec3(0.2, 0.0, 0.0),
+ vec3(1.0, 0.2, 0.2),
+ vec3(1.0, 0.0, 0.0),
+ LAMBERTIAN,
+ float(0.0),
+ float(0.0)
+ },
+ {
+ vec3(0.0, 0.0, 0.2),
+ vec3(0.2, 0.2, 1.0),
+ vec3(0.0, 0.0, 1.0),
+ LAMBERTIAN,
+ float(0.0),
+ float(0.0)
+ },
+ // LIGHTS
+ {
+ vec3(-5.0, 6.99, -6.0),
+ vec3(5.0, 7.0, -4.0),
+ vec3(1.4),
+ LAMBERTIAN | EMISSIVE,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(-5.0, 6.99, -2.0),
+ vec3(5.0, 7.0, 0.0),
+ vec3(1.4),
+ LAMBERTIAN | EMISSIVE,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(-5.0, 6.99, 2.0),
+ vec3(5.0, 7.0, 4.0),
+ vec3(1.4),
+ LAMBERTIAN | EMISSIVE,
+ float(0.4),
+ float(0.0)
+ },
+ // TABLE
+ {
+ vec3(1.0, 0.0, 1.0),
+ vec3(0.8, 2.0, 0.8),
+ vec3(0.8),
+ METAL,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(-1.0, 0.0, 1.0),
+ vec3(-0.8, 2.0, 0.8),
+ vec3(0.8),
+ METAL,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(1.0, 0.0, -1.0),
+ vec3(0.8, 2.0, -0.8),
+ vec3(0.8),
+ METAL,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(-1.0, 0.0, -1.0),
+ vec3(-0.8, 2.0, -0.8),
+ vec3(0.8),
+ METAL,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(-1.2, 2.0, -1.2),
+ vec3(1.2, 2.2, 1.2),
+ vec3(0.8),
+ METAL,
+ float(0.4),
+ float(0.0)
+ },
+ // WALLS
+ {
+ vec3(-5.0, 0.0, -5.0),
+ vec3(5.0, 3.0, -5.02),
+ vec3(0.8),
+ LAMBERTIAN,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(5.0, 0.0, -5.0),
+ vec3(5.02, 3.0, 5.0),
+ vec3(0.8),
+ LAMBERTIAN,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(5.0, 0.0, 5.0),
+ vec3(-5.0, 3.0, 5.02),
+ vec3(0.8),
+ LAMBERTIAN,
+ float(0.4),
+ float(0.0)
+ },
+ {
+ vec3(-5.02, 0.0, -5.0),
+ vec3(-5.0, 3.0, 5.02),
+ vec3(0.8),
+ LAMBERTIAN,
+ float(0.4),
+ float(0.0)
+ },
+};
+
+const sphere spheres[] = {
+ {
+ vec3(-2.5, 1.0, 3.0),
+ float(1.0),
+ vec3(0.9, 0.3, 1.0),
+ LAMBERTIAN,
+ float(0.0),
+ float(1.0)
+ },
+ {
+ vec3(0.0, 1.0, 3.0),
+ float(1.0),
+ vec3(0.8),
+ METAL,
+ float(0.0),
+ float(1.9)
+ },
+ {
+ vec3(2.5, 1.0, 3.0),
+ float(1.0),
+ vec3(1.0, 0.8, 0.2),
+ DIELECTRIC,
+ float(0.0),
+ float(1.4)
+ }
+};
+
+vec2 subpixels[16] = vec2[](
+ vec2(-1, 1), vec2(-0.33, 1), vec2(0.33, 1), vec2(1, 1),
+ vec2(-1, 0.33), vec2(-0.33, 0.33), vec2(0.33, 0.33), vec2(1, 0.33),
+ vec2(-1, -0.33), vec2(-0.33, -0.33), vec2(0.33, -0.33), vec2(1, -0.33),
+ vec2(-1, -1), vec2(-0.33, -1), vec2(0.33, -1), vec2(1, -1)
+);
+
+bool intersectBox(vec3 origin, vec3 dir, const box b, out float tNear, out vec3 norm) {
+ const float bias = 1.0002;
+ vec3 center = (b.min + b.max) * 0.5;
+ origin += (origin - center) * (1 - bias);
+
+ vec3 invDir = 1.0 / dir;
+ vec3 tMin = (b.min - origin) * invDir;
+ vec3 tMax = (b.max - origin) * invDir;
+ vec3 t1 = min(tMin, tMax);
+ vec3 t2 = max(tMin, tMax);
+ tNear = max(max(t1.x, t1.y), t1.z);
+ float tFar = min(min(t2.x, t2.y), t2.z);
+
+ vec3 p = bias * (origin + tNear * dir - center);
+ vec3 divisor = (b.min - b.max) * 0.5;
+ norm = normalize(vec3(ivec3(p / abs(divisor))));
+
+ return tNear < tFar;
+}
+
+bool intersectBoxes(vec3 origin, vec3 dir, out hitinfo info) {
+ float smallest = MAX_SCENE_BOUNDS;
+ bool found = false;
+ for (int i = 0; i < boxes.length; i++) {
+ vec3 norm;
+ float intersection;
+ if (intersectBox(origin, dir, boxes[i], intersection, norm) &&
+ intersection > 0.0 &&
+ intersection < smallest) {
+ info.intersection = intersection;
+ info.point = origin + intersection * dir;
+ info.normal = norm;
+ info.color = boxes[i].color;
+ info.material = boxes[i].material;
+ info.fuzz = boxes[i].fuzz;
+ info.refractionIndex = boxes[i].refractionIndex;
+ smallest = intersection;
+ found = true;
+ }
+ }
+ return found;
+}
+
+bool intersectSphere(vec3 origin, vec3 dir, const sphere s, out hitinfo hit) {
+ vec3 oc = origin - s.center;
+ float a = dot(dir, dir);
+ float b = 2.0 * dot(oc, dir);
+ float c = dot(oc, oc) - s.radius * s.radius;
+ float det = b*b - 4*a*c;
+ if (det > 0) {
+ float temp = (-b - sqrt(det)) / (2.0 * a);
+ if (temp > 0.0001) {
+ hit.intersection = temp;
+ hit.point = origin + temp * dir;
+ hit.normal = normalize((hit.point - s.center) / s.radius);
+ hit.color = s.color;
+ hit.material = s.material;
+ hit.fuzz = s.fuzz;
+ hit.refractionIndex = s.refractionIndex;
+ return true;
+ }
+ temp = (-b + sqrt(det)) / (2.0 * a);
+ if (temp > 0.0001) {
+ hit.intersection = temp;
+ hit.point = origin + temp * dir;
+ hit.normal = normalize((hit.point - s.center) / s.radius);
+ hit.color = s.color;
+ hit.material = s.material;
+ hit.fuzz = s.fuzz;
+ hit.refractionIndex = s.refractionIndex;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool intersectSpheres(vec3 origin, vec3 dir, out hitinfo info) {
+ float smallest = MAX_SCENE_BOUNDS;
+ bool found = false;
+ hitinfo hit;
+ for (int i = 0; i < spheres.length; i++) {
+ sphere sph = spheres[i];
+ if (MOTION_BLUR && i == 0) {
+ sph.center.z -= rand(vec2(globalTime)) * 0.4;
+ }
+ if (intersectSphere(origin, dir, sph, hit) &&
+ hit.intersection < smallest) {
+ info = hit;
+ smallest = hit.intersection;
+ found = true;
+ }
+ }
+ return found;
+}
+
+vec4 backgroundColor() {
+ return vec4(vec3(0.0), 1.0);
+}
+
+float raytraceShadow(vec3 origin) {
+ hitinfo info, hit;
+ float depth = MAX_SCENE_BOUNDS;
+ vec4 color = backgroundColor();
+ atomicCounterIncrement(raysTraced);
+
+ int light = 0;
+ int lights = 0;
+
+ for (int i = 0; i < boxes.length(); i++) {
+ vec3 boxOrigin = (boxes[i].min + boxes[i].max) / 2.0;
+ vec3 dir = boxOrigin - origin;
+
+ if (intersectBoxes(origin, dir, info) &&
+ info.intersection < depth) {
+ color = vec4(info.color, 1.0);
+ depth = info.intersection;
+ hit = info;
+ }
+ if (intersectSpheres(origin, dir, info) &&
+ info.intersection < depth) {
+ color = vec4(info.color, 1.0);
+ depth = info.intersection;
+ hit = info;
+ }
+
+ if (bool(hit.material & EMISSIVE)) {
+ light++;
+ }
+
+ if (bool(boxes[i].material & EMISSIVE)) {
+ lights++;
+ }
+ }
+
+ for (int i = 0; i < spheres.length(); i++) {
+ //vec3 dir = spheres[i].center - origin;
+
+ //if (intersectSpheres(origin, dir, info) &&
+ // info.intersection < depth) {
+ // if (bool(info.material & EMISSIVE)) {
+ // light += 1.0;
+ // }
+ //}
+
+ //if (bool(spheres[i].material & EMISSIVE)) {
+ // lights++;
+ //}
+ }
+
+ return light;
+}
+
+vec4 raytrace(vec3 origin, vec3 dir) {
+ vec4 c = backgroundColor();
+ vec4 bounceColor[8] = { c, c, c, c, c, c, c, c };
+ int bouncesMade = 0;
+
+ vec4 color = c;
+ hitinfo info, hit;
+ for (int bounce = 0; bounce < NUM_BOUNCES; bounce++) {
+ atomicCounterIncrement(raysTraced);
+ bouncesMade++;
+
+ float depth = MAX_SCENE_BOUNDS;
+ bool rayHit = false;
+ info = hit;
+
+ if (intersectBoxes(origin, dir, info) &&
+ info.intersection < depth) {
+ color = vec4(info.color, 1.0);
+ depth = info.intersection;
+ hit = info;
+ rayHit = true;
+ }
+ if (intersectSpheres(origin, dir, info) &&
+ info.intersection < depth) {
+ color = vec4(info.color, 1.0);
+ depth = info.intersection;
+ hit = info;
+ rayHit = true;
+ }
+
+ if (bounce == NUM_BOUNCES - 1) {
+ bounceColor[bounce] = c;
+ }
+
+ if (rayHit) {
+ bounceColor[bounce] = color;
+ if (bool(hit.material & LAMBERTIAN)) {
+ origin = hit.point;
+ dir = hit.normal + sphereRand(hit.point.xy + globalTime);
+ }
+ if (bool(hit.material & METAL)) {
+ vec3 reflected;
+ vec3 spRand = sphereRand(hit.point.xy + globalTime);
+ reflected = reflect(normalize(dir), hit.normal) + hit.fuzz * spRand;
+ if (dot(reflected, hit.normal) > 0.0) {
+ origin = hit.point;
+ dir = reflected;
+ }
+ else {
+ break;
+ }
+ }
+ if (bool(hit.material & DIELECTRIC)) {
+ bounceColor[bounce] = color;
+ dir = normalize(dir);
+ vec3 reflected = reflect(dir, hit.normal);
+ vec3 normal;
+ float eta;
+ float reflectProb;
+ float cosine;
+ if (dot(dir, hit.normal) > 0) {
+ normal = -hit.normal;
+ eta = hit.refractionIndex;
+ cosine = dot(dir, hit.normal);
+ }
+ else {
+ normal = hit.normal;
+ eta = 1.0 / hit.refractionIndex;
+ cosine = -dot(dir, hit.normal);
+ }
+ vec3 refracted = refract(dir, normal, eta);
+ if (refracted != vec3(0.0)) {
+ reflectProb = schlick(cosine, hit.refractionIndex);
+ }
+ else {
+ reflectProb = 1.0;
+ }
+ if (rand(hit.point.xy + globalTime) < reflectProb) {
+ dir = reflected;
+ }
+ else {
+ dir = refracted;
+ }
+ dir += hit.fuzz * sphereRand(hit.point.xy + globalTime);
+ origin = hit.point;
+ }
+ if (bool(hit.material & EMISSIVE)) {
+ break;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ for (int bounce = 0; bounce < bouncesMade; bounce++) {
+ color *= bounceColor[bounce];
+ }
+
+ return color;
+}
+
+void main() {
+ ivec2 pix = ivec2(gl_GlobalInvocationID.xy);
+ ivec2 size = imageSize(framebuffer);
+ if (pix.x >= size.x || pix.y >= size.y) {
+ return;
+ }
+
+ vec4 color = vec4(0.0);
+ if (ANTIALIASING) {
+ for (int i = 1; i <= MULTISAMPLING_RAYS_EMITTED; i++) {
+ vec2 randVec = vec2(rand(pix + i + globalTime), rand(pix * i + globalTime)) * 2.0 - 1.0;
+
+ vec2 jitter = randVec * subpixels[int(rand(vec2(i + globalTime)) * 16)];
+ vec2 pos = (vec2(pix) + jitter) / (size.xy - 1);
+
+ vec3 eyeJittered = eye;
+ vec3 dir = mix(mix(ray00, ray01, pos.y), mix(ray10, ray11, pos.y), pos.x);
+
+ if (DEPTH_OF_FIELD) {
+ vec3 du = length(ray10 - ray00) / size.x * normalize(ray10 - ray00);
+ vec3 dv = length(ray01 - ray00) / size.y * normalize(ray01 - ray00);
+ eyeJittered += 8 * (randVec.x * du + randVec.y * dv);
+ dir -= 3 * (randVec.x * du + randVec.y * dv);
+ }
+
+ color += raytrace(eyeJittered, dir) / MULTISAMPLING_RAYS_EMITTED;
+ }
+ }
+ else {
+ vec2 pos = vec2(pix) / vec2(size.x - 1, size.y - 1);
+ vec3 dir = mix(mix(ray00, ray01, pos.y), mix(ray10, ray11, pos.y), pos.x);
+ color += raytrace(eye, dir);
+ }
+
+ vec4 previousColor = imageLoad(framebuffer, pix);
+ if (frameCount != 0) {
+ color = (previousColor * frameCount + color) / (frameCount + 1);
+ }
+ if (any(isinf(color)) || any(isnan(color))) {
+ return;
+ }
+
+ imageStore(framebuffer, pix, color);
+}
diff --git a/Compute/Fragment.glsl b/Compute/Fragment.glsl
new file mode 100644
index 0000000..06dbeab
--- /dev/null
+++ b/Compute/Fragment.glsl
@@ -0,0 +1,12 @@
+#version 430 core
+
+in vec2 texcoord;
+out vec4 color;
+
+uniform sampler2D tex;
+const float GAMMA = 2.2;
+
+void main() {
+ color = texture(tex, texcoord);
+ color = pow(color, vec4(1.0 / GAMMA));
+}
\ No newline at end of file
diff --git a/Compute/Vertex.glsl b/Compute/Vertex.glsl
new file mode 100644
index 0000000..d6145eb
--- /dev/null
+++ b/Compute/Vertex.glsl
@@ -0,0 +1,11 @@
+#version 430 core
+
+out vec2 texcoord;
+
+void main() {
+ float x = float(((uint(gl_VertexID) + 2u) / 3u)%2u);
+ float y = float(((uint(gl_VertexID) + 1u) / 3u)%2u);
+
+ gl_Position = vec4(-1.0f + x*2.0f, -1.0f+y*2.0f, 0.0f, 1.0f);
+ texcoord = vec2(x, y);
+}
\ No newline at end of file
diff --git a/Compute/main.cpp b/Compute/main.cpp
new file mode 100644
index 0000000..cb34d7b
--- /dev/null
+++ b/Compute/main.cpp
@@ -0,0 +1,341 @@
+
+
+#include "main.h"
+
+bool checkForOpenGLErrors() {
+ int numErrors = 0;
+ GLenum err;
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ numErrors++;
+ int errNum = 0;
+ switch (err) {
+ case GL_INVALID_ENUM:
+ errNum = 1;
+ break;
+ case GL_INVALID_VALUE:
+ errNum = 2;
+ break;
+ case GL_INVALID_OPERATION:
+ errNum = 3;
+ break;
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ errNum = 4;
+ break;
+ case GL_OUT_OF_MEMORY:
+ errNum = 5;
+ break;
+ case GL_STACK_UNDERFLOW:
+ errNum = 6;
+ break;
+ case GL_STACK_OVERFLOW:
+ errNum = 7;
+ break;
+ }
+ printf("OpenGL ERROR: %s.\n", errNames[errNum]);
+ }
+ return (numErrors != 0);
+}
+
+void resizeComputeTexture() {
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_RGBA, GL_FLOAT, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+void createMatrices() {
+ projectionMatrix = glm::perspective(fov, (double)windowWidth / windowHeight, 1.0, 2.0);
+ viewMatrix = glm::lookAt(eyePos, lookAt, upVec);
+ inverseProjectionViewMatrix = glm::inverse(projectionMatrix * viewMatrix);
+}
+
+void createBufferObjects() {
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ glGenBuffers(1, &rayCount);
+ glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, rayCount);
+ glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(int), NULL, GL_DYNAMIC_DRAW);
+ glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, rayCount);
+}
+
+void loadShaders() {
+ using namespace std::chrono;
+ high_resolution_clock::time_point t1 = high_resolution_clock::now();
+
+ rasterShader = Shader("Vertex.glsl", "Fragment.glsl").programId;
+ computeShader = Shader("ComputeShader.glsl").programId;
+
+ glUseProgram(computeShader);
+ int* workGroupSize = new int[3];
+ glGetProgramiv(computeShader, GL_COMPUTE_WORK_GROUP_SIZE, workGroupSize);
+ workGroupSizeX = workGroupSize[0];
+ workGroupSizeY = workGroupSize[1];
+
+ uniforms.eye = glGetUniformLocation(computeShader, "eye");
+ uniforms.ray00 = glGetUniformLocation(computeShader, "ray00");
+ uniforms.ray01 = glGetUniformLocation(computeShader, "ray01");
+ uniforms.ray10 = glGetUniformLocation(computeShader, "ray10");
+ uniforms.ray11 = glGetUniformLocation(computeShader, "ray11");
+ uniforms.time = glGetUniformLocation(computeShader, "globalTime");
+ uniforms.frameCount = glGetUniformLocation(computeShader, "frameCount");
+ uniforms.transposeInverseViewMatrix = glGetUniformLocation(computeShader, "transposeInverseViewMatrix");
+
+ listenerFlags.load = false;
+ globalFrameCount = 0;
+
+ high_resolution_clock::time_point t2 = high_resolution_clock::now();
+ auto duration = duration_cast(t2 - t1).count();
+
+ std::cout << std::endl << std::endl;
+ std::cout << "Shader Loaded in " << duration << " milliseconds" << std::endl;
+}
+
+void shaderFileListener() {
+ using std::chrono::milliseconds;
+ using std::chrono::system_clock;
+ using std::experimental::filesystem::last_write_time;
+ using std::this_thread::sleep_for;
+
+ system_clock::time_point now = system_clock::now();
+ while (!listenerFlags.exit) {
+ for (const char* str: shaderFiles) {
+ if (last_write_time(str) > now) {
+ listenerFlags.load = true;
+
+ printf("Loading Shader");
+ while (listenerFlags.load != false) {
+ printf(".");
+ sleep_for(milliseconds(700));
+ }
+
+ break;
+ }
+ }
+
+ now = system_clock::now();
+ sleep_for(milliseconds(100));
+ }
+}
+
+void initShaders() {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
+ shaderFileListenerThread = std::thread(shaderFileListener);
+ loadShaders();
+ resizeComputeTexture();
+
+ glfwSetTime(0.0);
+ checkForOpenGLErrors();
+}
+
+void renderToTexture() {
+ GLuint null = 0;
+ int worksizeX = nextPowerOfTwo(windowWidth);
+ int worksizeY = nextPowerOfTwo(windowHeight);
+
+ glUseProgram(computeShader);
+ uniforms.setFrameCount(globalFrameCount);
+ uniforms.setTime(globalTime);
+ uniforms.setRays(eyePos);
+ uniforms.setViewMatrix(viewMatrix);
+
+ glBindImageTexture(0, tex, 0, false, 0, GL_WRITE_ONLY, GL_RGBA32F);
+ glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, rayCount);
+ glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(int), &null);
+ glDispatchCompute(worksizeX / workGroupSizeX, worksizeY / workGroupSizeY, 1);
+ glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+ checkForOpenGLErrors();
+}
+
+void renderToScreen() {
+ glUseProgram(rasterShader);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ checkForOpenGLErrors();
+}
+
+void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+ if (action == GLFW_RELEASE) {
+ return;
+ }
+ switch (key) {
+ case GLFW_KEY_ESCAPE:
+ glfwSetWindowShouldClose(window, true);
+ return;
+ case GLFW_KEY_LEFT:
+ eyePos = glm::rotateY(eyePos, -0.003f);
+ break;
+ case GLFW_KEY_RIGHT:
+ eyePos = glm::rotateY(eyePos, 0.003f);
+ break;
+ case GLFW_KEY_UP:
+ eyePos = glm::rotateX(eyePos, -0.003f);
+ break;
+ case GLFW_KEY_DOWN:
+ eyePos = glm::rotateX(eyePos, 0.003f);
+ break;
+ case GLFW_KEY_R:
+ spinning = !spinning;
+ return;
+ default:
+ return;
+ }
+
+ spinning = false;
+ globalFrameCount = 0;
+}
+
+void cursorCallback(GLFWwindow* window, double x, double y) {
+ int left = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT);
+ int right = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT);
+
+ if (right != lastMousePosition.z) {
+ lookAt = glm::vec3(0.0, 0.5, 0.0);
+ globalFrameCount = 0;
+ }
+
+ if (left != GLFW_PRESS && right != GLFW_PRESS ||
+ left == GLFW_PRESS && right == GLFW_PRESS) {
+ lastMousePosition.x = (float)x;
+ lastMousePosition.y = (float)y;
+ lastMousePosition.z = (right == GLFW_PRESS);
+
+ return;
+ }
+
+ spinning = false;
+
+ int dx = (int)(lastMousePosition.x - x);
+ int dy = (int)(lastMousePosition.y - y);
+
+ if (right == GLFW_PRESS) {
+ glm::vec3 newLookAt = lookAt - glm::vec3(eyePos.x, 0, eyePos.z);
+ newLookAt = glm::rotateY(newLookAt, 0.003f * dx);
+ newLookAt += glm::vec3(eyePos.x, -eyePos.y, 0);
+ newLookAt = glm::rotateX(newLookAt, 0.003f * dy);
+ newLookAt += glm::vec3(0, eyePos.y, eyePos.z);
+ lookAt = newLookAt;
+ }
+ else {
+ eyePos = glm::rotateY(glm::rotateX(eyePos, 0.003f * dy), 0.003f * dx);
+ }
+
+ lastMousePosition.x = (float)x;
+ lastMousePosition.y = (float)y;
+ lastMousePosition.z = (right == GLFW_PRESS);
+ globalFrameCount = 0;
+}
+
+void scrollCallback(GLFWwindow* window, double x, double y) {
+ int ctrl = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL);
+
+ if (ctrl == GLFW_PRESS) {
+ fov += y * 0.01;
+ }
+ else {
+ eyePos += eyePos * (float)y * 0.01f;
+ }
+ globalFrameCount = 0;
+}
+
+void windowSizeCallback(GLFWwindow* window, int width, int height) {
+ glViewport(0, 0, width, height);
+
+ if (windowWidth != width || windowHeight != height) {
+ windowWidth = width;
+ windowHeight = height;
+ resizeComputeTexture();
+ globalFrameCount = 0;
+ }
+
+ checkForOpenGLErrors();
+}
+
+void errorCallback(int error, const char* description) {
+ fputs(description, stderr);
+}
+
+int main() {
+ glEnable(GL_DEBUG_OUTPUT);
+ glfwSetErrorCallback(errorCallback);
+ glfwInit();
+
+ GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, "Compute Shader Pathtracing", NULL, NULL);
+ if (window == NULL) {
+ printf("Failed to create GLFW window!\n");
+ return -1;
+ }
+ glfwMakeContextCurrent(window);
+
+ if (GLEW_OK != glewInit()) {
+ printf("Failed to initialize GLEW!.\n");
+ return -1;
+ }
+
+ printf("Renderer: %s\n", glGetString(GL_RENDERER));
+ printf("OpenGL version supported %s\n", glGetString(GL_VERSION));
+#ifdef GL_SHADING_LANGUAGE_VERSION
+ printf("Supported GLSL version is %s.\n", (char *)glGetString(GL_SHADING_LANGUAGE_VERSION));
+#endif
+ printf("Using GLEW version %s.\n", glewGetString(GLEW_VERSION));
+ printf("------------------------------\n");
+ printf("Press ESCAPE to exit.\n");
+
+ glfwSetFramebufferSizeCallback(window, windowSizeCallback);
+ glfwSetKeyCallback(window, keyCallback);
+ glfwSetCursorPosCallback(window, cursorCallback);
+ glfwSetScrollCallback(window, scrollCallback);
+ windowSizeCallback(window, windowWidth, windowHeight);
+
+ initShaders();
+ createBufferObjects();
+
+ double previousTime = glfwGetTime();
+ int frameCount = 0;
+ std::cout.imbue(std::locale(""));
+
+ while (!glfwWindowShouldClose(window)) {
+ if (listenerFlags.load) {
+ loadShaders();
+ }
+
+ if (spinning) {
+ eyePos = glm::rotateY(eyePos, 0.001f);
+ globalFrameCount = 0;
+ }
+
+ createMatrices();
+ renderToTexture();
+ renderToScreen();
+
+ double currentTime = glfwGetTime();
+ frameCount++;
+ globalFrameCount++;
+ globalTime = 0.001f * globalFrameCount;
+
+ if (currentTime - previousTime >= 1.0) {
+ GLuint* rayData = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(int), GL_MAP_READ_BIT);
+ if (rayData) {
+ glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
+ std::cout << "Rays Traced: " << *rayData << std::endl;
+ printf("Anti-Aliased Samples per Pixel: %d\n", globalFrameCount);
+ checkForOpenGLErrors();
+ }
+
+ printf("Framerate: %d\n", frameCount);
+ frameCount = 0;
+ previousTime = currentTime;
+ }
+
+ glfwSwapBuffers(window);
+ glfwWaitEventsTimeout(1.0/300.0);
+ }
+
+ listenerFlags.exit = true;
+ shaderFileListenerThread.join();
+ glfwTerminate();
+ return 0;
+}
diff --git a/Compute/main.h b/Compute/main.h
new file mode 100644
index 0000000..698e99d
--- /dev/null
+++ b/Compute/main.h
@@ -0,0 +1,213 @@
+#pragma once
+
+#define GLEW_STATIC
+#pragma comment(lib, "opengl32.lib")
+#pragma comment(lib, "glu32.lib")
+#pragma comment(lib, "glfw3.lib")
+#pragma comment(lib, "glew32s.lib")
+#pragma comment(lib, "glew32.lib")
+
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+GLuint tex;
+GLuint vao;
+GLuint rayCount;
+
+int windowWidth = 400;
+int windowHeight = 300;
+int workGroupSizeX;
+int workGroupSizeY;
+
+bool spinning = false;
+int globalFrameCount = 0;
+float globalTime = 0.0;
+glm::vec3 lastMousePosition;
+
+double fov = glm::pi() * glm::third();
+glm::vec3 eyePos = { 4.0, 5.0, 5.0 };
+glm::vec3 lookAt = { 0.0, 0.5, 0.0 };
+glm::vec3 upVec = { 0.0, 1.0, 0.0 };
+glm::mat4 projectionMatrix;
+glm::mat4 viewMatrix;
+glm::mat4 inverseProjectionViewMatrix;
+
+std::thread shaderFileListenerThread;
+
+unsigned int rasterShader;
+unsigned int computeShader;
+
+std::vector shaderFiles = {
+ "ComputeShader.glsl", "Vertex.glsl", "Fragment.glsl"
+};
+
+char errNames[8][36] = {
+ "Unknown OpenGL error",
+ "GL_INVALID_ENUM", "GL_INVALID_VALUE", "GL_INVALID_OPERATION",
+ "GL_INVALID_FRAMEBUFFER_OPERATION", "GL_OUT_OF_MEMORY",
+ "GL_STACK_UNDERFLOW", "GL_STACK_OVERFLOW"
+};
+
+glm::vec3 getEyeRay(float x, float y, glm::vec3 eyePos) {
+ glm::vec4 temp = inverseProjectionViewMatrix * glm::vec4(x, y, 0, 1);
+ glm::vec3 point = glm::vec3(temp.x, temp.y, temp.z) / temp.w;
+ return point - eyePos;
+}
+
+int nextPowerOfTwo(int x) {
+ x--;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ x++;
+ return x;
+}
+
+struct ListenerFlags {
+ bool load = false;
+ bool exit = false;
+} listenerFlags;
+
+struct Uniforms {
+ unsigned int eye, ray00, ray01, ray10, ray11;
+ unsigned int transposeInverseViewMatrix;
+ unsigned int frameCount, time;
+
+ void setRays(glm::vec3 eyePos) {
+ glm::vec3 temp = eyePos;
+ glUniform3f(eye, temp.x, temp.y, temp.z);
+ temp = getEyeRay(-1, -1, eyePos);
+ glUniform3f(ray00, temp.x, temp.y, temp.z);
+ temp = getEyeRay(-1, 1, eyePos);
+ glUniform3f(ray01, temp.x, temp.y, temp.z);
+ temp = getEyeRay(1, -1, eyePos);
+ glUniform3f(ray10, temp.x, temp.y, temp.z);
+ temp = getEyeRay(1, 1, eyePos);
+ glUniform3f(ray11, temp.x, temp.y, temp.z);
+ }
+
+ void setViewMatrix(glm::mat4 mat) {
+ glm::mat3x3 viewMatrix = glm::inverse(glm::transpose(mat));
+ glUniformMatrix3fv(transposeInverseViewMatrix, 1, GL_FALSE, glm::value_ptr(viewMatrix));
+ }
+
+ void setFrameCount(int frames) {
+ glUniform1i(frameCount, frames);
+ }
+
+ void setTime(float t) {
+ glUniform1f(time, t);
+ }
+} uniforms;
+
+struct Shader {
+private:
+ std::string readShader(const GLchar* path) {
+ std::ifstream file;
+ std::stringstream stream;
+ file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+ file.open(path);
+ stream << file.rdbuf();
+ file.close();
+ return stream.str();
+ }
+
+ unsigned int compileShader(GLenum type, std::string string) {
+ int success;
+ GLchar infoLog[512];
+
+ unsigned int shader = glCreateShader(type);
+ const char* cstr = string.c_str();
+ glShaderSource(shader, 1, &cstr, NULL);
+ glCompileShader(shader);
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+
+ if (!success) {
+ glGetShaderInfoLog(shader, 512, NULL, infoLog);
+ std::cout << infoLog << std::endl;
+ }
+
+ return shader;
+ };
+
+public:
+ unsigned int programId;
+
+ Shader(const GLchar* vertexPath, const GLchar* fragmentPath) {
+ std::string vertexString, fragmentString;
+
+ try {
+ vertexString = readShader(vertexPath);
+ fragmentString = readShader(fragmentPath);
+ }
+ catch (std::ifstream::failure e) {
+ std::cout << e.what() << std::endl;
+ exit(1);
+ }
+
+ unsigned int vertex = compileShader(GL_VERTEX_SHADER, vertexString);
+ unsigned int fragment = compileShader(GL_FRAGMENT_SHADER, fragmentString);
+
+ int success;
+ GLchar infoLog[512];
+
+ programId = glCreateProgram();
+ glAttachShader(programId, vertex);
+ glAttachShader(programId, fragment);
+ glLinkProgram(programId);
+ glGetProgramiv(programId, GL_LINK_STATUS, &success);
+
+ if (!success) {
+ glGetProgramInfoLog(programId, 512, NULL, infoLog);
+ std::cout << infoLog << std::endl;
+ }
+
+ glDeleteShader(vertex);
+ glDeleteShader(fragment);
+ }
+
+ Shader(const GLchar* computePath) {
+ std::string computeString;
+
+ try {
+ computeString = readShader(computePath);
+ }
+ catch (std::ifstream::failure e) {
+ std::cout << e.what() << std::endl;
+ exit(1);
+ }
+
+ unsigned int compute = compileShader(GL_COMPUTE_SHADER, computeString);
+
+ int success;
+ GLchar infoLog[512];
+
+ programId = glCreateProgram();
+ glAttachShader(programId, compute);
+ glLinkProgram(programId);
+ glGetProgramiv(programId, GL_LINK_STATUS, &success);
+
+ if (!success) {
+ glGetProgramInfoLog(programId, 512, NULL, infoLog);
+ std::cout << infoLog << std::endl;
+ }
+
+ glDeleteShader(compute);
+ }
+};
diff --git a/GLPathtracing.sln b/GLPathtracing.sln
new file mode 100644
index 0000000..cd5b635
--- /dev/null
+++ b/GLPathtracing.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.168
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Compute", "Compute\Compute.vcxproj", "{C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Debug|x64.ActiveCfg = Debug|x64
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Debug|x64.Build.0 = Debug|x64
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Debug|x86.ActiveCfg = Debug|Win32
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Debug|x86.Build.0 = Debug|Win32
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Release|x64.ActiveCfg = Release|x64
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Release|x64.Build.0 = Release|x64
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Release|x86.ActiveCfg = Release|Win32
+ {C2FE7B7E-3D75-4E01-8A79-8834B1CDBBAE}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D2C2AACF-5220-4E14-ACB2-D02E9B49D3E9}
+ EndGlobalSection
+EndGlobal
diff --git a/Photos/0.png b/Photos/0.png
new file mode 100644
index 0000000..377c70f
Binary files /dev/null and b/Photos/0.png differ
diff --git a/Photos/1.png b/Photos/1.png
new file mode 100644
index 0000000..0ca3727
Binary files /dev/null and b/Photos/1.png differ
diff --git a/Photos/10.png b/Photos/10.png
new file mode 100644
index 0000000..bd3b041
Binary files /dev/null and b/Photos/10.png differ
diff --git a/Photos/11.png b/Photos/11.png
new file mode 100644
index 0000000..88720df
Binary files /dev/null and b/Photos/11.png differ
diff --git a/Photos/12.png b/Photos/12.png
new file mode 100644
index 0000000..f382d4b
Binary files /dev/null and b/Photos/12.png differ
diff --git a/Photos/13.png b/Photos/13.png
new file mode 100644
index 0000000..65c321a
Binary files /dev/null and b/Photos/13.png differ
diff --git a/Photos/14.png b/Photos/14.png
new file mode 100644
index 0000000..8063a17
Binary files /dev/null and b/Photos/14.png differ
diff --git a/Photos/15.png b/Photos/15.png
new file mode 100644
index 0000000..8b8f711
Binary files /dev/null and b/Photos/15.png differ
diff --git a/Photos/16.png b/Photos/16.png
new file mode 100644
index 0000000..a4be9af
Binary files /dev/null and b/Photos/16.png differ
diff --git a/Photos/17.png b/Photos/17.png
new file mode 100644
index 0000000..fd9fb0a
Binary files /dev/null and b/Photos/17.png differ
diff --git a/Photos/18.png b/Photos/18.png
new file mode 100644
index 0000000..7ad6ab6
Binary files /dev/null and b/Photos/18.png differ
diff --git a/Photos/19.png b/Photos/19.png
new file mode 100644
index 0000000..15baf17
Binary files /dev/null and b/Photos/19.png differ
diff --git a/Photos/2.png b/Photos/2.png
new file mode 100644
index 0000000..e797213
Binary files /dev/null and b/Photos/2.png differ
diff --git a/Photos/20.png b/Photos/20.png
new file mode 100644
index 0000000..8c80fa6
Binary files /dev/null and b/Photos/20.png differ
diff --git a/Photos/21.png b/Photos/21.png
new file mode 100644
index 0000000..f39ad8a
Binary files /dev/null and b/Photos/21.png differ
diff --git a/Photos/22.png b/Photos/22.png
new file mode 100644
index 0000000..401cb9c
Binary files /dev/null and b/Photos/22.png differ
diff --git a/Photos/23.png b/Photos/23.png
new file mode 100644
index 0000000..0e8e01c
Binary files /dev/null and b/Photos/23.png differ
diff --git a/Photos/24.png b/Photos/24.png
new file mode 100644
index 0000000..5a67493
Binary files /dev/null and b/Photos/24.png differ
diff --git a/Photos/25.png b/Photos/25.png
new file mode 100644
index 0000000..c2596e3
Binary files /dev/null and b/Photos/25.png differ
diff --git a/Photos/26.png b/Photos/26.png
new file mode 100644
index 0000000..5d77664
Binary files /dev/null and b/Photos/26.png differ
diff --git a/Photos/27.png b/Photos/27.png
new file mode 100644
index 0000000..5e5a4c1
Binary files /dev/null and b/Photos/27.png differ
diff --git a/Photos/28.png b/Photos/28.png
new file mode 100644
index 0000000..1e181e4
Binary files /dev/null and b/Photos/28.png differ
diff --git a/Photos/29.png b/Photos/29.png
new file mode 100644
index 0000000..af77eef
Binary files /dev/null and b/Photos/29.png differ
diff --git a/Photos/3.png b/Photos/3.png
new file mode 100644
index 0000000..0994f82
Binary files /dev/null and b/Photos/3.png differ
diff --git a/Photos/30.png b/Photos/30.png
new file mode 100644
index 0000000..28b96ab
Binary files /dev/null and b/Photos/30.png differ
diff --git a/Photos/31.png b/Photos/31.png
new file mode 100644
index 0000000..0dfe1ba
Binary files /dev/null and b/Photos/31.png differ
diff --git a/Photos/32.png b/Photos/32.png
new file mode 100644
index 0000000..9deb604
Binary files /dev/null and b/Photos/32.png differ
diff --git a/Photos/33.png b/Photos/33.png
new file mode 100644
index 0000000..dfc3a97
Binary files /dev/null and b/Photos/33.png differ
diff --git a/Photos/34.png b/Photos/34.png
new file mode 100644
index 0000000..667bc18
Binary files /dev/null and b/Photos/34.png differ
diff --git a/Photos/35.png b/Photos/35.png
new file mode 100644
index 0000000..0398917
Binary files /dev/null and b/Photos/35.png differ
diff --git a/Photos/36.png b/Photos/36.png
new file mode 100644
index 0000000..a75f3e5
Binary files /dev/null and b/Photos/36.png differ
diff --git a/Photos/37.png b/Photos/37.png
new file mode 100644
index 0000000..45df404
Binary files /dev/null and b/Photos/37.png differ
diff --git a/Photos/38.png b/Photos/38.png
new file mode 100644
index 0000000..6082f2f
Binary files /dev/null and b/Photos/38.png differ
diff --git a/Photos/39.png b/Photos/39.png
new file mode 100644
index 0000000..0de36ab
Binary files /dev/null and b/Photos/39.png differ
diff --git a/Photos/4.png b/Photos/4.png
new file mode 100644
index 0000000..46eeac6
Binary files /dev/null and b/Photos/4.png differ
diff --git a/Photos/40.png b/Photos/40.png
new file mode 100644
index 0000000..537beca
Binary files /dev/null and b/Photos/40.png differ
diff --git a/Photos/41.png b/Photos/41.png
new file mode 100644
index 0000000..5448243
Binary files /dev/null and b/Photos/41.png differ
diff --git a/Photos/42.png b/Photos/42.png
new file mode 100644
index 0000000..40d4add
Binary files /dev/null and b/Photos/42.png differ
diff --git a/Photos/43.png b/Photos/43.png
new file mode 100644
index 0000000..497411a
Binary files /dev/null and b/Photos/43.png differ
diff --git a/Photos/44.png b/Photos/44.png
new file mode 100644
index 0000000..51f7d10
Binary files /dev/null and b/Photos/44.png differ
diff --git a/Photos/45.png b/Photos/45.png
new file mode 100644
index 0000000..b8cd634
Binary files /dev/null and b/Photos/45.png differ
diff --git a/Photos/46.png b/Photos/46.png
new file mode 100644
index 0000000..eb81537
Binary files /dev/null and b/Photos/46.png differ
diff --git a/Photos/47.png b/Photos/47.png
new file mode 100644
index 0000000..6e4a165
Binary files /dev/null and b/Photos/47.png differ
diff --git a/Photos/48.png b/Photos/48.png
new file mode 100644
index 0000000..f2ea6a9
Binary files /dev/null and b/Photos/48.png differ
diff --git a/Photos/49.png b/Photos/49.png
new file mode 100644
index 0000000..49b65c0
Binary files /dev/null and b/Photos/49.png differ
diff --git a/Photos/5.png b/Photos/5.png
new file mode 100644
index 0000000..2965ace
Binary files /dev/null and b/Photos/5.png differ
diff --git a/Photos/50.png b/Photos/50.png
new file mode 100644
index 0000000..1415fd9
Binary files /dev/null and b/Photos/50.png differ
diff --git a/Photos/51.png b/Photos/51.png
new file mode 100644
index 0000000..a755b4f
Binary files /dev/null and b/Photos/51.png differ
diff --git a/Photos/52.png b/Photos/52.png
new file mode 100644
index 0000000..29ab6b3
Binary files /dev/null and b/Photos/52.png differ
diff --git a/Photos/53.png b/Photos/53.png
new file mode 100644
index 0000000..6ce6d52
Binary files /dev/null and b/Photos/53.png differ
diff --git a/Photos/54.png b/Photos/54.png
new file mode 100644
index 0000000..8eea6b4
Binary files /dev/null and b/Photos/54.png differ
diff --git a/Photos/55.png b/Photos/55.png
new file mode 100644
index 0000000..2cf47e0
Binary files /dev/null and b/Photos/55.png differ
diff --git a/Photos/56.png b/Photos/56.png
new file mode 100644
index 0000000..684656a
Binary files /dev/null and b/Photos/56.png differ
diff --git a/Photos/57.png b/Photos/57.png
new file mode 100644
index 0000000..352977e
Binary files /dev/null and b/Photos/57.png differ
diff --git a/Photos/58.png b/Photos/58.png
new file mode 100644
index 0000000..2f4cdd0
Binary files /dev/null and b/Photos/58.png differ
diff --git a/Photos/59.png b/Photos/59.png
new file mode 100644
index 0000000..bd8ad0d
Binary files /dev/null and b/Photos/59.png differ
diff --git a/Photos/60.png b/Photos/60.png
new file mode 100644
index 0000000..cb44fbc
Binary files /dev/null and b/Photos/60.png differ
diff --git a/Photos/61.png b/Photos/61.png
new file mode 100644
index 0000000..3b7f739
Binary files /dev/null and b/Photos/61.png differ
diff --git a/Photos/7.png b/Photos/7.png
new file mode 100644
index 0000000..1acf0a5
Binary files /dev/null and b/Photos/7.png differ
diff --git a/Photos/8.png b/Photos/8.png
new file mode 100644
index 0000000..81301ac
Binary files /dev/null and b/Photos/8.png differ
diff --git a/Photos/9.png b/Photos/9.png
new file mode 100644
index 0000000..defb392
Binary files /dev/null and b/Photos/9.png differ
diff --git a/README.md b/README.md
index 99f660c..45f62d2 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,20 @@
-# gl-pathtracing
-Compute Shader Pathtracing in OpenGL
+# OpenGL Path-Tracing
+
+Realtime Compute Shader Path-Tracing in OpenGL
+
+Recommend not running this at full screen, and messing with shader parameters for more interactive framerates
+
+## Controls
+
+- Click-and-drag to rotate camera about focus
+- Hold right-click to rotate camera in first-person (will snap back to focus after releasing right-click)
+- Scroll to move towards or away from focus
+- Hold left control and scroll to change the field of view
+
+## Shader Recompilation
+
+- Relevant shader code can be found in `ComputeShader.glsl`
+ - Modify during execution: the program will recompile shaders on file save
+ - Mess with constants (Lines 3-7)
+- All shapes and objects are stored in the shader code, modify `boxes` and `spheres` arrays for different material parameters, sizes, and adding/removing objects
+ - (will see performance penalty with more objects, no acceleration structure present)