title: How to Implement PBR shader - LWJGL?
body: <p>I've been following Thinmatrix tutorials to practice GLSL and Vector math, and now I'm trying to implement a PBR shader, that I borrowed from <a href="https://learnopengl.com/code_viewer_gh.php?code=src/6.pbr/1.2.lighting_textured/lighting_textured.cpp" rel="nofollow noreferrer">here(learnOpenGL)</a>. My first go made sure the models would not render. My old lambert shader works fine, but implementing the shader has been tricky. Otherwise I'm gonna have to port the c++ code block by block to java, which while I want to try over a period of time, I would like to implement a PBR Shader in my existing engine. I believe I've initiated the attributes:</p>
  
  <pre><code>@Override
      protected void bindAttributes() {
          super.bindAttribute(0, "aPos");
          super.bindAttribute(1, "aTexCoords");
          super.bindAttribute(2, "aNormal");
      }
  </code></pre>
  
  <p>but unsure how I should add the uniforms, at this point I'm taking a stab at it in the dark. I've noticed that the shader has a mat4 uniform view, mat4 uniform camPos and mat4 uniform model which I don't have binded in LWJGL (excuse my poor terminology). Currently my uniforms are:</p>
  
  <pre><code>@Override
  protected void getAllUniformLocations() {
      location_transformationMatrix = super.getUniformLocation("transformationMatrix");
      location_projectionMatrix = super.getUniformLocation("projection");
      location_viewMatrix = super.getUniformLocation("view");
      location_shineDamper = super.getUniformLocation("shineDamper");
      location_reflectivity = super.getUniformLocation("reflectivity");
      location_useFakeLighting = super.getUniformLocation("useFakeLighting");
      location_skyColour = super.getUniformLocation("skyColour");
      location_numberOfRows = super.getUniformLocation("numberOfRows");
      location_offset = super.getUniformLocation("offset");
      location_lightPosition = new int[MAX_LIGHTS];
      location_lightColour = new int[MAX_LIGHTS];
      location_attenuation = new int [MAX_LIGHTS];
      for(int i=0;i&lt;MAX_LIGHTS;i++) {
          location_lightPosition[i] = super.getUniformLocation("lightPositions[" + i + "]");
          location_lightColour[i] = super.getUniformLocation("lightColors[" + i + "]");
  }
  </code></pre>
  
  <p>Vertex shader from learnOpengl:</p>
  
  <pre><code>#version 330 core
  layout (location = 0) in vec3 aPos;
  layout (location = 1) in vec2 aTexCoords;
  layout (location = 2) in vec3 aNormal;
  
  out vec2 TexCoords;
  out vec3 WorldPos;
  out vec3 Normal;
  
  uniform mat4 projection;
  uniform mat4 view;
  uniform mat4 model;
  
  void main()
  {
      TexCoords = aTexCoords;
      WorldPos = vec3(model * vec4(aPos, 1.0));
      Normal = mat3(model) * aNormal;   
  
      gl_Position =  projection * view * vec4(WorldPos, 1.0);
  }
  </code></pre>
  
  <p>Fragment shader from learnopengl:</p>
  
  <pre><code>#version 330 core
  out vec4 FragColor;
  in vec2 TexCoords;
  in vec3 WorldPos;
  in vec3 Normal;
  
  // material parameters
  uniform sampler2D albedoMap;
  uniform sampler2D normalMap;
  uniform sampler2D metallicMap;
  uniform sampler2D roughnessMap;
  uniform sampler2D aoMap;
  
  // lights
  uniform vec3 lightPositions[4];
  uniform vec3 lightColors[4];
  
  uniform vec3 camPos;
  
  const float PI = 3.14159265359;
  // ----------------------------------------------------------------------------
  // Easy trick to get tangent-normals to world-space to keep PBR code simplified.
  // Don't worry if you don't get what's going on; you generally want to do normal 
  // mapping the usual way for performance anways; I do plan make a note of this 
  // technique somewhere later in the normal mapping tutorial.
  vec3 getNormalFromMap()
  {
      vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;
  
      vec3 Q1  = dFdx(WorldPos);
      vec3 Q2  = dFdy(WorldPos);
      vec2 st1 = dFdx(TexCoords);
      vec2 st2 = dFdy(TexCoords);
  
      vec3 N   = normalize(Normal);
      vec3 T  = normalize(Q1*st2.t - Q2*st1.t);
      vec3 B  = -normalize(cross(N, T));
      mat3 TBN = mat3(T, B, N);
  
      return normalize(TBN * tangentNormal);
  }
  // ----------------------------------------------------------------------------
  float DistributionGGX(vec3 N, vec3 H, float roughness)
  {
      float a = roughness*roughness;
      float a2 = a*a;
      float NdotH = max(dot(N, H), 0.0);
      float NdotH2 = NdotH*NdotH;
  
      float nom   = a2;
      float denom = (NdotH2 * (a2 - 1.0) + 1.0);
      denom = PI * denom * denom;
  
      return nom / denom;
  }
  // ----------------------------------------------------------------------------
  float GeometrySchlickGGX(float NdotV, float roughness)
  {
      float r = (roughness + 1.0);
      float k = (r*r) / 8.0;
  
      float nom   = NdotV;
      float denom = NdotV * (1.0 - k) + k;
  
      return nom / denom;
  }
  // ----------------------------------------------------------------------------
  float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
  {
      float NdotV = max(dot(N, V), 0.0);
      float NdotL = max(dot(N, L), 0.0);
      float ggx2 = GeometrySchlickGGX(NdotV, roughness);
      float ggx1 = GeometrySchlickGGX(NdotL, roughness);
  
      return ggx1 * ggx2;
  }
  // ----------------------------------------------------------------------------
  vec3 fresnelSchlick(float cosTheta, vec3 F0)
  {
      return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
  }
  // ----------------------------------------------------------------------------
  void main()
  {       
      vec3 albedo     = pow(texture(albedoMap, TexCoords).rgb, vec3(2.2));
      float metallic  = texture(metallicMap, TexCoords).r;
      float roughness = texture(roughnessMap, TexCoords).r;
      float ao        = texture(aoMap, TexCoords).r;
  
      vec3 N = getNormalFromMap();
      vec3 V = normalize(camPos - WorldPos);
  
      // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0 
      // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)    
      vec3 F0 = vec3(0.04); 
      F0 = mix(F0, albedo, metallic);
  
      // reflectance equation
      vec3 Lo = vec3(0.0);
      for(int i = 0; i &lt; 4; ++i) 
      {
          // calculate per-light radiance
          vec3 L = normalize(lightPositions[i] - WorldPos);
          vec3 H = normalize(V + L);
          float distance = length(lightPositions[i] - WorldPos);
          float attenuation = 1.0 / (distance * distance);
          vec3 radiance = lightColors[i] * attenuation;
  
          // Cook-Torrance BRDF
          float NDF = DistributionGGX(N, H, roughness);   
          float G   = GeometrySmith(N, V, L, roughness);      
          vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);
  
          vec3 nominator    = NDF * G * F; 
          float denominator = 4 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; // 0.001 to prevent divide by zero.
          vec3 specular = nominator / denominator;
  
          // kS is equal to Fresnel
          vec3 kS = F;
          // for energy conservation, the diffuse and specular light can't
          // be above 1.0 (unless the surface emits light); to preserve this
          // relationship the diffuse component (kD) should equal 1.0 - kS.
          vec3 kD = vec3(1.0) - kS;
          // multiply kD by the inverse metalness such that only non-metals 
          // have diffuse lighting, or a linear blend if partly metal (pure metals
          // have no diffuse light).
          kD *= 1.0 - metallic;     
  
          // scale light by NdotL
          float NdotL = max(dot(N, L), 0.0);        
  
          // add to outgoing radiance Lo
          Lo += (kD * albedo / PI + specular) * radiance * NdotL;  // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
      }   
  
      // ambient lighting (note that the next IBL tutorial will replace 
      // this ambient lighting with environment lighting).
      vec3 ambient = vec3(0.03) * albedo * ao;
  
      vec3 color = ambient + Lo;
  
      // HDR tonemapping
      color = color / (color + vec3(1.0));
      // gamma correct
      color = pow(color, vec3(1.0/2.2)); 
  
      FragColor = vec4(color, 1.0);
  }
  </code></pre>
  
  <p>I noticed that their are uniforms I'm not binding in lwjgl, some I'm unsure how to initiate or why it needs to be done (uniform mat4 model). I thought I could leave the uniforms from the thinmatrix tutorial and assumed that it wouldn't process. Please correct me if I'm wrong.</p>
  
  <p>Any insight to the process behind making this will be much appreciated. I've been mostly watching talks and shadertoy based videos dealing with PBR.</p>
  
  <p>heres my code based on thinmatrix tutorials:
  <a href="https://github.com/winnieTheWind/lwjglPBRTest" rel="nofollow noreferrer">here</a></p>
  
  <p>UPDATE:
  This is close, but now I'm trying out the textures and the albedo appears very dark and the others are indistinguishable. I also don't know what I'm supposed to do with the camPos uniform, or if its even neccesary.</p>
  
  <p><a href="https://i.stack.imgur.com/T8Poe.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/T8Poe.jpg" alt="enter image description here"></a></p>
  
  <p>lastest source code: <a href="https://github.com/winnieTheWind/PBRTest-AlbedoRoughnessMetallicAOMaps" rel="nofollow noreferrer">here</a></p>
  
  <p>update:</p>
  
  <p>So the albedo seems to work but the ao, roughness and metallic seem to not want to bind. And its very dark.</p>
  
  <p>In the renderer class that is supposed to help bind the textures.</p>
  
  <pre><code> //Activate texture albedo texture 0
    GL13.glActiveTexture(GL13.GL_TEXTURE0);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getAlbedo().getID());
  
    //Activate texture roughness texture 1
    GL13.glActiveTexture(GL13.GL_TEXTURE1);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getRoughness().getID());
  
    //Activate texture metallic texture 2
    GL13.glActiveTexture(GL13.GL_TEXTURE2);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getMetallic().getID());
  
    //Activate texture ao texture 0
    GL13.glActiveTexture(GL13.GL_TEXTURE3);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getAo().getID());
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/9kxJM.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/9kxJM.jpg" alt="enter image description here"></a></p>
  
  <p>UPDATE:</p>
  
  <p><a href="https://i.stack.imgur.com/AgHPX.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/AgHPX.jpg" alt="enter image description here"></a></p>
  
  <p>At the moment I've got the textures working. I feel like I can figure out the rest now. My only question would be: whats up with this huge texture seam? Is it because the texture maps im using are not seamless? I'm assuming it would go away if I make the normal, ao, rough and metallic seamless. The albedo seems seamless already, or perhaps not. Im being a bit nitpicky but just curious if that would help make it look better.</p>
  
  <p><a href="https://i.stack.imgur.com/cIzhN.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/cIzhN.jpg" alt="enter image description here"></a></p>