2022-10-23 23:46:12 -04:00
|
|
|
/*
|
|
|
|
* Created by Brett Terpstra 6920201 on 16/10/22.
|
|
|
|
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
|
|
|
|
*/
|
|
|
|
#include "engine/raytracing.h"
|
|
|
|
#include <queue>
|
|
|
|
#include <functional>
|
2022-10-24 17:06:20 -04:00
|
|
|
#include <utility>
|
2022-10-23 23:46:12 -04:00
|
|
|
#include <engine/util/debug.h>
|
|
|
|
|
|
|
|
namespace Raytracing {
|
2022-10-24 17:06:20 -04:00
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
Ray Camera::projectRay(PRECISION_TYPE x, PRECISION_TYPE y) {
|
|
|
|
// transform the x and y to points from image coords to be inside the camera's viewport.
|
|
|
|
double transformedX = (x / (image.getWidth() - 1));
|
|
|
|
auto transformedY = (y / (image.getHeight() - 1));
|
|
|
|
// then generate a ray which extends out from the camera position in the direction with respects to its position on the image
|
|
|
|
return {position, imageOrigin + transformedX * horizontalAxis + transformedY * verticalAxis - position};
|
|
|
|
}
|
2022-10-24 17:06:20 -04:00
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
void Camera::lookAt(const Vec4& pos, const Vec4& lookAtPos, const Vec4& up) {
|
|
|
|
// standard camera lookAt function
|
|
|
|
auto w = (pos - lookAtPos).normalize();
|
|
|
|
auto u = (Vec4::cross(up, w)).normalize();
|
|
|
|
auto v = Vec4::cross(w, u);
|
2022-10-24 17:06:20 -04:00
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
position = pos;
|
|
|
|
horizontalAxis = viewportWidth * u;
|
|
|
|
verticalAxis = viewportHeight * v;
|
2022-10-24 17:06:20 -04:00
|
|
|
imageOrigin = position - horizontalAxis / 2 - verticalAxis / 2 - w;
|
2022-10-23 23:46:12 -04:00
|
|
|
}
|
2022-10-24 17:06:20 -04:00
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
void Camera::setRotation(const PRECISION_TYPE yaw, const PRECISION_TYPE pitch, const PRECISION_TYPE roll) {
|
|
|
|
// TODO:
|
|
|
|
}
|
2022-10-25 01:06:26 -04:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
*Vec4 Raycaster::raycast(const Ray& ray, int depth) {
|
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
if (depth > maxBounceDepth)
|
2022-10-24 17:06:20 -04:00
|
|
|
return {0, 0, 0};
|
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
auto hit = world.checkIfHit(ray, 0.001, infinity);
|
2022-10-24 17:06:20 -04:00
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
if (hit.first.hit) {
|
|
|
|
auto object = hit.second;
|
|
|
|
auto scatterResults = object->getMaterial()->scatter(ray, hit.first);
|
|
|
|
// if the material scatters the ray, ie casts a new one,
|
|
|
|
if (scatterResults.scattered) // attenuate the recursive raycast by the material's color
|
|
|
|
return scatterResults.attenuationColor * raycast(scatterResults.newRay, depth + 1);
|
|
|
|
//tlog << "Not scattered? " << object->getMaterial() << "\n";
|
2022-10-24 17:06:20 -04:00
|
|
|
return {0, 0, 0};
|
2022-10-23 23:46:12 -04:00
|
|
|
}
|
2022-10-24 17:06:20 -04:00
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
// skybox color
|
|
|
|
return {0.5, 0.7, 1.0};
|
|
|
|
}
|
2022-10-25 01:06:26 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
struct RayData {
|
|
|
|
Ray ray;
|
|
|
|
int depth;
|
|
|
|
Vec4 color;
|
|
|
|
};
|
|
|
|
|
|
|
|
Vec4 Raycaster::raycast(const Ray& ray, int depth) {
|
|
|
|
auto* rayQueue = new std::queue<Ray>();
|
|
|
|
rayQueue->push(ray);
|
|
|
|
Vec4 color {1.0, 1.0, 1.0};
|
|
|
|
int currentDepth = 0;
|
|
|
|
do {
|
|
|
|
Ray r = rayQueue->front();
|
|
|
|
|
|
|
|
auto hit = world.checkIfHit(r, 0.001, infinity);
|
|
|
|
if (hit.first.hit) {
|
|
|
|
auto object = hit.second;
|
|
|
|
auto scatterResults = object->getMaterial()->scatter(r, hit.first);
|
|
|
|
// if the material scatters the ray, ie casts a new one,
|
|
|
|
if (scatterResults.scattered) { // attenuate the recursive raycast by the material's color
|
|
|
|
color = scatterResults.attenuationColor * color;
|
|
|
|
rayQueue->push(scatterResults.newRay);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
color = color * Vec4{0.5, 0.7, 1.0};
|
|
|
|
rayQueue->pop();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
rayQueue->pop();
|
|
|
|
currentDepth++;
|
|
|
|
//tlog << currentDepth << " " << rayQueue->size() << "\n";
|
|
|
|
} while (currentDepth < maxBounceDepth && !rayQueue->empty());
|
|
|
|
delete(rayQueue);
|
|
|
|
return color;
|
|
|
|
/*if (depth > maxBounceDepth)
|
|
|
|
return {0, 0, 0};
|
|
|
|
|
|
|
|
auto hit = world.checkIfHit(ray, 0.001, infinity);
|
|
|
|
|
|
|
|
if (hit.first.hit) {
|
|
|
|
auto object = hit.second;
|
|
|
|
auto scatterResults = object->getMaterial()->scatter(ray, hit.first);
|
|
|
|
// if the material scatters the ray, ie casts a new one,
|
|
|
|
if (scatterResults.scattered) // attenuate the recursive raycast by the material's color
|
|
|
|
return scatterResults.attenuationColor * raycast(scatterResults.newRay, depth + 1);
|
|
|
|
//tlog << "Not scattered? " << object->getMaterial() << "\n";
|
|
|
|
return {0, 0, 0};
|
|
|
|
}
|
|
|
|
|
|
|
|
// skybox color
|
|
|
|
return {0.5, 0.7, 1.0};*/
|
|
|
|
}
|
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
void Raycaster::runSingle() {
|
|
|
|
executors.push_back(new std::thread([this]() -> void {
|
2022-10-25 01:06:26 -04:00
|
|
|
profiler::start("Raytracer Results", "Single Thread");
|
2022-10-24 17:06:20 -04:00
|
|
|
for (int i = 0; i < image.getWidth(); i++) {
|
|
|
|
for (int j = 0; j < image.getHeight(); j++) {
|
2022-10-23 23:46:12 -04:00
|
|
|
Raytracing::Vec4 color;
|
|
|
|
// TODO: profile for speed;
|
2022-10-24 17:06:20 -04:00
|
|
|
for (int s = 0; s < raysPerPixel; s++) {
|
2022-10-23 23:46:12 -04:00
|
|
|
// simulate anti aliasing by generating rays with very slight random directions
|
|
|
|
color = color + raycast(camera.projectRay(i + rnd.getDouble(), j + rnd.getDouble()), 0);
|
|
|
|
}
|
|
|
|
PRECISION_TYPE sf = 1.0 / raysPerPixel;
|
|
|
|
// apply pixel color with gamma correction
|
|
|
|
image.setPixelColor(i, j, {std::sqrt(sf * color.r()), std::sqrt(sf * color.g()), std::sqrt(sf * color.b())});
|
|
|
|
}
|
|
|
|
}
|
2022-10-25 01:06:26 -04:00
|
|
|
profiler::end("Raytracer Results", "Single Thread");
|
2022-10-23 23:46:12 -04:00
|
|
|
finishedThreads++;
|
|
|
|
}));
|
|
|
|
}
|
2022-10-24 17:06:20 -04:00
|
|
|
|
2022-10-23 23:46:12 -04:00
|
|
|
void Raycaster::runMulti(unsigned int t) {
|
|
|
|
// calculate the max divisions we can have per side
|
|
|
|
// say we have 16 threads, making divs 4
|
|
|
|
// 4 divs per axis, two axis, 16 total quadrants
|
|
|
|
// matching the 16 threads.
|
|
|
|
if (t == 0)
|
|
|
|
t = system_threads;
|
2022-10-25 01:06:26 -04:00
|
|
|
int divs = int(std::log(t) / std::log(2));
|
2022-10-23 23:46:12 -04:00
|
|
|
// now double the divs, splitting each quadrant into 4 sub-quadrants which we can queue
|
|
|
|
// the reason to do this is that some of them will finish before others, and the now free threads can keep working
|
|
|
|
// do it without a queue like this leads to a single thread critical path and isn't optimally efficient.
|
2022-10-25 01:06:26 -04:00
|
|
|
divs *= 4; // 2 because two axis getting split makes 4 sub-quadrants, but I tested 4, and it was faster by two seconds, so I'm keeping 4.
|
2022-10-23 23:46:12 -04:00
|
|
|
|
2022-10-24 17:06:20 -04:00
|
|
|
for (int dx = 0; dx < divs; dx++) {
|
|
|
|
for (int dy = 0; dy < divs; dy++) {
|
2022-10-25 01:06:26 -04:00
|
|
|
// sending functions wasn't working. (fixed, however it feels janky sending lambda functions w/ captures)
|
2022-10-24 17:06:20 -04:00
|
|
|
unprocessedQuads->push({
|
2022-10-25 01:06:26 -04:00
|
|
|
image.getWidth() / divs,
|
|
|
|
image.getHeight() / divs,
|
|
|
|
(image.getWidth() / divs) * dx,
|
|
|
|
(image.getHeight() / divs) * dy
|
2022-10-24 17:06:20 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < t; i++) {
|
2022-10-25 01:06:26 -04:00
|
|
|
executors.push_back(new std::thread([this, i, divs, t]() -> void {
|
2022-10-24 17:06:20 -04:00
|
|
|
// run through all the quadrants
|
2022-10-25 01:06:26 -04:00
|
|
|
std::stringstream str;
|
|
|
|
str << "Threading of #";
|
|
|
|
str << (i+1);
|
|
|
|
profiler::start("Raytracer Results", str.str());
|
2022-10-24 17:06:20 -04:00
|
|
|
int j = 0;
|
|
|
|
while (true) {
|
|
|
|
std::vector<int> func;
|
|
|
|
// get the function for the quadrant
|
|
|
|
queueSync.lock();
|
|
|
|
if (unprocessedQuads->empty()) {
|
|
|
|
queueSync.unlock();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
func = unprocessedQuads->front();
|
|
|
|
unprocessedQuads->pop();
|
|
|
|
queueSync.unlock();
|
2022-10-25 01:06:26 -04:00
|
|
|
// the run it
|
2022-10-24 17:06:20 -04:00
|
|
|
for (int kx = 0; kx <= func[0]; kx++) {
|
|
|
|
for (int ky = 0; ky < func[1]; ky++) {
|
2022-10-23 23:46:12 -04:00
|
|
|
try {
|
2022-10-24 17:06:20 -04:00
|
|
|
int x = func[2] + kx;
|
|
|
|
int y = func[3] + ky;
|
2022-10-23 23:46:12 -04:00
|
|
|
Raytracing::Vec4 color;
|
|
|
|
// TODO: profile for speed;
|
|
|
|
for (int s = 0; s < raysPerPixel; s++) {
|
|
|
|
// simulate anti aliasing by generating rays with very slight random directions
|
|
|
|
color = color + raycast(camera.projectRay(x + rnd.getDouble(), y + rnd.getDouble()), 0);
|
|
|
|
}
|
|
|
|
PRECISION_TYPE sf = 1.0 / raysPerPixel;
|
|
|
|
// apply pixel color with gamma correction
|
|
|
|
image.setPixelColor(x, y, {std::sqrt(sf * color.r()), std::sqrt(sf * color.g()), std::sqrt(sf * color.b())});
|
2022-10-24 17:06:20 -04:00
|
|
|
} catch (std::exception& error) {
|
2022-10-23 23:46:12 -04:00
|
|
|
flog << "Possibly fatal error in the multithreaded raytracer!\n";
|
|
|
|
flog << error.what() << "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
finishedThreads++;
|
2022-10-25 01:06:26 -04:00
|
|
|
profiler::end("Raytracer Results", str.str());
|
2022-10-23 23:46:12 -04:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|