The code is compiled with cmake, please check out CMakeLists.txt
icon-padding
I made some changes to the code, please check out this link
Quadrilaterals
A quadrilateral(quad for short) in R3 can be defined by 3 vectors in R3, Q,u,v, as:
P(s,t)=Q+su+tv
Which Q indicates one vertex of the quad, and the other two vectors spans a plane. If we constrain the range of s and t to be a finite set A and B, the set {P(s,t):s∈A,t∈B} contains all the points on the quad.
If we recall the way we generate AABB, we will notice a problem: when we generate AABB for a plane:
MathUtil.cpp
1 2 3 4 5
AABB::AABB(const Point3 &a, const Point3 &b) { x = Interval(fmin(a[0], b[0]), fmax(a[0], b[0])); y = Interval(fmin(a[1], b[1]), fmax(a[1], b[1])); z = Interval(fmin(a[2], b[2]), fmax(a[2], b[2])); }
If the plane lies on XY, YZ, or XZ plane, the AABB will have thickness of 0 and will result in numeric error in our intersection function:
Now we have to find a way to check whether the ray hits the quad. This can be broken down into three steps:
Find the plane containing the quad.
Solve for the intersection.
Check the intersection lies in the quad or not.
Plane intersection
We know the scalar form of a plane is:
Ax+By+Cz=D
where
⎣⎢⎡ABC⎦⎥⎤
is a vector that is orthogonal to the plane.
By observation, we see this is just a dot product of the normal vector of the plane and the position vector of any point on the plane. The point can be the intersection point of our ray and the plane. Therefore, denote our ray as R(t)=P+td we can rewrite the equation as:
Do some simplification:
From this equation, we can see that if n⋅d=0, we don’t have an intersection.
On the steps above we assumed we know the equation of the plane, but it’s quite simple to obtain. We know Q must be on the plane, and normalized u×v is the normal vector. Therefore, D=Q⋅(u×v), and we obtained the scalar equation with the information known.
Quad intersection
When doing the quad intersection, since we already know the intersection point is on the plane of quad lying on, we reduced the problem to happen on R2. There are two vectors, and we know 2 linear independent vectors can span the whole R2, we can treat u and v as basis of the space.
We know, for a plane defined by:
P=Q+αu+βv
We can find any point on that plane by finding its α and β. And if we want to see if the point lays inside the quad created by u and v, we just need to check the vector originated from Q to the hit point has both 0≤α≤1 and 0≤β≤1.
Denote p as the vector on the uv plane, originated from the origin to the hit point, we have:
to solve for α and β, we cross each side by u and v:
both side dot by n, the normal vector, and move the terms to one side of the equal sign, we get:
To reduce the complexity of computation, we flip the cross order in the expression with alpha:
α=n⋅(u×v)n⋅(p×v)
And extract a common constant from each value, denote it as w
and we get the simplified version of α and β:
Implementation
First we have to implement a quad class to store those values:
auto blue = std::make_shared<Lambertian>(Color{0.36, 0.81, 0.98}); auto pink = std::make_shared<Lambertian>(Color{0.96, 0.66, 0.72}); auto white = std::make_shared<Lambertian>(Color{1, 1, 1});
For triangles, we can still use the plain-interior way to check intersection. Therefore, we only need to think of a way to check whether the point is inside the triangle or not.
If we connect the vertex of a triangle with the intersection point, we get a vector pointing towards the intersection point, and there are only two situations we need to consider:
The vertex-intersection vector is in the left of the vertex-(next vertex) vector
The vertex-intersection vector is in the right of the vertex-(next vertex) vector, or they are in the same direction
We can use cross product to differentiate the first two cases: since they have different directions, the cross product must have same magnitude but opposite direction. Therefore, we only need to check its relative direction to the normal vector, which can be done by checking the sign of dot product.
Similar to quad, our triangle class has similar definition:
auto blue = std::make_shared<Lambertian>(Color{0.36, 0.81, 0.98}); auto pink = std::make_shared<Lambertian>(Color{0.96, 0.66, 0.72}); auto white = std::make_shared<Lambertian>(Color{1, 1, 1});