diff --git a/Step 3/include/engine/types.h b/Step 3/include/engine/types.h index dafb549..73e5d04 100644 --- a/Step 3/include/engine/types.h +++ b/Step 3/include/engine/types.h @@ -32,6 +32,8 @@ namespace Raytracing { Vec4 normal{}; // the length of the vector from its origin in its direction. PRECISION_TYPE length{0}; + // Texture UV Coords. + PRECISION_TYPE u,v; }; struct ScatterResults { @@ -53,6 +55,7 @@ namespace Raytracing { // returns true if the ray was scattered along with the scattered ray, otherwise will return false with empty ray. // the returned vec4 is the attenuation color [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const = 0; + [[nodiscard]] virtual Vec4 getColor(PRECISION_TYPE u, PRECISION_TYPE v, const Vec4& point) const = 0; [[nodiscard]] Vec4 getBaseColor() const { return baseColor; } virtual ~Material() = default; diff --git a/Step 3/include/engine/world.h b/Step 3/include/engine/world.h index 4cab61e..e95bd15 100644 --- a/Step 3/include/engine/world.h +++ b/Step 3/include/engine/world.h @@ -64,7 +64,10 @@ namespace Raytracing { public: explicit DiffuseMaterial(const Vec4& scatterColor): Material(scatterColor) {} - [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const; + [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const override; + [[nodiscard]] virtual Vec4 getColor(PRECISION_TYPE u, PRECISION_TYPE v, const Vec4& point) const override { + return this->baseColor; + } }; class MetalMaterial : public Material { @@ -76,7 +79,10 @@ namespace Raytracing { public: explicit MetalMaterial(const Vec4& metalColor): Material(metalColor) {} - [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const; + [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const override; + [[nodiscard]] virtual Vec4 getColor(PRECISION_TYPE u, PRECISION_TYPE v, const Vec4& point) const override { + return this->baseColor; + } }; class BrushedMetalMaterial : public MetalMaterial { @@ -85,14 +91,23 @@ namespace Raytracing { public: explicit BrushedMetalMaterial(const Vec4& metalColor, PRECISION_TYPE fuzzyness): MetalMaterial(metalColor), fuzzyness(fuzzyness) {} - [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const; + [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const override; + [[nodiscard]] virtual Vec4 getColor(PRECISION_TYPE u, PRECISION_TYPE v, const Vec4& point) const override { + return this->baseColor; + } }; class TexturedMaterial : public Material { + protected: + int width{}, height{}, channels{}, rowWidth{}; + unsigned char* data; public: - explicit TexturedMaterial(const std::string& file): Material({}) { - - } + explicit TexturedMaterial(const std::string& file); + + [[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const override; + [[nodiscard]] virtual Vec4 getColor(PRECISION_TYPE u, PRECISION_TYPE v, const Vec4& point) const override; + + ~TexturedMaterial(); }; struct WorldConfig { diff --git a/Step 3/resources/029a_-_Survival_of_the_Idiots_349.jpg b/Step 3/resources/029a_-_Survival_of_the_Idiots_349.jpg new file mode 100755 index 0000000..5a9cdbf Binary files /dev/null and b/Step 3/resources/029a_-_Survival_of_the_Idiots_349.jpg differ diff --git a/Step 3/resources/1616466348379.png b/Step 3/resources/1616466348379.png new file mode 100644 index 0000000..a0a3505 Binary files /dev/null and b/Step 3/resources/1616466348379.png differ diff --git a/Step 3/resources/760213.png b/Step 3/resources/760213.png new file mode 100644 index 0000000..6353f73 Binary files /dev/null and b/Step 3/resources/760213.png differ diff --git a/Step 3/resources/debug.mtl b/Step 3/resources/debug.mtl new file mode 100644 index 0000000..9b8de8c --- /dev/null +++ b/Step 3/resources/debug.mtl @@ -0,0 +1,2 @@ +# Blender 3.3.1 MTL File: 'None' +# www.blender.org diff --git a/Step 3/resources/debug.obj b/Step 3/resources/debug.obj new file mode 100644 index 0000000..6317a4e --- /dev/null +++ b/Step 3/resources/debug.obj @@ -0,0 +1,47 @@ +# Blender 3.3.1 +# www.blender.org +mtllib debug.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0000 1.0000 -0.0000 +vt 0.000000 0.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 1.000000 1.000000 +s 0 +f 2/2/1 3/6/1 1/1/1 +f 4/8/2 7/14/2 3/4/2 +f 8/16/3 5/9/3 7/14/3 +f 6/12/4 1/1/4 5/10/4 +f 7/15/5 1/1/5 3/5/5 +f 4/8/6 6/13/6 8/16/6 +f 2/2/1 4/7/1 3/6/1 +f 4/8/2 8/16/2 7/14/2 +f 8/16/3 6/11/3 5/9/3 +f 6/12/4 2/2/4 1/1/4 +f 7/15/5 5/10/5 1/1/5 +f 4/8/6 2/3/6 6/13/6 diff --git a/Step 3/resources/shaders/world.fs b/Step 3/resources/shaders/world.fs index 30ee58b..72bdeb0 100644 --- a/Step 3/resources/shaders/world.fs +++ b/Step 3/resources/shaders/world.fs @@ -14,7 +14,7 @@ void main() { vec4 textureColor = texture(tex, outUv); //FragColor = vec4(textureColor.rgb, 1.0f); if (useWhite == 0) - FragColor = vec4(vec3(1.0, 0.0f, 0.0f) * dot(lightDir, outNormal), 1.0f); + FragColor = vec4(vec3(1.0, 0.0f, 0.0f) * dot(lightDir, outNormal) * vec3(outUv, 1.0), 1.0f); else FragColor = vec4(color, 1.0f); } \ No newline at end of file diff --git a/Step 3/src/engine/main.cpp b/Step 3/src/engine/main.cpp index fd9700f..04a2745 100644 --- a/Step 3/src/engine/main.cpp +++ b/Step 3/src/engine/main.cpp @@ -126,6 +126,7 @@ int main(int argc, char** args) { Raytracing::ModelData spider = Raytracing::OBJLoader::loadModel(parser.getOptionValue("--resources") + "spider.obj"); Raytracing::ModelData house = Raytracing::OBJLoader::loadModel(parser.getOptionValue("--resources") + "house.obj"); Raytracing::ModelData plane = Raytracing::OBJLoader::loadModel(parser.getOptionValue("--resources") + "plane.obj"); + Raytracing::ModelData debugCube = Raytracing::OBJLoader::loadModel(parser.getOptionValue("--resources") + "debug.obj"); world.add("greenDiffuse", new Raytracing::DiffuseMaterial{Raytracing::Vec4{0, 1.0, 0, 1}}); world.add("redDiffuse", new Raytracing::DiffuseMaterial{Raytracing::Vec4{1.0, 0, 0, 1}}); @@ -134,6 +135,7 @@ int main(int argc, char** args) { world.add("greenMetal", new Raytracing::MetalMaterial{Raytracing::Vec4{0.4, 1.0, 0.4, 1}}); world.add("redMetal", new Raytracing::BrushedMetalMaterial{Raytracing::Vec4{1.0, 0.4, 0.4, 1}, 0.6f}); world.add("blueMetal", new Raytracing::MetalMaterial{Raytracing::Vec4{0.4, 0.4, 1.0, 1}}); + world.add("test", new Raytracing::TexturedMaterial{parser.getOptionValue("--resources") + "029a_-_Survival_of_the_Idiots_349.jpg"}); world.add(new Raytracing::SphereObject({0, -100.5, -1, 0}, 100, world.getMaterial("greenDiffuse"))); @@ -142,6 +144,7 @@ int main(int argc, char** args) { world.add(new Raytracing::ModelObject({5, 1, 0}, house, world.getMaterial("redDiffuse"))); world.add(new Raytracing::ModelObject({0, 0, -5}, house, world.getMaterial("blueDiffuse"))); world.add(new Raytracing::ModelObject({0, 0, 5}, house, world.getMaterial("blueDiffuse"))); + world.add(new Raytracing::ModelObject({0, 5, 0}, debugCube, world.getMaterial("test"))); if (parser.hasOption("--gui") || parser.hasOption("-g")) { #ifdef COMPILE_GUI diff --git a/Step 3/src/engine/world.cpp b/Step 3/src/engine/world.cpp index ba04630..854f1a0 100644 --- a/Step 3/src/engine/world.cpp +++ b/Step 3/src/engine/world.cpp @@ -4,6 +4,7 @@ */ #include "engine/world.h" #include "engine/raytracing.h" +#include "engine/image/stb_image.h" namespace Raytracing { @@ -53,7 +54,10 @@ namespace Raytracing { } else tlog << "ray outside sphere\n"; */ - return {true, RayAtRoot, normal, root}; + // calculate the uv coords and normalize to [0, 1] + PRECISION_TYPE u = (atan2(-RayAtRoot.z(), RayAtRoot.x()) + std::numbers::pi) / (2 * std::numbers::pi); + PRECISION_TYPE v = acos(RayAtRoot.y()) / std::numbers::pi; + return {true, RayAtRoot, normal, root, u, v}; } std::pair World::checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const { @@ -131,7 +135,56 @@ namespace Raytracing { bool shouldReflect = Vec4::dot(newRay, hitData.normal) > 0; return {shouldReflect, Ray{hitData.hitPoint, newRay + Raycaster::randomUnitVector() * fuzzyness}, getBaseColor()}; } - + + ScatterResults TexturedMaterial::scatter(const Ray& ray, const HitData& hitData) const { + Vec4 newRay = hitData.normal + Raytracing::Raycaster::randomUnitVector().normalize(); + + // rays that are close to zero are liable to floating point precision errors + if (newRay.x() < EPSILON && newRay.y() < EPSILON && newRay.z() < EPSILON && newRay.w() < EPSILON) + newRay = hitData.normal; + + return {true, Ray{hitData.hitPoint, newRay}, getColor(hitData.u, hitData.v, hitData.hitPoint)}; + } + Vec4 TexturedMaterial::getColor(PRECISION_TYPE u, PRECISION_TYPE v, const Vec4& point) const { + // if we are unable to load the image return the debug color. + // This causes major issues (force this to happen, you'll see), indicates issue + looks really cool. + if (!data) + Vec4{0, 1, 0.2} * Vec4{u, v, 1.0}; + + // if you render out the debug color above you'll notice that the UV coords are rotated. + // you can also see this from the debug view, which *as of now* is rendering based on UV coords * normals * red + // so let's transform it back and ensure that our UV coords are within image bounds. + u = clamp(u, 0, 1); + // fix that pesky issue + v = 1.0 - clamp(v, 0, 1); + + auto imageX = (int)(width * u); + auto imageY = (int)(height * v); + + if (imageX >= width) imageX = width-1; + if (imageY >= height) imageY = height-1; + + // since stbi loads in RGB8 [0, 255] but the engine works on [0, 1] we need to scale the data down. + // this is best done with a single division followed by multiple multiplication. + // since this function needs to be cheap to run. + const PRECISION_TYPE colorFactor = 1.0 / 255.0; + const auto pixelData = data + (imageY * rowWidth + imageX * channels); + + return {pixelData[0] * colorFactor, pixelData[1] * colorFactor, pixelData[2] * colorFactor}; + } + TexturedMaterial::TexturedMaterial(const std::string& file) : Material({}) { + // we are going to have to ignore transparency for now. TODO:? + data = stbi_load(file.c_str(), &width, &height, &channels, 0); + if (!data) + flog << "Unable to load image file " << file << "!\n"; + else + ilog << "Loaded image " << file << "!\n"; + rowWidth = width * channels; + } + TexturedMaterial::~TexturedMaterial() { + delete(data); + } + static HitData checkIfTriangleGotHit(const Triangle& theTriangle, const Vec4& position, const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) { // Möller–Trumbore intersection algorithm // https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection @@ -174,8 +227,26 @@ namespace Raytracing { normal = Vec4{edge1.y() * edge2.z(), edge1.z() * edge2.x(), edge1.x() * edge2.y()} - Vec4{edge1.z() * edge2.y(), edge1.x() * edge2.z(), edge1.y() * edge2.x()}; } - return {true, rayIntersectionPoint, normal, t}; + + // calculate triangle UV + // calculate the vector that runs between the vertex and the intersection point for all three vertices + auto vertex1ToIntersect = theTriangle.vertex1 - rayIntersectionPoint; + auto vertex2ToIntersect = theTriangle.vertex2 - rayIntersectionPoint; + auto vertex3ToIntersect = theTriangle.vertex3 - rayIntersectionPoint; + + // the magnitude of the cross product of two vectors is double the area formed by the triangle of their intersection. + auto fullArea = 1 / Vec4::cross(theTriangle.vertex1 - theTriangle.vertex2, theTriangle.vertex1 - theTriangle.vertex3).magnitude(); + // scale the area of sub triangles to be proportion to the area of the triangle + auto areaVert1 = Vec4::cross(vertex2ToIntersect, vertex3ToIntersect).magnitude() * fullArea; + auto areaVert2 = Vec4::cross(vertex3ToIntersect, vertex1ToIntersect).magnitude() * fullArea; + auto areaVert3 = Vec4::cross(vertex1ToIntersect, vertex2ToIntersect).magnitude() * fullArea; + + // that area is how much each UV factors into the final UV coord + auto uv = theTriangle.uv1 * areaVert1 + theTriangle.uv2 * areaVert2 + theTriangle.uv3 * areaVert3; + + return {true, rayIntersectionPoint, normal, t, uv.x(), uv.y()}; } + return {false, Vec4(), Vec4(), 0}; } diff --git a/Step 3/src/opencl/cl.cpp b/Step 3/src/opencl/cl.cpp index 3ef7b6e..356f77a 100644 --- a/Step 3/src/opencl/cl.cpp +++ b/Step 3/src/opencl/cl.cpp @@ -12,7 +12,7 @@ namespace Raytracing { OpenCL openCl {0}; void OpenCL::init() { - openCl = OpenCL{0}; + } OpenCL::OpenCL(int platformID, int deviceID): m_activePlatform(platformID) { m_CL_ERR = CL_SUCCESS;