accelerated-raytracer/src/Scene.cpp

224 lines
10 KiB
C++

#include "Scene.h"
#include "../thirdparty/OBJ_Loader.h"
#include "Sphere.h"
#include <QApplication>
#include <QMatrix4x4>
#include <QQuaternion>
raytry::Scene::Scene() {
background.emplace_back(ground);
for (int i = 0; i < spheres.size(); ++i) {
float x = -5.f + static_cast<float>(i);
float spec = static_cast<float>(i) / static_cast<float>(spheres.size() - 1);
auto mat = RenderMaterial{{197, 52, 27}};
mat.specularIntensity = spec;
spheres[i] = Sphere(camera.pos() + camera.fwd() * 5 + QVector3D(x, 0.f, 0.f), 0.4f, mat);
background.emplace_back(spheres[i]);
}
const auto &triangleTransformation = camera.pos() + camera.fwd() * 10;
const auto &triangleBase = camera.pos() + camera.fwd() * 10;
triangles[0] = Triangle(QVector3D{triangleBase[0] - 3, triangleBase[1], 0.0},
QVector3D{triangleBase[0] + 3, triangleBase[1], 0.0},
QVector3D{triangleBase[0], triangleBase[1], 6.0});
background.emplace_back(triangles[0]);
QMatrix4x4 transformation{};
transformation.translate(camera.pos() + (camera.fwd() * 5));
transformation.rotate(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f));
transformation.rotate(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f));
std::setlocale(LC_ALL, "C");// the obj parser uses locale-specific number format...
objl::Loader objLoader{};
if (!objLoader.LoadFile("newell_teaset/teapot.obj")) {
qWarning() << "Could not load teapot..";
} else {
auto mat = RenderMaterial{{37, 96, 185}};
mat.transparency = 0.2f;
mat.reflectivity = 0.5f;
teapotMesh.reserve(teapotMesh.size() + (objLoader.LoadedIndices.size() / 3));
auto iter = objLoader.LoadedIndices.begin();
while (iter != objLoader.LoadedIndices.end()) {
auto a = objLoader.LoadedVertices[*iter++];
auto b = objLoader.LoadedVertices[*iter++];
auto c = objLoader.LoadedVertices[*iter++];
QVector3D qta{a.Position.X, a.Position.Y, a.Position.Z};
QVector3D qtb{b.Position.X, b.Position.Y, b.Position.Z};
QVector3D qtc{c.Position.X, c.Position.Y, c.Position.Z};
Triangle& tri = teapotMesh.emplace_back(transformation.map(qta), transformation.map(qtb), transformation.map(qtc));
tri.setMaterial(mat);
}
}
qInfo() << "Loaded teapot with" << teapotMesh.size() << "triangles.";
foregroundTree = KD::Tree::build(teapotMesh);
}
std::list<std::pair<std::reference_wrapper<const raytry::RenderObject>, float>> raytry::Scene::findIntersections(const raytry::Ray &ray) {
std::list<std::pair<std::reference_wrapper<const RenderObject>, float>> matches{};
if (!foregroundTree.getNodes().empty()) {
const QVector3D invDirection{1 / ray.directionVec.x(), 1 / ray.directionVec.y(), 1 / ray.directionVec.z()};
const std::optional<std::pair<QVector3D, QVector3D>> &treeHit = foregroundTree.getBounds().fastslabtest(ray, invDirection);
if (treeHit) {
// KD!
const KD::Node *node = foregroundTree.getNodes().data();
float tMin = treeHit->first[node->getAxis()];
float tMax = treeHit->second[node->getAxis()];
unsigned int currentNodeIdx = 0;
std::list<std::tuple<unsigned int, float, float>> nodes{};
while (node != nullptr) {
if (node->isLeaf()) {
assert(node->farIdx() >= 0);
assert(node->farIdx() < foregroundTree.getLeafs().size());
for (const auto &item: foregroundTree.getLeafs()[node->farIdx()].triangles) {
const auto distance = item->intersects(ray);
++triangleIntersections;
if (distance) {
bool emplaced = false;
const RenderObject &triangle = *item;
for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) {
if ((*mpos).second > distance.value()) {
matches.emplace(mpos, triangle, distance.value());
emplaced = true;
}
}
if (!emplaced) {
matches.emplace_back(triangle, distance.value());
}
}
}
if (nodes.empty()) {
node = nullptr;
} else {
std::tie(currentNodeIdx, tMin, tMax) = nodes.front();
assert(currentNodeIdx < foregroundTree.getNodes().size());
node = &foregroundTree.getNodes()[currentNodeIdx];
nodes.pop_front();
}
} else {
const auto tPlane = (node->val() - ray.originVec[node->getAxis()]) * invDirection[node->getAxis()];
unsigned int firstChild, secondChild;
int belowFirst = (ray.originVec[node->getAxis()] < node->val()) ||
(ray.originVec[node->getAxis()] == node->val() && ray.directionVec[node->getAxis()] <= 0);
if (belowFirst) {
firstChild = currentNodeIdx + 1;
secondChild = currentNodeIdx + node->farIdx();
} else {
firstChild = currentNodeIdx + node->farIdx();
secondChild = currentNodeIdx + 1;
}
if (tPlane > tMax || tPlane <= 0) {
currentNodeIdx = firstChild;
} else if (tPlane < tMin) {
currentNodeIdx = secondChild;
} else {
nodes.emplace_back(secondChild, tPlane, tMax);
currentNodeIdx = firstChild;
tMax = tPlane;
}
node = &foregroundTree.getNodes()[currentNodeIdx];
}
}
}
}
for (RenderObject &obj: background) {
const auto distance = obj.intersects(ray);
if (distance) {
bool emplaced = false;
for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) {
if ((*mpos).second > distance.value()) {
matches.emplace(mpos, obj, distance.value());
emplaced = true;
}
}
if (!emplaced) {
matches.emplace_back(obj, distance.value());
}
}
}
return matches;
}
void raytry::Scene::rayTrace(const raytry::Ray &ray, unsigned int depth, QColor& color) {
if (depth > maxDepth) {
color = QColor{0, 0, 0};
} else {
auto matches = findIntersections(ray);
if (matches.empty()) {
color = backgroundColor;
} else {
auto [object, distance] = matches.front();
QVector3D hitpoint = ray.originVec + (ray.directionVec * distance);
const QVector3D &normal = object.get().calculateNormal(hitpoint);
assert(normal.normalized() == normal);
QVector3D toLight = pointLight - hitpoint;
float lightDistance = toLight.length();
toLight.normalize();
const Ray &shadowRay{hitpoint, toLight};
auto shadowMatches = findIntersections(shadowRay);
float lightSourceIntensity = 1.0f;
if (!shadowMatches.empty()) {
for (const auto &occlusion: shadowMatches) {
if (occlusion.second > lightDistance) {
break;
}
lightSourceIntensity *= occlusion.first.get().material().transparency;
}
}
const RenderMaterial &material = object.get().material();
assert(globalIllumination >= 0 && globalIllumination <= 1);
color = mixColor(material.ambientColor, material.ambientIntensity * globalIllumination);
const float &diffusionModifier = std::max(0.0f, QVector3D::dotProduct(normal, toLight));
color = addColors(color, mixColor(mixColor(mixColor(material.diffuseColor, diffusionModifier), material.diffuseIntensity), lightSourceIntensity));
if (material.reflectivity > 0) {
const Ray &reflectionRay = material.reflectionRay(ray, hitpoint, normal);
QColor reflectionColor{};
rayTrace(reflectionRay, depth + 1u, reflectionColor);
color = addColors(mixColor(color, 1 - material.reflectivity), mixColor(reflectionColor, material.reflectivity));
}
const QVector3D &fromLight = (hitpoint - pointLight).normalized();
const QVector3D &lightReflection = fromLight - (2 * QVector3D::dotProduct(fromLight, normal) * normal);
const float &specularModifier = std::pow(std::max(0.0f, QVector3D::dotProduct(lightReflection, -ray.directionVec)), material.specularFalloff);
color = addColors(color, mixColor(mixColor(mixColor(material.specularColor, specularModifier), material.specularIntensity), lightSourceIntensity));
if (material.transparency > 0) {
const std::optional<Ray> &optionalRefractionRay = material.refractionRay(ray, hitpoint, normal);
QColor refractionColor{backgroundColor};
if (optionalRefractionRay) {
rayTrace(optionalRefractionRay.value(), depth + 1u, refractionColor);
}
color = addColors(mixColor(color, 1 - material.transparency), mixColor(refractionColor, material.transparency));
}
}
}
}
void raytry::Scene::render(raytry::ViewerWindow &window, std::function<void()> updateImage) {
size_t rayC = 0;
QImage &img = window.getDisplayImage();
unsigned int pixels = img.width() * img.height();
camera.for_each_ray(img, [&](int x, int y, Ray r) {
++rayC;
if (rayC % (pixels / 20) == 0) {
qInfo() << "Raytracing #" << rayC << "/" << pixels << "(" << rayC * 100 / pixels << "%)";
}
QColor color{};
rayTrace(r, 0, color);
img.setPixelColor(x, y, color);
if (rayC % (pixels / 1000) == 0) {
updateImage();
}
});
qInfo() << "All pixels computed. Tested" << triangleIntersections << "ray-triangle intersections.";
}