Commit 022ba1fa authored by yhesper's avatar yhesper
Browse files

pathtracer doc 1st draft

parent e2ed4635
---
layout: default
title: "Environment Light Importance Sampling"
permalink: /pathtracer/importance_sampling
---
# Environment Light Importance Sampling
A pixel with coordinate ![environment_eq1](environment_eq1.png) subtends an area ![environment_eq2](environment_eq2.png) on the unit sphere (where ![environment_eq3](environment_eq3.png) and ![environment_eq4](environment_eq4.png) the angles subtended by each pixel -- as determined by the resolution of the texture). Thus, the flux through a pixel is proportional to ![environment_eq5](environment_eq5.png). (We only care about the relative flux through each pixel to create a distribution.)
**Summing the fluxes for all pixels, then normalizing the values so that they sum to one, yields a discrete probability distribution for picking a pixel based on flux through its corresponding solid angle on the sphere.**
The question is now how to sample from this 2D discrete probability distribution. We recommend the following process which reduces the problem to drawing samples from two 1D distributions, each time using the inversion method discussed in class:
* Given ![environment_eq6](environment_eq6.png) the probability distribution for all pixels, compute the marginal probability distribution ![environment_eq7](environment_eq7.png) for selecting a value from each row of pixels.
* Given for any pixel, compute the conditional probability ![environment_eq8](environment_eq8.png).
Given the marginal distribution for ![environment_eq9](environment_eq9.png) and the conditional distributions ![environment_eq10](environment_eq10.png) for environment map rows, it is easy to select a pixel as follows:
1. Use the inversion method to first select a "row" of the environment map according to ![environment_eq11](environment_eq11.png).
2. Given this row, use the inversion method to select a pixel in the row according to ![environment_eq12](environment_eq12.png).
\ No newline at end of file
---
layout: default
title: "(Task 2) Intersecting Objects"
permalink: /pathtracer/intersecting_objects
---
# (Task 2) Intersecting Objects
Now that your ray tracer generates camera rays, we need to be able to answer the core query in ray tracing: "does this ray hit this object?" Here, you will start by implementing ray-object intersection routines against the two types of objects in the starter code: triangles and spheres. Later, we will use a BVH to accelerate these queries, but for now we consider an intersection test against a single object.
The `Object` interface is implemented by both the `Triangle` class and `Sphere` class. You can find the definition for `Object`, `Triangle` and `Sphere` in `rays/object.h`, `src/rays/shapes.h`, and `rays/tri_mesh.h`, respectively.
The `hit` routine that you need to implement (for both kinds of objects) takes in a ray, and returns a `Trace` structure, which contains information on whether the ray hits the object and if hits, the information describing the surface at the point of the hit. See `rays/trace.h` for the definition of `Trace`.
In order to correctly implement `hit` you need to understand some of the fields in the Ray structure defined in `lib/ray.h`.
* `point`: represents the 3D point of origin of the ray
* `dir`: represents the 3D direction of the ray (this direction will be normalized)
* `time_bound`: correspond to the minimum and maximum points on the ray with its x-component as the lower bound and y-component as the upper bound. That is, intersections that lie outside the [`ray.time_bound.x`, `ray.time_bound.y`] range should not be considered valid intersections with the primitive.
One important detail of the Ray structure is that `time_bound` is a mutable field of the Ray. This means that this fields can be modified by constant member functions such as `Triangle::hit`. When finding the first intersection of a ray and the scene, you almost certainly want to update the ray's `time_bound` value after finding each hit with scene geometry. By bounding the ray as tightly as possible, your ray tracer will be able to avoid unnecessary tests with scene geometry that is known to not be able to result in a closest hit, resulting in higher performance.
---
### **Step 1: Intersecting Triangles**
The first intersect routine that the `hit` routines for the triangle mesh in `student/tri_mesh.cpp`.
While faster implementations are possible, we recommend you implement ray-triangle intersection using the method described in the [lecture slides](http://15462.courses.cs.cmu.edu/fall2017/lecture/acceleratingqueries). Further details of implementing this method efficiently are given in [these notes](ray_triangle_intersection.md).
There are two important details you should be aware of about intersection:
* When finding the first-hit intersection with a triangle, you need to fill in the `Trace` structure with details of the hit. The structure should be initialized with:
* `hit`: a boolean representing if there is a hit or not
* `time`: the ray's _t_-value of the hit point
* `position`: the exact position of the hit point. This can be easily computed by the `time` above as with the ray's `point` and `dir`.
* `normal`: the normal of the surface at the hit point. This normal should be the interpolated normal (obtained via interpolation of the per-vertex normals according to the barycentric coordinates of the hit point)
* When intersection occurs with the back-face of a triangle (the side of the triangle opposite the direction of the normal) you should flip the returned normal to point in that direction. That is, always return a normal pointing in the direction the ray came from!
Once you've successfully implemented triangle intersection, you will be able to render many of the scenes in the media directory. However, your ray tracer will be very slow!
While you are working with `student/tri_mesh.cpp`, you should implement `Triangle::bbox` as well, which are important for task 3.
### **Step 2: Intersecting Spheres**
You also need to implement the `hit` routines for the `Sphere` class in `student/sphapes.cpp`. Remember that your intersection tests should respect the ray's `time_bound`. Because spheres always represent closed surfaces, you should not flip back-facing normals you did with triangles.
---
[Visualization of normals](visualization_of_normals.md) might be very helpful with debugging.
---
layout: default
title: "(Task 6) Materials"
permalink: /pathtracer/materials
---
# (Task 6) Materials
Now that you have implemented the ability to sample more complex light paths, it's finally time to add support for more types of materials (other than the fully Lambertian material that you have implemented in Task 5). In this task you will add support for two types of materials: a perfect mirror and glass (a material featuring both specular reflection and transmittance) in `student/bsdf.cpp`.
To get started take a look at the BSDF interface in `rays/bsdf.h`. There are a number of key methods you should understand in `BSDF class`:
* `Spectrum evaluate(Vec3 out_dir, Vec3 in_dir)`: evaluates the distribution function for a given pair of directions.
* `BSDF_Sample sample(Vec3 out_dir)`: given the `out_dir`, generates a random sample of the in-direction (which may be a reflection direction or a refracted transmitted light direction). It returns a `BSDF_Sample`, which contains the in-direction(`direction`), its probability (`pdf`), as well as the `absorbtion` for this pair of directions. (You do not need to worry about the `emissive` for the materials that we are asking you to implement, since those materials do not emit light.)
There are also two helper functions in the BSDF class in `student/bsdf.cpp` that you will need to implement:
* `Vec3 reflect(Vec3 dir)` returns a direction that is the **perfect specular reflection** direction corresponding to `dir` (reflection of `dir` about the normal, which in the surface coordinate space is [0,1,0]). More detail about specular reflection is [here](http://15462.courses.cs.cmu.edu/fall2015/lecture/reflection/slide_028).
* `Vec3 refract(Vec3 out_dir, float index_of_refraction, bool& was_internal)` returns the ray that results from refracting the ray in `out_dir` about the surface according to [Snell's Law](http://15462.courses.cs.cmu.edu/fall2015/lecture/reflection/slide_032). The surface's index of refraction is given by the argument `index_of_refraction`. Your implementation should assume that if the ray in `out_dir` **is entering the surface** (that is, if `cos(out_dir, N=[0,1,0]) > 0`) then the ray is currently in vacuum (index of refraction = 1.0). If `cos(out_dir, N=[0,1,0]) < 0` then your code should assume the ray is leaving the surface and entering vacuum. **In the case of total internal reflection, you should set `*was_internal` to `true`.**
* Note that in `reflect` and `refract`, both the `out_dir` and the returned in-direction are pointing away from the intersection point of the ray and the surface, as illustrated in this picture below.
![rays_dir](rays_dir.png)
## Step 1
Implement the class `BSDF_Mirror` which represents a material with perfect specular reflection (a perfect mirror). You should Implement `BSDF_Mirror::sample`, `BSDF_Mirror::evaluate`, and `reflect`. **(Hint: what should the pdf sampled by `BSDF_Mirror::sample` be? What should the reflectance function `BSDF_Mirror::evalute` be?)**
## Step 2
Implement the class `BSDF_Glass` which is a glass-like material that both reflects light and transmit light. As discussed in class the fraction of light that is reflected and transmitted through glass is given by the dielectric Fresnel equations, which are [documented in detail here](dielectrics_and_transmission.md). Specifically your implementation should:
* Implement `refract` to add support for refracted ray paths.
* Implement `BSDF_refract::sample` as well as `BSDF_Glass::sample`. Your implementation should use the Fresnel equations to compute the fraction of reflected light and the fraction of transmitted light. The returned ray sample should be either a reflection ray or a refracted ray, with the probability of which type of ray to use for the current path proportional to the Fresnel reflectance. (e.g., If the Fresnel reflectance is 0.9, then you should generate a reflection ray 90% of the time. What should the pdf be in this case?) Note that you can also use [Schlick's approximation](https://en.wikipedia.org/wiki/Schlick's_approximation) instead.
* You should read the [provided notes](dielectrics_and_transmission.md) on the Fresnel equations as well as on how to compute a transmittance BSDF.
When you are done, you will be able to render images like these:
When you are done, you will be able to render images like these:
![cornell_classic](cornell_classic.png)
---
layout: default
title: "PathTracer Overview"
permalink: /pathtracer/
---
# PathTracer Overview
PathTrace is (as the name suggests) a simple path tracer that can render pictures with global illumination effects. The first part of the assignment will focus on providing an efficient implementation of **ray-scene geometry queries**. In the second half of the assignment you will **add the ability to simulate how light bounces around the scene**, which will allow your renderer to synthesize much higher-quality images. Much like in MeshEdit, input scenes are defined in COLLADA files, so you can create your own scenes for your scenes to render using free software like [Blender](https://www.blender.org/).)
![CBsphere](CBsphere.png)
The files that you will work with for PathTracer are all under `src/student` directory. Some of the particularly important ones are outlined below. Methods that we expect you to implement are marked with "TODO (PathTracer)", which you may search for.
| File(s) | Purpose | Need to modify? |
|----------|-------------------|------------------|
| `student/pathtracer.cpp` | This is the main workhorse class. Inside the ray tracer class everything begins with the method `Pathtracer::trace_pixel` in pathtracer.cpp. This method computes the value of the specified pixel in the output image. | Yes |
| `student/camera.cpp` | You will need to modify `Camera::generate_ray` in Part 1 of the assignment to generate the camera rays that are sent out into the scene. | Yes |
| `student/tri_mesh.cpp`, `student/sphapes.cpp` | Scene objects (e.g., triangles and spheres) are instances of the `Object` class interface defined in `rays/object.h`. You will need to implement the `bbox` and intersect routine `hit` for both triangles and spheres. | Yes |
|`student/bvh.cpp`|A major portion of the first half of the assignment concerns implementing a bounding volume hierarchy (BVH) that accelerates ray-scene intersection queries. Note that a BVH is also an instance of the Object interface (A BVH is a scene object that itself contains other primitives.)|Yes|
|`rays/light.h`|Describes lights in the scene. The initial starter code has working implementations of directional lights and constant hemispherical lights.|No|
|`lib/spectrum.h`|Light energy is represented by instances of the Spectrum class. While it's tempting, we encourage you to avoid thinking of spectrums as colors -- think of them as a measurement of energy over many wavelengths. Although our current implementation only represents spectrums by red, green, and blue components (much like the RGB representations of color you've used previously in this class), this abstraction makes it possible to consider other implementations of spectrum in the future. Spectrums can be converted into a vector using the `Spectrum::to_vec` method.| No|
|`student/bsdf.cpp`|Contains implementations of several BSDFs (diffuse, mirror, glass). For each, you will define the distribution of the BSDF and write a method to sample from that distribution.|Yes|
|`student/samplers.cpp`|When implementing raytracing and environment light, we often want to sample randomly from a hemisphere, uniform grid, or shphere. This file contains various functions that simulate such random sampling.|Yes|
Implementing the functionality of PathTracer is split in to 7 tasks; the sidebar on the right contains links to a page for each.
---
layout: default
title: "(Task 5) Path Tracing"
permalink: /pathtracer/path_tracing
---
# (Task 5) Path Tracing
Up to this point, your renderer simulates light which begins at a source, bounces off a surface, and hits a camera. However in the real world, light can take much more complicated paths, bouncing of many surfaces before eventually reaching the camera. Simulating this multi-bounce light is referred to as _indirect illumination_, and it is critical to producing realistic images, especially when specular surfaces are present. In this task you will modify your ray tracer to simulate multi-bounce light, adding support for indirect illumination.
You must modify `Pathtracer::trace_ray` to simulate multiple bounces. We recommend using the [Russian Roulette](http://15462.courses.cs.cmu.edu/spring2020/lecture/montecarloraytracing/slide_044) algorithm discussed in class.
The basic structure will be as follows:
* (1) Randomly select a new ray direction using `bsdf.sample` (which you will implement in Step 2)
* (2) Potentially terminate the path (using Russian roulette)
* (3) Recursively trace the ray to evaluate weighted reflectance contribution due to light from this direction. Remember to respect the maximum number of bounces from `max_depth` (which is a member of class `Pathtracer`).
## Step 2
Now, Implement `BSDF_Lambertian::sample` for diffusely reflecting, which randomly samples from the distribution and returns a `BSDF_Sample`. Note that the interface is in `rays/bsdf.h`. Task 6 contains further discussion of sampling BSDFs, reading ahead may help your understanding. The implementation of `BSDF_Lambertian::evaluate` is already provided to you.
Note:
* Functions in `student/sampler.cpp` from class `Sampler` contains helper functions for random sampling, which you will use for sampling. Our starter code uses uniform hemisphere sampling `Samplers::Hemisphere::Uniform sampler`(see `rays/bsdf.h` and `student/sampler.cpp`) which is already implemented. You are welcome to implement Cosine-Weighted Hemisphere sampling for extra credit, but it is not required. If you want to implement Cosine-Weighted Hemisphere sampling, fill in `Hemisphere::Cosine::sample` in `student/samplers.cpp` and then change `Samplers::Hemisphere::Uniform sampler` to `Samplers::Hemisphere::Cosine sampler` in `rays/bsdf.h`.
---
After correctly implementing path tracing, your renderer should be able to make a beautifully lit picture of the Cornell Box with:
```
./scotty3d -s 1024 -m 4 -t 8 ../media/pathtracer/advanced/CBspheres_lambertian.dae
```
![cornell_lambertian](cornell_lambertian.png)
Note the time-quality tradeoff here. With these commandline arguments, your path tracer will be running with 8 worker threads at a sample rate of 1024 camera rays per pixel, with a max ray depth of 4. This will produce an image with relatively high quality but will take quite some time to render. Rendering a high quality image will take a very long time as indicated by the image sequence below, so start testing your path tracer early!
![spheres](spheres.png)
Here are a few tips:
* The termination probability of paths can be determined based on the [overall throughput](http://15462.courses.cs.cmu.edu/fall2015/lecture/globalillum/slide_044) of the path (you'll likely need to add a field to the Ray structure to implement this) or based on the value of the BSDF given wo and wi in the current step. Keep in mind that delta function BSDFs can take on values greater than one, so clamping termination probabilities derived from BSDF values to 1 is wise.
* To convert a Spectrum to a termination probability, we recommend you use the luminance (overall brightness) of the Spectrum, which is available via Spectrum::illum()
* We've given you some [pretty good notes](http://15462.courses.cs.cmu.edu/fall2015/lecture/globalillum/slide_047) on how to do this part of the assignment, but it can still be tricky to get correct.
---
layout: default
title: "Ray Triangle Intersection"
permalink: /pathtracer/ray_triangle_intersection
---
# Ray Triangle Intersection
Given a triangle defined by points _p0_, _p1_ and _p2_ and a ray given by its origin _o_ and direction _d_, the barycentrics of the hit point as well as the _t_-value of the hit can be obtained by solving the system:
![triangle_eq1](triangle_eq1.png)
Where:
![triangle_eq2](triangle_eq2.png)
This system can be solved by Cramer's Rule, yielding:
![triangle_eq3](triangle_eq3.png)
In the above, |a b c| denotes the determinant of the 3x3 with column vectors _a_, _b_, _c_.
Note that since the determinant is given by:![triangle_eq4](triangle_eq4.png)
you can rewrite the above as:
![triangle_eq5](triangle_eq5.png)
Of which you should notice a few common subexpressions that, if exploited in an implementation, make computation of _t_, _u_, and _v_ substantially more efficient.
A few final notes and thoughts:
If the denominator _dot((e1 x d), e2)_ is zero, what does that mean about the relationship of the ray and the triangle? Can a triangle with this area be hit by a ray? Given _u_ and _v_, how do you know if the ray hits the triangle? Don't forget that the intersection point on the ray should be within the ray's `time_bound`.
\ No newline at end of file
---
layout: default
title: "(Task 4) Shadow Rays"
permalink: /pathtracer/shadow_rays
---
# (Task 4) Shadow Rays
In this task you will modify `Pathtracer::trace_ray` to implement accurate shadows.
Currently `Pathtracer::trace_ray` computes the following:
* It computes the intersection of ray `r` with the scene.
* It computes the amount of light arriving at the hit point `result.position` (the irradiance at the hit point) by integrating radiance from all scene light sources.
* It computes the radiance reflected from the hit point in the direction of -`r`. (The amount of reflected light is based on the BSDF of the surface at the hit point.)
Shadows occur when another scene object blocks light emitted from scene light sources towards the hit point. Fortunately, determining whether or not a ray of light from a light source to the hit point is occluded by another object is easy given a working ray tracer (which you have at this point!). **You simply want to know whether a ray originating from the hit point (`result.position`), and traveling towards the light source (direction to light) hits any scene geometry before reaching the light.** (Note that you need to consider light's distance from the hit point is given, more on this in the notes below.)
Your job is to implement the logic needed to compute whether hit point is in shadow with respect to the current light source sample. Below are a few notes:
* In the starter code, when we call `light.sample(result.position)`, it returns us a `Light_sample sample` at the hit point . (You might want to take a look at `rays/light.h` for the definition of `struct Light_sample` and `class light`.) A `Light_sample` contains fields `radiance`, `pdf`, `direction`, and `distance`. In particular, `sample.direction` is the direction from the hit point to the light source, and `sample.distance` is the distance from the hit point to the light source.
* A common ray tracing pitfall is for the "shadow ray" shot into the scene to accidentally hit the same objecr as `r` (the surface is erroneously determined to be occluded because the shadow ray is determined to hit the surface!). We recommend that you make sure the origin of the shadow ray is offset from the surface to avoid these erroneous "self-intersections". For example, consider setting the origin of the shadow ray to be `result.position + epsilon * sample.direction` instead of simply `result.position`. `EPS_F` is defined in for this purpose(see `lib/mathlib.h`).
* Another common pitfall is forgetting that it doesn't matter if the shadow ray hits any scene geometry after reaching the light. Note that the light's distance from the hit point is given by `sample.distance`. Also note that `Ray` has a member called `time_bound`...
* You will find it useful to debug your shadow code using the `DirectionalLight` since it produces hard shadows that are easy to reason about.
* You would want to comment out the line `Spectrum radiance_out = Spectrum(0.5f);` and initialize the `radiance_out` to a more reasonable value. Hint: is there supposed to have any amount of light before we even start considering each light sample?
At this point you should be able to render very striking images. For example, here is the Stanford Dragon model rendered with both a directional light (light coming from a single direction only) and a hemispherical light (light coming from all directions). Notice how both have realistic shadows in response to the the lighting conditions.
![shadow_directional](shadow_directional.png)
![shadow_hemisphere](shadow_hemisphere.png)
\ No newline at end of file
---
layout: default
title: "Visualization of normals"
permalink: /pathtracer/visualization_of_normals
---
# Visualization of normals
For debugging purposes:
You can replace the `radiance_out = Spectrum(5.f)` in `student/pathtracer.cpp` that the starter code gives you with:
```
(result.n).normalize();
return Spectrum(result.normal.n.x/2.0 + 0.5, result.normal.y/2.0 + 0.5, result.normal.z/2.0 + 0.5);
```
Reference results for CBspheres, bunny, dragon, and wall-e:
![normalviz](normalviz.png)
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment