aboutsummaryrefslogtreecommitdiff
path: root/shaders/cook_torrance.frag
blob: 1975491e63023f2da17c43fc3350f8febcb8b1b2 (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
precision highp float;

uniform vec4  BaseColorFactor;
uniform float MetallicFactor;
uniform float RoughnessFactor;
uniform vec3  EmissiveFactor;

#ifdef HAS_ALBEDO_MAP
uniform sampler2D BaseColorTexture;
#endif
#ifdef HAS_METALLIC_ROUGHNESS_MAP
uniform sampler2D MetallicRoughnessTexture;
#endif
#ifdef HAS_EMISSIVE_MAP
uniform sampler2D EmissiveTexture;
#endif
#ifdef HAS_OCCLUSION_MAP
uniform sampler2D AmbientOcclusionTexture;
#endif
#ifdef 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;
#ifdef HAS_NORMALS
in vec3 Normal;
#endif
#ifdef HAS_TANGENTS
in vec4 Tangent;
#endif
#ifdef 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);
#ifdef 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

float trowbridge_reitz_GGX(float roughness, float NdotH) {
  float a = roughness * roughness;
  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);
}

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);
  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);
  vec3  F = fresnel_schlick_roughness(F0, NdotV, roughness);
  vec3 Kd = mix(vec3(1.0) - F, vec3(0.0), metallic);
  vec3 diffuse = Kd * albedo * INV_PI * irradiance;
  vec3 specular = prefiltered_env * (F * BRDF_env.x + BRDF_env.y);
  return occlusion * (diffuse + specular);
}

void main()
{
  // TODO: Also use the specular F0 map from the model, and emissive. Make sure
  // to use all maps.
  // https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
#ifdef HAS_NORMAL_MAP
  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.

  // TODO: BaseColorFactor and BaseColorTexture are vec4/rgba quantities
  // respectively. Handle the alpha channel.
  // TODO: Other factors.
#ifdef HAS_ALBEDO_MAP
  vec3 albedo = vec3(BaseColorFactor) * texture(BaseColorTexture, Texcoord).rgb;
#else
  vec3 albedo = vec3(BaseColorFactor);
#endif
#ifdef 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
#ifdef HAS_EMISSIVE_MAP
  vec3 emissive = EmissiveFactor * texture(EmissiveTexture, Texcoord).rgb;
#else
  vec3 emissive = EmissiveFactor;
#endif
#ifdef 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;

  // 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, 1.0);
}