Anisotropic GGX BRDF implementation. How is it related to isotropic GGX BRDF? Introduction

I am implementing anisotropic GGX BRDF and have encountered strange behaviour of my implementation. I thought that if I compare the microfacet distribution function I have with the one of an isotropic GGX, then they should be equal when the anisotropy parameter is 0. I haven't been able to do that, though.

Here is the GGX formula I have used:



Here is the anisotropic GGX formula I have used:



If anisotropy is 0, then  is equal to , therefore I can get the following from the second formula:



Problem 1

The problem now is that the third formula can never match the first one because of the negative 1 in the first one.

Problem 2

The specific issue I have when rendering using my anisotropic GGX is that the normals seem to be ignored in the result.

Here is a visualization of the distribution function of isotropic GGX on a flat material patch with normal mapping:



And here is the anisotropic one:



Notation

To complete the explanation of my solution, I use  for the half-vector,  for the normal,  for roughness,  for the tangent and  for the bi-tangent (in my case these are simply aligned with the x and y axes respectively).  and  represent roughness in the corresponding directions.

Questions


Is there a mistake in my reasoning? Is my anisotropic GGX correct?
What is the relationship between the two? Is there a simple explanation for the extension from GGX to anisotropic GGX?
Do you have any general tips for verifying the correctness of one's BRDF implementation?


References

Here are my main references:


Anisotropic GGX implementation
BRDF formulas
After coming back to the problem now with a fresh pair of eyes, I have managed to find my mistake.

Ad Question 1

Yes, the formula is correct. The problem is hidden in the tangent  and bi-tangent . When performing normal mapping, the tangent frame has to be perpendicular to the mapped normal, otherwise the term 
 will not carry any information about the normal mapping.

Another issue here might be that once the normal is mapped, the tangent frame is not uniquely determined. Some more info on that can be found here: http://www.thetenthplanet.de/archives/1180. I have decided to solve this the same way as Blender does (look for make_orthonormals_tangent() in their repo) because I need to be compatible with it. Here is my algorithm for that:

bitangent_ortho = norm(cross(normal, tangent))
tangent_ortho = cross(bitangent_ortho, normal)


(Assuming that the normal is mapped and has unit length and the tangent was a valid tangent before the normal was mapped.)

Ad Question 2

This was indeed well answered by user @lightxbulb in a comment to my question. My only problem with the "Physically based shading at Disney" course notes was that the variable symbols weren't very clearly explained. I have since learned that the convention is the following:


 is the angle between the normal and  
 is the azimuthal angle between the tangent and 


Ad Question 3

The only pieces of advice that were useful for me here were the following:


Always come up with the simplest toy examples possible
Verify your assumptions
Visualize everything


Conclusion

I hope this is helpful for somebody else than just me. Good luck implementing BRDFs!