Recently I asked a question regarding how to mix a glossy and diffuse shader in my path tracer: Mix shader looks wrong on my path tracer. I thought it was incorrect, but a comparison between mine and Blender's seems correct. However, now I am lost in how I would approach making the surface of a mirror look rough as this example shows:
I have done some research, and discovered that there are different types of reflectance models such as Beckmann and GGX. I've found some explanations of how to find the BRDF such as this site: Specular BRDF, but I can't find any explanation on how to do the reflection ray with explicit lighting. As shown in the pseudo code of my path tracer (below), for every object I shoot a ray towards a light and use the surface transport rendering equation to incorporate the BRDF. I am assuming this is where the GGX/Beckmann BRDF would be plugged in. (I'm guessing it's not quite that simple though and some probability must be involved). What really gets me though is that reflection ray. For diffuse it's easy because I just send off a random ray anywhere in the hemisphere of the surface normal. However, for specular, there's a more sharp bump in the BRDF. How would I translate that into a reflected ray? If I just jittered the ideal reflection ray a little, that wouldn't relate to how the microfacets are modeled in the GGX/Beckmann reflection.
Explicit lighting equation from Peter Shirley's Realistic Ray Tracing:
$$\large L_S(\mathbf x,\mathbf k_o)\approx\frac{\rho(\mathbf k_i,\mathbf k_o)L_e(\mathbf x',\mathbf x-\mathbf x')v(\mathbf x,\mathbf x')\cos\theta_i\cos\theta'}{p(\mathbf x')\left\lVert \mathbf x-\mathbf x'\right\rVert^2}$$
Where $p(\mathbf x)$ is the density function of the light triangle $1/\text{Area}$
$\mathbf x'$ is a random point on the light triangle
$\mathbf x$ is the hit point on the object
The cosines are the angles between the light's normal and the light ray, and the object's normal and the light ray
$\rho$ is the BRDF ($1/\pi$ for ideal diffuse)
And $v$ is either $1$ or $0$ depending on if it's in shadow
Pseudo code:
rayColor(ray r, depth, int E=1)
{
if(r doesn't hit triangle)
return 0
if(r hit is a light)
{
if(E)
return light_emission
else
return 0
}
vector x = r.origin + r.direction*t // x is point where r hit tri
vector n = normal where ray hit triangle
n.normalize()
vector nl = n.dot(r.d) < 0 ? n : n*(-1) // properly orient normal
if(++depth > 5) return 0 // max bounces
float triangle_area = area of emitting triangle
vector x_light_random = random point on emitting triangle
vector light_normal = normal of emitting triangle
vector d = x_light_random = x_converted;
if(light_normal.dot(d) > 0) light_normal *= -1; // make it emit
// both sides
object_normal.normalize();
light_normal.normalize();
BRDF = 1/PI // perfect diffuse
light_emitted = 1 // emission of 1
vector light_out = 0
if(ray starting at x towards d hits light (i.e. not in shadow))
{
light_out = BRDF*light_emitted*(object_normal.dot(d))*
(-1*light_normal.dot(d)*triangle_area)/
(d.length*d.length*d.length*d.length)
}
vector direct_light = color_of_object_triangle*light_out;
//----SPECULAR-----
vector d2 = r.dir-n*2*n.dot(r.dir); // ideal reflection
vector light_color = 1 // white since dialectics don't change spec
vector specular = light_color*rayColor(createRay(x, d2),depth)
float P = 0.5; // 50/50 chance of mirror/diffuse
if(erand(Xi) < P)
return direct_light/P
else
return spec/(1-P)