Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ltc_lib.glsl « shaders « eevee « engines « draw « blender « source - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: dbfc7ad5a71edac0725e345a265db38c71a9ee09 (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
/**
 * Adapted from :
 * Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
 * Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
 * ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016.
 * Project page: https://eheitzresearch.wordpress.com/415-2/
 */

#define USE_LTC

#ifndef UTIL_TEX
#  define UTIL_TEX
uniform sampler2DArray utilTex;
#  define texelfetch_noise_tex(coord) texelFetch(utilTex, ivec3(ivec2(coord) % LUT_SIZE, 2.0), 0)
#endif /* UTIL_TEX */

/* Diffuse *clipped* sphere integral. */
float diffuse_sphere_integral(float avg_dir_z, float form_factor)
{
#if 1
  /* use tabulated horizon-clipped sphere */
  vec2 uv = vec2(avg_dir_z * 0.5 + 0.5, form_factor);
  uv = uv * (LUT_SIZE - 1.0) / LUT_SIZE + 0.5 / LUT_SIZE;

  return texture(utilTex, vec3(uv, 3.0)).x;
#else
  /* Cheap approximation. Less smooth and have energy issues. */
  return max((form_factor * form_factor + avg_dir_z) / (form_factor + 1.0), 0.0);
#endif
}

/**
 * An extended version of the implementation from
 * "How to solve a cubic equation, revisited"
 * http://momentsingraphics.de/?p=105
 */
vec3 solve_cubic(vec4 coefs)
{
  /* Normalize the polynomial */
  coefs.xyz /= coefs.w;
  /* Divide middle coefficients by three */
  coefs.yz /= 3.0;

  float A = coefs.w;
  float B = coefs.z;
  float C = coefs.y;
  float D = coefs.x;

  /* Compute the Hessian and the discriminant */
  vec3 delta = vec3(-coefs.zy * coefs.zz + coefs.yx, dot(vec2(coefs.z, -coefs.y), coefs.xy));

  /* Discriminant */
  float discr = dot(vec2(4.0 * delta.x, -delta.y), delta.zy);

  /* Clamping avoid NaN output on some platform. (see T67060) */
  float sqrt_discr = sqrt(clamp(discr, 0.0, FLT_MAX));

  vec2 xlc, xsc;

  /* Algorithm A */
  {
    float A_a = 1.0;
    float C_a = delta.x;
    float D_a = -2.0 * B * delta.x + delta.y;

    /* Take the cubic root of a normalized complex number */
    float theta = atan(sqrt_discr, -D_a) / 3.0;

    float _2_sqrt_C_a = 2.0 * sqrt(-C_a);
    float x_1a = _2_sqrt_C_a * cos(theta);
    float x_3a = _2_sqrt_C_a * cos(theta + (2.0 / 3.0) * M_PI);

    float xl;
    if ((x_1a + x_3a) > 2.0 * B) {
      xl = x_1a;
    }
    else {
      xl = x_3a;
    }

    xlc = vec2(xl - B, A);
  }

  /* Algorithm D */
  {
    float A_d = D;
    float C_d = delta.z;
    float D_d = -D * delta.y + 2.0 * C * delta.z;

    /* Take the cubic root of a normalized complex number */
    float theta = atan(D * sqrt_discr, -D_d) / 3.0;

    float _2_sqrt_C_d = 2.0 * sqrt(-C_d);
    float x_1d = _2_sqrt_C_d * cos(theta);
    float x_3d = _2_sqrt_C_d * cos(theta + (2.0 / 3.0) * M_PI);

    float xs;
    if (x_1d + x_3d < 2.0 * C) {
      xs = x_1d;
    }
    else {
      xs = x_3d;
    }

    xsc = vec2(-D, xs + C);
  }

  float E = xlc.y * xsc.y;
  float F = -xlc.x * xsc.y - xlc.y * xsc.x;
  float G = xlc.x * xsc.x;

  vec2 xmc = vec2(C * F - B * G, -B * F + C * E);

  vec3 root = vec3(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y);

  if (root.x < root.y && root.x < root.z) {
    root.xyz = root.yxz;
  }
  else if (root.z < root.x && root.z < root.y) {
    root.xyz = root.xzy;
  }

  return root;
}

/* from Real-Time Area Lighting: a Journey from Research to Production
 * Stephen Hill and Eric Heitz */
vec3 edge_integral_vec(vec3 v1, vec3 v2)
{
  float x = dot(v1, v2);
  float y = abs(x);

  float a = 0.8543985 + (0.4965155 + 0.0145206 * y) * y;
  float b = 3.4175940 + (4.1616724 + y) * y;
  float v = a / b;

  float theta_sintheta = (x > 0.0) ? v : 0.5 * inversesqrt(max(1.0 - x * x, 1e-7)) - v;

  return cross(v1, v2) * theta_sintheta;
}

mat3 ltc_matrix(vec4 lut)
{
  /* load inverse matrix */
  mat3 Minv = mat3(vec3(lut.x, 0, lut.y), vec3(0, 1, 0), vec3(lut.z, 0, lut.w));

  return Minv;
}

void ltc_transform_quad(vec3 N, vec3 V, mat3 Minv, inout vec3 corners[4])
{
  /* Avoid dot(N, V) == 1 in ortho mode, leading T1 normalize to fail. */
  V = normalize(V + 1e-8);

  /* construct orthonormal basis around N */
  vec3 T1, T2;
  T1 = normalize(V - N * dot(N, V));
  T2 = cross(N, T1);

  /* rotate area light in (T1, T2, R) basis */
  Minv = Minv * transpose(mat3(T1, T2, N));

  /* Apply LTC inverse matrix. */
  corners[0] = normalize(Minv * corners[0]);
  corners[1] = normalize(Minv * corners[1]);
  corners[2] = normalize(Minv * corners[2]);
  corners[3] = normalize(Minv * corners[3]);
}

/* If corners have already pass through ltc_transform_quad(),
 * then N **MUST** be vec3(0.0, 0.0, 1.0), corresponding to the Up axis of the shading basis. */
float ltc_evaluate_quad(vec3 corners[4], vec3 N)
{
  /* Approximation using a sphere of the same solid angle than the quad.
   * Finding the clipped sphere diffuse integral is easier than clipping the quad. */
  vec3 avg_dir;
  avg_dir = edge_integral_vec(corners[0], corners[1]);
  avg_dir += edge_integral_vec(corners[1], corners[2]);
  avg_dir += edge_integral_vec(corners[2], corners[3]);
  avg_dir += edge_integral_vec(corners[3], corners[0]);

  float form_factor = length(avg_dir);
  float avg_dir_z = dot(N, avg_dir / form_factor);
  return form_factor * diffuse_sphere_integral(avg_dir_z, form_factor);
}

/* If disk does not need to be transformed and is already front facing. */
float ltc_evaluate_disk_simple(float disk_radius, float NL)
{
  float r_sqr = disk_radius * disk_radius;
  float one_r_sqr = 1.0 + r_sqr;
  float form_factor = r_sqr * inversesqrt(one_r_sqr * one_r_sqr);
  return form_factor * diffuse_sphere_integral(NL, form_factor);
}

/* disk_points are WS vectors from the shading point to the disk "bounding domain" */
float ltc_evaluate_disk(vec3 N, vec3 V, mat3 Minv, vec3 disk_points[3])
{
  /* Avoid dot(N, V) == 1 in ortho mode, leading T1 normalize to fail. */
  V = normalize(V + 1e-8);

  /* construct orthonormal basis around N */
  vec3 T1, T2;
  T1 = normalize(V - N * dot(V, N));
  T2 = cross(N, T1);

  /* rotate area light in (T1, T2, R) basis */
  mat3 R = transpose(mat3(T1, T2, N));

  /* Intermediate step: init ellipse. */
  vec3 L_[3];
  L_[0] = mul(R, disk_points[0]);
  L_[1] = mul(R, disk_points[1]);
  L_[2] = mul(R, disk_points[2]);

  vec3 C = 0.5 * (L_[0] + L_[2]);
  vec3 V1 = 0.5 * (L_[1] - L_[2]);
  vec3 V2 = 0.5 * (L_[1] - L_[0]);

  /* Transform ellipse by Minv. */
  C = Minv * C;
  V1 = Minv * V1;
  V2 = Minv * V2;

  /* Compute eigenvectors of new ellipse. */

  float d11 = dot(V1, V1);
  float d22 = dot(V2, V2);
  float d12 = dot(V1, V2);
  float a, b;                     /* Eigenvalues */
  const float threshold = 0.0007; /* Can be adjusted. Fix artifacts. */
  if (abs(d12) / sqrt(d11 * d22) > threshold) {
    float tr = d11 + d22;
    float det = -d12 * d12 + d11 * d22;

    /* use sqrt matrix to solve for eigenvalues */
    det = sqrt(det);
    float u = 0.5 * sqrt(tr - 2.0 * det);
    float v = 0.5 * sqrt(tr + 2.0 * det);
    float e_max = (u + v);
    float e_min = (u - v);
    e_max *= e_max;
    e_min *= e_min;

    vec3 V1_, V2_;
    if (d11 > d22) {
      V1_ = d12 * V1 + (e_max - d11) * V2;
      V2_ = d12 * V1 + (e_min - d11) * V2;
    }
    else {
      V1_ = d12 * V2 + (e_max - d22) * V1;
      V2_ = d12 * V2 + (e_min - d22) * V1;
    }

    a = 1.0 / e_max;
    b = 1.0 / e_min;
    V1 = normalize(V1_);
    V2 = normalize(V2_);
  }
  else {
    a = 1.0 / d11;
    b = 1.0 / d22;
    V1 *= sqrt(a);
    V2 *= sqrt(b);
  }

  /* Now find front facing ellipse with same solid angle. */

  vec3 V3 = normalize(cross(V1, V2));
  if (dot(C, V3) < 0.0) {
    V3 *= -1.0;
  }

  float L = dot(V3, C);
  float inv_L = 1.0 / L;
  float x0 = dot(V1, C) * inv_L;
  float y0 = dot(V2, C) * inv_L;

  float L_sqr = L * L;
  a *= L_sqr;
  b *= L_sqr;

  float t = 1.0 + x0 * x0;
  float c0 = a * b;
  float c1 = c0 * (t + y0 * y0) - a - b;
  float c2 = (1.0 - a * t) - b * (1.0 + y0 * y0);
  float c3 = 1.0;

  vec3 roots = solve_cubic(vec4(c0, c1, c2, c3));
  float e1 = roots.x;
  float e2 = roots.y;
  float e3 = roots.z;

  vec3 avg_dir = vec3(a * x0 / (a - e2), b * y0 / (b - e2), 1.0);

  mat3 rotate = mat3(V1, V2, V3);

  avg_dir = rotate * avg_dir;
  avg_dir = normalize(avg_dir);

  /* L1, L2 are the extends of the front facing ellipse. */
  float L1 = sqrt(-e2 / e3);
  float L2 = sqrt(-e2 / e1);

  /* Find the sphere and compute lighting. */
  float form_factor = max(0.0, L1 * L2 * inversesqrt((1.0 + L1 * L1) * (1.0 + L2 * L2)));
  return form_factor * diffuse_sphere_integral(avg_dir.z, form_factor);
}