1/**************************************************************************** 2** 3** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the Qt3D module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:BSD$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** BSD License Usage 18** Alternatively, you may use this file under the terms of the BSD license 19** as follows: 20** 21** "Redistribution and use in source and binary forms, with or without 22** modification, are permitted provided that the following conditions are 23** met: 24** * Redistributions of source code must retain the above copyright 25** notice, this list of conditions and the following disclaimer. 26** * Redistributions in binary form must reproduce the above copyright 27** notice, this list of conditions and the following disclaimer in 28** the documentation and/or other materials provided with the 29** distribution. 30** * Neither the name of The Qt Company Ltd nor the names of its 31** contributors may be used to endorse or promote products derived 32** from this software without specific prior written permission. 33** 34** 35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46** 47** $QT_END_LICENSE$ 48** 49****************************************************************************/ 50 51#pragma include light.inc.frag 52 53int mipLevelCount(const in samplerCube cube) 54{ 55 int baseSize = textureSize(cube, 0).x; 56 int nMips = int(log2(float(baseSize > 0 ? baseSize : 1))) + 1; 57 return nMips; 58} 59 60float remapRoughness(const in float roughness) 61{ 62 // As per page 14 of 63 // http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf 64 // we remap the roughness to give a more perceptually linear response 65 // of "bluriness" as a function of the roughness specified by the user. 66 // r = roughness^2 67 const float maxSpecPower = 999999.0; 68 const float minRoughness = sqrt(2.0 / (maxSpecPower + 2)); 69 return max(roughness * roughness, minRoughness); 70} 71 72float alphaToMipLevel(float alpha) 73{ 74 float specPower = 2.0 / (alpha * alpha) - 2.0; 75 76 // We use the mip level calculation from Lys' default power drop, which in 77 // turn is a slight modification of that used in Marmoset Toolbag. See 78 // https://docs.knaldtech.com/doku.php?id=specular_lys for details. 79 // For now we assume a max specular power of 999999 which gives 80 // maxGlossiness = 1. 81 const float k0 = 0.00098; 82 const float k1 = 0.9921; 83 float glossiness = (pow(2.0, -10.0 / sqrt(specPower)) - k0) / k1; 84 85 // TODO: Optimize by doing this on CPU and set as 86 // uniform int envLight.specularMipLevels say (if present in shader). 87 // Lookup the number of mips in the specular envmap 88 int mipLevels = mipLevelCount(envLight_specular); 89 90 // Offset of smallest miplevel we should use (corresponds to specular 91 // power of 1). I.e. in the 32x32 sized mip. 92 const float mipOffset = 5.0; 93 94 // The final factor is really 1 - g / g_max but as mentioned above g_max 95 // is 1 by definition here so we can avoid the division. If we make the 96 // max specular power for the spec map configurable, this will need to 97 // be handled properly. 98 float mipLevel = (mipLevels - 1.0 - mipOffset) * (1.0 - glossiness); 99 return mipLevel; 100} 101 102float normalDistribution(const in vec3 n, const in vec3 h, const in float alpha) 103{ 104 // Blinn-Phong approximation - see 105 // http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html 106 float specPower = 2.0 / (alpha * alpha) - 2.0; 107 return (specPower + 2.0) / (2.0 * 3.14159) * pow(max(dot(n, h), 0.0), specPower); 108} 109 110vec3 fresnelFactor(const in vec3 color, const in float cosineFactor) 111{ 112 // Calculate the Fresnel effect value 113 vec3 f = color; 114 vec3 F = f + (1.0 - f) * pow(1.0 - cosineFactor, 5.0); 115 return clamp(F, f, vec3(1.0)); 116} 117 118float geometricModel(const in float lDotN, 119 const in float vDotN, 120 const in vec3 h) 121{ 122 // Implicit geometric model (equal to denominator in specular model). 123 // This currently assumes that there is no attenuation by geometric shadowing or 124 // masking according to the microfacet theory. 125 return lDotN * vDotN; 126} 127 128vec3 specularModel(const in vec3 F0, 129 const in float sDotH, 130 const in float sDotN, 131 const in float vDotN, 132 const in vec3 n, 133 const in vec3 h) 134{ 135 // Clamp sDotN and vDotN to small positive value to prevent the 136 // denominator in the reflection equation going to infinity. Balance this 137 // by using the clamped values in the geometric factor function to 138 // avoid ugly seams in the specular lighting. 139 float sDotNPrime = max(sDotN, 0.001); 140 float vDotNPrime = max(vDotN, 0.001); 141 142 vec3 F = fresnelFactor(F0, sDotH); 143 float G = geometricModel(sDotNPrime, vDotNPrime, h); 144 145 vec3 cSpec = F * G / (4.0 * sDotNPrime * vDotNPrime); 146 return clamp(cSpec, vec3(0.0), vec3(1.0)); 147} 148 149vec3 pbrModel(const in int lightIndex, 150 const in vec3 wPosition, 151 const in vec3 wNormal, 152 const in vec3 wView, 153 const in vec3 baseColor, 154 const in float metalness, 155 const in float alpha, 156 const in float ambientOcclusion) 157{ 158 // Calculate some useful quantities 159 vec3 n = wNormal; 160 vec3 s = vec3(0.0); 161 vec3 v = wView; 162 vec3 h = vec3(0.0); 163 164 float vDotN = dot(v, n); 165 float sDotN = 0.0; 166 float sDotH = 0.0; 167 float att = 1.0; 168 169 if (lights[lightIndex].type != TYPE_DIRECTIONAL) { 170 // Point and Spot lights 171 vec3 sUnnormalized = vec3(lights[lightIndex].position) - wPosition; 172 s = normalize(sUnnormalized); 173 174 // Calculate the attenuation factor 175 sDotN = dot(s, n); 176 if (sDotN > 0.0) { 177 if (lights[lightIndex].constantAttenuation != 0.0 178 || lights[lightIndex].linearAttenuation != 0.0 179 || lights[lightIndex].quadraticAttenuation != 0.0) { 180 float dist = length(sUnnormalized); 181 att = 1.0 / (lights[lightIndex].constantAttenuation + 182 lights[lightIndex].linearAttenuation * dist + 183 lights[lightIndex].quadraticAttenuation * dist * dist); 184 } 185 186 // The light direction is in world space already 187 if (lights[lightIndex].type == TYPE_SPOT) { 188 // Check if fragment is inside or outside of the spot light cone 189 if (degrees(acos(dot(-s, lights[lightIndex].direction))) > lights[lightIndex].cutOffAngle) 190 sDotN = 0.0; 191 } 192 } 193 } else { 194 // Directional lights 195 // The light direction is in world space already 196 s = normalize(-lights[lightIndex].direction); 197 sDotN = dot(s, n); 198 } 199 200 h = normalize(s + v); 201 sDotH = dot(s, h); 202 203 // Calculate diffuse component 204 vec3 diffuseColor = (1.0 - metalness) * baseColor * lights[lightIndex].color; 205 vec3 diffuse = diffuseColor * max(sDotN, 0.0) / 3.14159; 206 207 // Calculate specular component 208 vec3 dielectricColor = vec3(0.04); 209 vec3 F0 = mix(dielectricColor, baseColor, metalness); 210 vec3 specularFactor = vec3(0.0); 211 if (sDotN > 0.0) { 212 specularFactor = specularModel(F0, sDotH, sDotN, vDotN, n, h); 213 specularFactor *= normalDistribution(n, h, alpha); 214 } 215 vec3 specularColor = lights[lightIndex].color; 216 vec3 specular = specularColor * specularFactor; 217 218 // Blend between diffuse and specular to conserver energy 219 vec3 color = att * lights[lightIndex].intensity * (specular + diffuse * (vec3(1.0) - specular)); 220 221 // Reduce by ambient occlusion amount 222 color *= ambientOcclusion; 223 224 return color; 225} 226 227vec3 pbrIblModel(const in vec3 wNormal, 228 const in vec3 wView, 229 const in vec3 baseColor, 230 const in float metalness, 231 const in float alpha, 232 const in float ambientOcclusion) 233{ 234 // Calculate reflection direction of view vector about surface normal 235 // vector in world space. This is used in the fragment shader to sample 236 // from the environment textures for a light source. This is equivalent 237 // to the l vector for punctual light sources. Armed with this, calculate 238 // the usual factors needed 239 vec3 n = wNormal; 240 vec3 l = reflect(-wView, n); 241 vec3 v = wView; 242 vec3 h = normalize(l + v); 243 float vDotN = dot(v, n); 244 float lDotN = dot(l, n); 245 float lDotH = dot(l, h); 246 247 // Calculate diffuse component 248 vec3 diffuseColor = (1.0 - metalness) * baseColor; 249 vec3 diffuse = diffuseColor * texture(envLight_irradiance, l).rgb; 250 251 // Calculate specular component 252 vec3 dielectricColor = vec3(0.04); 253 vec3 F0 = mix(dielectricColor, baseColor, metalness); 254 vec3 specularFactor = specularModel(F0, lDotH, lDotN, vDotN, n, h); 255 256 float lod = alphaToMipLevel(alpha); 257//#define DEBUG_SPECULAR_LODS 258#ifdef DEBUG_SPECULAR_LODS 259 if (lod > 7.0) 260 return vec3(1.0, 0.0, 0.0); 261 else if (lod > 6.0) 262 return vec3(1.0, 0.333, 0.0); 263 else if (lod > 5.0) 264 return vec3(1.0, 1.0, 0.0); 265 else if (lod > 4.0) 266 return vec3(0.666, 1.0, 0.0); 267 else if (lod > 3.0) 268 return vec3(0.0, 1.0, 0.666); 269 else if (lod > 2.0) 270 return vec3(0.0, 0.666, 1.0); 271 else if (lod > 1.0) 272 return vec3(0.0, 0.0, 1.0); 273 else if (lod > 0.0) 274 return vec3(1.0, 0.0, 1.0); 275#endif 276 vec3 specularSkyColor = textureLod(envLight_specular, l, lod).rgb; 277 vec3 specular = specularSkyColor * specularFactor; 278 279 // Blend between diffuse and specular to conserve energy 280 vec3 color = specular + diffuse * (vec3(1.0) - specularFactor); 281 282 // Reduce by ambient occlusion amount 283 color *= ambientOcclusion; 284 285 return color; 286} 287 288vec3 toneMap(const in vec3 c) 289{ 290 return c / (c + vec3(1.0)); 291} 292 293vec3 gammaCorrect(const in vec3 color) 294{ 295 return pow(color, vec3(1.0 / gamma)); 296} 297 298vec4 metalRoughFunction(const in vec4 baseColor, 299 const in float metalness, 300 const in float roughness, 301 const in float ambientOcclusion, 302 const in vec3 worldPosition, 303 const in vec3 worldView, 304 const in vec3 worldNormal) 305{ 306 vec3 cLinear = vec3(0.0); 307 308 // Remap roughness for a perceptually more linear correspondence 309 float alpha = remapRoughness(roughness); 310 311 for (int i = 0; i < envLightCount; ++i) { 312 cLinear += pbrIblModel(worldNormal, 313 worldView, 314 baseColor.rgb, 315 metalness, 316 alpha, 317 ambientOcclusion); 318 } 319 320 for (int i = 0; i < lightCount; ++i) { 321 cLinear += pbrModel(i, 322 worldPosition, 323 worldNormal, 324 worldView, 325 baseColor.rgb, 326 metalness, 327 alpha, 328 ambientOcclusion); 329 } 330 331 // Apply exposure correction 332 cLinear *= pow(2.0, exposure); 333 334 // Apply simple (Reinhard) tonemap transform to get into LDR range [0, 1] 335 vec3 cToneMapped = toneMap(cLinear); 336 337 // Apply gamma correction prior to display 338 vec3 cGamma = gammaCorrect(cToneMapped); 339 340 return vec4(cGamma, 1.0); 341} 342