From c858b4dcde3f59e629e43960d4ebd01d9c043b9d Mon Sep 17 00:00:00 2001 From: Godzil Date: Thu, 12 Mar 2020 17:45:29 +0000 Subject: [PATCH] A new scene and some optimisations. --- source/include/intersection.h | 27 +------ source/include/shape.h | 7 ++ source/intersect.cpp | 21 ++++- source/intersection.cpp | 41 +++++++--- source/matrix.cpp | 10 ++- source/renderstat.cpp | 2 +- source/shapes/objfile.cpp | 5 +- source/shapes/shape.cpp | 26 ++++++- tests/CMakeLists.txt | 23 +++++- tests/dragon_scene.cpp | 143 ++++++++++++++++++++++++++++++++++ tests/objfile_test.cpp | 4 +- 11 files changed, 259 insertions(+), 50 deletions(-) create mode 100644 tests/dragon_scene.cpp diff --git a/source/include/intersection.h b/source/include/intersection.h index d691799..c5b267e 100644 --- a/source/include/intersection.h +++ b/source/include/intersection.h @@ -25,31 +25,7 @@ struct Computation object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside), overHitPoint(overHitP), underHitPoint(underHitP), reflectVector(reflectV), n1(n1), n2(n2), material(objMat) { }; - double schlick() - { - /* Find the cos of the angle betzeen the eye and normal vector */ - double cos = this->eyeVector.dot(this->normalVector); - double r0; - /* Total internal reflection can only occur when n1 > n2 */ - if (this->n1 > this->n2) - { - double n, sin2_t; - n = this->n1 / this->n2; - sin2_t = (n * n) * (1.0 - (cos * cos)); - if (sin2_t > 1.0) - { - return 1.0; - } - /* Compute the cos of theta */ - cos = sqrt(1.0 - sin2_t); - } - - - r0 = ((this->n1 - this->n2) / (this->n1 + this->n2)); - r0 = r0 * r0; - - return r0 + (1 - r0) * ((1 - cos)*(1 - cos)*(1 - cos)*(1 - cos)*(1 - cos)); - }; + double schlick(); Shape *object; double t; @@ -73,7 +49,6 @@ class Intersection public: double t; Shape *object; - double u, v; public: diff --git a/source/include/shape.h b/source/include/shape.h index 3d3e7cd..86ae7ad 100644 --- a/source/include/shape.h +++ b/source/include/shape.h @@ -42,11 +42,14 @@ protected: ShapeType type; Matrix localTransformMatrix; bool locked; + uint64_t objectId; protected: virtual void localIntersect(Ray r, Intersect &xs) = 0; virtual Tuple localNormalAt(Tuple point, Intersection *hit) = 0; + static uint64_t newObjectId(); + public: Matrix transformMatrix; Matrix inverseTransform; @@ -65,6 +68,9 @@ public: virtual void intersect(Ray &r, Intersect &xs); Tuple normalAt(Tuple point, Intersection *hit = nullptr); + uint64_t getObjectId() { return this->objectId; }; + void setObjectId(uint64_t oid) { this->objectId = oid; }; + /* Bounding box points are always world value */ virtual BoundingBox getLocalBounds(); virtual BoundingBox getBounds(); @@ -92,6 +98,7 @@ public: void setTransform(Matrix transform); void setMaterial(Material material) { this->material = material; this->materialSet = true; }; + Material *getMaterial(); Ray transform(Ray &r) { return Ray(this->transformMatrix * r.origin, this->transformMatrix * r.direction); }; Ray invTransform(Ray &r) { return Ray(this->inverseTransform * r.origin, this->inverseTransform * r.direction); }; diff --git a/source/intersect.cpp b/source/intersect.cpp index c90b011..0a96523 100644 --- a/source/intersect.cpp +++ b/source/intersect.cpp @@ -24,9 +24,17 @@ Intersect::Intersect() { this->allocated = MIN_ALLOC; this->list = (Intersection **)calloc(sizeof(Intersection *), MIN_ALLOC); - stats.addMalloc(); - stats.addIntersect(); - this->num = 0; + if (this->list != nullptr) + { + stats.addMalloc(); + stats.addIntersect(); + this->num = 0; + } + else + { + printf("ABORT: Allocation error [%s]!\n", __FUNCTION__); + exit(-1); + } } Intersect::~Intersect() @@ -34,12 +42,17 @@ Intersect::~Intersect() int i; for(i = 0; i < this->num; i++) { - delete this->list[i]; + if (this->list[i] != nullptr) + { + delete this->list[i]; + this->list[i] = nullptr; + } } /* Free stuff */ if (this->list != nullptr) { free(this->list); + this->list = nullptr; } } diff --git a/source/intersection.cpp b/source/intersection.cpp index a63e1f5..bb29bb4 100644 --- a/source/intersection.cpp +++ b/source/intersection.cpp @@ -10,6 +10,32 @@ #include #include +double Computation::schlick() +{ + /* Find the cos of the angle betzeen the eye and normal vector */ + double cos = this->eyeVector.dot(this->normalVector); + double r0; + /* Total internal reflection can only occur when n1 > n2 */ + if (this->n1 > this->n2) + { + double n, sin2_t; + n = this->n1 / this->n2; + sin2_t = (n * n) * (1.0 - (cos * cos)); + if (sin2_t > 1.0) + { + return 1.0; + } + /* Compute the cos of theta */ + cos = sqrt(1.0 - sin2_t); + } + + + r0 = ((this->n1 - this->n2) / (this->n1 + this->n2)); + r0 = r0 * r0; + + return r0 + (1 - r0) * ((1 - cos)*(1 - cos)*(1 - cos)*(1 - cos)*(1 - cos)); +}; + Computation Intersection::prepareComputation(Ray r, Intersect *xs) { double n1 = 1.0; @@ -42,7 +68,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) Tuple reflectV = r.direction.reflect(normalV); /* If the hit object is not transparent, there is no need to do that. I think .*/ - if ((xs != nullptr) && (xs->hit().object->material.transparency > 0)) + if ((xs != nullptr) && (xs->hit().object->getMaterial()->transparency > 0)) { List containers; int j; @@ -54,7 +80,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) { if (!containers.isEmpty()) { - n1 = containers.last()->material.refractiveIndex; + n1 = containers.last()->getMaterial()->refractiveIndex; } } @@ -71,7 +97,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) { if (!containers.isEmpty()) { - n2 = containers.last()->material.refractiveIndex; + n2 = containers.last()->getMaterial()->refractiveIndex; } /* End the loop */ @@ -80,10 +106,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) } } - Shape *s = this->object; - - /* For now don't get root group material */ - while((!s->materialSet) && (s->parent != nullptr)) { s = s->parent; } + Material *m = this->object->getMaterial(); return Computation(this->object, this->t, @@ -96,5 +119,5 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) n1, n2, underHitP, - &s->material); -} \ No newline at end of file + m); +} diff --git a/source/matrix.cpp b/source/matrix.cpp index 801a7ee..ee25549 100644 --- a/source/matrix.cpp +++ b/source/matrix.cpp @@ -101,13 +101,15 @@ Matrix Matrix::operator*(const Matrix &b) const return ret; } +#define FastGet4(_x, _y) (this->data[4 * (_x) + (_y)]) + /* TODO: Check if we can optimise this function. It is called a lot */ Tuple Matrix::operator*(const Tuple &b) const { - return Tuple(b.x * this->get(0, 0) + b.y * this->get(0, 1) + b.z * this->get(0, 2) + b.w * this->get(0, 3), - b.x * this->get(1, 0) + b.y * this->get(1, 1) + b.z * this->get(1, 2) + b.w * this->get(1, 3), - b.x * this->get(2, 0) + b.y * this->get(2, 1) + b.z * this->get(2, 2) + b.w * this->get(2, 3), - b.x * this->get(3, 0) + b.y * this->get(3, 1) + b.z * this->get(3, 2) + b.w * this->get(3, 3)); + return Tuple(b.x * FastGet4(0, 0) + b.y * FastGet4(0, 1) + b.z * FastGet4(0, 2) + b.w * FastGet4(0, 3), + b.x * FastGet4(1, 0) + b.y * FastGet4(1, 1) + b.z * FastGet4(1, 2) + b.w * FastGet4(1, 3), + b.x * FastGet4(2, 0) + b.y * FastGet4(2, 1) + b.z * FastGet4(2, 2) + b.w * FastGet4(2, 3), + b.x * FastGet4(3, 0) + b.y * FastGet4(3, 1) + b.z * FastGet4(3, 2) + b.w * FastGet4(3, 3)); } Matrix Matrix::identity() diff --git a/source/renderstat.cpp b/source/renderstat.cpp index 46d1f0d..7bd487f 100644 --- a/source/renderstat.cpp +++ b/source/renderstat.cpp @@ -181,7 +181,7 @@ void RenderStats::printStats() printf("Malloc called : %lld\n", this->mallocCallCount); printf("Realloc called : %lld\n", this->reallocCallCount); printf("Bounding box missed : %lld\n", this->discardedIntersectCount); - printf("Min depth atteined : %lld\n", this->maxDepthAttained); + printf("Min depth attained : %lld\n", this->maxDepthAttained); printf("Max intersect count : %lld\n", this->maxIntersectOnARay); printf("==================================================\n"); }; diff --git a/source/shapes/objfile.cpp b/source/shapes/objfile.cpp index 8d886ed..d6dd7a5 100644 --- a/source/shapes/objfile.cpp +++ b/source/shapes/objfile.cpp @@ -484,6 +484,9 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine) this->verticesNormal(vn[2]), this->verticesNormal(vn[3])); } + /* Set the object id to the OBJ one */ + t->setObjectId(this->getObjectId()); + this->currentGroup->addObject(t); ret = 0; } @@ -523,7 +526,7 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine) this->verticesNormal(vn[i]), this->verticesNormal(vn[i + 1])); } - + t->setObjectId(this->getObjectId()); this->currentGroup->addObject(t); } ret = 0; diff --git a/source/shapes/shape.cpp b/source/shapes/shape.cpp index d071004..5d4b9cd 100644 --- a/source/shapes/shape.cpp +++ b/source/shapes/shape.cpp @@ -15,6 +15,7 @@ Shape::Shape(ShapeType type) { + this->objectId = Shape::newObjectId(); this->locked = false; this->parent = nullptr; this->dropShadow = true; @@ -25,6 +26,17 @@ Shape::Shape(ShapeType type) this->updateTransform(); } +uint64_t Shape::newObjectId() +{ + static uint64_t id = 0; + uint64_t ret; + + ret = id++; + + return ret; +} + + void Shape::intersect(Ray &r, Intersect &xs) { this->localIntersect(this->invTransform(r), xs); @@ -95,6 +107,16 @@ BoundingBox Shape::getBounds() return ret; } +Material *Shape::getMaterial() +{ + Shape *s = this; + while((!s->materialSet) && (s->parent != nullptr)) + { + s = s->parent; + } + return &s->material; +} + void Shape::dumpMe(FILE *fp) { if (this->materialSet) @@ -112,9 +134,9 @@ void Shape::dumpMe(FILE *fp) this->getBounds().dumpMe(fp); fprintf(fp, "},\n"); } - fprintf(fp, "\"id\": %p,\n", this); + fprintf(fp, "\"id\": %ld,\n", this->getObjectId()); if (this->parent) { - fprintf(fp, "\"parentId\": %p,\n", this->parent); + fprintf(fp, "\"parentId\": %ld,\n", this->parent->getObjectId()); } } \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6290501..93192e2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -128,7 +128,7 @@ file(DOWNLOAD ) add_custom_command( TARGET uvmap_skybox POST_BUILD - COMMAND unzip -o ${CMAKE_SOURCE_DIR}/external/LancellottiChapel.zip -d LancellottiChapel + COMMAND unzip -qq -o ${CMAKE_SOURCE_DIR}/external/LancellottiChapel.zip -d LancellottiChapel WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/external/ ) add_custom_command( @@ -138,6 +138,26 @@ add_custom_command( ${CMAKE_CURRENT_BINARY_DIR}/ ) +# Dragon scene +add_executable(dragon_scene) +target_sources(dragon_scene PRIVATE dragon_scene.cpp) +file(DOWNLOAD + http://raytracerchallenge.com/bonus/assets/dragon.zip + ${CMAKE_SOURCE_DIR}/external/dragon.zip + EXPECTED_HASH MD5=308b0f2aca1d48d24e6fc4584dfdf345 + ) +add_custom_command( + TARGET dragon_scene POST_BUILD + COMMAND unzip -qq -o ${CMAKE_SOURCE_DIR}/external/dragon.zip + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/external/ +) +add_custom_command( + TARGET dragon_scene POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_SOURCE_DIR}/external/dragon.obj + ${CMAKE_CURRENT_BINARY_DIR}/ +) + add_test(NAME Chapter05_Test COMMAND $) add_test(NAME Chapter06_Test COMMAND $) add_test(NAME Chapter07_Test COMMAND $) @@ -160,6 +180,7 @@ add_test(NAME UVMap_AlignCheckPlane COMMAND $ add_test(NAME UVMap_CheckeredCube COMMAND $) add_test(NAME UVMap_Earth COMMAND $) add_test(NAME UVMap_Skybox COMMAND $) +add_test(NAME Dragon_Sceme COMMAND $) add_test(NAME Test_Rendering COMMAND $) add_test(NAME Triangle_RenderTest COMMAND $) add_test(NAME ChristmasBall_Rendering COMMAND $) diff --git a/tests/dragon_scene.cpp b/tests/dragon_scene.cpp new file mode 100644 index 0000000..fd16a4b --- /dev/null +++ b/tests/dragon_scene.cpp @@ -0,0 +1,143 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Render test for OBJ File using teapots in chapter 15. + * + * Created by Manoƫl Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +int main() +{ + World w = World(); + + /* Add lights */ + /* Light light1 = Light(POINT_LIGHT, Point(10, 100, 10), Colour(.1, .1, .1)); + w.addLight(&light1); + Light light2 = Light(POINT_LIGHT, Point(10, 100, -10), Colour(.1, .1, .2)); + w.addLight(&light2); + Light light3 = Light(POINT_LIGHT, Point(-10, 100, 10), Colour(.1, .1, .1)); + w.addLight(&light3); + Light light4 = Light(POINT_LIGHT, Point(-10, 100, -10), Colour(.1, .1, .1)); + w.addLight(&light4);*/ + + Light light1 = Light(POINT_LIGHT, Point(0, 100, 0), Colour(.3, .3, .3)); + w.addLight(&light1); + + + Point lightPos = Point(3.8, 6.8, 4.5); + Light mouthLight = Light(POINT_LIGHT, lightPos, Colour(0.5, 0.5, 0.5)); + w.addLight(&mouthLight); + + Light mainLight = Light(POINT_LIGHT, Point(8, 10, 16), Colour(1, 1, 1)); + w.addLight(&mainLight); + + /* ----------------------------- */ +#if 0 + /* Spot light */ + Sphere spot = Sphere(); + spot.dropShadow = false; + spot.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * scaling(0.2, 0.2, 0.2)); + w.addObject(&spot); + + Cylinder X = Cylinder(); + X.minCap = 0; + X.maxCap = 4; + Cylinder Y = Cylinder(); + Y.minCap = 0; + Y.maxCap = 4; + Cylinder Z = Cylinder(); + Z.minCap = 0; + Z.maxCap = 4; + Z.materialSet = Y.materialSet = X.materialSet = true; + X.material.ambient = 1; + X.material.specular = 0; + X.material.diffuse = 0; + Z.material = Y.material = X.material; + Z.dropShadow = Y.dropShadow = X.dropShadow = false; + X.material.colour = Colour(1, 0, 0); + Y.material.colour = Colour(0, 1, 0); + Z.material.colour = Colour(0, 0, 1); + + Y.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * scaling(0.1, 1, 0.1)); + X.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * rotationZ(M_PI/2) * scaling(0.1, 1, 0.1)); + Z.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * rotationX(M_PI/2) * scaling(0.1, 1, 0.1)); + w.addObject(&X); + w.addObject(&Y); + w.addObject(&Z); +#endif + + /* Floor */ + Material floorMaterial; + floorMaterial.colour = Colour(0.2, 0.3, 0.3); + floorMaterial.reflective = 0.3; + floorMaterial.specular = 0.4; + floorMaterial.shininess = 5; + floorMaterial.ambient = 0; + floorMaterial.diffuse = 0.8; + + /* Let's use a cube for the floor */ + Cube floor = Cube(); + floor.setMaterial(floorMaterial); + floor.setTransform(translation(0, -0.1, 0) * scaling(10, 0.1, 10)); + w.addObject(&floor); + + Material dragonMat; + dragonMat.colour = Colour(0.23, 0.75, 0.39); + dragonMat.reflective = 0.9; + dragonMat.transparency = 0.99; + dragonMat.specular = 0.9; + dragonMat.shininess = 50; + dragonMat.refractiveIndex = 1.6; + dragonMat.ambient = 0; + dragonMat.diffuse = 0.7; + + Shape *dragon = OBJFile("dragon.obj").getBaseGroup(); + dragon->setTransform(rotationY(M_PI * 0.75) * scaling(2, 2, 2)); + dragon->setMaterial(dragonMat); + w.addObject(dragon); + + /* ----------------------------- */ +/* + FILE *fpOut = fopen("dragon_worlddump.json", "wt"); + if (fpOut) + { + w.dumpMe(fpOut); + fclose(fpOut); + } +*/ + OctreeOptimisation opt; + w.finalise(opt); + + /* Set the camera */ + Camera camera = Camera(800, 600, M_PI/2); + camera.setTransform(viewTransform(Point(-5, 10, 13), + Point(0, 1, 0), + Vector(0, 1, 0))); + + /* Now render it */ + Canvas image = camera.render(w, 5); + + image.SaveAsPNG("ch15_teapot_objfile.png"); + + return 0; +} \ No newline at end of file diff --git a/tests/objfile_test.cpp b/tests/objfile_test.cpp index d5b601b..2d3c25e 100644 --- a/tests/objfile_test.cpp +++ b/tests/objfile_test.cpp @@ -156,8 +156,8 @@ TEST(OBJFileTest, Faces_with_normal) Group *g0 = parser.groups(OBJ_DEFAULT_GROUP); - SmoothTriangle *t1 = (SmoothTriangle *)(*g0)[0]; - SmoothTriangle *t2 = (SmoothTriangle *)(*g0)[1]; + SmoothTriangle *t1 = (SmoothTriangle *)g0->getObject(0); + SmoothTriangle *t2 = (SmoothTriangle *)g0->getObject(1); ASSERT_EQ(t1->p1, parser.vertices(1)); ASSERT_EQ(t1->p2, parser.vertices(2));