0%

Learning Journal: RayTracing

This article is a summary of my self-study about Ray-Tracing.

In the traditional rendering pipeline, every object, primitive, and pixel are calculated lighting independently. They know less or even nothing about nearby objects. As a result, it’s hard to calculate indirect illumination. For example, those objects out of view frustum will be culled. But in reality, they make great contributions to the scene. Though, these years many methods have be proposed, they are more like some tricks.

The theory of Ray-Tracing has been advanced for almost 40 years, but due to its giant amount of calculation, it has not been widely applied. Recently, the breakthrough of graphics technology makes it possible to apply this technology in real-time rendering and greatly improves the rendering quality.

Introduction

Ray-Tracing is a recursive process. Firstly, cast a ray from the view point to a pixel in the screen, then get the closest intersection object in this direction, calculate the lighting in the intersection point. Next, reflect or refract the ray and repeat the Ray Casting, Closest Intersection Object, Reflect and Refract until the ray reach the light source or exceed the maximum recursion depth.

Ray Casting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ray {
public:
ray();
ray(const point3& origin, const vec3& direction);

point3 origin() const;
vec3 direction() const;

point3 at(double t) const;

private:
point3 orig;
vec3 dir;
};

A ray is defined by an origin point and a casting direction. Every point in this ray can be defined by \(p = orig + t *dirp=orig+t∗dir\).

Object Intersection

Now, we have the representation of the ray and the surface \(f(p) = 0\). By replacing the \(p\) in the surface equation, we get a new equation in terms of \(t\), \(f(orig + t * dir) = 0\). If this equation has no solution means not intersecting, and the smallest solution is the intersection point.

Intersection Equation

  • Sephere Equation A sephere with the center \(c(x_{c}, y_{c}, z_{c})\)and radius \(R\) can be represented by: \[\begin{align} (x-x_c)^2+(y-y_c)^2+(z-z_c)^2-R^2=0 \nonumber \end{align}\] Rewrite the equation in vector form: \[\begin{align} (p-c)\cdot(p-c)-R^2=0 \nonumber \end{align}\] Replace pp by equation \(p = orig + t*dir\): \[\begin{align} (orig + t * dic - c)\cdot(orig + t * dic - c)-R^2=0 \nonumber \\ (dir\cdot dir)t^2 + 2dir\cdot(orig-c)t + (orig - c)\cdot(orig - c)-R^2=0 \nonumber \end{align}\] It’s a Quadratic Polynomial, \(At^2+Bt+C=0\): \[\begin{align} A=dir\cdot dir \nonumber \\ B=2dir\cdot(orig - c) \nonumber \\ C=(orig-c)\cdot(orig-c)-R^2 \nonumber \\ t=(-B\pm \sqrt{B^2-4AC}\over 2A \nonumber \\ t=(-B/2\pm \sqrt{(B/2)^2-AC}\over A \nonumber \\ \end{align}\]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool sphere::hit(const ray& r, double tmin, double tmax) const
{
vec3 oc = r.origin() - center;
auto a = r.direction().length_squared();
auto half_b = dot(oc, r.direction());
auto c = oc.length_squared() - radius * radius;
auto discriminant = half_b * half_b - a * c;

bool hitted = false;
if (discriminant > 0) {
auto root = sqrt(discriminant);
auto temp = (-half_b - root) / a;
if (temp < tmax && temp > tmin) {
hitted = true;
}
else {
temp = (-half_b + root) / a;
if (temp < tmax && temp > tmin) {
hitted = true;
}
}
}
return hitted;
}
  • Triangle Equation Giving a triangle with vertices \(a, b, c\) the ray intersects with the triangle surface at: \[\begin{align} orig + t * dir=\alpha + \beta(b-a) +\gamma(c-a) \nonumber \end{align}\]

Only if \(\beta>0\), \(\gamma>0\), \(\beta+\gamma<1\), the intersection is inside the triangle.

Materials

  • Diffuse Surfaces don’t emit light but only scatter light equality in all directions.
    1
    2
    vec3 scatter_direction = hit_record.normal + random_in_hemisphere(hit_record.normal);
    scattered = ray(hit_record.p, scatter_direction);
  • Reflection The smooth surfaces will scatter light in a specific direction. The relationship between incident light and reflected light is shown in the following figure:

As shown in the figure, we can get the reflected light direction equation:

1
2
3
4
vec3 reflect(const vec3& v, const vec3 n)
{
return v - 2 * dot(v, n) * n;
}
* Refraction When a light hits materials like glass, and water, it will split into reflected and refracted light. According to the Snell’s law: \[\begin{align} sin\theta_r=n_1sin\theta_i \over n_2 \nonumber \end{align}\]

We assume that the incident ray \(R\), and the refrated ray \(R'\) are unit vectors. The reflacted ray \(R'\) can be splited into parts \(R_{\parallel}'\) which parallels to \(n\) and \(R'_{\perp}\) which is perpendicular to \(n\). \[\begin{align} |R_\perp|=|R|*sin\theta_i \nonumber \\ |R'_\perp|=|R'|*sin\theta_r \nonumber \\ |R'_\perp|={ {sin\theta_r * |R_\perp|} \over {sin\theta_i} } \nonumber \\ R'_{\perp} = {-{n_1*R_{\perp}\over n_2}} \nonumber \\ R_{\perp}=-(R+|R|cos\theta_i n) \nonumber \\ R'_\perp={n_1\over n_2}(R+cos\theta_i n) \nonumber \\ R'_\parallel = -\sqrt{1-|{R'_\perp}^2n} \nonumber \end{align}\]

1
2
3
4
5
6
7
8
9
vec3 unit_direction = unit_vector(r_in.direction());

double cos_theta = fmin(dot(-unit_direction, hit_record.normal), 1.0);
double sin_theta = sqrt(1.0 - cos_theta * cos_theta);

vec3 r_out_perp = etai_over_etat * (unit_direction + cos_theta * hit_record.normal);
vec3 r_out_parallel = -sqrt(1.0 - r_out_parallel.length_squared()) * n;

vec3 refracted = r_out_parallel + r_out_perp;