Experimenting with Physical Based Render (PBR) using LibGDX – 1 Phase 1: porting a basic shader in LibGDX

Abstract: PBR LibGDX: a series of artcilese showing some experimentation with Physical Based Render using LibGDX.

Introduction

This series of article will describe how to implement Physical Based Render using LibGDX

In last few year PBR (Physical Based Render) had gained more and more popularity in the game development community. PBR can be implemented in various way but the idea is that shading capability in modern system are advanced enough that some old approximation, used in the past to improve rendering performance, can be now dropped.

PBR LibGDX-img1

PBR LibGDX: Various effect of the final shader

 

At this point a question can arise in your mind: why does investigate this technology in LibGDX witch main use is in the field of mobile devices, where GPU horsepower is very limited?

The answer is reusability! A game, or whatever else graphic application, that relay on PBR can easily  have a great look on high end device like a desktop PC and run smooth (with some approximation) on a low end device like a smart phone. Reintroduce in a PBR shader a set of approximation will produce a render quality comparable with a non PBR environment, but will allow us to use the same textures on both high and low end device (the only difference should be in resolution: on low end device we should use a low resolution copy of our textures to gain performance and preserve memory).

Another advantage is the possibility to use library of material and model designed for PBR in our rendering engine.

On the internet there is plenty of well written article that introduce the argument, if you are not used with the basis of PBR you can check some of them before. Here there is a reference I found useful for myself:

Marmoset: PBR Theory [1]

In this first article we will import a basic PBR shader in the LibGDX environment. We will start from this work:

Android Physically Based Rendering [2]

written by Hackuro Matsuda witch I thanks.

A first look at the PBR shader

If you are used with standard Blinn-Phong reflection most of the code in the shader will be already clear for you.  Be awarded that this shader use only one global directional source of light.

Starting from the vertex shader (assets/shaders/VS_ShaderPlain.vsh in the original code) the first passages are the same of most of the shader you have worked with in the past: the gl_position is calculate from world transformation and camera transposition matrix (note that in the original code uPMatrix is the product of this 2 matrix). Than the texture coordinate are passed to the Vertex Shader:

After that, some useful values are pre calculated for the Vertex Shader. First the vector normal is project in world space (remember that normal is used, in conjunction to light direction, to determine the angle at witch light hit the surface, so it so necessary to transform it in camera space, what matter is that both this value are represented in the same space, see OpenGL tutoria 8: basic shading [3] for major detail) and passed to the Vertex Shader:

The Blinn-Phong Shader is fast and render nice in many condition, but is not physically accurate, it is just an approximation. To make this model more physically accurate there are 2 aspect to implement: first it must respect the principle of energy conservation (see [1]) this can be achieved applying normalization factor to each component of the computed fragment light (Sebastian Lagrande: introduction to PBR [4]) , second the reflection must be adequate to obtain a Fresnell effect. This shader used the Schlick’s Approximation of the Fresnel Factor to calculate reflection (Unity: Specular Highlight Silouette [5] ), that is represented by this formula:

The 2 term eyeNormalized ed halfvecLigth0 are precalculated in vertex Shader

Note that eye vector is precalculated in vertex shader but normalized in Fragment Shader to enhance precision. Both this value are passed to the Fragment Shader.

Finally the Vertex Shader calculate the dynamicDiffuse value (see any dissertation about phong reflection for details, like Nvdia CGTutorial Chapter 5 [6] ). The diffuse component of the color is, as always, the diffuse color multiplied by the brightness of the point witch is proportional to the dot product of the point normal and light direction

The 3.14 factor (approximation for π), is the normalization factor as obtained in [4].

In the Vertext Shader all the component of the point color are calculated and properly mixed together. The first component it’s the ambient color witch is obtained from a cube map representing the environment surrounding our scene. Here we can see a nice trick to adapt the granularity of the reflection to the rougness parameter of our object. A 6 level mipmap of the cube map environment is used, the level is dynamically selected based on the roughness of the surface, this will give us a more detailed reflection on gloss surface and less detailed on roughness surface, easily miming real word behavior:

In the first implementation of this Shader this detail will be dropped (but basic calculation will be keep on to be reintroduced later).

Next the Fresnell Factor is calculate as described before:

The ambient diffuse color is obtained improving the result of the normal Blinn-Phong model by taking in consideration Fresnell approximation:

As always Blinn-Phong reflection is calculated starting from the dot product of the surface normal and halfway light direction vector, the exponent power of the Blinn-Phong approximation is derived by the roughness parameter: high roughness will provide more distributed reflection light and vice versa.

The Fresnell term is added to the obtained specular color and then a normalize factor is applied (see [4]).

The last term witch is calculated is the specular component of the  ambient light, that part is well described in [4] and here the code is just showed:

Finally the various component are added to compute the final fragment color:

Porting the Shader in LibGDX environment

Lest’s start creating our PBR LibGDX Shader.

The changes in Vertex Shader are minimal. We changed the name of the input parameter to adapt to the LibGDX standard: myVertex became a_position, myNormal became a_normal, myUV became a_texCoord0. These change simplified the work because LibGDX will automatically recognize these variable and bind to the value extracted from the mesh.

The second major change is to remove the reference for the textureLod  function, which doesn’t support cubeMap on OpenGL ES 2.0. I will use a standard textureCube lookup. We will reintroduce mipMap for environment map in a future article when a simple reflection feature will be introducted.

With these minimum changes it is possible to start using the Shader in LibGDX quickly. All we have to do it’s write a Shader class witch will load the GLSL code for the PBR LibGDX Shader into the GPU and map its parameter (see PBRShader.java in the source code):

Load the cubemap texture it’s quite simple:

The begin function will simple bind the variable Renderable independent (in this version only the camera transformation matrix), and set the rendering flag for the OpenGL environment:

Finally in the render function we will pass to the PBR LibGDX shader all the Renderable dependent value like roughness etc… (note that the global light direction at this moment is not Renderable dependent and should be moved to begin metod like many other parameter).

The attribute of the mesh are bindend using the getAttibuteLocations function witch will retrieve their position in the vertex structure order of the renderable.

Finally the effective render is executed.

Add parameter and improvement

Now that we have a better understanding of what it’s going under the hood, and our PBR LibGDX Shader is ready, we can start to add simple improvement.

The first will be to bound together the material diffuse color and reflection, this will help us on the next phase: add texture to our Shader witch will be the object of the next article.

We introduce the metallic value used in most PBR environment, a value of 1 for this value will indicate a metallic surface. Metallic surface do not have a diffuse color but only a reflective color, instead not metallic surface have a  reflective color somehow related to the diffuse color. I will use the approximation of the standard unity Shader:

In this new configuration Diffuse and Specular color are calculated based on the albedo color and the metallic level. This calculation can be made (for the moment) in the Vertext Shader.

Finally some controls are added in the main window to change the various parameter of the shader at run time. Just as an example here the code to add the roughness slider:

The roughness value of the shader is bounded to the current value of the Shader so that at every render cycle the new value will be used.

Once a renderable its created loading a mesh from a model and passing to it an instance of our new PBR shader, render it its quite simple:

Here an image of how the application should look since now using our PBR LibGDX Shader:

PBR LibGDX-img1

PBR LibGDX Shader: Final result of the first phase

The code can be download from: PBR LibGDX implementation

Conclusion

We reached our objective, and thank to the flexibility of LibGDX was really simple. In the next article we will create a textured material for our shader and will apply some real PBR material from library. Stay tuned for other article of the series PBR LibGDX.

Thank’s for reading, I’m sure there are imprecision and point that need to be clarified, I’ll look forward for you input

Facebooktwitteryoutube
Tags: ,

5 comments to Experimenting with Physical Based Render (PBR) using LibGDX – 1 Phase 1: porting a basic shader in LibGDX

  • Maurizio

    Hello,
    thanks for sharing a so interesting article.
    Can you provide the link of the LibGDX PBR implementation source code?

    Regards
    Maurizio

    Reply
    • wp_7428094

      Thank you. You can download the source here: https://github.com/PWorlds/LibGDX-PBR

      In the git repository are present also commit relative to the next phase (NB: the texture are in low resolution to reduce the dimension of the GitHub repository).

      Reply
  • mtzisam

    Hi really appreciate the article since there is not much resources on advanced shaders in libgdx online

    are you planing to continue the series?

    Reply
    • wp_7428094

      Thank you for yours appreciation. I definitively want to complete this series. I’m going to complete a side project, that is blowing away all my spare time, in the next few week and then I will start again this series.

      Reply
      • mtzisam

        I’m looking forward to it and if you can do one on volume rendering using ray casting shaders in the future it would be great i’m struggling with it now. best of luck in your project.
        thanks.

        Reply

Lascia un commento

Comment
Name*
Mail*
Website*