Optimizing GGX Shaders with dot(L,H)

http://filmicworlds.com/blog/optimizing-ggx-shaders-with-dotlh/

the ggx distribution is quickly becoming the dominant lighting model games. but the obvious downside over the previous model is shader cost. so i have been looking at ways of optimizing it and the image above shows the before and after.

Optimizing GGX Shaders with dot(L,H)

the “standard” lighting model takes the cook-torrance separation of terms as:
Specular = DFV
and most games are using sth. along the lines of:

  1. D——ggx distribution
  2. F——schlick-fresnel
  3. V——schlick approximation of smith solved with ggx

this approach gives a much better specular term than anything we were seeing last generation. but we have added quite a few instructions so i wanted to see if we can cut it down a bit while still retaining the quality. Spoiler alert: 据透 we can do it by doing some tricks with dot(L,H).

typical ggx specular function: there are many places to find code examples of these functions, but i took the functions from the UE4 shading presentation by brian karis https://blog.selfshadow.com/publications/s2013-shading-course/#course_content.
there are a bouch of good presentations from the siggraph 2013 physically based shading course https://blog.selfshadow.com/publications/s2013-shading-course/ which u can find on stephen hill’s website, but the formulas i am using here came from Brian Karis’s talk. u should also make sure to check out Brent Burley’s talk (“te disney BRDF”) from the previous year.

in my facial animation talk at GDC, i was still using a distribution based on a bunch of Blinn-Phong lobes, but now that the talk is finished i was able to look more deeply into GGX and i really like the softer tails (just like everyone else).

here is the shader that i will be startinig with. to make your life easier, i will just cut and paste the code. there is a link to all the shader functions at the bottom of the page.

Optimizing GGX Shaders with dot(L,H)

assuming i did not make any mistakes, the formula above is your typical game shader. i am ignoring the “hotness remapping” that Brent Burley mentioned because i prefer the look of the hot highlights, but u an go either way. now, let us start optimizing.

visibility functions:
ideally, i would like to come up with sth. that is close to schlick-smith, but cheapter.
there are several other options for the visibility term. but the one that interests me the most is the Kelemen-Szirmay-Kalos approximation of Cook-Torrance as described in the Siggraph 2010 course by Naty Hoffman. http://renderwonk.com/publications/s2010-shading-course/hoffman/s2010_physically_based_shading_hoffman_b.pdf

before we get too far ahead, let me apologize in advance for taking screenshots of different presentations with different notation. In the image below, the entire left side is just the Visibility term V.
Optimizing GGX Shaders with dot(L,H)

the nice thing about this function is that it is very cheap, but the problem that was mentioned several times is that it is too bright. but why?

when i think of lighting functions, i think about the endpoints. the KSK visibility term is lowest when dot(L,H) is one and is highest when dot(L,H) is zero. note that since H is the half vector between L and V, dot(L,H) are the same thing, so do not get confused if i say dot(L,H) but the formula says dot(V,H) (or vice-versa).

Optimizing GGX Shaders with dot(L,H)

the image above shows the high and low cases for KSK visibility. coincidentally, these are the same as the high and low cases for fresnel. if the light and the camera are pointing straight at the normal, then the V term is 1.0. but as u get towards grazing angles the V term goes to infinity. in reality, u do not actually get to infinity because the entire function is modulated by dot(N,L). that would explain why the Black Ops team thought that KSK was too bright in their talk on on Physically Based Shading in Black Ops. https://blog.selfshadow.com/publications/s2013-shading-course/#course_content

what does Schlick-Smith look like for GGX?
Optimizing GGX Shaders with dot(L,H)

first off, i am going to refer to that a parameter as k. so, what do the extremes look like? when u are straight on (N=V=L), it is 1.0 just like KSK or Cook-Torrance. but when u go to grazing angles it converges to 1/(k^2), since dot(N,L) and dot(N,V) both go to zero.

that makes sense. KSK and Cook-Torrance say that every single surface’s visibility term goes to infinity at grazing angles. but Schlick-Smith says that the visibility term converges to 1/(k^2). and k is a value that increases as roughness increases, so smoother surfaces have brighter grazing angles than rougher surfaces.