Very thanks for I have learn a lot from this page.
This is an intermediate article to record my progress in implementing real-time physcis based rendering and imaged based lighitng. I will introduce how do I change everything from Blinn-Phong workflow to PBR_MetallicRoughness workflow.


Visialization of a PBR material:
Series tutorials:
Comparison between brdf functions:
Cook-torrance BRDF and sampling theory:
Convert HDR image to cube map:
Free HDR image resources:


What did I do to shift the work flow from Blinn-Phong to PBR?

  • Get a model with PBR textures
  • Implemented normal mapping
  • Use those textures in BlinnPhong and test if they look good
  • Create PBR fragment shader, and releated material class
  • Modify directional light and point light logic to fit PBR shading
  • Use 25 sphere to test metalness & roughness
  • Modify shadow function to fit PBR shading

Until here, basic PBR_MR is done. The next step is to add Image based lighting to create an illusion of global illumination.

First thing to do is to capture the scene. I did not capture the skybox directly like the tutorial said. Instead, I capture the whole scene before the actual rendering. To achieve this, I made two functions for two threads call:

  • BeforeUpdate() // In application thread
  • PreRenderFrame() // In render thread

I passed the scene that I wanted to be captured from application thread to the render thead, and the pre-render pass will render the scene to the capture frameBuffer, this frame buffer is a HDR cubemap(GL_TEXTURE_CUBEMAP, GL_RGB16, 2048 * 2048). Here is the result:

After I got the environment cubemap, I generated 2 cubemaps, 1 texture2D one by one:

  • Irradiance map (32 * 32)
  • Pre-filter cubemap (With LOD of 5, from 256*256 to 8*8)
  • BRDF-Integration map (2048 * 2048)

The first two process the environment cubemap, generate a new version of cubemap to represent the environment information.

Irradiance map is used to estimate the whole environment influence to the diffuse term of the cook-torrance BRDF. Since irradiance map does not require too many detail in specific direction, it is usually in a very low resolution, in my case: 32 * 32.

Irradiance map (32 * 32)

Pre-filter cubemap is used to estimate the environment in certain direction, specifically, in the perfect reflection direction, that is the specualr term of the cook-torrance BRDF. In PBR, the roughness determines the reflectance of the material. So if a surface is smooth, then for this surface, it should use lower LOD of the pre-filter cubemap to get detail reflection. On the other hand, a rougher surface needs less detail, so it should use higher LOD of the pre-filter cubemap, which is more blury.

Pre-filter cubemap (LOD 0, 256 * 256)

Pre-filter cubemap (LOD 1, 128 * 128)

The last one, BRDF integration map, AKA, 2D lookup texture of roughness. Theoratically, the engine did not need to generate this map, since it is a parameter independent texture. For the sake of demoing, I genereate the texture in run-time.

BRDF integration map (2048 * 2048)

Screen shots

metalness & roughness comparasion among 25 spheres

roughness intensity changes from left to right: 0.25 to 0.05

A high resolution gun with high resolution texture, doesn’t look good

PBR teapot, check the environment reflection

Normal mapping and the environment reflection


To be honest, I did not understand some of the math inside, but it was fun to look into the math.

Also, my scene looks very low contrast and I will find a way to solve it in the future. What I plan to do next, is either improve the lighting or start to do defered shading & lighting.