2023-08-24 10:54:59 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2017-2023 Blender Authors
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2024-10-04 15:48:22 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
2025-09-25 10:57:02 +02:00
|
|
|
#include "infos/eevee_common_infos.hh"
|
2024-12-02 21:26:15 +01:00
|
|
|
|
2022-08-11 08:13:47 +02:00
|
|
|
/**
|
|
|
|
|
* 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/
|
|
|
|
|
*/
|
|
|
|
|
|
2025-09-15 12:07:26 +02:00
|
|
|
#include "gpu_shader_math_constants_lib.glsl"
|
|
|
|
|
#include "gpu_shader_math_matrix_construct_lib.glsl"
|
2024-10-04 15:48:22 +02:00
|
|
|
#include "gpu_shader_utildefines_lib.glsl"
|
2023-10-13 17:59:46 +02:00
|
|
|
|
2022-08-11 08:13:47 +02:00
|
|
|
/* Diffuse *clipped* sphere integral. */
|
|
|
|
|
float ltc_diffuse_sphere_integral(sampler2DArray utility_tx, float avg_dir_z, float form_factor)
|
|
|
|
|
{
|
|
|
|
|
#if 1
|
|
|
|
|
/* use tabulated horizon-clipped sphere */
|
2025-04-14 13:46:41 +02:00
|
|
|
float2 uv = float2(avg_dir_z * 0.5f + 0.5f, form_factor);
|
2022-08-11 08:13:47 +02:00
|
|
|
uv = uv * UTIL_TEX_UV_SCALE + UTIL_TEX_UV_BIAS;
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
return texture(utility_tx, float3(uv, UTIL_DISK_INTEGRAL_LAYER))[UTIL_DISK_INTEGRAL_COMP];
|
2022-08-11 08:13:47 +02:00
|
|
|
#else
|
|
|
|
|
/* Cheap approximation. Less smooth and have energy issues. */
|
2025-04-11 18:28:45 +02:00
|
|
|
return max((form_factor * form_factor + avg_dir_z) / (form_factor + 1.0f), 0.0f);
|
2022-08-11 08:13:47 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An extended version of the implementation from
|
|
|
|
|
* "How to solve a cubic equation, revisited"
|
|
|
|
|
* http://momentsingraphics.de/?p=105
|
|
|
|
|
*/
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 ltc_solve_cubic(float4 coefs)
|
2022-08-11 08:13:47 +02:00
|
|
|
{
|
|
|
|
|
/* Normalize the polynomial */
|
|
|
|
|
coefs.xyz /= coefs.w;
|
|
|
|
|
/* Divide middle coefficients by three */
|
2025-04-11 18:28:45 +02:00
|
|
|
coefs.yz /= 3.0f;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
float A = coefs.w;
|
|
|
|
|
float B = coefs.z;
|
|
|
|
|
float C = coefs.y;
|
|
|
|
|
float D = coefs.x;
|
|
|
|
|
|
|
|
|
|
/* Compute the Hessian and the discriminant */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 delta = float3(-coefs.zy * coefs.zz + coefs.yx, dot(float2(coefs.z, -coefs.y), coefs.xy));
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* Discriminant */
|
2025-04-14 13:46:41 +02:00
|
|
|
float discr = dot(float2(4.0f * delta.x, -delta.y), delta.zy);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2023-02-12 14:37:16 +11:00
|
|
|
/* Clamping avoid NaN output on some platform. (see #67060) */
|
2025-04-11 18:28:45 +02:00
|
|
|
float sqrt_discr = sqrt(clamp(discr, 0.0f, FLT_MAX));
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float2 xlc, xsc;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* Algorithm A */
|
|
|
|
|
{
|
|
|
|
|
float C_a = delta.x;
|
2025-04-11 18:28:45 +02:00
|
|
|
float D_a = -2.0f * B * delta.x + delta.y;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* Take the cubic root of a normalized complex number */
|
2025-04-11 18:28:45 +02:00
|
|
|
float theta = atan(sqrt_discr, -D_a) / 3.0f;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-11 18:28:45 +02:00
|
|
|
float _2_sqrt_C_a = 2.0f * sqrt(-C_a);
|
2022-08-11 08:13:47 +02:00
|
|
|
float x_1a = _2_sqrt_C_a * cos(theta);
|
2025-04-11 18:28:45 +02:00
|
|
|
float x_3a = _2_sqrt_C_a * cos(theta + (2.0f / 3.0f) * M_PI);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
float xl;
|
2025-04-11 18:28:45 +02:00
|
|
|
if ((x_1a + x_3a) > 2.0f * B) {
|
2022-08-11 08:13:47 +02:00
|
|
|
xl = x_1a;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
xl = x_3a;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
xlc = float2(xl - B, A);
|
2022-08-11 08:13:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Algorithm D */
|
|
|
|
|
{
|
|
|
|
|
float C_d = delta.z;
|
2025-04-11 18:28:45 +02:00
|
|
|
float D_d = -D * delta.y + 2.0f * C * delta.z;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* Take the cubic root of a normalized complex number */
|
2025-04-11 18:28:45 +02:00
|
|
|
float theta = atan(D * sqrt_discr, -D_d) / 3.0f;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-11 18:28:45 +02:00
|
|
|
float _2_sqrt_C_d = 2.0f * sqrt(-C_d);
|
2022-08-11 08:13:47 +02:00
|
|
|
float x_1d = _2_sqrt_C_d * cos(theta);
|
2025-04-11 18:28:45 +02:00
|
|
|
float x_3d = _2_sqrt_C_d * cos(theta + (2.0f / 3.0f) * M_PI);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
float xs;
|
2025-04-11 18:28:45 +02:00
|
|
|
if (x_1d + x_3d < 2.0f * C) {
|
2022-08-11 08:13:47 +02:00
|
|
|
xs = x_1d;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
xs = x_3d;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
xsc = float2(-D, xs + C);
|
2022-08-11 08:13:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float E = xlc.y * xsc.y;
|
|
|
|
|
float F = -xlc.x * xsc.y - xlc.y * xsc.x;
|
|
|
|
|
float G = xlc.x * xsc.x;
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float2 xmc = float2(C * F - B * G, -B * F + C * E);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 root = float3(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
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 */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 ltc_edge_integral_vec(float3 v1, float3 v2)
|
2022-08-11 08:13:47 +02:00
|
|
|
{
|
|
|
|
|
float x = dot(v1, v2);
|
|
|
|
|
float y = abs(x);
|
|
|
|
|
|
2025-04-11 18:28:45 +02:00
|
|
|
float a = 0.8543985f + (0.4965155f + 0.0145206f * y) * y;
|
|
|
|
|
float b = 3.4175940f + (4.1616724f + y) * y;
|
2022-08-11 08:13:47 +02:00
|
|
|
float v = a / b;
|
|
|
|
|
|
2025-04-11 18:28:45 +02:00
|
|
|
float theta_sintheta = (x > 0.0f) ? v : 0.5f * inversesqrt(max(1.0f - x * x, 1e-7f)) - v;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
return cross(v1, v2) * theta_sintheta;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3x3 ltc_matrix(float4 lut)
|
2022-08-11 08:13:47 +02:00
|
|
|
{
|
|
|
|
|
/* Load inverse matrix. */
|
2025-04-14 13:46:41 +02:00
|
|
|
return float3x3(float3(lut.x, 0, lut.y), float3(0, 1, 0), float3(lut.z, 0, lut.w));
|
2022-08-11 08:13:47 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3x3 ltc_tangent_basis(float3 N, float3 V)
|
2022-08-11 08:13:47 +02:00
|
|
|
{
|
2023-12-13 04:06:27 +01:00
|
|
|
float NV = dot(N, V);
|
2025-04-11 18:28:45 +02:00
|
|
|
if (NV > 0.999999f) {
|
2023-12-13 04:06:27 +01:00
|
|
|
/* Mostly for orthographic view and surfel light eval. */
|
|
|
|
|
return from_up_axis(N);
|
|
|
|
|
}
|
|
|
|
|
/* Construct orthonormal basis around N. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 T1 = normalize(V - N * NV);
|
|
|
|
|
float3 T2 = cross(N, T1);
|
|
|
|
|
return float3x3(T1, T2, N);
|
2023-12-13 04:06:27 +01:00
|
|
|
}
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
void ltc_transform_quad(float3 N, float3 V, float3x3 Minv, inout float3 corners[4])
|
2023-12-13 04:06:27 +01:00
|
|
|
{
|
2022-08-11 08:13:47 +02:00
|
|
|
/* Construct orthonormal basis around N. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3x3 T = ltc_tangent_basis(N, V);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* Rotate area light in (T1, T2, R) basis. */
|
2023-12-13 04:06:27 +01:00
|
|
|
Minv = Minv * transpose(T);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* 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(),
|
2025-04-14 13:46:41 +02:00
|
|
|
* then N **MUST** be float3(0.0f, 0.0f, 1.0f), corresponding to the Up axis of the shading basis.
|
|
|
|
|
*/
|
|
|
|
|
float ltc_evaluate_quad(sampler2DArray utility_tx, float3 corners[4], float3 N)
|
2022-08-11 08:13:47 +02:00
|
|
|
{
|
|
|
|
|
/* Approximation using a sphere of the same solid angle than the quad.
|
|
|
|
|
* Finding the clipped sphere diffuse integral is easier than clipping the quad. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 avg_dir;
|
2022-08-11 08:13:47 +02:00
|
|
|
avg_dir = ltc_edge_integral_vec(corners[0], corners[1]);
|
|
|
|
|
avg_dir += ltc_edge_integral_vec(corners[1], corners[2]);
|
|
|
|
|
avg_dir += ltc_edge_integral_vec(corners[2], corners[3]);
|
|
|
|
|
avg_dir += ltc_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 * ltc_diffuse_sphere_integral(utility_tx, avg_dir_z, form_factor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If disk does not need to be transformed and is already front facing. */
|
|
|
|
|
float ltc_evaluate_disk_simple(sampler2DArray utility_tx, float disk_radius, float NL)
|
|
|
|
|
{
|
|
|
|
|
float r_sqr = disk_radius * disk_radius;
|
2025-04-11 18:28:45 +02:00
|
|
|
float form_factor = r_sqr / (1.0f + r_sqr);
|
2022-08-11 08:13:47 +02:00
|
|
|
return form_factor * ltc_diffuse_sphere_integral(utility_tx, NL, form_factor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* disk_points are WS vectors from the shading point to the disk "bounding domain" */
|
2025-04-14 13:46:41 +02:00
|
|
|
float ltc_evaluate_disk(
|
|
|
|
|
sampler2DArray utility_tx, float3 N, float3 V, float3x3 Minv, float3 disk_points[3])
|
2022-08-11 08:13:47 +02:00
|
|
|
{
|
2023-12-13 04:06:27 +01:00
|
|
|
/* Construct orthonormal basis around N. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3x3 T = ltc_tangent_basis(N, V);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2023-12-13 04:06:27 +01:00
|
|
|
/* Rotate area light in (T1, T2, R) basis. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3x3 R = transpose(T);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* Intermediate step: init ellipse. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 L_[3];
|
2023-10-13 17:59:46 +02:00
|
|
|
L_[0] = R * disk_points[0];
|
|
|
|
|
L_[1] = R * disk_points[1];
|
|
|
|
|
L_[2] = R * disk_points[2];
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 C = 0.5f * (L_[0] + L_[2]);
|
|
|
|
|
float3 V1 = 0.5f * (L_[1] - L_[2]);
|
|
|
|
|
float3 V2 = 0.5f * (L_[1] - L_[0]);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
/* 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);
|
2025-04-15 11:36:53 +02:00
|
|
|
float a, inv_b; /* Eigenvalues */
|
|
|
|
|
constexpr float threshold = 0.0007f; /* Can be adjusted. Fix artifacts. */
|
2022-08-11 08:13:47 +02:00
|
|
|
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);
|
2025-04-11 18:28:45 +02:00
|
|
|
float u = 0.5f * sqrt(tr - 2.0f * det);
|
|
|
|
|
float v = 0.5f * sqrt(tr + 2.0f * det);
|
2022-08-11 08:13:47 +02:00
|
|
|
float e_max = (u + v);
|
|
|
|
|
float e_min = (u - v);
|
|
|
|
|
e_max *= e_max;
|
|
|
|
|
e_min *= e_min;
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 V1_, V2_;
|
2022-08-11 08:13:47 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-11 18:28:45 +02:00
|
|
|
a = 1.0f / e_max;
|
2024-10-08 21:45:51 +02:00
|
|
|
inv_b = e_min;
|
2022-08-11 08:13:47 +02:00
|
|
|
V1 = normalize(V1_);
|
|
|
|
|
V2 = normalize(V2_);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-04-11 18:28:45 +02:00
|
|
|
a = 1.0f / d11;
|
2024-10-08 21:45:51 +02:00
|
|
|
inv_b = d22;
|
2022-08-11 08:13:47 +02:00
|
|
|
V1 *= sqrt(a);
|
2024-10-08 21:45:51 +02:00
|
|
|
V2 *= inversesqrt(inv_b);
|
2022-08-11 08:13:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Now find front facing ellipse with same solid angle. */
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 V3 = normalize(cross(V1, V2));
|
2025-04-11 18:28:45 +02:00
|
|
|
if (dot(C, V3) < 0.0f) {
|
|
|
|
|
V3 *= -1.0f;
|
2022-08-11 08:13:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float L = dot(V3, C);
|
2025-04-11 18:28:45 +02:00
|
|
|
float inv_L = 1.0f / L;
|
2022-08-11 08:13:47 +02:00
|
|
|
float x0 = dot(V1, C) * inv_L;
|
|
|
|
|
float y0 = dot(V2, C) * inv_L;
|
|
|
|
|
|
2024-10-08 21:45:51 +02:00
|
|
|
float ab = a * inv_b;
|
|
|
|
|
inv_b *= square(inv_L);
|
2025-04-11 18:28:45 +02:00
|
|
|
float t = 1.0f + x0 * x0;
|
2024-10-08 21:45:51 +02:00
|
|
|
|
|
|
|
|
/* Compared to the original LTC implementation, we scale the polynomial by `b` to avoid numerical
|
|
|
|
|
* issues when light size is small.
|
|
|
|
|
* i.e., instead of solving `c0 * e^3 + c1 * e^2 + c2 * e + c3 = 0`,
|
|
|
|
|
* we solve `c0/b^3 * (be)^3 + c1/b^2 * (be)^2 + c2/b * be + c3 = 0`. */
|
|
|
|
|
float c0 = ab * inv_b;
|
|
|
|
|
float c1 = ab * (t + y0 * y0) - c0 - inv_b;
|
2025-04-11 18:28:45 +02:00
|
|
|
float c2 = inv_b - ab * t - (1.0f + y0 * y0);
|
|
|
|
|
float c3 = 1.0f;
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 roots = ltc_solve_cubic(float4(c0, c1, c2, c3));
|
2022-08-11 08:13:47 +02:00
|
|
|
float e1 = roots.x;
|
|
|
|
|
float e2 = roots.y;
|
|
|
|
|
float e3 = roots.z;
|
|
|
|
|
|
2024-10-08 21:45:51 +02:00
|
|
|
/* Scale the root back by multiplying `b`.
|
|
|
|
|
* `a * x0 / (a - b * e2)` simplifies to `a/b * x0 / (a/b - e2)`,
|
2025-04-11 18:28:45 +02:00
|
|
|
* `b * y0 / (b - b * e2)` simplifies to `y0 / (1.0f - e2)`. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 avg_dir = float3(ab * x0 / (ab - e2), y0 / (1.0f - e2), 1.0f);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3x3 rotate = float3x3(V1, V2, V3);
|
2022-08-11 08:13:47 +02:00
|
|
|
|
|
|
|
|
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. */
|
2025-04-11 18:28:45 +02:00
|
|
|
float form_factor = max(0.0f, L1 * L2 * inversesqrt((1.0f + L1 * L1) * (1.0f + L2 * L2)));
|
2022-08-11 08:13:47 +02:00
|
|
|
return form_factor * ltc_diffuse_sphere_integral(utility_tx, avg_dir.z, form_factor);
|
|
|
|
|
}
|