Refraction seems to work. Still need to do a nice scene.
This commit is contained in:
@@ -18,14 +18,16 @@ class Intersect;
|
||||
struct Computation
|
||||
{
|
||||
Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP,
|
||||
bool inside, Tuple reflectV = Vector(0, 0, 0), double n1 = 1.0, double n2 = 1.0) :
|
||||
bool inside, Tuple reflectV = Vector(0, 0, 0), double n1 = 1.0, double n2 = 1.0,
|
||||
Tuple underHitP = Point(0, 0, 0)) :
|
||||
object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside),
|
||||
overHitPoint(overHitP), reflectVector(reflectV), n1(n1), n2(n2) { };
|
||||
overHitPoint(overHitP), underHitPoint(underHitP), reflectVector(reflectV), n1(n1), n2(n2) { };
|
||||
|
||||
Shape *object;
|
||||
double t;
|
||||
Tuple hitPoint;
|
||||
Tuple overHitPoint;
|
||||
Tuple underHitPoint;
|
||||
Tuple eyeVector;
|
||||
Tuple normalVector;
|
||||
Tuple reflectVector;
|
||||
|
||||
@@ -44,13 +44,31 @@ public:
|
||||
{
|
||||
ChainList *p = this->head;
|
||||
if (p == nullptr) { return; }
|
||||
|
||||
if ((p->next == nullptr) && (p->shape == s))
|
||||
{
|
||||
/* First element */
|
||||
this->tail = nullptr;
|
||||
free(this->head);
|
||||
this->head = nullptr;
|
||||
this->count = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while(p->next != nullptr)
|
||||
{
|
||||
if (p->next->shape == s)
|
||||
{
|
||||
this->count --;
|
||||
ChainList *found = p->next;
|
||||
|
||||
p->next = p->next->next;
|
||||
free(p->next);
|
||||
|
||||
free(found);
|
||||
|
||||
if (p->next == NULL) { this->tail = p; }
|
||||
|
||||
this->count --;
|
||||
return;
|
||||
}
|
||||
p = p->next;
|
||||
@@ -64,10 +82,10 @@ public:
|
||||
theNew->shape = s;
|
||||
|
||||
ChainList *p = this->tail;
|
||||
tail = theNew;
|
||||
this->tail = theNew;
|
||||
|
||||
if (p != nullptr) { p->next = theNew; }
|
||||
else { head = theNew; } /* If the tail is empty, it mean the list IS empty. */
|
||||
else { this->head = theNew; } /* If the tail is empty, it mean the list IS empty. */
|
||||
|
||||
this->count ++;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
bool isShadowed(Tuple point);
|
||||
|
||||
Colour reflectColour(Computation comps, uint32_t depthCount = 4);
|
||||
Colour refractedColour(Computation comps, uint32_t depthCount = 0);
|
||||
|
||||
Intersect intersect(Ray r);
|
||||
|
||||
|
||||
@@ -27,18 +27,18 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
|
||||
}
|
||||
|
||||
Tuple overHitP = hitP + normalV * getEpsilon();
|
||||
Tuple underHitP = hitP - normalV * getEpsilon();
|
||||
Tuple reflectV = r.direction.reflect(normalV);
|
||||
|
||||
if (xs != nullptr)
|
||||
{
|
||||
List containers;
|
||||
int j, k;
|
||||
Intersection hit = xs->hit();
|
||||
|
||||
for(j = 0; j < xs->count(); j++)
|
||||
{
|
||||
Intersection i = (*xs)[j];
|
||||
if (hit == i)
|
||||
if (*this == i)
|
||||
{
|
||||
if (!containers.isEmpty())
|
||||
{
|
||||
@@ -55,12 +55,11 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
|
||||
containers.append(i.object);
|
||||
}
|
||||
|
||||
if (hit == i)
|
||||
if (*this == i)
|
||||
{
|
||||
if (!containers.isEmpty())
|
||||
{
|
||||
Shape *cur = containers.last();
|
||||
n2 = cur->material.refractiveIndex;
|
||||
n2 = containers.last()->material.refractiveIndex;
|
||||
}
|
||||
|
||||
/* End the loop */
|
||||
@@ -78,5 +77,6 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
|
||||
inside,
|
||||
reflectV,
|
||||
n1,
|
||||
n2);
|
||||
n2,
|
||||
underHitP);
|
||||
}
|
||||
@@ -102,8 +102,9 @@ Tuple World::shadeHit(Computation comps, uint32_t depthCount)
|
||||
comps.normalVector, comps.object, isThereAnObstacle);
|
||||
|
||||
Tuple reflected = this->reflectColour(comps, depthCount);
|
||||
Tuple refracted = this->refractedColour(comps, depthCount);
|
||||
|
||||
return surface + reflected;
|
||||
return surface + reflected + refracted;
|
||||
}
|
||||
|
||||
Tuple World::colourAt(Ray r, uint32_t depthCount)
|
||||
@@ -156,4 +157,24 @@ Colour World::reflectColour(Computation comps, uint32_t depthCount)
|
||||
return Colour(hitColour.x, hitColour.y, hitColour.z);
|
||||
}
|
||||
|
||||
Colour World::refractedColour(Computation comps, uint32_t depthCount)
|
||||
{
|
||||
double nRatio = comps.n1 / comps.n2;
|
||||
double cos_i = comps.eyeVector.dot(comps.normalVector);
|
||||
double sin2_t = (nRatio*nRatio) * (1 - cos_i * cos_i);
|
||||
|
||||
if ((sin2_t > 1 ) || (depthCount == 0) || (comps.object->material.transparency == 0))
|
||||
{
|
||||
return Colour(0, 0, 0);
|
||||
}
|
||||
|
||||
double cos_t = sqrt(1.0 - sin2_t);
|
||||
Tuple direction = comps.normalVector * (nRatio * cos_i - cos_t) - comps.eyeVector * nRatio;
|
||||
|
||||
Ray refractedRay = Ray(comps.underHitPoint, direction);
|
||||
|
||||
Tuple hitColour = this->colourAt(refractedRay, depthCount - 1) * comps.object->material.transparency;
|
||||
|
||||
|
||||
return Colour(hitColour.x, hitColour.y, hitColour.z);
|
||||
}
|
||||
@@ -49,9 +49,15 @@ target_include_directories(ch11_reflection PUBLIC ../source/include)
|
||||
target_sources(ch11_reflection PRIVATE ch11_reflection.cpp)
|
||||
target_link_libraries(ch11_reflection rayonnement)
|
||||
|
||||
add_executable(ch11_refractiontest)
|
||||
target_include_directories(ch11_refractiontest PUBLIC ../source/include)
|
||||
target_sources(ch11_refractiontest PRIVATE ch11_refractiontest.cpp)
|
||||
target_link_libraries(ch11_refractiontest rayonnement)
|
||||
|
||||
add_test(NAME Chapter05_Test COMMAND $<TARGET_FILE:ch5_test>)
|
||||
add_test(NAME Chapter06_Test COMMAND $<TARGET_FILE:ch6_test>)
|
||||
add_test(NAME Chapter07_Test COMMAND $<TARGET_FILE:ch7_test>)
|
||||
add_test(NAME Chapter09_Test COMMAND $<TARGET_FILE:ch9_test>)
|
||||
add_test(NAME Chapter10_Test COMMAND $<TARGET_FILE:ch10_test>)
|
||||
add_test(NAME Chapter11_Reflection COMMAND $<TARGET_FILE:ch11_reflection>)
|
||||
add_test(NAME Chapter11_RefractionTest COMMAND $<TARGET_FILE:ch11_refractiontest>)
|
||||
|
||||
76
tests/ch11_refractiontest.cpp
Normal file
76
tests/ch11_refractiontest.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* DoRayMe - a quick and dirty Raytracer
|
||||
* Render test for reflection in chapter 11.
|
||||
*
|
||||
* Created by Manoël Trapier
|
||||
* Copyright (c) 2020 986-Studio.
|
||||
*
|
||||
*/
|
||||
#include <world.h>
|
||||
#include <light.h>
|
||||
#include <sphere.h>
|
||||
#include <plane.h>
|
||||
#include <material.h>
|
||||
#include <colour.h>
|
||||
#include <canvas.h>
|
||||
#include <camera.h>
|
||||
|
||||
#include <pattern.h>
|
||||
#include <strippattern.h>
|
||||
#include <gradientpattern.h>
|
||||
#include <checkerspattern.h>
|
||||
#include <ringpattern.h>
|
||||
|
||||
#include <transformation.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
World w = World();
|
||||
|
||||
/* First we need to construct the world */
|
||||
Plane floor = Plane();
|
||||
floor.material.specular = 0;
|
||||
floor.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0, 0, 0));
|
||||
floor.material.pattern->setTransform(scaling(0.7, 0.7, 0.7));
|
||||
floor.setTransform(translation(0, -3, 0));
|
||||
w.addObject(&floor);
|
||||
|
||||
/* Add some more reflective spheres */
|
||||
Sphere glassBall = Sphere();
|
||||
glassBall.setTransform(scaling(1.5, 1.5, 1.5));
|
||||
glassBall.material.refractiveIndex = 1.5;
|
||||
glassBall.material.transparency = 1.0;
|
||||
w.addObject(&glassBall);
|
||||
|
||||
Sphere airBubble = Sphere();
|
||||
airBubble.setTransform( scaling(0.9, 0.9, 0.9));
|
||||
airBubble.material.specular = 0;
|
||||
airBubble.material.ambient = 0;
|
||||
airBubble.material.refractiveIndex = 1.00029;
|
||||
airBubble.material.transparency = 1.0;
|
||||
w.addObject(&airBubble);
|
||||
|
||||
/* Add light */
|
||||
Light light = Light(POINT_LIGHT, Point(-10, 20, -10), Colour(1, 1, 1));
|
||||
|
||||
w.addLight(&light);
|
||||
|
||||
/* Set the camera */
|
||||
Camera camera = Camera(1000, 1000, M_PI / 3);
|
||||
#if 0
|
||||
camera.setTransform(viewTransform(Point(0, 1.5, -5),
|
||||
Point(0, 1, 0),
|
||||
Vector(0, 1, 0)));
|
||||
#else
|
||||
camera.setTransform(viewTransform(Point(0, 3.6, 0),
|
||||
Point(0, 0, 0),
|
||||
Vector(0, 0, 1)));
|
||||
#endif
|
||||
/* Now render it */
|
||||
Canvas image = camera.render(w);
|
||||
|
||||
image.SaveAsPNG("ch11_refractiontest.png");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -205,7 +205,6 @@ TEST(IntersectTest, Precomputing_the_reflection_vector)
|
||||
|
||||
TEST(IntersectTest, Finding_n1_and_n2_at_various_intersections)
|
||||
{
|
||||
#if 0
|
||||
int i;
|
||||
double n1_res[6] = { 1.0, 1.5, 2.0, 2.5, 2.5, 1.5 };
|
||||
double n2_res[6] = { 1.5, 2.0, 2.5, 2.5, 1.5, 1.0 };
|
||||
@@ -233,9 +232,25 @@ TEST(IntersectTest, Finding_n1_and_n2_at_various_intersections)
|
||||
|
||||
for(i = 0; i < xs.count(); i++)
|
||||
{
|
||||
Computation comps = xs[i].prepareComputation(r, &xs);
|
||||
Intersection inter = xs[i];
|
||||
Computation comps = inter.prepareComputation(r, &xs);
|
||||
ASSERT_EQ(comps.n1, n1_res[i]);
|
||||
ASSERT_EQ(comps.n2, n2_res[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(IntersectTest, The_under_point_is_offset_below_the_surface)
|
||||
{
|
||||
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
|
||||
GlassSphere shape = GlassSphere();
|
||||
shape.setTransform(translation(0, 0, 1));
|
||||
|
||||
Intersection i = Intersection(5, &shape);
|
||||
Intersect xs = Intersect();
|
||||
xs.add(i);
|
||||
|
||||
Computation comps = i.prepareComputation(r, &xs);
|
||||
|
||||
ASSERT_TRUE(double_equal(comps.underHitPoint.z, getEpsilon() / 2));
|
||||
ASSERT_LT(comps.hitPoint.z, comps.underHitPoint.z);
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <material.h>
|
||||
#include <transformation.h>
|
||||
#include <worldbuilder.h>
|
||||
#include <testpattern.h>
|
||||
#include <math.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <plane.h>
|
||||
@@ -255,6 +256,8 @@ TEST(WorldTest, Colour_at_with_mutually_reflective_surfaces)
|
||||
|
||||
/* It should just exit, we don't care about the actual colour */
|
||||
w.colourAt(r);
|
||||
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(WorldTest, The_reflected_colour_at_the_maximum_recursion_depth)
|
||||
@@ -273,4 +276,120 @@ TEST(WorldTest, The_reflected_colour_at_the_maximum_recursion_depth)
|
||||
|
||||
/* Temporary lower the precision */
|
||||
ASSERT_EQ(colour, Colour(0, 0, 0));
|
||||
}
|
||||
|
||||
TEST(WorldTest, The_refracted_colour_with_an_opaque_surface)
|
||||
{
|
||||
World w = DefaultWorld();
|
||||
Shape *shape = w.getObject(0);
|
||||
|
||||
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
|
||||
Intersect xs = Intersect();
|
||||
xs.add(Intersection(4, shape));
|
||||
xs.add(Intersection(6, shape));
|
||||
|
||||
Computation comps = xs[0].prepareComputation(r, &xs);
|
||||
Colour c = w.refractedColour(comps, 5);
|
||||
|
||||
ASSERT_EQ(c, Colour(0, 0, 0));
|
||||
}
|
||||
|
||||
TEST(WorldTest, The_refracted_colour_at_the_maximum_recursive_depth)
|
||||
{
|
||||
World w = DefaultWorld();
|
||||
Shape *shape = w.getObject(0);
|
||||
|
||||
shape->material.transparency = 1.0;
|
||||
shape->material.refractiveIndex = 1.5;
|
||||
|
||||
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
|
||||
Intersect xs = Intersect();
|
||||
xs.add(Intersection(4, shape));
|
||||
xs.add(Intersection(6, shape));
|
||||
|
||||
Computation comps = xs[0].prepareComputation(r, &xs);
|
||||
Colour c = w.refractedColour(comps, 0);
|
||||
|
||||
ASSERT_EQ(c, Colour(0, 0, 0));
|
||||
}
|
||||
|
||||
TEST(WorldTest, The_refracted_colour_under_total_internal_reflection)
|
||||
{
|
||||
World w = DefaultWorld();
|
||||
Shape *shape = w.getObject(0);
|
||||
|
||||
shape->material.transparency = 1.0;
|
||||
shape->material.refractiveIndex = 1.5;
|
||||
|
||||
Ray r = Ray(Point(0, 0, sqrt(2)/2), Vector(0, 1, 0));
|
||||
Intersect xs = Intersect();
|
||||
xs.add(Intersection(-sqrt(2)/2, shape));
|
||||
xs.add(Intersection(sqrt(2)/2, shape));
|
||||
|
||||
Computation comps = xs[1].prepareComputation(r, &xs);
|
||||
Colour c = w.refractedColour(comps, 5);
|
||||
|
||||
ASSERT_EQ(c, Colour(0, 0, 0));
|
||||
}
|
||||
|
||||
TEST(WorldTest, The_refracted_coloud_with_a_refracted_ray)
|
||||
{
|
||||
World w = DefaultWorld();
|
||||
|
||||
Shape *A = w.getObject(0);
|
||||
A->material.ambient = 1.0;
|
||||
A->material.pattern = new TestPattern();
|
||||
|
||||
Shape *B = w.getObject(1);
|
||||
B->material.transparency = 1.0;
|
||||
B->material.refractiveIndex = 1.5;
|
||||
|
||||
Ray r = Ray(Point(0, 0, 0.1), Vector(0, 1, 0));
|
||||
Intersect xs = Intersect();
|
||||
xs.add(Intersection(-0.9899, A));
|
||||
xs.add(Intersection(-0.4899, B));
|
||||
xs.add(Intersection(0.4899, B));
|
||||
xs.add(Intersection(0.9899, A));
|
||||
|
||||
Computation comps = xs[2].prepareComputation(r, &xs);
|
||||
Colour c = w.refractedColour(comps, 5);
|
||||
|
||||
/* Temporary lower the precision */
|
||||
set_equal_precision(0.00005);
|
||||
|
||||
ASSERT_EQ(c, Colour(0, 0.99888, 0.04725));
|
||||
|
||||
set_equal_precision(FLT_EPSILON);
|
||||
}
|
||||
|
||||
TEST(WorldTest, Shade_hit_with_a_transparent_material)
|
||||
{
|
||||
World w = DefaultWorld();
|
||||
|
||||
Plane floor = Plane();
|
||||
floor.setTransform(translation(0, -1, 0));
|
||||
floor.material.transparency = 0.5;
|
||||
floor.material.refractiveIndex = 1.5;
|
||||
w.addObject(&floor);
|
||||
|
||||
Sphere ball = Sphere();
|
||||
ball.material.colour = Colour(1, 0, 0);
|
||||
ball.material.ambient = 0.5;
|
||||
ball.setTransform(translation(0, -3.5, -0.5));
|
||||
w.addObject(&ball);
|
||||
|
||||
Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2));
|
||||
Intersect xs = Intersect();
|
||||
xs.add(Intersection(sqrt(2), &floor));
|
||||
|
||||
Computation comps = xs[0].prepareComputation(r, &xs);
|
||||
|
||||
Tuple c = w.shadeHit(comps, 5);
|
||||
|
||||
/* Temporary lower the precision */
|
||||
set_equal_precision(0.00001);
|
||||
|
||||
ASSERT_EQ(c, Colour(0.93642, 0.68642, 0.68642));
|
||||
|
||||
set_equal_precision(FLT_EPSILON);
|
||||
}
|
||||
Reference in New Issue
Block a user