0030700: Visualization, TKOpenGl - support PBR Metallic-Roughness shading model
[occt.git] / src / Shaders / PBREnvBaking.fs
diff --git a/src/Shaders/PBREnvBaking.fs b/src/Shaders/PBREnvBaking.fs
new file mode 100644 (file)
index 0000000..e795c46
--- /dev/null
@@ -0,0 +1,162 @@
+THE_SHADER_IN vec3 ViewDirection; //!< direction of fetching from environment cubemap
+
+uniform int uSamplesNum;     //!< number of samples in Monte-Carlo integration
+uniform int uCurrentLevel;   //!< current level of specular IBL map (ignored in case of diffuse map's processing)
+uniform int uEnvMapSize;     //!< one edge's size of source environtment map's zero mipmap level
+uniform int uYCoeff;         //!< coefficient of Y controlling horizontal flip of cubemap
+uniform int uZCoeff;         //!< coefficient of Z controlling vertical flip of cubemap
+uniform samplerCube uEnvMap; //!< source of baking (environment cubemap)
+
+//! Returns coordinates of point theNumber from Hammersley point set having size theSize.
+vec2 hammersley (in int theNumber,
+                 in int theSize)
+{
+  int aDenominator = 2;
+  int aNumber = theNumber;
+  float aVanDerCorput = 0.0;
+  for (int i = 0; i < 32; ++i)
+  {
+    if (aNumber > 0)
+    {
+      aVanDerCorput += float(aNumber % 2) / float(aDenominator);
+      aNumber /= 2;
+      aDenominator *= 2;
+    }
+  }
+  return vec2(float(theNumber) / float(theSize), aVanDerCorput);
+}
+
+//! This function does importance sampling on hemisphere surface using GGX normal distribution function
+//! in tangent space (positive z axis is surface normal direction).
+vec3 importanceSample (in vec2  theHammersleyPoint,
+                       in float theRoughness)
+{
+  float aPhi = PI_2 * theHammersleyPoint.x;
+  theRoughness *= theRoughness;
+  theRoughness *= theRoughness;
+  float aCosTheta = sqrt((1.0 - theHammersleyPoint.y) / (1.0 + (theRoughness - 1.0) * theHammersleyPoint.y));
+  float aSinTheta = sqrt(1.0 - aCosTheta * aCosTheta);
+  return vec3(aSinTheta * cos(aPhi),
+              aSinTheta * sin(aPhi),
+              aCosTheta);
+}
+
+//! This function uniformly generates samples on whole sphere.
+vec3 sphereUniformSample (in vec2 theHammersleyPoint)
+{
+  float aPhi = PI_2 * theHammersleyPoint.x;
+  float aCosTheta = 2.0 * theHammersleyPoint.y - 1.0;
+  float aSinTheta = sqrt(1.0 - aCosTheta * aCosTheta);
+  return vec3(aSinTheta * cos(aPhi),
+              aSinTheta * sin(aPhi),
+              aCosTheta);
+}
+
+//! Transforms resulted sampled direction from tangent space to world space considering the surface normal.
+vec3 fromTangentSpace (in vec3 theVector,
+                       in vec3 theNormal)
+{
+  vec3 anUp = (abs(theNormal.z) < 0.999) ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+  vec3 anX = normalize(cross(anUp, theNormal));
+  vec3 anY = cross(theNormal, anX);
+  return anX * theVector.x + anY * theVector.y + theNormal * theVector.z;
+}
+
+const float aSHBasisFuncCoeffs[9] = float[9]
+(
+  0.282095 * 0.282095,
+  0.488603 * 0.488603,
+  0.488603 * 0.488603,
+  0.488603 * 0.488603,
+  1.092548 * 1.092548,
+  1.092548 * 1.092548,
+  1.092548 * 1.092548,
+  0.315392 * 0.315392,
+  0.546274 * 0.546274
+);
+
+const float aSHCosCoeffs[9] = float[9]
+(
+  3.141593,
+  2.094395,
+  2.094395,
+  2.094395,
+  0.785398,
+  0.785398,
+  0.785398,
+  0.785398,
+  0.785398
+);
+
+//! Bakes diffuse IBL map's spherical harmonics coefficients.
+vec3 bakeDiffuseSH()
+{
+  int anIndex = int(gl_FragCoord.x);
+  vec3 aResult = vec3 (0.0);
+  for (int aSampleIter = 0; aSampleIter < uSamplesNum; ++aSampleIter)
+  {
+    vec2 aHammersleyPoint = hammersley (aSampleIter, uSamplesNum);
+    vec3 aDirection = sphereUniformSample (aHammersleyPoint);
+
+    vec3 aValue = occTextureCube (uEnvMap, cubemapVectorTransform (aDirection, uYCoeff, uZCoeff)).rgb;
+
+    float aBasisFunc[9];
+    aBasisFunc[0] = 1.0;
+
+    aBasisFunc[1] = aDirection.x;
+    aBasisFunc[2] = aDirection.y;
+    aBasisFunc[3] = aDirection.z;
+
+    aBasisFunc[4] = aDirection.x * aDirection.z;
+    aBasisFunc[5] = aDirection.y * aDirection.z;
+    aBasisFunc[6] = aDirection.x * aDirection.y;
+
+    aBasisFunc[7] = 3.0 * aDirection.z * aDirection.z - 1.0;
+    aBasisFunc[8] = aDirection.x * aDirection.x - aDirection.y * aDirection.y;
+
+    aResult += aValue * aBasisFunc[anIndex];
+  }
+
+  aResult *= 4.0 * aSHCosCoeffs[anIndex] * aSHBasisFuncCoeffs[anIndex] / float(uSamplesNum);
+  return aResult;
+}
+
+//! Bakes specular IBL map.
+vec3 bakeSpecularMap (in vec3  theNormal,
+                      in float theRoughness)
+{
+  vec3 aResult = vec3(0.0);
+  float aWeightSum = 0.0;
+  int aSamplesNum = (theRoughness == 0.0) ? 1 : uSamplesNum;
+  float aSolidAngleSource = 4.0 * PI / (6.0 * float(uEnvMapSize * uEnvMapSize));
+  for (int aSampleIter = 0; aSampleIter < aSamplesNum; ++aSampleIter)
+  {
+    vec2 aHammersleyPoint = hammersley (aSampleIter, aSamplesNum);
+    vec3 aHalf = importanceSample (aHammersleyPoint, occRoughness (theRoughness));
+    float aHdotV = aHalf.z;
+    aHalf = fromTangentSpace (aHalf, theNormal);
+    vec3  aLight = -reflect (theNormal, aHalf);
+    float aNdotL = dot (aLight, theNormal);
+    if (aNdotL > 0.0)
+    {
+      float aSolidAngleSample = 1.0 / (float(aSamplesNum) * (occPBRDistribution (aHdotV, theRoughness) * 0.25 + 0.0001) + 0.0001);
+      float aLod = (theRoughness == 0.0) ? 0.0 : 0.5 * log2 (aSolidAngleSample / aSolidAngleSource);
+      aResult += occTextureCubeLod (uEnvMap, aLight, aLod).rgb * aNdotL;
+      aWeightSum += aNdotL;
+    }
+  }
+  return aResult / aWeightSum;
+}
+
+void main()
+{
+  vec3 aViewDirection = normalize (ViewDirection);
+  if (occNbSpecIBLLevels == 0)
+  {
+    occSetFragColor (vec4 (bakeDiffuseSH (), 1.0));
+  }
+  else
+  {
+    occSetFragColor (vec4 (bakeSpecularMap (aViewDirection, float(uCurrentLevel) / float(occNbSpecIBLLevels - 1)), 1.0));
+  }
+}