summaryrefslogtreecommitdiff
path: root/data/shaders/pbr-frag.glsl
blob: 47ed573fc6993782584ba0ac8dc863f3e3f5b4cb (plain)
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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
// -*- mode: c -*-

struct Material {
  vec3 baseColorFactor;
  bool baseColorTextureEnabled;
  int baseColorTexcoord;
  float metallicFactor;
  float roughnessFactor;
  bool metallicRoughnessTextureEnabled;
  int metallicRoughnessTexcoord;
  vec3 normalFactor;
  bool normalTextureEnabled;
  int normalTexcoord;
  bool occlusionTextureEnabled;
  int occlusionTexcoord;
  vec3 emissiveFactor;
  bool emissiveTextureEnabled;
  int emissiveTexcoord;
  int alphaMode;
  float alphaCutoff;
};

struct Light {
  bool enabled;
  int type;
  vec3 position;
  vec3 direction;
  vec4 color;
  float cutOff;
};

#ifdef GLSL120
attribute vec3 fragWorldPos;
attribute vec3 fragNormal;
attribute vec2 fragTexcoord0;
attribute vec2 fragTexcoord1
attribute vec4 fragColor0;
#else
in vec3 fragWorldPos;
in vec3 fragNormal;
in vec2 fragTexcoord0;
in vec2 fragTexcoord1;
in vec4 fragColor0;
#endif

#ifdef GLSL330
out vec4 fragColor;
#endif

#define MAX_LIGHTS 4

uniform Material material;
uniform Light lights[MAX_LIGHTS];
uniform vec4 ambientLightColor;
uniform bool vertexColored;
uniform vec3 cameraPosition;
uniform sampler2D baseColorTexture;
uniform sampler2D metallicRoughnessTexture;
uniform sampler2D normalTexture;
uniform sampler2D occlusionTexture;
uniform sampler2D emissiveTexture;

const float PI = 3.14159265358979323846;
const float GAMMA = 2.2;

#ifndef GLSL330
// Compatibility shim for older GLSL versions.
vec2 texture(sampler2D tex, vec2 coord) {
  return texture2D(tex, coord);
}
#endif

float posDot(vec3 v1, vec3 v2) {
  return max(dot(v1, v2), 0.0);
}

vec3 fresnelSchlick(float cosTheta, vec3 baseColor)
{
    return baseColor + (1.0 - baseColor) * pow(max(1.0 - cosTheta, 0.0), 5.0);
}

float distributionGGX(vec3 normal, vec3 halfAngle, float roughness)
{
    float a = roughness * roughness * roughness * roughness;
    float ndoth = posDot(normal, halfAngle);
    float denominator = ndoth * ndoth * (a - 1.0) + 1.0;

    return a / (PI * denominator * denominator);
}

float geometrySchlickGGX(float ndotv, float roughness)
{
    float r = roughness + 1.0;
    float k = (r * r) / 8.0;

    return ndotv / (ndotv * (1.0 - k) + k);
}

float geometrySmith(vec3 normal, vec3 viewDirection, vec3 lightDirection,
                    float roughness)
{
    return geometrySchlickGGX(posDot(normal, viewDirection), roughness) *
      geometrySchlickGGX(posDot(normal, lightDirection), roughness);
}

vec4 applyAlpha(vec4 color) {
  // Apply alpha mode.
  if(material.alphaMode == 0) { // opaque
    return vec4(color.rgb, 1.0);
  } else if(material.alphaMode == 1) { // mask
    if(color.a >= material.alphaCutoff) {
      return vec4(color.rgb, 1.0);
    } else {
      discard;
    }
  } else if(material.alphaMode == 2) { // blend
    if(color.a <= 0.005) {
      discard;
    } else {
      return color;
    }
  }
}

vec2 texcoord(int i) {
  if(i == 0) {
    return fragTexcoord0;
  } else {
    return fragTexcoord1;
  }
}

vec4 sRGBtoLinear(vec4 srgb) {
  return vec4(pow(srgb.r, GAMMA),
              pow(srgb.g, GAMMA),
              pow(srgb.b, GAMMA),
              srgb.a);
}

float materialMetallic() {
  float m = material.metallicFactor;

  if(material.metallicRoughnessTextureEnabled) {
    m *= texture(metallicRoughnessTexture,
                 texcoord(material.metallicRoughnessTexcoord)).b;
  }

  return m;
}

float materialRoughness() {
  float r = material.roughnessFactor;

  if(material.metallicRoughnessTextureEnabled) {
    r *= texture(metallicRoughnessTexture,
                 texcoord(material.metallicRoughnessTexcoord)).g;
  }

  return r;
}

vec4 materialAlbedo() {
  vec4 color = vec4(0.0, 0.0, 1.0, 1.0);

  if(material.baseColorTextureEnabled) {
    vec4 texColor = texture(baseColorTexture,
                            texcoord(material.baseColorTexcoord));
    color = sRGBtoLinear(texColor);
  }

  color *= vec4(material.baseColorFactor, 1.0);

  if(vertexColored) {
    color *= fragColor0;
  }

  return color;
}

vec4 materialEmissive() {
  vec4 color = vec4(0.0);

  if(material.emissiveTextureEnabled) {
    vec4 texColor = texture(emissiveTexture,
                            texcoord(material.emissiveTexcoord));
    color = sRGBtoLinear(texColor);
  }

  return color * vec4(material.emissiveFactor, 1.0);
}

vec3 materialOcclusion() {
  if(material.occlusionTextureEnabled) {
    return vec3(texture(occlusionTexture,
                        texcoord(material.occlusionTexcoord)).r);
  } else {
    return vec3(1.0);
  }
}

vec3 materialNormal() {
  vec3 normal;

  if(material.normalTextureEnabled) {
    normal = texture(normalTexture,
                     texcoord(material.normalTexcoord)).rgb * 2.0 - 1.0;
  } else {
    normal = fragNormal;
  }

  return normalize(normal);
}

vec3 lightDirection(Light light) {
  if(light.type == 0 || light.type == 2) { // point and spot lights
    return normalize(light.position - fragWorldPos);
  } else if(light.type == 1) { // directional light
    return normalize(-light.direction);
  }

  return vec3(0.0); // should never be reached.
}

vec3 lightAttenuate(Light light) {
  float distance = length(light.position - fragWorldPos);
  float attenuation = 1.0 / (distance * distance);
  return light.color.rgb * attenuation;
}

vec3 lightRadiance(Light light, vec3 direction) {
  if(light.type == 0) { // point light
    return lightAttenuate(light);
  } else if(light.type == 1) { // directional light
    return light.color.rgb;
  } else if(light.type == 2) { // spot light
    float theta = dot(direction, normalize(-light.direction));
    // Spot lights only shine light in a specific conical area.
    // They have no effect outside of that area.
    if(theta > light.cutOff) {
      return lightAttenuate(light);
    } else {
      return vec3(0.0);
    }
  }

  return vec3(0.0); // should never be reached.
}

void main(void) {
  // The unit vector pointing from the fragment position to the viewer
  // position.
  vec3 viewDirection = normalize(cameraPosition - fragWorldPos);
  float metallic = materialMetallic();
  float roughness = materialRoughness();
  vec3 normal = materialNormal();
  // The "raw" albedo has an alpha channel which we need to preserve
  // so that we can apply the desired alpha blending method at the
  // end, but it is completely ignored for lighting calculations.
  vec4 rawAlbedo = materialAlbedo();
  vec3 albedo = rawAlbedo.rgb;
  // The ambient occlusion factor affects the degree to which ambient
  // lighting has an affect on the fragment.  Each element of the
  // vector is in the range [0, 1].
  vec3 ao = materialOcclusion();
  // The color that the fragment relects at zero incidence.  In other
  // words, the color reflected when looking directly at the fragment.
  // A material that is fully metallic will fully express its albedo
  // color.  A material that isn't metallic at all will have a gray
  // initial color.  The grayscale value of 0.04 apparently achieves a
  // good result so that's what we do.  Any other metallic value is
  // somewhere between both colors, as calculated by a linear
  // interpolation.
  vec3 baseColor = mix(vec3(0.04), albedo, metallic);
  // Stores the sum of all lighting operations.
  vec3 color = vec3(0.0);

  // Apply direct lighting.
  for(int i = 0; i < MAX_LIGHTS; ++i)
  {
    Light light = lights[i];

    if(!light.enabled) {
      continue;
    }

    // The unit vector pointing from the fragment position to the
    // light position.
    vec3 lightDirection = lightDirection(light);
    vec3 radiance = lightRadiance(light, lightDirection);
    // The half angle vector sits between the view direction and the
    // light direction.
    vec3 halfwayVector = normalize(viewDirection + lightDirection);
    // Apply the normal distribution function.
    float normalDistribution = distributionGGX(normal, halfwayVector, roughness);
    // Apply the geometry function.
    float geo = geometrySmith(normal, viewDirection, lightDirection, roughness);
    // Compute the fresnel factor via Schlick's approximation to get
    // the specular relection coeffecient.  Since this is a microfacet
    // lighting model, we need to use the dot product of the halfway
    // vector and the view direction to get the cos(theta) value,
    // according to:
    // https://en.wikipedia.org/wiki/Schlick%27s_approximation
    vec3 fresnelFactor = fresnelSchlick(posDot(halfwayVector, viewDirection), baseColor);
    vec3 refractionRatio = (vec3(1.0) - fresnelFactor) * (1.0 - metallic);
    vec3 specularNumerator = normalDistribution * geo * fresnelFactor;
    // The dot product of the surface normal and the light direction
    // gives a value in the range [0, 1]. 0 means the normal and light
    // direction form a >= 90 degree angle and thus the light should
    // have no effect.  1 means the light is directly hitting the
    // surface and the lighting should be applied with full intensity.
    float specularFactor = posDot(normal, lightDirection);
    float specularDenominator = 4.0 * posDot(normal, viewDirection) * specularFactor;
    vec3 specular = specularNumerator / max(specularDenominator, 0.001);

    color += (refractionRatio * albedo / PI + specular) * radiance * specularFactor;
  }

  // The emissive texture says which fragments emit light.  There's
  // probably something more complicated to do here, but for now we
  // just add it to the accumulator to get a decent result.
  color += materialEmissive().rgb;
  // Apply simple ambient lighting.  The affect of the ambient light
  // is dampened by the ambient occlusion factor.
  //
  // TODO: Use image based lighting.
  color += ambientLightColor.rgb * albedo * ao;
  // Apply Reinhard tone mapping to convert our high dynamic range
  // color value to low dynamic range.  All of the lighting
  // calculations stacked on top of each other is likely to create
  // color channel values that exceed the [0, 1] range of OpenGL's
  // linear color space, i.e. high dynamic range.  If we did nothing,
  // the resulting color values would be clamped to that range and the
  // final image would be mostly white and washed out.
  color = color / (color + vec3(1.0));
  // Apply gamma correction.  The power of 2.2 is the rough average
  // gamma value of most monitors.  So, scaling the color by the
  // inverse, the power of 1/2.2, the color that people see on the
  // monitor is closer to what we intend to present with our original
  // linear color value.
  color = pow(color, vec3(1.0 / GAMMA));
  // Add alpha channel back in and apply the appropriate blend mode.
  // Yay, we're done!
  vec4 finalColor = applyAlpha(vec4(color, rawAlbedo.a));

#ifdef GLSL330
  fragColor = finalColor;
#else
  gl_FragColor = finalColor;
#endif
}