added task 05 solution

This commit is contained in:
jp 2022-12-10 18:25:29 +01:00
parent fe68572fae
commit 04aabb67a5
6 changed files with 230 additions and 34 deletions

View file

@ -1,3 +1,3 @@
#include "ray.h" #include "ray.h"
int Ray::rayCount = 0; std::atomic<int> Ray::rayCount(0);

View file

@ -39,7 +39,7 @@ private:
int remainingBounces = ICG_RAY_BOUNCES; int remainingBounces = ICG_RAY_BOUNCES;
#endif #endif
static int rayCount; static std::atomic<int> rayCount;
}; };
#endif #endif

View file

@ -11,8 +11,33 @@ Texture KDTreeRenderer::renderImage(Scene const &scene, Camera const &camera, in
Texture KDTreeRenderer::renderKDTree(FastScene const &scene, Camera const &camera, int width, int height) { Texture KDTreeRenderer::renderKDTree(FastScene const &scene, Camera const &camera, int width, int height) {
Texture image(width, height); Texture image(width, height);
float const aspectRatio = static_cast<float>(height) / width;
// IMPLEMENT ME for (int x = 0; x < image.width(); ++x) {
for (int y = 0; y < image.height(); ++y) {
Ray ray = camera.createRay((static_cast<float>(x) / width * 2 - 1),
-(static_cast<float>(y) / height * 2 - 1) * aspectRatio);
image.setPixelAt(x, y, Color(float(scene.countNodeIntersections(ray)), 0.0f, 0.0f));
}
}
// map color to green -> red map
float maxVal = -INFINITY;
for (int x = 0; x < image.width(); ++x) {
for (int y = 0; y < image.height(); ++y) {
maxVal = std::max(maxVal, image.getPixelAt(x, y).r);
}
}
Color r(1, 0, 0);
Color g(0, 1, 0);
for (int x = 0; x < image.width(); ++x) {
for (int y = 0; y < image.height(); ++y) {
float factor = image.getPixelAt(x, y).r / maxVal;
image.setPixelAt(x, y, (1.0f - factor) * g + factor * r);
}
}
return image; return image;
} }

View file

@ -2,9 +2,25 @@
#include "renderer/simplerenderer.h" #include "renderer/simplerenderer.h"
#include "scene/scene.h" #include "scene/scene.h"
#include <iostream> #include <iostream>
#include <thread>
#include <chrono> #include <chrono>
#include <iomanip> #include <iomanip>
void SimpleRenderer::renderThread(const Scene *scene, Camera const *camera, Texture *image, int width, int widthStep, int widthOffset, int height, int heightStep, int heightOffset, std::atomic<int> *k, int const stepSize) {
float const aspectRatio = static_cast<float>(height) / width;
for (int y = heightOffset; y < height; y += heightStep) {
for (int x = widthOffset; x < width; x += widthStep) {
Ray ray = camera->createRay((static_cast<float>(x) / width * 2 - 1), -(static_cast<float>(y) / height * 2 - 1) * aspectRatio);
image->setPixelAt(x, y, clamped(scene->traceRay(ray)));
// Super hacky progress bar!
if (++*k % stepSize == 0) {
std::cout << "=" << std::flush;
}
}
}
}
Texture SimpleRenderer::renderImage(Scene const &scene, Camera const &camera, int width, int height) { Texture SimpleRenderer::renderImage(Scene const &scene, Camera const &camera, int width, int height) {
Texture image(width, height); Texture image(width, height);
@ -22,22 +38,24 @@ Texture SimpleRenderer::renderImage(Scene const &scene, Camera const &camera, in
for (int i = 0; i < barSize - 3 - 5; ++i) for (int i = 0; i < barSize - 3 - 5; ++i)
std::cout << " "; std::cout << " ";
std::cout << "100% |" << std::endl << "|"; std::cout << "100% |" << std::endl << "|";
int k = 0; std::atomic<int> k(0);
// Start timer // Start timer
start = std::chrono::steady_clock::now(); start = std::chrono::steady_clock::now();
float const aspectRatio = static_cast<float>(height) / width; // Spawn a thread for every logical processor -1, calling the renderThread function
for (int x = 0; x < image.width(); ++x) { int const nThreads = std::thread::hardware_concurrency();
for (int y = 0; y < image.height(); ++y) { std::vector<std::thread> threads;
Ray ray = camera.createRay((static_cast<float>(x) / width * 2 - 1), -(static_cast<float>(y) / height * 2 - 1) * aspectRatio); for (int t = 0; t < nThreads - 1; ++t) {
image.setPixelAt(x, y, clamped(scene.traceRay(ray))); threads.emplace_back(renderThread, &scene, &camera, &image, width, nThreads, t, height, 1, 0, &k, stepSize);
}
// Super hacky progress bar! // Call the renderThread function yourself
if (++k % stepSize == 0) { renderThread(&scene, &camera, &image, width, nThreads, nThreads - 1, height, 1, 0, &k, stepSize);
std::cout << "=" << std::flush;
} // Rejoin the threads
} for (int t = 0; t < nThreads - 1; ++t) {
threads[t].join();
} }
// Stop timer // Stop timer

View file

@ -1,9 +1,13 @@
#ifndef SIMPLERENDERER_H #ifndef SIMPLERENDERER_H
#define SIMPLERENDERER_H #define SIMPLERENDERER_H
#include <atomic>
#include "renderer/renderer.h" #include "renderer/renderer.h"
class SimpleRenderer : public Renderer { class SimpleRenderer : public Renderer {
static void renderThread(const Scene *scene, Camera const *camera, Texture *image, int width, int widthStep,
int widthOffset, int height, int heightStep, int heightOffset, std::atomic<int> *k,
int const stepSize);
public: public:
// Constructor / Destructor // Constructor / Destructor

View file

@ -5,69 +5,218 @@
#include <iostream> #include <iostream>
int Node::countNodeIntersections(const Ray &ray, float t0, float t1) const { int Node::countNodeIntersections(const Ray &ray, float t0, float t1) const {
// IMPLEMENT ME // If this is a leaf node, we return 0
if (isLeaf()) {
return 0; return 0;
} else {
// Determine the order in which we intersect the child nodes
float const d = (this->split - ray.origin[this->dimension]) / ray.direction[this->dimension];
int front = ray.direction[this->dimension] < 0 ? 1 : 0;
int back = 1 - front;
if (d <= t0 || d < 0) {
// t0..t1 is totally behind d, only go through the back node.
return this->child[back]->countNodeIntersections(ray, t0, t1);
} else if (d >= t1) {
// t0..t1 is totally in front of d, only go to front node.
return this->child[front]->countNodeIntersections(ray, t0, t1);
} else {
// Traverse *both* children. Front node first, back node last.
// Be sure to get even triangles which are coincident with the splitting plane
return 1 + this->child[front]->countNodeIntersections(ray, t0, d + SPLT_EPS) + this->child[back]->countNodeIntersections(ray, d - SPLT_EPS, t1);
}
}
} }
bool Node::findIntersection(Ray &ray, float t0, float t1) const { bool Node::findIntersection(Ray &ray, float t0, float t1) const {
// IMPLEMENT ME
// If this is a leaf node, we intersect with all the primitives... // If this is a leaf node, we intersect with all the primitives...
// ... otherwise we continue through the branches if (isLeaf()) {
bool hit = false;
for (const auto &primitive : this->primitives)
hit |= primitive->intersect(ray);
return hit;
} else { // ... otherwise we continue through the branches
// Determine the order in which we intersect the child nodes // Determine the order in which we intersect the child nodes
// Traverse the necessary children float const d = (this->split - ray.origin[this->dimension]) / ray.direction[this->dimension];
return false; int front = ray.direction[this->dimension] < 0 ? 1 : 0;
int back = 1 - front;
if (d <= t0 || d < 0) {
// t0..t1 is totally behind d, only go through the back node.
return this->child[back]->findIntersection(ray, t0, t1);
} else if (d >= t1) {
// t0..t1 is totally in front of d, only go to front node.
return this->child[front]->findIntersection(ray, t0, t1);
} else {
// Traverse *both* children. Front node first and then back node, if no hit in front of splitting plane.
// for whatever reason, this doesn't work in the fireplace scene?!
return ((this->child[front]->findIntersection(ray, t0 - SPLT_EPS, d + SPLT_EPS) && ray.length <= d + SPLT_EPS) || this->child[back]->findIntersection(ray, d - SPLT_EPS, t1 + SPLT_EPS));
// return (this->child[front]->findIntersection(ray, t0 - SPLT_EPS, d + SPLT_EPS) | this->child[back]->findIntersection(ray, d - SPLT_EPS, t1 + SPLT_EPS));
}
}
} }
bool Node::findOcclusion(Ray &ray, float t0, float t1) const { bool Node::findOcclusion(Ray &ray, float t0, float t1) const {
// IMPLEMENT ME // If this is a leaf node, we intersect with all the primitives...
if (isLeaf()) {
float const rayLength = ray.length;
for (const auto &primitive : this->primitives)
// since this is correct, but terribly slow:...
// if (!primitive->shader()->isTransparent() && primitive->intersect(ray))
// return true;
// we do it this way:
if (primitive->intersect(ray)) {
if (!primitive->shader()->isTransparent())
return true;
else
ray.length = rayLength;
}
return false; return false;
} else { // ... otherwise we continue through the branches
// Determine the order in which we intersect the child nodes
float const d = (this->split - ray.origin[this->dimension]) / ray.direction[this->dimension];
int front = ray.direction[this->dimension] < 0 ? 1 : 0;
int back = 1 - front;
if (d <= t0 || d < 0) {
// t0..t1 is totally behind d, only go through the back node.
return this->child[back]->findOcclusion(ray, t0, t1);
} else if (d >= t1) {
// t0..t1 is totally in front of d, only go to front node.
return this->child[front]->findOcclusion(ray, t0, t1);
} else {
// Traverse *both* children. Front node first and then back node, if no hit in front of splitting plane.
// for whatever reason, this doesn't work in the fireplace scene?!
return ((this->child[front]->findOcclusion(ray, t0, d + SPLT_EPS) && ray.length <= d + SPLT_EPS) || this->child[back]->findOcclusion(ray, d - SPLT_EPS, t1));
// return (this->child[front]->findOcclusion(ray, t0, d + SPLT_EPS) | this->child[back]->findOcclusion(ray, d - SPLT_EPS, t1));
}
}
} }
int FastScene::countNodeIntersections(const Ray &ray) const { int FastScene::countNodeIntersections(const Ray &ray) const {
// IMPLEMENT ME // Make sure the tree is set up
if (!this->root)
return false; return false;
// Bounding box intersection
Vector3d const min = componentQuotient(this->absoluteMinimum - ray.origin, ray.direction);
Vector3d const max = componentQuotient(this->absoluteMaximum - ray.origin, ray.direction);
float const tMin = std::max(std::max(std::min(min.x, max.x), std::min(min.y, max.y)), std::min(min.z, max.z));
float const tMax = std::min(std::min(std::max(min.x, max.x), std::max(min.y, max.y)), std::max(min.z, max.z));
// Traverse the tree recursively
if (0 <= tMax && tMin <= tMax)
return this->root->countNodeIntersections(ray, tMin, tMax);
else
return 0;
} }
bool FastScene::findIntersection(Ray &ray) const { bool FastScene::findIntersection(Ray &ray) const {
// IMPLEMENT ME // Make sure the tree is set up
if (!this->root)
return false;
// Bounding box intersection
Vector3d const min = componentQuotient(this->absoluteMinimum - ray.origin, ray.direction);
Vector3d const max = componentQuotient(this->absoluteMaximum - ray.origin, ray.direction);
float const tMin = std::max(std::max(std::min(min.x, max.x), std::min(min.y, max.y)), std::min(min.z, max.z));
float const tMax = std::min(std::min(std::max(min.x, max.x), std::max(min.y, max.y)), std::max(min.z, max.z));
// Traverse the tree recursively
if (0 <= tMax && tMin <= tMax)
return this->root->findIntersection(ray, tMin, tMax);
else
return false; return false;
} }
bool FastScene::findOcclusion(Ray &ray) const { bool FastScene::findOcclusion(Ray &ray) const {
// IMPLEMENT ME // Make sure the tree is set up
if (!this->root)
return false;
// Bounding box intersection
Vector3d const min = componentQuotient(this->absoluteMinimum - ray.origin, ray.direction);
Vector3d const max = componentQuotient(this->absoluteMaximum - ray.origin, ray.direction);
float const tMin = std::max(std::max(std::min(min.x, max.x), std::min(min.y, max.y)), std::min(min.z, max.z));
float const tMax = std::min(std::min(std::max(min.x, max.x), std::max(min.y, max.y)), std::max(min.z, max.z));
// Traverse the tree recursively
if (0 <= tMax && tMin <= tMax)
return this->root->findOcclusion(ray, tMin, tMax);
else
return false; return false;
} }
void FastScene::buildTree(int maximumDepth, int minimumNumberOfPrimitives) { void FastScene::buildTree(int maximumDepth, int minimumNumberOfPrimitives) {
// IMPLEMENT ME
// Set the new depth and number of primitives // Set the new depth and number of primitives
this->maximumDepth = maximumDepth;
this->minimumNumberOfPrimitives = minimumNumberOfPrimitives;
// Determine the bounding box of the kD-Tree // Determine the bounding box of the kD-Tree
this->absoluteMinimum = Vector3d(+INFINITY, +INFINITY, +INFINITY);
this->absoluteMaximum = Vector3d(-INFINITY, -INFINITY, -INFINITY);
for (const auto &primitive : this->primitives()) {
for (int d = 0; d < 3; ++d) {
this->absoluteMinimum[d] = std::min(this->absoluteMinimum[d], primitive->minimumBounds(d));
this->absoluteMaximum[d] = std::max(this->absoluteMaximum[d], primitive->maximumBounds(d));
}
}
// Recursively build the kD-Tree // Recursively build the kD-Tree
root = this->build(this->absoluteMinimum, this->absoluteMaximum, this->primitives(), 0);
std::cout << "(FastScene): " << this->primitives().size() << " primitives organized into tree" << std::endl;
} }
std::unique_ptr<Node> FastScene::build(Vector3d const &minimumBounds, Vector3d const &maximumBounds, const std::vector<std::shared_ptr<Primitive>> &primitives, int depth) { std::unique_ptr<Node> FastScene::build(Vector3d const &minimumBounds, Vector3d const &maximumBounds, const std::vector<std::shared_ptr<Primitive>> &primitives, int depth) {
// IMPLEMENT ME
// Determine the diameter of the bounding box // Determine the diameter of the bounding box
Vector3d const diameter = maximumBounds - minimumBounds;
// Test whether we have reached a leaf node... // Test whether we have reached a leaf node...
int minimumDimension = ((diameter.x < diameter.y) ? ((diameter.x < diameter.z) ? 0 : 2) : ((diameter.y < diameter.z) ? 1 : 2));
if (depth >= this->maximumDepth || (int)primitives.size() <= this->minimumNumberOfPrimitives || (diameter[minimumDimension]) <= EPSILON) {
auto leafNode = std::make_unique<Node>();
leafNode->primitives = primitives;
return leafNode;
}
// ... otherwise create a new inner node by splitting through the widest // ... otherwise create a new inner node by splitting through the widest dimension
// dimension auto node = std::make_unique<Node>();
node->dimension = ((diameter.x > diameter.y) ? ((diameter.x > diameter.z) ? 0 : 2) : ((diameter.y > diameter.z) ? 1 : 2));
// Determine the split position // Determine the split position
// Note: Use the median of the minimum bounds of the primitives // Note: Use the median of the minimum bounds of the primitives
std::vector<float> minimumValues;
for (const auto &primitive : primitives)
minimumValues.push_back(primitive->minimumBounds(node->dimension));
std::sort(minimumValues.begin(), minimumValues.end());
node->split = minimumValues[minimumValues.size() / 2];
// Divide primitives into the left and right lists // Divide primitives into the left and right lists
// Remember: A primitive can be in both lists! // Remember: A primitive can be in both lists!
// Also remember: You split exactly at the minimum of a primitive, // Also remember: You split exactly at the minimum of a primitive,
// make sure *that* primitive does *not* appear in both lists! // make sure *that* primitive does *not* appear in both lists!
std::vector<std::shared_ptr<Primitive>> leftPrimitives, rightPrimitives;
for (const auto &primitive : primitives) {
if (primitive->minimumBounds(node->dimension) < node->split)
leftPrimitives.push_back(primitive);
if (primitive->maximumBounds(node->dimension) >= node->split)
rightPrimitives.push_back(primitive);
}
// Print out the number of primitives in the left and right child node // Print out the number of primitives in the left and right child node
// std::cout << "(FastScene): Split " << leftPrimitives.size() << " | " << rightPrimitives.size() << std::endl;
// Set the left and right split vectors // Set the left and right split vectors
Vector3d minimumSplit = minimumBounds;
Vector3d maximumSplit = maximumBounds;
minimumSplit[node->dimension] = node->split;
maximumSplit[node->dimension] = node->split;
// Recursively build the tree // Recursively build the tree
return nullptr; depth += 1;
node->child[0] = this->build(minimumBounds, maximumSplit, leftPrimitives, depth);
node->child[1] = this->build(minimumSplit, maximumBounds, rightPrimitives, depth);
return node;
} }