Path: blob/master/drivers/gles3/shaders/area_lights_inc.glsl
59211 views
// COPIED FROM servers\rendering\renderer_rd\shaders\area_lights_inc.glsl
// Partially modified
// Functions related to area lights
#define M_PI 3.14159265359
#define M_TAU 6.28318530718
float acos_approx(float p_x) {
float x = abs(p_x);
float res = -0.156583f * x + (M_PI / 2.0);
res *= sqrt(1.0f - x);
return (p_x >= 0.0) ? res : M_PI - res;
}
vec3 integrate_edge_hill(vec3 p0, vec3 p1) {
// Approximation suggested by Hill and Heitz, calculating the integral of the spherical cosine distribution over the line between p0 and p1.
// Runs faster than the exact formula of Baum et al. (1989).
float cosTheta = dot(p0, p1);
float x = cosTheta;
float y = abs(x);
float a = 5.42031 + (3.12829 + 0.0902326 * y) * y;
float b = 3.45068 + (4.18814 + y) * y;
float theta_sintheta = a / b;
if (x < 0.0) {
theta_sintheta = M_PI * inversesqrt(1.0 - x * x) - theta_sintheta; // original paper: 0.5*inversesqrt(max(1.0 - x*x, 1e-7)) - theta_sintheta
}
return theta_sintheta * cross(p0, p1);
}
float integrate_edge(vec3 p_proj0, vec3 p_proj1, vec3 p0, vec3 p1) {
float epsilon = 0.00001;
bool opposite_sides = dot(p_proj0, p_proj1) < -1.0 + epsilon;
if (opposite_sides) {
// calculate the point on the line p0 to p1 that is closest to the vertex (origin)
vec3 half_point_t = p0 + normalize(p1 - p0) * dot(p0, normalize(p0 - p1));
vec3 half_point = normalize(half_point_t);
return integrate_edge_hill(p_proj0, half_point).y + integrate_edge_hill(half_point, p_proj1).y;
}
return integrate_edge_hill(p_proj0, p_proj1).y;
}
// Form factor function for area light, taken from Urena, Fajardo, et.al. (2013): An Area-Preserving Parametrization for Spherical Rectangles
float quad_solid_angle(vec3 L[4]) {
// The solid angle of a spherical rectangle is the difference of the sum of its angles
// and the sum of the angles of a plane rectangle (2*PI)
vec3 c1 = cross(L[0], L[1]);
vec3 c2 = cross(L[1], L[2]);
vec3 c3 = cross(L[2], L[3]);
vec3 c4 = cross(L[3], L[0]);
vec3 n0 = normalize(c1);
vec3 n1 = normalize(c2);
vec3 n2 = normalize(c3);
vec3 n3 = normalize(c4);
float g0 = acos(clamp(dot(-n0, n1), -1.0, 1.0));
float g1 = acos(clamp(dot(-n1, n2), -1.0, 1.0));
float g2 = acos(clamp(dot(-n2, n3), -1.0, 1.0));
float g3 = acos(clamp(dot(-n3, n0), -1.0, 1.0));
float angle_sum = g0 + g1 + g2 + g3;
return clamp(angle_sum - M_TAU, 0.0, M_TAU);
}
void clip_quad_to_horizon(inout vec3 L[5], out int vertex_count) {
// detect clipping config
int config = 0;
if (L[0].y > 0.0) {
config += 1;
}
if (L[1].y > 0.0) {
config += 2;
}
if (L[2].y > 0.0) {
config += 4;
}
if (L[3].y > 0.0) {
config += 8;
}
// clip
vertex_count = 0;
if (config == 0) {
// clip all
} else if (config == 1) { // V1 clip V2 V3 V4
vertex_count = 3;
L[1] = -L[1].y * L[0] + L[0].y * L[1];
L[2] = -L[3].y * L[0] + L[0].y * L[3];
} else if (config == 2) { // V2 clip V1 V3 V4
vertex_count = 3;
L[0] = -L[0].y * L[1] + L[1].y * L[0];
L[2] = -L[2].y * L[1] + L[1].y * L[2];
} else if (config == 3) { // V1 V2 clip V3 V4
vertex_count = 4;
L[2] = -L[2].y * L[1] + L[1].y * L[2];
L[3] = -L[3].y * L[0] + L[0].y * L[3];
} else if (config == 4) { // V3 clip V1 V2 V4
vertex_count = 3;
L[0] = -L[3].y * L[2] + L[2].y * L[3];
L[1] = -L[1].y * L[2] + L[2].y * L[1];
} else if (config == 5) { // V1 V3 clip V2 V4) impossible
vertex_count = 0;
} else if (config == 6) { // V2 V3 clip V1 V4
vertex_count = 4;
L[0] = -L[0].y * L[1] + L[1].y * L[0];
L[3] = -L[3].y * L[2] + L[2].y * L[3];
} else if (config == 7) { // V1 V2 V3 clip V4
vertex_count = 5;
L[4] = -L[3].y * L[0] + L[0].y * L[3];
L[3] = -L[3].y * L[2] + L[2].y * L[3];
} else if (config == 8) { // V4 clip V1 V2 V3
vertex_count = 3;
L[0] = -L[0].y * L[3] + L[3].y * L[0];
L[1] = -L[2].y * L[3] + L[3].y * L[2];
L[2] = L[3];
} else if (config == 9) { // V1 V4 clip V2 V3
vertex_count = 4;
L[1] = -L[1].y * L[0] + L[0].y * L[1];
L[2] = -L[2].y * L[3] + L[3].y * L[2];
} else if (config == 10) { // V2 V4 clip V1 V3) impossible
vertex_count = 0;
} else if (config == 11) { // V1 V2 V4 clip V3
vertex_count = 5;
L[4] = L[3];
L[3] = -L[2].y * L[3] + L[3].y * L[2];
L[2] = -L[2].y * L[1] + L[1].y * L[2];
} else if (config == 12) { // V3 V4 clip V1 V2
vertex_count = 4;
L[1] = -L[1].y * L[2] + L[2].y * L[1];
L[0] = -L[0].y * L[3] + L[3].y * L[0];
} else if (config == 13) { // V1 V3 V4 clip V2
vertex_count = 5;
L[4] = L[3];
L[3] = L[2];
L[2] = -L[1].y * L[2] + L[2].y * L[1];
L[1] = -L[1].y * L[0] + L[0].y * L[1];
} else if (config == 14) { // V2 V3 V4 clip V1
vertex_count = 5;
L[4] = -L[0].y * L[3] + L[3].y * L[0];
L[0] = -L[0].y * L[1] + L[1].y * L[0];
} else if (config == 15) { // V1 V2 V3 V4
vertex_count = 4;
}
if (vertex_count == 3) {
L[3] = L[0];
}
if (vertex_count == 4) {
L[4] = L[0];
}
}
float ltc_integrate_clipped_quad(vec3 L[5], vec3 L_proj[5], int vertices_above_horizon) {
float I;
I = integrate_edge(L_proj[0], L_proj[1], L[0], L[1]);
I += integrate_edge(L_proj[1], L_proj[2], L[1], L[2]);
I += integrate_edge(L_proj[2], L_proj[3], L[2], L[3]);
if (vertices_above_horizon >= 4) {
I += integrate_edge(L_proj[3], L_proj[4], L[3], L[4]);
}
if (vertices_above_horizon == 5) {
I += integrate_edge(L_proj[4], L_proj[0], L[4], L[0]);
}
return abs(I);
}
void ltc_evaluate(vec3 normal, vec3 eye_vec, mat3 M_inv, vec3 points[4], out float integral) {
// construct the orthonormal basis around the normal vector
vec3 x, z;
z = -normalize(eye_vec - normal * dot(eye_vec, normal)); // expanding the angle between view and normal vector to 90 degrees, this gives a normal vector
x = cross(normal, z);
// rotate area light in (T1, normal, T2) basis
M_inv = M_inv * transpose(mat3(x, normal, z));
vec3 L[5];
L[0] = M_inv * points[0];
L[1] = M_inv * points[1];
L[2] = M_inv * points[2];
L[3] = M_inv * points[3];
vec3 L_unclipped[4];
L_unclipped[0] = L[0];
L_unclipped[1] = L[1];
L_unclipped[2] = L[2];
L_unclipped[3] = L[3];
int n;
clip_quad_to_horizon(L, n);
if (n == 0) {
integral = 0.0;
return;
}
// project onto unit sphere
vec3 L_proj[5];
L_proj[0] = normalize(L[0]);
L_proj[1] = normalize(L[1]);
L_proj[2] = normalize(L[2]);
L_proj[3] = normalize(L[3]);
L_proj[4] = normalize(L[4]);
// Prevent abnormal values when the light goes through (or close to) the fragment
vec3 pnorm = normalize(cross(L_proj[0] - L_proj[1], L_proj[2] - L_proj[1]));
if (abs(dot(pnorm, L_proj[0])) < 1e-10) {
// we could just return black, but that would lead to some black pixels in front of the light.
// Better, we check if the fragment is on the light, and return white if so.
vec3 r10 = points[0] - points[1];
vec3 r12 = points[2] - points[1];
float alpha = -dot(points[1], r10) / dot(r10, r10);
float beta = -dot(points[1], r12) / dot(r12, r12);
if (0.0 < alpha && alpha < 1.0 && 0.0 < beta && beta < 1.0) { // fragment is on light {
integral = 1.0;
return;
} else {
integral = 0.0;
return;
}
}
float I = ltc_integrate_clipped_quad(L, L_proj, n);
integral = I / (2.0 * M_PI);
}
void ltc_evaluate_specular(vec3 normal, vec3 eye_vec, float roughness, vec3 points[4], sampler2D ltc_lut1, sampler2D ltc_lut2, out float ltc_specular, out vec2 fresnel) {
float theta = acos_approx(dot(normal, eye_vec));
const float LTC_LUT_SIZE = float(64.0);
vec2 lut_pos = vec2(max(roughness, float(0.02)), theta / float(0.5 * M_PI));
vec2 lut_uv = vec2(lut_pos * (float(63.0) / LTC_LUT_SIZE) + vec2(float(0.5) / LTC_LUT_SIZE)); // offset by 1 pixel
vec4 M_brdf_abcd = texture(ltc_lut1, lut_uv);
vec3 M_brdf_e_mag_fres = texture(ltc_lut2, lut_uv).xyz;
float scale = 1.0 / (M_brdf_abcd.x * M_brdf_e_mag_fres.x - M_brdf_abcd.y * M_brdf_abcd.w);
mat3 M_inv = mat3(
vec3(0, 0, 1.0 / M_brdf_abcd.z),
vec3(-M_brdf_abcd.w * scale, M_brdf_abcd.x * scale, 0),
vec3(-M_brdf_e_mag_fres.x * scale, M_brdf_abcd.y * scale, 0));
ltc_evaluate(normal, eye_vec, M_inv, points, ltc_specular);
fresnel = vec2(M_brdf_e_mag_fres.yz);
}