diff --git a/CMakeLists.txt b/CMakeLists.txt index 341f1ab..db933ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ find_package(imgui CONFIG REQUIRED) add_executable(RayTracer "") target_include_directories(RayTracer PUBLIC src/) +target_include_directories(RayTracer PUBLIC ${VCPKG_INSTALLED_DIR}/x64-windows/include) target_link_libraries(RayTracer PUBLIC Vulkan::Vulkan) target_link_libraries(RayTracer PUBLIC Vulkan::Headers) target_link_libraries(RayTracer PUBLIC assimp::assimp) @@ -29,5 +30,6 @@ target_link_libraries(RayTracer PUBLIC imgui::imgui) target_link_libraries(RayTracer PUBLIC GLEW::GLEW) target_link_libraries(RayTracer PUBLIC glm::glm) target_link_libraries(RayTracer PUBLIC KTX::ktx) +target_link_libraries(RayTracer PUBLIC ${VCPKG_INSTALLED_DIR}/x64-windows/lib/slang.lib) add_subdirectory(src/) \ No newline at end of file diff --git a/src/ThreadPool.cpp b/src/ThreadPool.cpp index b61170b..df0f0b3 100644 --- a/src/ThreadPool.cpp +++ b/src/ThreadPool.cpp @@ -22,6 +22,22 @@ ThreadPool::~ThreadPool() } } +void ThreadPool::cancel() +{ + { + std::unique_lock l(queueLock); + numRemaining = numRunning; + taskQueue.clear(); + } + while (true) + { + std::unique_lock l(queueLock); + if (taskQueue.empty()) + return; + completedCV.wait(l); + } +} + void ThreadPool::runBatch(Batch&& batch) { { @@ -53,11 +69,13 @@ void ThreadPool::work() } job = taskQueue.front().jobs.front(); taskQueue.front().jobs.pop_front(); + numRunning++; } job.handle(); { std::unique_lock l(queueLock); numRemaining--; + numRunning--; if (numRemaining == 0) { taskQueue.pop_front(); diff --git a/src/ThreadPool.h b/src/ThreadPool.h index b5832b3..9f079e4 100644 --- a/src/ThreadPool.h +++ b/src/ThreadPool.h @@ -15,6 +15,9 @@ class ThreadPool public: ThreadPool(uint32_t numWorkers = std::thread::hardware_concurrency()); ~ThreadPool(); + + // cancel running jobs + void cancel(); void runBatch(Batch&& batch); private: std::atomic_bool running = true; @@ -23,6 +26,7 @@ private: std::condition_variable queueCV; std::condition_variable completedCV; uint32_t numRemaining; + uint32_t numRunning; std::list taskQueue; std::vector workers; }; \ No newline at end of file diff --git a/src/gpu/Renderer.cpp b/src/gpu/Renderer.cpp index a63fad7..45efed7 100644 --- a/src/gpu/Renderer.cpp +++ b/src/gpu/Renderer.cpp @@ -1,4 +1,6 @@ #include "Renderer.h" +#include +#include Renderer::Renderer() : instance(nullptr), physicalDevice(nullptr), device(nullptr), queue(nullptr), cmdPool(nullptr), cmdBuffers(nullptr), @@ -10,7 +12,8 @@ Renderer::Renderer() Renderer::~Renderer() {} -void Renderer::createDevice() { +void Renderer::createDevice() +{ vk::ApplicationInfo appInfo("RayTracer", 1, "RayTracer", 1, VK_API_VERSION_1_3); vk::InstanceCreateInfo instanceCreateInfo({}, &appInfo); instance = Instance(context, instanceCreateInfo); @@ -52,7 +55,8 @@ void Renderer::createCommands() cmdBuffers = vk::raii::CommandBuffers(device, commandBufferAllocateInfo); } -void Renderer::createDescriptors() { +void Renderer::createDescriptors() +{ vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex); vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo({}, descriptorSetLayoutBinding); descriptorLayout = DescriptorSetLayout(device, descriptorSetLayoutCreateInfo); @@ -62,8 +66,44 @@ void Renderer::createDescriptors() { pipelineLayout = PipelineLayout(device, pipelineLayoutCreateInfo); } -void Renderer::createShaders() { - +using namespace slang; + +void Renderer::createShaders() +{ + Slang::ComPtr globalSession; + SlangGlobalSessionDesc desc = {}; + createGlobalSession(&desc, globalSession.writeRef()); + SessionDesc sessionDesc; + TargetDesc targetDesc; + targetDesc.format = SLANG_SPIRV; + targetDesc.profile = globalSession->findProfile("glsl_450"); + sessionDesc.targets = &targetDesc; + sessionDesc.targetCount = 1; + const char* searchPaths[] = {"res/shaders/"}; + sessionDesc.searchPaths = searchPaths; + sessionDesc.searchPathCount = 1; + /* ... fill in `sessionDesc` ... */ + Slang::ComPtr session; + globalSession->createSession(sessionDesc, session.writeRef()); + + Slang::ComPtr diagnostics; + IModule* module = session->loadModule("MyShaders", diagnostics.writeRef()); + if (diagnostics) + { + std::cout << (const char*)diagnostics->getBufferPointer() << std::endl; + } + Slang::ComPtr computeEntryPoint; + module->findEntryPointByName("myComputeMain", computeEntryPoint.writeRef()); + IComponentType* components[] = {module, computeEntryPoint}; + Slang::ComPtr program; + session->createCompositeComponentType(components, 2, program.writeRef()); + Slang::ComPtr linkedProgram; + Slang::ComPtr diagnosticBlob; + program->link(linkedProgram.writeRef(), diagnosticBlob.writeRef()); + int entryPointIndex = 0; // only one entry point + int targetIndex = 0; // only one target + Slang::ComPtr kernelBlob; + linkedProgram->getEntryPointCode(entryPointIndex, targetIndex, kernelBlob.writeRef(), diagnostics.writeRef()); } void Renderer::render(Camera cam, RenderParameter param) {} \ No newline at end of file diff --git a/src/scene/AABB.h b/src/scene/AABB.h index 50479d7..1d0d678 100644 --- a/src/scene/AABB.h +++ b/src/scene/AABB.h @@ -44,7 +44,7 @@ struct AABB max = glm::vec3(std::max(max.x, transformed.x), std::max(max.y, transformed.y), std::max(max.z, transformed.z)); } } - bool intersects(Ray ray, float tmin, float tmax) + bool intersects(Ray ray, float tmin, float tmax) const { glm::vec3 invD = 1.0f / ray.direction; glm::vec3 t0s = glm::vec3(min - ray.origin) * invD; diff --git a/src/scene/BVH.cpp b/src/scene/BVH.cpp index 0a84f7c..77ec76d 100644 --- a/src/scene/BVH.cpp +++ b/src/scene/BVH.cpp @@ -4,110 +4,102 @@ void BVH::addModel(PModel model, glm::mat4 transform) { - model->boundingBox.transform(transform); - for (auto& point : model->positions) - { - point = glm::vec3(transform * glm::vec4(point, 1)); - } - models.push_back(std::move(model)); + model->transform(transform); + models.push_back(std::move(model)); } void BVH::addModels(std::vector _models, glm::mat4 transform) { - for (auto & _model : _models) - { - _model->boundingBox.transform(transform); - for (auto& point : _model->positions) - { - point = glm::vec3(transform * glm::vec4(point, 1)); - } - models.push_back(std::move(_model)); - } + for (auto& _model : _models) + { + _model->transform(transform); + models.push_back(std::move(_model)); + } } void BVH::generate() { - std::vector pendingNodes; - while (!models.empty()) + std::vector pendingNodes; + while (!models.empty()) + { + pendingNodes.push_back(std::make_unique(std::move(models.back()))); + models.pop_back(); + } + while (pendingNodes.size() > 1) + { + int lhs = pendingNodes.size(); + int rhs = pendingNodes.size(); + float minSurface = std::numeric_limits::max(); + for (int i = 0; i < pendingNodes.size(); ++i) { - pendingNodes.push_back(std::make_unique(std::move(models.back()))); - models.pop_back(); - } - while (pendingNodes.size() > 1) - { - int lhs = pendingNodes.size(); - int rhs = pendingNodes.size(); - float minSurface = std::numeric_limits::max(); - for (int i = 0; i < pendingNodes.size(); ++i) + for (int j = 0; j < pendingNodes.size(); ++j) + { + if (i == j) + continue; + AABB combined = AABB::combine(pendingNodes[i]->aabb, pendingNodes[j]->aabb); + float surface = combined.surfaceArea(); + if (minSurface > surface) { - for (int j = 0; j < pendingNodes.size(); ++j) - { - if (i == j) - continue; - AABB combined = AABB::combine(pendingNodes[i]->aabb, pendingNodes[j]->aabb); - float surface = combined.surfaceArea(); - if (minSurface > surface) - { - lhs = i; - rhs = j; - minSurface = surface; - } - } + lhs = i; + rhs = j; + minSurface = surface; } - PNode newNode = std::make_unique(AABB::combine(pendingNodes[lhs]->aabb, pendingNodes[rhs]->aabb)); - newNode->left = std::move(pendingNodes[lhs]); - newNode->right = std::move(pendingNodes[rhs]); - pendingNodes.erase(pendingNodes.begin() + lhs); - pendingNodes.erase(pendingNodes.begin() + rhs); - pendingNodes.push_back(std::move(newNode)); + } } - hierarchy = std::move(pendingNodes[0]); + PNode newNode = std::make_unique(AABB::combine(pendingNodes[lhs]->aabb, pendingNodes[rhs]->aabb)); + newNode->left = std::move(pendingNodes[lhs]); + newNode->right = std::move(pendingNodes[rhs]); + pendingNodes.erase(pendingNodes.begin() + lhs); + pendingNodes.erase(pendingNodes.begin() + rhs); + pendingNodes.push_back(std::move(newNode)); + } + hierarchy = std::move(pendingNodes[0]); } -std::optional BVH::traceRay(Ray ray) +std::optional BVH::traceRay(Ray ray) const { - auto results = generateIntersections(hierarchy, ray); - float closestT = std::numeric_limits::max(); - IntersectionInfo info; - for (uint32_t i = 0; i < results.size(); ++i) + auto results = generateIntersections(hierarchy, ray); + float closestT = std::numeric_limits::max(); + IntersectionInfo info; + for (uint32_t i = 0; i < results.size(); ++i) + { + if (results[i].t < closestT) { - if (results[i].t < closestT) - { - closestT = results[i].t; - info = results[i]; - } - } - if (closestT < std::numeric_limits::max()) - { - return info; + closestT = results[i].t; + info = results[i]; } + } + if (closestT < std::numeric_limits::max()) + { + return info; + } + return {}; +} + +std::vector BVH::generateIntersections(const PNode& currentNode, Ray ray) const +{ + if (!currentNode->aabb.intersects(ray, 0, std::numeric_limits::max())) + { return {}; -} + } + if (currentNode->model != nullptr) + { + auto result = currentNode->model->intersect(ray); + if (result.has_value()) + { + return {*result}; + } + else + { + return {}; + } + } + auto leftResults = generateIntersections(currentNode->left, ray); + auto rightResults = generateIntersections(currentNode->right, ray); -std::vector BVH::generateIntersections(PNode& currentNode, Ray ray) -{ - if (!currentNode->aabb.intersects(ray, 0, std::numeric_limits::max())) - { - return {}; - } - if (currentNode->model != nullptr) - { - auto result = currentNode->model->intersect(ray); - if (result.has_value()) - { - return {*result}; - } - else - { - return {}; - } - } - auto leftResults = generateIntersections(currentNode->left, ray); - auto rightResults = generateIntersections(currentNode->right, ray); - - for (auto& it : rightResults) - { - leftResults.push_back(std::move(it)); - } - return leftResults; + for (auto& it : rightResults) + { + leftResults.push_back(std::move(it)); + } + return leftResults; } \ No newline at end of file diff --git a/src/scene/BVH.h b/src/scene/BVH.h index 0ad7beb..be8fe2d 100644 --- a/src/scene/BVH.h +++ b/src/scene/BVH.h @@ -1,34 +1,33 @@ #pragma once #include "AABB.h" #include "util/Model.h" -#include -#include #include "util/Ray.h" +#include #include - +#include class BVH { - public: - void addModel(PModel model, glm::mat4 transform); - void addModels(std::vector models, glm::mat4 transform); - void generate(); +public: + void addModel(PModel model, glm::mat4 transform); + void addModels(std::vector models, glm::mat4 transform); + void generate(); - std::optional traceRay(Ray ray); + std::optional traceRay(Ray ray) const; - private: - DECLARE_REF(Node) - struct Node - { - PNode left; - PNode right; - AABB aabb; - PModel model; - Node(AABB aabb) : aabb(aabb) {} - Node(PModel model) : aabb(model->boundingBox), model(std::move(model)) {} - }; - PNode hierarchy; - std::vector models; +private: + DECLARE_REF(Node) + struct Node + { + PNode left; + PNode right; + AABB aabb; + PModel model; + Node(AABB aabb) : aabb(aabb) {} + Node(PModel model) : aabb(model->boundingBox), model(std::move(model)) {} + }; + PNode hierarchy; + std::vector models; - std::vector generateIntersections(PNode& currentNode, Ray ray); + std::vector generateIntersections(const PNode& currentNode, Ray ray) const; }; \ No newline at end of file diff --git a/src/scene/Scene.cpp b/src/scene/Scene.cpp index 2e58f82..a3d426e 100644 --- a/src/scene/Scene.cpp +++ b/src/scene/Scene.cpp @@ -17,13 +17,7 @@ Scene::~Scene() {} static bool firstTime = true; void Scene::startRender(Camera cam, RenderParameter params) { - if (!firstTime) - { - pendingCancel = true; - worker.join(); - firstTime = false; - } - pendingCancel = false; + threadPool.cancel(); image.clear(); accumulator.clear(); image.resize(params.width * params.height); @@ -31,22 +25,23 @@ void Scene::startRender(Camera cam, RenderParameter params) worker = std::thread(&Scene::render, this, cam, params); } +glm::vec3 rand01(glm::uvec3 x) +{ // pseudo-random number generator + for (int i = 3; i-- > 0;) + x = ((x >> 8U) ^ glm::uvec3(x.y, x.z, x.x)) * 1103515245U; + return glm::vec3(x) * (1.0f / float(0xffffffffU)); +} + void Scene::render(Camera camera, RenderParameter params) { - std::random_device rd; for (int samp = 0; samp < params.numSamples; ++samp) { - if (pendingCancel) - return; Batch batch; for (int w = 0; w < params.width; ++w) { batch.jobs.push_back( - [&](int w) -> Task + [&](int w, int samp) -> Task { - std::mt19937 gen(rd()); - std::uniform_real_distribution rnd01(0.0, 1.0); - std::uniform_real_distribution rnd02(0.0, 2.0); for (int h = 0; h < params.height; ++h) { Ray cam = Ray(camera.position, glm::normalize(camera.direction)); @@ -59,7 +54,8 @@ void Scene::render(Camera camera, RenderParameter params) //-- sample sensor glm::uvec2 pix = glm::uvec2(w, h); - glm::vec2 rnd2 = glm::vec2(rnd02(gen), rnd02(gen)); // vvv tent filter sample + glm::vec3 rnd1 = rand01(glm::uvec3(pix, samp)); + glm::vec2 rnd2 = 2.0f * glm::vec2(rnd1); // vvv tent filter sample glm::vec2 tent = glm::vec2(rnd2.x < 1 ? sqrt(rnd2.x) - 1 : 1 - sqrt(2 - rnd2.x), rnd2.y < 1 ? sqrt(rnd2.y) - 1 : 1 - sqrt(2 - rnd2.y)); glm::vec2 s = @@ -76,7 +72,7 @@ void Scene::render(Camera camera, RenderParameter params) glm::vec3 lensX = glm::cross(lensN, glm::vec3(0, 1, 0)); // the exact vector doesnt matter glm::vec3 lensY = glm::cross(lensN, lensX); - glm::vec3 lensSample = lensP + rnd01(gen) * camera.A * lensX + rnd01(gen) * camera.A * lensY; + glm::vec3 lensSample = lensP + rnd1.x * camera.A * lensX + rnd1.y * camera.A * lensY; glm::vec3 focalPoint = cam.origin + (camera.S_O + S_I) * cam.direction; float t = glm::dot(focalPoint - r.origin, lensN) / glm::dot(r.direction, lensN); @@ -91,7 +87,7 @@ void Scene::render(Camera camera, RenderParameter params) } } co_return; - }(w)); + }(w, samp)); } auto start = std::chrono::high_resolution_clock::now(); threadPool.runBatch(std::move(batch)); diff --git a/src/scene/Scene.h b/src/scene/Scene.h index f4f3e53..ddd1c8f 100644 --- a/src/scene/Scene.h +++ b/src/scene/Scene.h @@ -11,6 +11,19 @@ struct RenderParameter int numSamples; }; +struct PointLight +{ + glm::vec3 position; + glm::vec3 color; + float attenuation; +}; + +struct DirectionalLight +{ + glm::vec3 direction; + glm::vec3 color; +}; + class Scene { public: @@ -20,12 +33,13 @@ class Scene constexpr const std::vector& getImage() const { return image; } private: virtual void render(Camera cam, RenderParameter params); - std::atomic_bool pendingCancel = false; ThreadPool threadPool; std::thread worker; // the thing being displayed std::vector image; // radiance accumulator std::vector accumulator; + std::vector pointLights; + std::vector directionalLights; BVH bvh; }; \ No newline at end of file diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index e154cb1..0ea555d 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -6,4 +6,8 @@ target_sources(RayTracer ModelLoader.h ModelLoader.cpp Ray.h + Texture.h + Texture.cpp + TextureLoader.h + TextureLoader.cpp ) \ No newline at end of file diff --git a/src/util/Model.cpp b/src/util/Model.cpp index 60e9417..acecbbb 100644 --- a/src/util/Model.cpp +++ b/src/util/Model.cpp @@ -1,6 +1,26 @@ #include "Model.h" -std::optional Model::intersect(const Ray ray) +void Model::transform(glm::mat4 matrix) +{ + for (auto& pos : positions) + { + pos = glm::vec3(matrix * glm::vec4(pos, 1)); + } + + boundingBox.transform(matrix); + + for (int i = 0; i < indices.size(); i+=3) + { + auto e0 = positions[indices[i + 1]] - positions[indices[i + 0]]; + auto e1 = positions[indices[i + 2]] - positions[indices[i + 0]]; + es.push_back(e0); + es.push_back(e1); + + faceNormals.push_back(glm::cross(e0, e1)); + } +} + +std::optional Model::intersect(const Ray ray) const { std::optional intersection = {}; float distance = 0; @@ -50,4 +70,5 @@ std::optional Model::intersect(const Ray ray) } return intersection; -} \ No newline at end of file +} + diff --git a/src/util/Model.h b/src/util/Model.h index 83160e9..424008f 100644 --- a/src/util/Model.h +++ b/src/util/Model.h @@ -1,8 +1,8 @@ #pragma once #include "Minimal.h" #include "scene/AABB.h" -#include #include +#include // material infos // shading parameter @@ -19,11 +19,12 @@ struct IntersectionInfo class Model { public: - AABB boundingBox; - std::vector positions; - std::vector indices; - std::vector es; - std::vector faceNormals; - std::optional intersect(Ray ray); + AABB boundingBox; + std::vector positions; + std::vector indices; + std::vector es; + std::vector faceNormals; + void transform(glm::mat4 matrix); + std::optional intersect(Ray ray) const; }; DECLARE_REF(Model) \ No newline at end of file diff --git a/src/util/ModelLoader.cpp b/src/util/ModelLoader.cpp index 02a8954..39f0dbe 100644 --- a/src/util/ModelLoader.cpp +++ b/src/util/ModelLoader.cpp @@ -28,12 +28,6 @@ std::vector ModelLoader::loadModel(std::string_view filename) model->indices.push_back(face.mIndices[1]); model->indices.push_back(face.mIndices[2]); - auto e0 = model->positions[face.mIndices[1]] - model->positions[face.mIndices[0]]; - auto e1 = model->positions[face.mIndices[2]] - model->positions[face.mIndices[0]]; - model->es.push_back(e0); - model->es.push_back(e1); - - model->faceNormals.push_back(glm::cross(e0, e1)); } model->boundingBox = aabb; result.push_back(std::move(model)); diff --git a/src/util/Texture.cpp b/src/util/Texture.cpp new file mode 100644 index 0000000..dbb5d43 --- /dev/null +++ b/src/util/Texture.cpp @@ -0,0 +1,8 @@ +#include "Texture.h" + +glm::vec3 Texture::sample(glm::vec2 texCoords) const +{ + uint32_t x = texCoords.x * width; + uint32_t y = texCoords.y * height; + return textureData[y * width + x]; +} \ No newline at end of file diff --git a/src/util/Texture.h b/src/util/Texture.h new file mode 100644 index 0000000..1f7b716 --- /dev/null +++ b/src/util/Texture.h @@ -0,0 +1,15 @@ +#pragma once +#include "Minimal.h" +#include +#include +#include + +class Texture +{ +public: + glm::vec3 sample(glm::vec2 texCoords) const; + std::vector textureData; + int width; + int height; +}; +DECLARE_REF(Texture) \ No newline at end of file diff --git a/src/util/TextureLoader.cpp b/src/util/TextureLoader.cpp new file mode 100644 index 0000000..216f597 --- /dev/null +++ b/src/util/TextureLoader.cpp @@ -0,0 +1,19 @@ +#include "TextureLoader.h" +#define STB_IMAGE_IMPLEMENTATION +#include + +PTexture TextureLoader::loadTexture(std::string_view filename) +{ + int x, y, n; + auto* data = stbi_load(filename.data(), &x, &y, &n, 3); + std::vector texData(x * y); + for (uint32_t i = 0; i < texData.size(); ++i) + { + texData[i] = glm::vec3(data[i * 3 + 0] / 256.f, data[i * 3 + 1] / 256.f, data[i * 3 + 2] / 256.f); + } + auto result = std::make_unique(); + result->textureData = std::move(texData); + result->width = x; + result->height = y; + return result; +} \ No newline at end of file diff --git a/src/util/TextureLoader.h b/src/util/TextureLoader.h new file mode 100644 index 0000000..c0a9001 --- /dev/null +++ b/src/util/TextureLoader.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include "Texture.h" + +class TextureLoader +{ +public: + static PTexture loadTexture(std::string_view filename); + +private: +}; \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index b1444c1..e880352 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -10,6 +10,7 @@ "glfw3", "glew", "glm", - "fmt" + "fmt", + "shader-slang" ] } \ No newline at end of file