Commit ae2b9dc5 authored by TheNumbat's avatar TheNumbat
Browse files

Release new version

Features:
    - Particle systems can now specify a maximum dt per step
    - Animation key-framing & timing system now supports objects with simulation
    - Mixture/multiple importance sampling for correct low-variance direct lighting
        - New BSDF, point light, and environment light APIs that separate sampling, evaluation, and pdf
        - Area light sampling infrastructure
        - Removed rectangle area lights; all area lights are now emissive meshes
        - Reworked PathTracer tasks 4-6, adjusted/improved instructions for the other tasks

Bug fixes:
    - Use full rgb/srgb conversion equation instead of approximation
    - Material albedo now specified in srgb (matching the displayed color)
    - ImGui input fields becoming inactive no longer apply to a newly selected object
    - Rendering animations with path tracing correctly steps simulations each frame
    - Rasterization based renderer no longer inherits projection matrix from window
    - Scene file format no longer corrupts particle emitter enable states
    - Documentation videos no longer autoplay
    - Misc. refactoring
    - Misc. documentation website improvements
parent afa3f68f
---
layout: default
title: Environment Light Importance Sampling
grand_parent: "A3: Pathtracer"
parent: (Task 7) Environment Lighting
permalink: /pathtracer/importance_sampling
---
# Environment Light Importance Sampling
A pixel with coordinate <img src="environment_eq1.png" style="height:18px"> subtends an area <img src="environment_eq2.png" style="height:18px"> on the unit sphere (where <img src="environment_eq3.png" style="height:16px"> and <img src="environment_eq4.png" style="height:18px"> the angles subtended by each pixel -- as determined by the resolution of the texture). Thus, the flux through a pixel is proportional to <img src="environment_eq5.png" style="height:14px">. (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 <img src="environment_eq6.png" style="height:18px"> the probability distribution for all pixels, compute the marginal probability distribution <img src="environment_eq7.png" style="height:32px"> for selecting a value from each row of pixels.
* Given for any pixel, compute the conditional probability <img src="environment_eq8.png" style="height:40px">.
Given the marginal distribution for <img src="environment_eq9.png" style="height:14px"> and the conditional distributions <img src="environment_eq10.png" style="height:18px"> 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 <img src="environment_eq11.png" style="height:18px">.
2. Given this row, use the inversion method to select a pixel in the row according to <img src="environment_eq12.png" style="height:18px">.
...@@ -21,47 +21,17 @@ In order to correctly implement `hit` you need to understand some of the fields ...@@ -21,47 +21,17 @@ In order to correctly implement `hit` you need to understand some of the fields
* `dir`: the 3D direction of the ray (always normalized) * `dir`: the 3D direction of the ray (always normalized)
* `dist_bounds`: the minimum and maximum distance along the ray. Primitive intersections that lie outside the [`ray.dist_bounds.x`, `ray.dist_bounds.y`] range should be disregarded. * `dist_bounds`: the minimum and maximum distance along the ray. Primitive intersections that lie outside the [`ray.dist_bounds.x`, `ray.dist_bounds.y`] range should be disregarded.
* `depth`: the recursive depth of the ray (Used in task 5). * `depth`: the recursive depth of the ray (Used in task 5).
* `throughput`: the fraction of incoming light along this ray that will contribute to the final image (Used in task 5). * `throughput`: the fraction of incoming light along this ray that will contribute to the final image (Task 5).
One important detail of the ray structure is that `dist_bounds` is a mutable field. This means that it can be modified even in `const` rays, for example within `Triangle::hit`. When finding the first intersection of a ray and the scene, you will want to update the ray's `dist_bounds` 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. One important detail of the ray structure is that `dist_bounds` is a mutable field. This means that it can be modified even in `const` rays, for example within `Triangle::hit`. When finding the first intersection of a ray and the scene, you will want to update the ray's `dist_bounds` 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: `Triangle::hit` ## Reference Results
The first intersect routine that the `hit` routines for the triangle mesh in `student/tri_mesh.cpp`. You should now be able to render all of the example scenes colored based on surface normals. Note that scenes with high geometric complexity will be extremely slow until you implement task 3. Here is `dodecahedron.dae`, `cbox.dae`, and `cow.dae`:
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). ![dodecahedron](images/dodecahedron_normals.png)
![cbox](images/cbox_normals.png)
![cow](images/cow_normals.png)
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.
* `distance`: the distance from the origin of the ray to the hit point.
* `position`: the position of the hit point. This may also be computed from `distance` and the query ray.
* `normal`: the normal of the surface at the hit point. If the intersection is with a triangle, the normal should be interpolated from per-vertex normals according to the barycentric coordinates of the hit point.
* `origin`: the origin of the query ray (ignore).
* `material`: the material ID of the hit object (ignore).
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!
**Tip:** While you are working with `student/tri_mesh.cpp`, you can choose to implement `Triangle::bbox` as well (pretty straightforward to do), which is needed for task 3.
## Step 2: `Sphere::hit`
You also need to implement the `hit` routines for the `Sphere` class in `student/shapes.cpp`. Remember that your intersection tests should respect the ray's `dist_bound`, and that normals should be out-facing.
**Tip 1:** take care **NOT** to use the `Vec3::normalize()` method when computing your
normal vector. You should instead use `Vec3::unit()`, since `Vec3::normalize()`
will actually change the `Vec3` calling object rather than returning a
normalized version.
**Tip 2:** A common mistake is to forget to check the case where the first
interesection time t1 is out of bounds but the second interesection time t2 is
(in which case you should return t2).
---
[Visualization of normals](visualization_of_normals.md) might be very helpful with debugging.
--- ---
layout: default layout: default
title: (Task 6) Materials title: (Task 5) Materials
permalink: /pathtracer/materials permalink: /pathtracer/materials
parent: "A3: Pathtracer" parent: "A3: Pathtracer"
has_children: false has_children: false
has_toc: false has_toc: false
--- ---
# (Task 6) Materials # (Task 5) Materials
## Walkthrough
<video width="750" height="500" controls>
<source src="videos/Task6_Materials.mp4" type="video/mp4">
</video>
### Walkthrough Video Now that you have implemented the ability to sample more complex light paths, it's time to add support for more types of materials. In this task you will add support for two types of specular materials: mirrors and glass. These materials are implemented in `student/bsdf.cpp`.
<iframe width="750" height="500" src="Task6_Materials.mp4" frameborder="0" allowfullscreen></iframe>
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`. - In the diagrams below, both `out_dir` and `in_dir` are pointing _away_ from the intersection point. This is so that we can more consistently consider their angles with respect to the surface normal.
- Remember that we are tracing rays _backwards_, from the camera into the scene. This is why the computed scattering direction (`in_dir`) corresponds with the _incoming_ light.
**Note:** In the BSDF diagrams and documentation below, 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. This is so that it is easy to define the angles with respect to the surface normal. <center><img src="figures\rays_dir.png" style="height:420px"></center>
Also, remember that in pathtracing, we are tracing _backwards_, from the camera to the scene, which is why the output of these BSDF diagrams (and hence your BSDF functions) correspond with the input rays of the pathtracing procedure. First, take another at the BSDF interface in `rays/bsdf.h`. There are a number of key methods you should understand in `BSDF`:
<center><img src="rays_dir.png" style="height:420px"></center>
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`: - `Scatter scatter(Vec3 out_dir)`: given `out_dir`, generates a random sample for `in_dir`. It returns a `Scatter`, which contains both the sampled `direction` and the `attenuation` for the in/out pair.
- `Spectrum evaluate(Vec3 out_dir, Vec3 in_dir)`: evaluates the BSDF for a given pair of directions. This is only defined for continuous BSDFs.
- `float pdf(Vec3 out_dir, Vec3 in_dir)`: computes the PDF for sampling `in_dir` from the BSDF distribution, given `out_dir`. This is only defined for continuous BSDFs.
- `Spectrum emissive()`: returns emitted light. This is only defined for the diffuse light source BSDF.
* `Spectrum evaluate(Vec3 out_dir, Vec3 in_dir)`: evaluates the distribution function for a given pair of directions. To complete the mirror and glass materials, you will only need to implement their `scatter` functions, as they are both discrete BSDFs (i.e. they have a finite number of possible `in_dir`s for any `out_dir`). Additionally, you will want to complete two helper functions:
* `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 `attenuation` 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** of `dir` about `{0, 1, 0}`. More detail about specular reflection can be found [here](http://15462.courses.cs.cmu.edu/fall2015/lecture/reflection/slide_028).
* `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 `out_dir` through the surface according to [Snell's Law](http://15462.courses.cs.cmu.edu/fall2015/lecture/reflection/slide_032). Your implementation should assume that when `in_dir` **enters** the surface (that is, if `cos(theta_out) > 0`) then the ray was previously travelling in a vacuum (i.e. index of refraction = 1.0). If `cos(theta_out) < 0`, then `in_dir` is **leaving** the surface and entering a vacuum.
* `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. Remember to **flip the sign of the x and z components** of the output ray direction from the refract method. Finally, when working with Snell's law, there is a special case to account for: total internal reflection. This occurs when a ray hits a refractive boundary at an angle greater than the _critical angle_. The critical angle is the incident \theta_i that causes the refracted \theta_t to be >= 90 degrees, hence can produce no real solution to Snell's Law. In this case, you should set `was_internal` to `true`.
* There is a special case to account for, specifically known as **total internal <center><img src="figures\tir_eqns.png" width="200"></center>
reflection**. This happens for certain angles when the incoming ray is in the material with the
higher refractive index. These certain angles are angles that are greater than the _critical angle_, which is the incident angle \theta_i that causes the
refracted angle \theta_t to be >= 90 degrees, which causes there to be no real solution to Snell's
Law.
<center><img src="tir_eqns.png" width="200"></center> <center><img src="figures\bsdf_diagrams.png" style="height:200px"></center>
**In the case of total internal reflection, you should set `*was_internal` to `true`**.
---
<center><img src="bsdf_diagrams.png" style="height:200px"></center>
## Step 1: `BSDF_Mirror` ## Step 1: `BSDF_Mirror`
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`. Implement `reflect` and `BSDF_Mirror::scatter()`.
Because discrete BSDFs do not require Monte Carlo integration (we can simply analytically evaluate each possible direction), we do not implement `BSDF::pdf`. Perhaps more interestingly, we also do not require `BSDF::evaluate`. This is because evaluating the BSDF is only necessary when sampling directions from distributions other than the BSDF itself. When the BSDF is discrete, like a perfect mirror, we can assume other distributions never sample the single (infinitesimal) direction at which the BSDF is non-zero.
**Hint:** the mirror BSDF is a Dirac Delta. What does this mean for the pdf and the evaluate function? Therefore, we must update our path tracing procedure in `Pathtracer::sample_(in)direct_lighting`: when the BSDF is discrete (`BSDF::is_discrete`), we are not doing a Monte Carlo estimate, hence should not use `BSDF::pdf`. Instead, simply multiply the scattering attenuation and the incoming light. Note that failing to make this check will cause the invalid BSDF calls to abort.
## Step 2: `BSDF_Glass` ## Step 2: `BSDF_Glass`
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. Implement `refract` and `BSDF_Glass::scatter()`.
Specifically your implementation should: Glass is a material that can both reflect and transmit light. As discussed in class, the fraction of light that is reflected vs. transmitted is governed by the dielectric (non-conductive) Fresnel equations. To simulate this, we may sample `in_dir` from either reflection or refraction with probability proportional to the Fresnel reflectance. For example, if the Fresnel reflectance is 0.9, then you should generate a reflected ray 90% of the time. Note that instead of computing the full Fresnel equations, you have the option to use [Schlick's approximation](https://en.wikipedia.org/wiki/Schlick's_approximation) instead.
* Implement `refract` to add support for refracted ray paths. In the description below, <img src="figures/dielectric_eq1.png" width="18"> and <img src="figures/dielectric_eq2.png" width="15"> refer to the index of refraction of the medium containing the incoming ray and the angle of that ray with respect to the boundary surface normal. <img src="figures/dielectric_eq3.png" width="18"> and <img src="figures/dielectric_eq4.png" width="15"> refer to the index of refraction of the new medium and the angle to the boundary normal of the transmitted ray.
* 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 notes below on the Fresnel equations as well as on how to compute a transmittance BSDF.
### Dielectrics and Transmission The Fresnel equations state that reflection from a surface is a function of the surface's index of refraction and the polarity of the incoming light. Since our renderer doesn't account for polarity, we'll apply a common approximation of averaging the reflectance of light polarized in perpendicular and parallel directions:
### Fresnel Equations for Dielectric <img src="figures/dielectric_eq5.png" width="200">
The [Fresnel Equations](https://en.wikipedia.org/wiki/Fresnel_equations) (another [link](http://hyperphysics.phy-astr.gsu.edu/hbase/phyopt/freseq.html) here) describe the amount of reflection from a surface. The description below is an approximation for dielectric materials (materials that don't conduct electricity). In this assignment you're asked to implement a glass material, which is a dielectric. The parallel and perpendicular terms are given by:
In the description below, <img src="dielectric_eq1.png" width="18"> and <img src="dielectric_eq2.png" width="15"> refer to the index of refraction of the medium containing an incoming ray, and the zenith angle of the ray to the surface of a new medium. <img src="dielectric_eq3.png" width="18"> and <img src="dielectric_eq4.png" width="15"> refer to the index of refraction of the new medium and the angle to the surface normal of a transmitted ray. <img src="figures/dielectric_eq6.png" width="200">
The Fresnel equations state that reflection from a surface is a function of the surface's index of refraction, as well as the polarity of the incoming light. Since our renderer doesn't account for polarity, we'll apply a common approximation of averaging the reflectance of polarizes light in perpendicular and parallel polarized light: <img src="figures/dielectric_eq7.png" width="200">
<img src="dielectric_eq5.png" width="200"> Therefore, for a dielectric material, the fraction of reflected light will be given by <img src="figures/dielectric_eq8.png" width="18">, and the amount of transmitted light will be given by <img src="figures/dielectric_eq9.png" width="50">.
The parallel and perpendicular terms are given by: Alternatively, you may compute <img src="figures/dielectric_eq8.png" width="18"> using [Schlick's approximation](https://en.wikipedia.org/wiki/Schlick%27s_approximation).
<img src="dielectric_eq6.png" width="200"> ### Distribution Function for Transmitted Light
<img src="dielectric_eq7.png" width="200"> Although we described the BRDF for perfect specular reflection in class, we did not discuss the distribution function for transmitted light. Unlike reflection, refraction "spreads" or "condenses" a differential beam of light. Hence, a refraction event should change the radiance along a ray.
Therefore, for a dielectric material, the fraction of reflected light will be given by <img src="dielectric_eq8.png" width="18">, and the amount of transmitted light will be given by <img src="dielectric_eq9.png" width="50">. After using Snell's Law to find the direction of refracted rays, compute the BSDF attenuation using the distribution function found in Pharr, Jakob, and and Humphries's book [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Reflection_Models/Specular_Reflection_and_Transmission.html). It also includes a derivation based Snell's Law and the relation <img src="figures/dielectric_eq10.png" width="150">. Of course, you are more than welcome to attempt a derivation on your own!
Alternatively, you may compute <img src="dielectric_eq8.png" width="18"> using [Schlick's approximation](https://en.wikipedia.org/wiki/Schlick%27s_approximation). ---
### Distribution Function for Transmitted Light ## Tips
We described the BRDF for perfect specular reflection in class, however we did not discuss the distribution function for transmitted light. Since refraction "spreads" or "condenses" a beam, unlike perfect reflection, the radiance along the ray changes due to a refraction event. In your assignment you should use Snell's Law to compute the direction of refraction rays, and use the following distribution function to compute the radiance of transmitted rays. We refer you guys to Pharr, Jakob, and and Humphries's book [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Reflection_Models/Specular_Reflection_and_Transmission.html) for a derivation based on Snell's Law and the relation <img src="dielectric_eq10.png" width="150">. (But you are more than welcome to attempt a derivation on your own!) - Check your sphere intersection code, as you may have bugs there that were not exposed by rendering Lambertian spheres in Task 4.
- Check the behavior of your refract function when `index_of_refraction = 1.f`. This should not change the transmitted direction, hence make the glass sphere transparent.
- Test reflection and refraction separately, i.e. ignore the Fresnel coefficient and only refract or reflect. Once you've verified that those are correct, then go ahead and reintroduce the Fresnel coefficient and split rays between reflection and refraction.
<center><img src="images/cbox_debug.png"></center>
When you are done, you will be able to render images like this one, the Cornell Box with a metal and glass sphere (`cbox.dae`): ---
<center><img src="new_results/32k_large.png"></center> ## Reference Results
## Tips for Debugging Glass BSDF When you are done, you will be able to render images with specular materials, like the Cornell Box with a metal and glass sphere (`cbox.dae`, 1024 samples, max depth 8):
* Check your sphere intersection code, as you may have bugs there that were not <center><img src="images/cbox.png"></center>
encountered by the Lambertian spheres from Task 5.
* Check that your refract function is correct by setting `ior = 1.f` in the ---
glass BSDF function. This will make the glass sphere transparent
and check that the refracted ray is indeed obeying Snell's Law.
* Test reflection and refraction separately, i.e. ignore the Fresnel coefficient
and only refract or reflect. Once you've verified that those are correct,
then go ahead and reintroduce the Fresnel coefficient for determining the
portion of rays being reflected vs. refracted.
<center><img src="glass_debug_outputs.png"></center> ## Extra Credit
- Add a more advanced parameterized BSDF, such as Blinn-Phong, GGX, or Disney. More information about evaluating and sampling each of these distributions can be found in [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/) chapters 8 & 9.
- Add textures (either procedural or mapped) defining spatially varying BSDF attributes within an object. Refer to [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/) chapter 10.
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