# Implementing the example of Walter et al. (2007) paper

As the title suggests I'm trying to implement the last images of the "Microfacet Models for Refraction through Rough Surfaces" Paper by Walter et al. in OpenGL

I "think" (I had to put that into quotation marks) I know how the microfacet model should work and hopefully am only stuck on practical execution.

So I started by implementing the GGX formulas for the Normal Distribution, Fresnel and Geometry Function.

Initially and still I'm not so sure how to get the color of the object behind the glass into the formulas. My first thought was to create a cubemap around the slate of glass and then use the outgoing direction vectors (o_t and o_r) to sample the cubemap. But then I'm still not so sure how to incorporate that into the larger picture. Going by my first thought again I just multiplied the result of the BTFD with the sample of the o_t vector but apparently the BTDF was really big so the whole slate turned white (possible bug?). After dividing the result a few times some washed-up version of what was supposed to show up appeared.

Also I think I read somewhere that the reflection and transmission should be handled seperatly depending on the result of the Fresnel formula. Or should it be handled at the same time (possibly somethink like : BRDF * sample_of_reflection + BTDF * sample_of_refraction)?

And another thing I found curious is the given equation for sampling:

$$\theta = arctan(a * sqrt(rand1) / sqrt(1 - rand1))$$

$$\phi = 2 * \pi * rand2$$

After some playing around with this function I noticed that is basically something akin to a gaussian distribution pointing towards the positive z-axis. But shouldn't the distribution give samples which are distributed around the surface normal?

I'm wondering if I have to add an additional pair of quotation marks to my earlier statement...

So anyway here is the pseudocode for my implementation until now:

for(#samplecount)
{
//get a sample m
vec3 m = drawsample();

//calculate normal and incoming vector
vec3 i = normalize(viewPosition - fragmentPosition);
vec3 n = normalize(Normal);

// calculate outgoing vector
vec3 o_r = calculate_like_paper();
vec3 o_t = calculate_like_paper();

//sample cubemap
vec3 reflection = sampleCubemap();
vec3 refraction = sampleCubemap();

//calculate halfvectors
vec3 h_r = calculate_like_paper();
vec3 h_t = calculate_like_paper();

//calculate NDF, Fresnel and Geometry
float D = GGX_D(h_r);
float G = GGX_G(i, o_r, h_r);
float F = GGX_F(i, h_r);

//calculate BSDF, BRDF & BTDF
float BRDF = F * G * D / (4.0 * abs(dot(i, n)) * abs(dot(o_r, n)));
float BTDF = longFormula();

float BSDF = BRDF + BTDF

vec3 result += BRDF * reflection + BTDF * refraction //?
}
result /= #samplecount;

Thank you if you take your time to answer and I'm relatively new to StackExchange so I could definitely do with some feedback to improve the way I ask questions.

Edit: So if anyone found this question and has similar questions Epic Games has some nice notes on this topic: https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf

Initially and still I'm not so sure how to get the color of the object behind the glass into the formulas.

Normal, this was written with path / ray tracers in mind where that part is easy. Your idea of a cube map might be ok for some use cases (eg. reasonably rough surface for the distance to the surrounding objects). It is not a general solution however. That would likely require GPU ray tracing.

Going by my first thought again I just multiplied the result of the BTFD with the sample of the o_t vector but apparently the BTDF was really big so the whole slate turned white (possible bug?).

I'm writing this from memory but I think you need to divide by the PDF. Which means that your weights will be close to 1. But not quite 1 as the sampling distributions don't include the shadowing terms.

Also I think I read somewhere that the reflection and transmission should be handled seperatly depending on the result of the Fresnel formula. Or should it be handled at the same time (possibly somethink like : BRDF * sample_of_reflection + BTDF * sample_of_refraction)?

A classic path tracer would randomly pick either reflection or refraction based on the fresnel coefficient. That does efficient sampling. But if you can easily get both from a cube map, you can also blend them with the fresnel coefficient.

After some playing around with this function I noticed that is basically something akin to a gaussian distribution pointing towards the positive z-axis. But shouldn't the distribution give samples which are distributed around the surface normal?

Yes, you need to transform the generated direction. It is mentionned in section 5.2, after equation (24), that $$\theta_m$$ is the angle between $$m$$ (microfacet normal) and $$n$$ (surface normal). They don't go into great detail as this way of defining microfacet models goes back to much older articles.

The short version is that you build an orthonormal basis using the surface normal as z and two other perpendicular vectors as x and y. Here it does not matter which as the distribution is isotropic, as long as they are perpendicular to each other and to the normal. Then you transform using that basis.

• I'm writing this from memory but I think you need to divide by the PDF. Which means that your weights will be close to 1. But not quite 1 as the sampling distributions don't include the shadowing terms. <br> So this probably sounds like beginner question, but how exactly do I divide by a PDF. Do you know some good sources where I might read up on that? – Clutterhead Dec 14 '19 at 15:37
• Is it just dividing the BTDF by D(m)|m * n|? – Clutterhead Dec 14 '19 at 17:49
• @Clutterhead Yes, that's your sampling distribution and it is normalized (integrates to 1) so it is a PDF. You can see (sampling with D(m) + division by PDF) as an alternative to (uniform sampling everywhere). The former yielding lower noise but otherwise the same result. – Olivier Dec 15 '19 at 16:10
• I tried blending the reflection sample and the refraction sample with the corresponding Fresnel values and got the following image, with which I'm quite satisfied: !MyImage. But still I didn't use any weight or the BxDF terms, so I feel this isn't done properly. Every time I tried to combine a sample with a weight it just ended in a value too big or too small. I tried dividing the BTDF by the PDF, or by the weight (given in 5.3). Or just dividing the sample by them Or is there something else I'm supposed to do with the BxDF value? – Clutterhead Dec 16 '19 at 11:08