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