1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
  | 
/*
[1] BRDF reference: https://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
[2] glTF reference implementation: https://github.com/KhronosGroup/glTF-Sample-Renderer/blob/main/source/Renderer/shaders/brdf.glsl
[3] Learn OpenGL reference: https://learnopengl.com/PBR/Theory (see other chapters also for IBL)
*/
precision highp float;
uniform vec4  BaseColorFactor;
uniform float MetallicFactor;
uniform float RoughnessFactor;
uniform vec3  EmissiveFactor;
#if HAS_TRANSPARENCY
#define ALPHA_MODE_MASK  1
#define ALPHA_MODE_BLEND 2
uniform int   AlphaMode;
uniform float AlphaCutoff;
#endif
#if HAS_ALBEDO_MAP
uniform sampler2D BaseColorTexture;
#endif
#if HAS_METALLIC_ROUGHNESS_MAP
uniform sampler2D MetallicRoughnessTexture;
#endif
#if HAS_EMISSIVE_MAP
uniform sampler2D EmissiveTexture;
#endif
#if HAS_OCCLUSION_MAP
uniform sampler2D AmbientOcclusionTexture;
#endif
#if HAS_NORMAL_MAP
uniform sampler2D NormalMap;
#endif
// TODO: Handle case in which there is no sky. Pass a boolean.
uniform samplerCube Sky;
uniform samplerCube IrradianceMap;
uniform samplerCube PrefilteredEnvironmentMap;
uniform sampler2D   BRDFIntegrationMap;
uniform float       MaxReflectionLOD;
uniform vec3 CameraPosition; // World space.
// World-space position, normal and tangent.
in vec3 Position;
#if HAS_NORMALS
in vec3 Normal;
#endif
#if HAS_TANGENTS
in vec4 Tangent;
#endif
#if HAS_TEXCOORDS
in vec2 Texcoord;
#endif
layout (location = 0) out vec4 Colour;
#define PI 3.1415926535897932384626433832795
#define INV_PI 0.3183098861837907
/// Transform a normal map sample into world space.
///
/// |normalWs| is the surface normal in world space.
/// |normalMapSample| is the normal map sample, not necessarily normalized.
///
/// TODO: Move to "normal.h"
#if defined(HAS_NORMAL_MAP) && (defined(HAS_TANGENTS) || defined(HAS_TEXCOORDS))
vec3 get_ws_normal(vec3 normalWs, vec3 normalMapSample) {
  vec3 N = normalize(Normal);
#if HAS_TANGENTS
  //vec3 T = normalize(tangent.xyz - dot(tangent.xyz, N) * N);
  vec3 T = Tangent.xyz;
  vec3 B = Tangent.w * cross(N, T);
#elif HAS_TEXCOORDS // No tangents, but must have texcoords.
  vec3 pos_dx = dFdx(Position);
  vec3 pos_dy = dFdy(Position);
  // vec3 uv_dx = vec3(dFdx(Texcoord), 0.0);
  // vec3 uv_dy = vec3(dFdy(Texcoord), 0.0);
  vec3 uv_dx = dFdx(vec3(Texcoord, 0.0));
  vec3 uv_dy = dFdy(vec3(Texcoord, 0.0));
  vec3 T = (uv_dy.t * pos_dx - uv_dx.t * pos_dy) /
           (uv_dx.s * uv_dy.t - uv_dy.s * uv_dx.t);
  // vec3 T = pos_dx * uv_dy.t - pos_dy * uv_dx.t;
  T = normalize(T - dot(T, N) * N);
  vec3 B = normalize(cross(N, T));
#endif
  if (gl_FrontFacing == false) {
    T = -T;
    B = -B;
    N = -N;
  }
  vec3 s = normalMapSample;
  //return normalize(s.x * T + s.y * B + s.z * N);
  return normalize(mat3(T,B,N) * s);
}
#endif // HAS_TANGENTS || HAS_TEXCOORDS
// Normal distribution function (NDF).
// Eq (4) in reference [1].
float trowbridge_reitz_GGX(float roughness, float NdotH) {
  float a = roughness * roughness; // "alpha roughness" in the reference.
  float a2 = a * a;
  float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
  return a2 / (PI * d * d);
}
float geometry_schlick_GGX(float k, float NdotV) {
  return NdotV / (NdotV * (1.0 - k) + k);
}
// Geometry function.
// See "Smith" under "Geometric Shadowing" in reference [1].
float geometry_smith(float roughness, float NdotL, float NdotV) {
  float k = roughness * roughness / 2.0; // IBL
  return geometry_schlick_GGX(k, NdotV) * geometry_schlick_GGX(k, NdotL);
}
vec3 fresnel_schlick(vec3 F0, float HdotV) {
  return F0 + (1.0 - F0) * pow(clamp(1.0 - HdotV, 0.0, 1.0), 5.0);
}
vec3 fresnel_schlick_roughness(vec3 F0, float NdotV, float roughness) {
  return F0
    + (max(vec3(1.0 - roughness), F0) - F0)
    * pow(clamp(1.0 - NdotV, 0.0, 1.0), 5.0);
}
// Cook-Torrance BRDF for a single light direction.
vec3 cook_torrance(
    vec3 albedo, float metallic, float roughness,
    float NdotL, float NdotV, float NdotH, float HdotV) {
  vec3 F0 = mix(vec3(0.04), albedo, metallic);
  float D = trowbridge_reitz_GGX(roughness, NdotH);
  vec3  F = fresnel_schlick(F0, HdotV);
  float G = geometry_smith(roughness, NdotL, NdotV);
  vec3 Kd = mix(vec3(1.0) - F, vec3(0.0), metallic);
  // A non-HDR environment map essentially has the 1/pi baked in as it does not
  // use physical units. See:
  // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
  // vec3 diffuse = Kd * albedo * INV_PI;
  vec3 diffuse = Kd * albedo * INV_PI;
  // Take a max to prevent division by 0 when either dot product is 0.
  vec3 specular = (D*F*G) / max(4.0 * NdotV * NdotL, 0.0001);
  return diffuse + specular;
}
// Cook-Torrance BRDF for IBL.
vec3 cook_torrance_IBL(
    vec3 albedo, float metallic, float roughness, float occlusion,
    float NdotV,
    vec3 irradiance, vec3 prefiltered_env, vec2 BRDF_env) {
  vec3 F0 = mix(vec3(0.04), albedo, metallic); // albedo = F0 for metals
  vec3 F  = fresnel_schlick_roughness(F0, NdotV, roughness);
  vec3 Kd = (vec3(1.0) - F) * (1.0 - metallic);
  // A non-HDR environment map essentially has the 1/pi baked in as it does not
  // use physical units. See:
  // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
  // vec3 diffuse = Kd * albedo * INV_PI * irradiance;
  vec3 diffuse = Kd * albedo * irradiance;
  vec3 specular = prefiltered_env * (F * BRDF_env.x + BRDF_env.y);
  return occlusion * (diffuse + specular);
}
void main()
{
  // TODO: Other factors.
#if HAS_ALBEDO_MAP
  vec4 base_colour = vec4(BaseColorFactor) * texture(BaseColorTexture, Texcoord);
#else
  vec4 base_colour = vec4(BaseColorFactor);
#endif
  vec3 albedo = base_colour.rgb;
#if HAS_TRANSPARENCY
  if ((AlphaMode == ALPHA_MODE_MASK) && (base_colour.a < AlphaCutoff)) {
    discard;
  }
#endif
#if HAS_METALLIC_ROUGHNESS_MAP
  // Spec: "Its green channel contains roughness values and its blue channel
  // contains metalness values."
  vec2 metal_roughness
    = vec2(MetallicFactor, RoughnessFactor)
    * texture(MetallicRoughnessTexture, Texcoord).bg;
#else
  vec2 metal_roughness = vec2(MetallicFactor, RoughnessFactor);
#endif
#if HAS_EMISSIVE_MAP
  vec3 emissive = EmissiveFactor * texture(EmissiveTexture, Texcoord).rgb;
#else
  vec3 emissive = EmissiveFactor;
#endif
#if HAS_OCCLUSION_MAP
  float occlusion = texture(AmbientOcclusionTexture, Texcoord).r;
#else
  float occlusion = 1.0;
#endif
  float metallic  = metal_roughness.x;
  float roughness = metal_roughness.y;
  // TODO: Also use the specular F0 map from the model, and emissive. Make sure
  // to use all maps.
  // https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
#if HAS_NORMAL_MAP
  // Spec: "After dequantization, texel values MUST be mapped as follows:
  //   red   [0.0 .. 1.0] to X [-1 .. 1],
  //   green [0.0 .. 1.0] to Y [-1 .. 1],
  //   blue  (0.5 .. 1.0] to Z ( 0 .. 1].
  // Normal textures SHOULD NOT contain blue values less than or equal to 0.5."
  vec3 normalMapSample = texture(NormalMap, Texcoord).xyz * 2.0 - 1.0;
  vec3 N = get_ws_normal(Normal, normalMapSample);
#elif HAS_NORMALS
  vec3 N = normalize(Normal);
#endif
  vec3 V = normalize(CameraPosition - Position);
  vec3 R = reflect(-V, N);
  // Not needed for IBL.
  //vec3 L = N;
  //vec3 H = normalize(L + V);
  float NdotV = max(0.0, dot(N, V));
  // Not needed for IBL.
  //float NdotL = max(0.0, dot(N,L));
  //float NdotH = max(0.0, dot(N,H));
  //float HdotV = clamp(dot(H,V), 0.0, 1.0);  // Clamp to prevent black spots.
  // For a single light direction:
  // vec3 brdf = cook_torrance(albedo, metallic, roughness, NdotL, NdotV, NdotH, HdotV);
  // vec3 Li = texture(Sky, N).rgb;
  // vec3 colour = brdf * Li * NdotL;
  // For IBL:
  vec3 irradiance = texture(IrradianceMap, N).rgb;
  vec3 prefiltered_env = textureLod(PrefilteredEnvironmentMap, R, roughness * MaxReflectionLOD).rgb;
  vec2 BRDF_env = texture(BRDFIntegrationMap, vec2(NdotV, roughness)).rg;
  vec3 colour = cook_torrance_IBL(
    albedo, metallic, roughness, occlusion, NdotV, irradiance, prefiltered_env, BRDF_env);
  colour += emissive;
  // Reinhard tone mapping.
  colour = colour / (colour + vec3(1.0));
  // Gamma correction.
  colour = pow(colour, vec3(1.0 / 2.2));
  // Debugging.
  //
  // Normal texture.
  //colour = normalMapSample * 0.5 + 0.5;
  //
  // Geometry normal.
  //colour = normalize(Normal) * 0.5 + 0.5;
  //
  // Shading normal.
  //colour = N * 0.5 + 0.5;
  //
  // Tangent and bitangent.
  // {
  //   vec3 pos_dx = dFdx(Position);
  //   vec3 pos_dy = dFdy(Position);
  //   // vec3 uv_dx = vec3(dFdx(Texcoord), 0.0);
  //   // vec3 uv_dy = vec3(dFdy(Texcoord), 0.0);
  //   vec3 uv_dx = dFdx(vec3(Texcoord, 0.0));
  //   vec3 uv_dy = dFdy(vec3(Texcoord, 0.0));
  //   vec3 T = (uv_dy.t * pos_dx - uv_dx.t * pos_dy) /
  //            (uv_dx.s * uv_dy.t - uv_dy.s * uv_dx.t);
  //   // vec3 T = pos_dx * uv_dy.t - pos_dy * uv_dx.t;
  //   vec3 N = normalize(Normal);
  //   T = normalize(T - dot(T, N) * N);
  //   vec3 B = normalize(cross(N, T));
  //   if (gl_FrontFacing == false) {
  //     T = -T;
  //     B = -B;
  //     N = -N;
  //   }
  //   // Tangent.
  //   //colour = T * 0.5 + 0.5;
  //   // Bitangent.
  //   //colour = B * 0.5 + 0.5;
  // }
  Colour = vec4(colour, base_colour.a);
}
 
  |