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)