I've built a renderer which should render glTF materials, as they are defined in their specification. So I should be able to render satisfying (similar to Cycles and Ospray) results just from the base color, metallic and roughness factors. I've implemented their suggested BRDF, but I'm afraid I'm using it wrong, or I'm making a mistake somewhere else.
I'll now describe the steps in my renderer, using the simplest example: light is given by an HDR background and there is one object in the scene with roughness = 0 and metallic = 1. I've also attached a sketch of what I'm talking about so it's easier to follow.
Steps in bouncing:
- Ray is shot from the camera
- Ray hits object
- Ray bounces in a mirror-like fashion because of the mentioned material properties
- Ray hits HDR (hit on the HDR = miss all other objects)
- Path ends
Reporting works like this:
- I have the entire path with all three points (HDR, object, camera).
- I start from the HDR and assign the path RGB values to the RGB values at the sampled point on the HDR. For example, path.RGB = (0.2, 0.8, 0.9).
- I continue along the path and come to the point on the object. From the intersection data I get all the necessary information (base color, metallic, roughness and the normal at that point).
- I provide all that info to the BRDF function and it returns some RGB values.
- I multiply the RGB values which I got from the BRDF with the current path RGB (which is just the HDR values at this point) and that becomes the new path RGB.
- I get to the camera point, and save the path RGB for writing to image later.
I simplified some parts which I'm fairly certain are not the cause of the problem, like the distance dependency and sampling and stuff like that.
So to get to the actual problem,
at the second point on the path (object intersection), my light vector is pointing towards the point on the HDR and my view vector is pointing towards the camera. Since the roughness at this point is 0, these two vectors are mirror images of one another, around the normal vector. So their half vector is the same vector as the normal vector. And when H (half vector) and N (normal vector) are the same, the BRDF produces a specular highlight, that happens because the D term (Trowbridge-Reitz/GGX microfacet distribution) ends up as a ridiculously large value. Now I'm aware that this should happen, If I were to use a point light source instead of the HDR, I would get the appropriate specular highlight, but that's because the light vector in that case is defined by the position of point light, and only a few points on the surface would fulfill the condition where H and N are the same (or no rays would in case of no roughness and point light with no radius). Problem appears when the light direction is the next point on the path since the direction towards it is the perfect reflection of the incident ray.
In the image I've attached, all rays hitting the object would bounce mirror-like, and each point on the object would be considered a specular highlight by the BRDF and it would return RGB values which go up to 8e14. By multiplying the path.RGB with those values, I would break the energy conservation, so currently I normalize them, but that also leads to problems. Black objects report with values around 5e10, and white objects as 8e14, as mentioned. Both end up normalized to (1, 1, 1) and look the same, even though they're opposites.
Now I'm not sure where exactly I'm I making a mistake, I've had my implementation of the BRDF checked multiple times, it's exactly like the specification.
Is my mistake considering the light vector to be the next (or previous, depends where you start from) point on the path? Or is it something else?