summaryrefslogtreecommitdiffstats
path: root/src/extras/shaders/gl3/metalrough.frag
diff options
context:
space:
mode:
Diffstat (limited to 'src/extras/shaders/gl3/metalrough.frag')
-rw-r--r--src/extras/shaders/gl3/metalrough.frag229
1 files changed, 192 insertions, 37 deletions
diff --git a/src/extras/shaders/gl3/metalrough.frag b/src/extras/shaders/gl3/metalrough.frag
index 99624dd24..7f2f3d20e 100644
--- a/src/extras/shaders/gl3/metalrough.frag
+++ b/src/extras/shaders/gl3/metalrough.frag
@@ -61,13 +61,9 @@ out vec4 fragColor;
uniform vec3 eyePosition; // World space eye position
uniform float time; // Time in seconds
-// Pre-convolved environment maps
-uniform samplerCube skyIrradiance; // For diffuse contribution
-uniform samplerCube skySpecular; // For specular contribution
-
// PBR Material maps
uniform sampler2D baseColorMap;
-uniform sampler2D metallicMap;
+uniform sampler2D metalnessMap;
uniform sampler2D roughnessMap;
uniform sampler2D normalMap;
uniform sampler2D ambientOcclusionMap;
@@ -75,16 +71,32 @@ uniform sampler2D ambientOcclusionMap;
// User control parameters
uniform float metalFactor = 1.0;
-// Roughness -> mip level mapping
-uniform float maxT = 0.939824;
-uniform float mipLevels = 11.0;
-uniform float mipOffset = 5.0;
-
// Exposure correction
uniform float exposure = 0.0;
// Gamma correction
uniform float gamma = 2.2;
+#pragma include light.inc.frag
+
+int mipLevelCount(const in samplerCube cube)
+{
+ int baseSize = textureSize(cube, 0).x;
+ int nMips = int(log2(float(baseSize>0 ? baseSize : 1))) + 1;
+ return nMips;
+}
+
+float remapRoughness(const in float roughness)
+{
+ // As per page 14 of
+ // http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf
+ // we remap the roughness to give a more perceptually linear response
+ // of "bluriness" as a function of the roughness specified by the user.
+ // r = roughness^2
+ const float maxSpecPower = 999999.0;
+ const float minRoughness = sqrt(2.0 / (maxSpecPower + 2));
+ return max(roughness * roughness, minRoughness);
+}
+
mat3 calcWorldSpaceToTangentSpaceMatrix(const in vec3 wNormal, const in vec4 wTangent)
{
// Make the tangent truly orthogonal to the normal by using Gram-Schmidt.
@@ -105,12 +117,42 @@ mat3 calcWorldSpaceToTangentSpaceMatrix(const in vec3 wNormal, const in vec4 wTa
return worldToTangentMatrix;
}
-float roughnessToMipLevel(float roughness)
+float alphaToMipLevel(float alpha)
{
- // HACK: Improve the roughness -> mip level mapping for roughness map from substace painter
- // TODO: Use mathematica or similar to improve this mapping more generally
- roughness = 0.75 + (1.7 * (roughness - 0.5));
- return (mipLevels - 1.0 - mipOffset) * (1.0 - (1.0 - roughness) / maxT);
+ float specPower = 2.0 / (alpha * alpha) - 2.0;
+
+ // We use the mip level calculation from Lys' default power drop, which in
+ // turn is a slight modification of that used in Marmoset Toolbag. See
+ // https://docs.knaldtech.com/doku.php?id=specular_lys for details.
+ // For now we assume a max specular power of 999999 which gives
+ // maxGlossiness = 1.
+ const float k0 = 0.00098;
+ const float k1 = 0.9921;
+ float glossiness = (pow(2.0, -10.0 / sqrt(specPower)) - k0) / k1;
+
+ // TODO: Optimize by doing this on CPU and set as
+ // uniform int envLight.specularMipLevels say (if present in shader).
+ // Lookup the number of mips in the specular envmap
+ int mipLevels = mipLevelCount(envLight.specular);
+
+ // Offset of smallest miplevel we should use (corresponds to specular
+ // power of 1). I.e. in the 32x32 sized mip.
+ const float mipOffset = 5.0;
+
+ // The final factor is really 1 - g / g_max but as mentioned above g_max
+ // is 1 by definition here so we can avoid the division. If we make the
+ // max specular power for the spec map configurable, this will need to
+ // be handled properly.
+ float mipLevel = (mipLevels - 1.0 - mipOffset) * (1.0 - glossiness);
+ return mipLevel;
+}
+
+float normalDistribution(const in vec3 n, const in vec3 h, const in float alpha)
+{
+ // Blinn-Phong approximation - see
+ // http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html
+ float specPower = 2.0 / (alpha * alpha) - 2.0;
+ return (specPower + 2.0) / (2.0 * 3.14159) * pow(max(dot(n, h), 0.0), specPower);
}
vec3 fresnelFactor(const in vec3 color, const in float cosineFactor)
@@ -128,37 +170,113 @@ float geometricModel(const in float lDotN,
// Implicit geometric model (equal to denominator in specular model).
// This currently assumes that there is no attenuation by geometric shadowing or
// masking according to the microfacet theory.
- return 1.0;
+ return lDotN * vDotN;
}
vec3 specularModel(const in vec3 F0,
- const in float lDotH,
- const in float lDotN,
+ const in float sDotH,
+ const in float sDotN,
const in float vDotN,
const in vec3 n,
const in vec3 h)
{
- // Clamp lDotN and vDotN to small positive value to prevent the
+ // Clamp sDotN and vDotN to small positive value to prevent the
// denominator in the reflection equation going to infinity. Balance this
// by using the clamped values in the geometric factor function to
// avoid ugly seams in the specular lighting.
- float sDotNPrime = max(lDotN, 0.001);
+ float sDotNPrime = max(sDotN, 0.001);
float vDotNPrime = max(vDotN, 0.001);
- vec3 F = fresnelFactor(F0, lDotH);
+ vec3 F = fresnelFactor(F0, sDotH);
float G = geometricModel(sDotNPrime, vDotNPrime, h);
- // TODO: Verify which parts of the BRDF Lys is preconvolving and multiply
- // by the remaining factors here.
vec3 cSpec = F * G / (4.0 * sDotNPrime * vDotNPrime);
return clamp(cSpec, vec3(0.0), vec3(1.0));
}
+vec3 pbrModel(const in int lightIndex,
+ const in vec3 wPosition,
+ const in vec3 wNormal,
+ const in vec3 wView,
+ const in vec3 baseColor,
+ const in float metalness,
+ const in float alpha,
+ const in float ambientOcclusion)
+{
+ // Calculate some useful quantities
+ vec3 n = wNormal;
+ vec3 s = vec3(0.0);
+ vec3 v = wView;
+ vec3 h = vec3(0.0);
+
+ float vDotN = dot(v, n);
+ float sDotN = 0.0;
+ float sDotH = 0.0;
+ float att = 1.0;
+
+ if (lights[lightIndex].type != TYPE_DIRECTIONAL) {
+ // Point and Spot lights
+ vec3 sUnnormalized = vec3(lights[lightIndex].position) - wPosition;
+ s = normalize(sUnnormalized);
+
+ // Calculate the attenuation factor
+ sDotN = dot(s, n);
+ if (sDotN > 0.0) {
+ if (lights[lightIndex].constantAttenuation != 0.0
+ || lights[lightIndex].linearAttenuation != 0.0
+ || lights[lightIndex].quadraticAttenuation != 0.0) {
+ float dist = length(sUnnormalized);
+ att = 1.0 / (lights[lightIndex].constantAttenuation +
+ lights[lightIndex].linearAttenuation * dist +
+ lights[lightIndex].quadraticAttenuation * dist * dist);
+ }
+
+ // The light direction is in world space already
+ if (lights[lightIndex].type == TYPE_SPOT) {
+ // Check if fragment is inside or outside of the spot light cone
+ if (degrees(acos(dot(-s, lights[lightIndex].direction))) > lights[lightIndex].cutOffAngle)
+ sDotN = 0.0;
+ }
+ }
+ } else {
+ // Directional lights
+ // The light direction is in world space already
+ s = normalize(-lights[lightIndex].direction);
+ sDotN = dot(s, n);
+ }
+
+ h = normalize(s + v);
+ sDotH = dot(s, h);
+
+ // Calculate diffuse component
+ vec3 diffuseColor = (1.0 - metalness) * baseColor;
+ vec3 diffuse = diffuseColor * max(sDotN, 0.0) / 3.14159;
+
+ // Calculate specular component
+ vec3 dielectricColor = vec3(0.04);
+ vec3 F0 = mix(dielectricColor, baseColor, metalness);
+ vec3 specularFactor = vec3(0.0);
+ if (sDotN > 0.0) {
+ specularFactor = specularModel(F0, sDotH, sDotN, vDotN, n, h);
+ specularFactor *= normalDistribution(n, h, alpha);
+ }
+ vec3 specularColor = lights[lightIndex].color;
+ vec3 specular = specularColor * specularFactor;
+
+ // Blend between diffuse and specular to conserver energy
+ vec3 color = lights[lightIndex].intensity * (specular + diffuse * (vec3(1.0) - specular));
+
+ // Reduce by ambient occlusion amount
+ color *= ambientOcclusion;
+
+ return color;
+}
+
vec3 pbrIblModel(const in vec3 wNormal,
const in vec3 wView,
const in vec3 baseColor,
- const in float metallic,
- const in float roughness,
+ const in float metalness,
+ const in float alpha,
const in float ambientOcclusion)
{
// Calculate reflection direction of view vector about surface normal
@@ -175,16 +293,35 @@ vec3 pbrIblModel(const in vec3 wNormal,
float lDotH = dot(l, h);
// Calculate diffuse component
- vec3 diffuseColor = (1.0 - metallic) * baseColor;
- vec3 diffuse = diffuseColor * texture(skyIrradiance, l).rgb;
+ vec3 diffuseColor = (1.0 - metalness) * baseColor;
+ vec3 diffuse = diffuseColor * texture(envLight.irradiance, l).rgb;
// Calculate specular component
vec3 dielectricColor = vec3(0.04);
- vec3 F0 = mix(dielectricColor, baseColor, metallic);
+ vec3 F0 = mix(dielectricColor, baseColor, metalness);
vec3 specularFactor = specularModel(F0, lDotH, lDotN, vDotN, n, h);
- float lod = roughnessToMipLevel(roughness);
- vec3 specularSkyColor = textureLod(skySpecular, l, lod).rgb;
+ float lod = alphaToMipLevel(alpha);
+//#define DEBUG_SPECULAR_LODS
+#ifdef DEBUG_SPECULAR_LODS
+ if (lod > 7.0)
+ return vec3(1.0, 0.0, 0.0);
+ else if (lod > 6.0)
+ return vec3(1.0, 0.333, 0.0);
+ else if (lod > 5.0)
+ return vec3(1.0, 1.0, 0.0);
+ else if (lod > 4.0)
+ return vec3(0.666, 1.0, 0.0);
+ else if (lod > 3.0)
+ return vec3(0.0, 1.0, 0.666);
+ else if (lod > 2.0)
+ return vec3(0.0, 0.666, 1.0);
+ else if (lod > 1.0)
+ return vec3(0.0, 0.0, 1.0);
+ else if (lod > 0.0)
+ return vec3(1.0, 0.0, 1.0);
+#endif
+ vec3 specularSkyColor = textureLod(envLight.specular, l, lod).rgb;
vec3 specular = specularSkyColor * specularFactor;
// Blend between diffuse and specular to conserve energy
@@ -193,9 +330,6 @@ vec3 pbrIblModel(const in vec3 wNormal,
// Reduce by ambient occlusion amount
iblColor *= ambientOcclusion;
- // Apply exposure correction
- iblColor *= pow(2.0, exposure);
-
return iblColor;
}
@@ -211,6 +345,8 @@ vec3 gammaCorrect(const in vec3 color)
void main()
{
+ vec3 cLinear = vec3(0.0);
+
// Calculate the perturbed texture coordinates from parallax occlusion mapping
mat3 worldToTangentMatrix = calcWorldSpaceToTangentSpaceMatrix(worldNormal, worldTangent);
vec3 wView = normalize(eyePosition - worldPosition);
@@ -218,18 +354,37 @@ void main()
// Sample the inputs needed for the metal-roughness PBR BRDF
vec3 baseColor = texture(baseColorMap, texCoord).rgb;
- float metallic = texture(metallicMap, texCoord).r * metalFactor;
+ float metalness = texture(metalnessMap, texCoord).r * metalFactor;
float roughness = texture(roughnessMap, texCoord).r;
float ambientOcclusion = texture(ambientOcclusionMap, texCoord).r;
vec3 tNormal = 2.0 * texture(normalMap, texCoord).rgb - vec3(1.0);
vec3 wNormal = normalize(transpose(worldToTangentMatrix) * tNormal);
- vec3 cLinear = pbrIblModel(wNormal,
+ // Remap roughness for a perceptually more linear correspondence
+ float alpha = remapRoughness(roughness);
+
+ for (int i = 0; i < envLightCount; ++i) {
+ cLinear += pbrIblModel(wNormal,
wView,
baseColor,
- metallic,
- roughness,
+ metalness,
+ alpha,
ambientOcclusion);
+ }
+
+ for (int i = 0; i < lightCount; ++i) {
+ cLinear += pbrModel(i,
+ worldPosition,
+ wNormal,
+ wView,
+ baseColor.rgb,
+ metalness,
+ alpha,
+ ambientOcclusion);
+ }
+
+ // Apply exposure correction
+ cLinear *= pow(2.0, exposure);
// Apply simple (Reinhard) tonemap transform to get into LDR range [0, 1]
vec3 cToneMapped = toneMap(cLinear);