diff options
Diffstat (limited to 'src/extras/shaders/gl3/metalrough.frag')
-rw-r--r-- | src/extras/shaders/gl3/metalrough.frag | 229 |
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); |