Sunday, September 6, 2015

An Intuitive Guide to Fresnel



Let me preface this with a couple of things, this is not aimed at physicists or mathmaticians, but rather at graphics software developers looking to get an intuitive understanding of the Fresnel effect. I'm not an expert on the Fresnel equations and so most of this is just my own findings and hunt to understand what Fresnel shaders are really trying to model, so don't take any of what I say below as expert opinion in anyway.

 I'm assuming that most developers reading this are already aware of the Fresnel effect and have likely implemented it in the form of Schlick's approximation or have heard of it, and are just trying to understand what the formula represents. If you're not as familiar on the Fresnel shading, here's some really great sources to get you started:
http://kylehalladay.com/blog/tutorial/2014/02/18/Fresnel-Shaders-From-The-Ground-Up.html
http://filmicgames.com/archives/557


However even after digging through those, I still couldn't get an intuitive understanding of what the hell Schlick's approximation was actually trying to represent. Why does index of refraction determine the reflectiveness of a surface? Why in the world do grazing angles make surfaces more reflective??

Lets see if we can break down Schlick's approximation and understand what he was trying to model. Note that Schlick's Approximation is an approximation, if I wanted to really break it down I would look at the Fresnel equations but Schlick's approximation is much simpler and will be sufficient for what I want to talk about. Don't worry, we will try to avoid jumping into too much math in favor of a more intuitive understanding.


So here is Schlicks approximation:

R₀ = ((n₁ - n₂) / (n₁ + n₂))²

R(θ) = R₀ + (1 - R₀)(1 - cos(N•V))⁵


First lets talk about R₀. We can think of R₀ as the base value of how reflective a material is. What you'll find is regardless of N, L, or θ is, R(θ) will always return a value >= R₀. What you'll find is that for most values, R(θ) will return a value close to R₀.


I'm going to make a few assumptions to simplify things, first let always assume that n₁ is air, and n₁=1 (actually it's 1.00029 but that's okay). Also lets assume n₂ is going to be materials we generally see in real life and so generally n₂ >= 1 (for those interested in some example indexes of refractions check here: http://hyperphysics.phy-astr.gsu.edu/hbase/tables/indrf.html). So our simplified R₀ calculation is:

R₀ = ((1 - n₂) / (1 + n₂))²

When graphing this you get something like:





So a couple of interesting observations we can make:



  • At IOR = 1, there is no reflectivity. That makes sense as you a ray traversing from one section of air to another section of air shouldn't cause any kind of reflection. 
  • At IOR >= 1 (the range we are considering), the amount of reflection goes up with the IOR. We need to break down the definition of IOR to get to the bottom of this one, lets take a look at the wiki definition: https://en.wikipedia.org/wiki/Refractive_index#Definition. So the index of refraction = c / v, where c is the speed of light and v is the speed at which light can travel through the given material. This tells us that materials with a high IOR are materials where light moves more slowly through and material with a low IOR are materials where light moves quickly through. It is helpful to think of IOR as a kind of density, physics folks will get upset by this because you can have a high IOR for low density materials due to polarity/magnetism/etc., but for the sake of imagining IOR, I like to think of it as "the amount of resistance light deals with going through a surface". This makes it much easier to rationalize why reflectivity is based on IOR, surfaces with a low IOR or low "resistance to light" are much more likely to absorb light where as high IOR surface or high "resistance to light" are much more likely to "reject" or reflect light. 
Now lets talk about the interesting part:

R(θ) = R₀ + (1 - R₀)(1 - cos(N•V))⁵

And actually the first part R₀ + (1 - R₀) is just a lerp based on R₀. The ⁵ is Schlick playing with the curve to get it close to what the Fresnel equations, this is interesting as well but out of the scope of what I want to talk about. What's really interesting about this is the remaining:

(1 - cos(N•V))

What this says is that when N and V are close (you are facing the surface dead on), the value is 0. When N and V are perpendicular (glancing angle), the value is 1. I thought about it and I simply couldn't accept why this made any sense, so I did some digging. There's a whole lot of justification about how this all makes sense due to the Maxwell equations but the only thing that spoke to me on an intuitive level was this: http://physics.stackexchange.com/questions/12035/why-does-light-reflect-more-intensely-when-it-hits-a-surface-at-a-large-angle. The easiest way to imagine it is that at glancing angles, it takes much less energy to redirect the light than it does to redirect light when hitting a surface dead on.






The analogy given on the stack exchange link mentions the idea of a bullet, you can imagine that the bullets momentum will be slowed down quite a bit when hitting a wall straight on, but may still keep a lot of the momentum when grazing off a wall. In the case of light, it's not really momentum but more related to electromagnetism, but I find that even this analogy is enough for me to be able to swallow what's going on due to Schlick's approximation.

If you have any feedback, please let me feel free to comment (particularly you light/physics experts, I'd love to hear your details and especially corrections on whether my explanations are really true to the physics of what's going on).


















Wednesday, March 25, 2015

Screen Space Reflections

Here's a completely brute force implementation of screen space reflection, done in DX11.



At a high-level, screen space reflections is ray marching along pixels to see if you can use on-screen information to determine if you can calculate what the reflection should be. It's actually a fairly simple concept, but the math turns out to be fairly nasty if you aren't aware of the caveats.

Some tricky points to be aware of:
  1. You'll need to march along the ray in screen space coordinates since you want to go pixel by pixel 
    • You can optimize using a binary search, interesting blog entry on it here
  2. The depth in screen space coordinates is not linear 99% of the time.
    • This is due to the way the projection matrix is created, the depth is made logarithmic to prevent z-fighting between objects that are really close to the camera. 
  3. Because depth isn't linear in screen space, you'll actually want to do the ray intersection in view space
Now if you're reading closely, you may be wondering do I need to trace in screen space or view space? The tricky part is that you're kind of doing both. You'll want to traverse pixel by pixel, but then convert each point back to view space to calculate the intersection.

Source:

  1. Github
  2. Zipped Source

References:

  1. Morgan McGuire's post on Screen Space Reflection + Shader Source - Original source that my screen space tracing is based on
  2. Kode80's Unity 5 Implementation - Great descriptions of how you can take screen space reflections further
  3. D3D11 Samples - Original code was pulled from here