Eevee: Implement Sun area lighting and few fixes/opti.

Sun is treated as a unit distant disk like in cycles.

Opti: Since computing the diffuse contribution via LTC is the same as not using the Linear Transformation, we can bypass most of the LTC code.
This replaces the sphere analytical diffuse computation as it gives a more pleasing result very close to cycles' AND cheaper.

Lights power have been retweaked to be coherent with cycles (except sun lamp with large radius where cycles has a non-uniform light distribution).
This commit is contained in:
Clément Foucault 2018-01-18 21:51:23 +01:00
parent 0cec092eca
commit 03e432bcdb
4 changed files with 72 additions and 76 deletions

View File

@ -599,13 +599,14 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli)
}
else if (la->type == LA_SPOT || la->type == LA_LOCAL) {
power = 1.0f / (4.0f * evli->radius * evli->radius * M_PI * M_PI) * /* 1/(4*r²*Pi²) */
M_PI * M_PI * M_PI * 10.0; /* XXX : Empirical, Fit cycles power */
M_PI * M_PI * 10.0; /* XXX : Empirical, Fit cycles power */
/* for point lights (a.k.a radius == 0.0) */
// power = M_PI * M_PI * 0.78; /* XXX : Empirical, Fit cycles power */
}
else {
power = 1.0f;
power = 1.0f / (4.0f * evli->radius * evli->radius * M_PI * M_PI) * /* 1/(r²*Pi) */
12.5f; /* XXX : Empirical, Fit cycles power */
}
mul_v3_fl(evli->color, power * la->energy);

View File

@ -28,54 +28,30 @@ float direct_diffuse_sun(LightData ld, vec3 N)
return bsdf;
}
/* From Frostbite PBR Course
* Analytical irradiance from a sphere with correct horizon handling
* http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf */
#ifdef USE_LTC
float direct_diffuse_sphere(LightData ld, vec3 N, vec4 l_vector)
{
float dist = l_vector.w;
vec3 L = l_vector.xyz / dist;
float radius = max(ld.l_sizex, 0.0001);
float costheta = clamp(dot(N, L), -0.999, 0.999);
float h = min(ld.l_radius / dist , 0.9999);
float h2 = h*h;
float costheta2 = costheta * costheta;
float bsdf;
float NL = dot(N, l_vector.xyz / l_vector.w);
if (costheta2 > h2) {
bsdf = M_PI * h2 * clamp(costheta, 0.0, 1.0);
}
else {
float sintheta = sqrt(1.0 - costheta2);
float x = sqrt(1.0 / h2 - 1.0);
float y = -x * (costheta / sintheta);
float sinthetasqrty = sintheta * sqrt(1.0 - y * y);
bsdf = (costheta * acos(y) - x * sinthetasqrty) * h2 + atan(sinthetasqrty / x);
}
bsdf = max(bsdf, 0.0);
bsdf *= M_1_PI2;
return bsdf;
return ltc_evaluate_disk_simple(ld.l_radius / l_vector.w, NL);
}
#ifdef USE_LTC
float direct_diffuse_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector)
{
vec3 corners[4];
corners[0] = l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * ld.l_sizey;
corners[1] = l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * -ld.l_sizey;
corners[2] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * -ld.l_sizey;
corners[3] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * ld.l_sizey;
corners[0] = normalize(l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * ld.l_sizey);
corners[1] = normalize(l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * -ld.l_sizey);
corners[2] = normalize(l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * -ld.l_sizey);
corners[3] = normalize(l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * ld.l_sizey);
return ltc_evaluate_quad_diffuse(corners);
return ltc_evaluate_quad(corners, N);
}
#endif
#if 0
float direct_diffuse_unit_disc(vec3 N, vec3 L)
float direct_diffuse_unit_disc(LightData ld, vec3 N, vec3 V)
{
float NL = dot(N, -ld.l_forward);
return ltc_evaluate_disk_simple(ld.l_radius, NL);
}
#endif
@ -125,7 +101,6 @@ vec3 direct_ggx_sphere(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughn
float bsdf = ltc_evaluate_disk(N, V, ltc_mat, points);
bsdf *= brdf_lut.b; /* Bsdf intensity */
bsdf *= M_1_PI;
vec3 spec = F_area(f0, brdf_lut.xy) * bsdf;
@ -145,18 +120,37 @@ vec3 direct_ggx_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector, float rou
vec4 ltc_lut = texture(utilTex, vec3(uv, 0.0)).rgba;
mat3 ltc_mat = ltc_matrix(ltc_lut);
float bsdf = ltc_evaluate_quad(N, V, ltc_mat, corners);
ltc_transform_quad(N, V, ltc_mat, corners);
float bsdf = ltc_evaluate_quad(corners, vec3(0.0, 0.0, 1.0));
bsdf *= brdf_lut.b; /* Bsdf intensity */
vec3 spec = F_area(f0, brdf_lut.xy) * bsdf;
return spec;
}
#endif
#if 0
float direct_ggx_disc(vec3 N, vec3 L)
vec3 direct_ggx_unit_disc(LightData ld, vec3 N, vec3 V, float roughness, vec3 f0)
{
roughness = clamp(roughness, 0.0004, 0.999); /* Fix low roughness artifacts. */
vec2 uv = lut_coords(dot(N, V), sqrt(roughness));
vec3 brdf_lut = texture(utilTex, vec3(uv, 1.0)).rgb;
vec4 ltc_lut = texture(utilTex, vec3(uv, 0.0)).rgba;
mat3 ltc_mat = ltc_matrix(ltc_lut);
vec3 Px = ld.l_right * ld.l_radius;
vec3 Py = ld.l_up * ld.l_radius;
vec3 points[3];
points[0] = -ld.l_forward - Px - Py;
points[1] = -ld.l_forward + Px - Py;
points[2] = -ld.l_forward + Px + Py;
float bsdf = ltc_evaluate_disk(N, V, ltc_mat, points);
bsdf *= brdf_lut.b; /* Bsdf intensity */
vec3 spec = F_area(f0, brdf_lut.xy) * bsdf;
return spec;
}
#endif

View File

@ -234,8 +234,7 @@ float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector)
{
#ifdef USE_LTC
if (ld.l_type == SUN) {
/* TODO disk area light */
return direct_diffuse_sun(ld, N);
return direct_diffuse_unit_disc(ld, N, V);
}
else if (ld.l_type == AREA) {
return direct_diffuse_rectangle(ld, N, V, l_vector);
@ -257,8 +256,7 @@ vec3 light_specular(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness
{
#ifdef USE_LTC
if (ld.l_type == SUN) {
/* TODO disk area light */
return direct_ggx_sun(ld, N, V, roughness, f0);
return direct_ggx_unit_disc(ld, N, V, roughness, f0);
}
else if (ld.l_type == AREA) {
return direct_ggx_rectangle(ld, N, V, l_vector, roughness, f0);

View File

@ -15,17 +15,17 @@ uniform sampler2DArray utilTex;
#endif /* UTIL_TEX */
/* Diffuse *clipped* sphere integral. */
float diffuse_sphere_integral_lut(vec3 avg_dir, float form_factor)
float diffuse_sphere_integral_lut(float avg_dir_z, float form_factor)
{
vec2 uv = vec2(avg_dir.z * 0.5 + 0.5, form_factor);
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, 1.0)).w;
}
float diffuse_sphere_integral_cheap(vec3 avg_dir, float form_factor)
float diffuse_sphere_integral_cheap(float avg_dir_z, float form_factor)
{
return max((form_factor * form_factor + avg_dir.z) / (form_factor + 1.0), 0.0);
return max((form_factor * form_factor + avg_dir_z) / (form_factor + 1.0), 0.0);
}
/**
@ -149,7 +149,7 @@ mat3 ltc_matrix(vec4 lut)
return Minv;
}
float ltc_evaluate_quad(vec3 N, vec3 V, mat3 Minv, vec3 corners[4])
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);
@ -167,24 +167,11 @@ float ltc_evaluate_quad(vec3 N, vec3 V, mat3 Minv, vec3 corners[4])
corners[1] = normalize(Minv * corners[1]);
corners[2] = normalize(Minv * corners[2]);
corners[3] = normalize(Minv * corners[3]);
/* 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 sphere_cosine_integral = form_factor * diffuse_sphere_integral_lut(avg_dir, form_factor);
return abs(sphere_cosine_integral);
}
/* Same as above but without the matrix transform. */
float ltc_evaluate_quad_diffuse(vec3 corners[4])
/* 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. */
@ -195,10 +182,27 @@ float ltc_evaluate_quad_diffuse(vec3 corners[4])
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);
float sphere_cosine_integral = form_factor * diffuse_sphere_integral_lut(avg_dir, form_factor);
#if 1 /* use tabulated horizon-clipped sphere */
return form_factor * diffuse_sphere_integral_lut(avg_dir_z, form_factor);
#else /* Less accurate version, a bit cheaper. */
return form_factor * diffuse_sphere_integral_cheap(avg_dir_z, form_factor);
#endif
}
return abs(sphere_cosine_integral);
/* 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);
#if 1 /* use tabulated horizon-clipped sphere */
return form_factor * diffuse_sphere_integral_lut(NL, form_factor);
#else /* Less accurate version, a bit cheaper. */
return form_factor * diffuse_sphere_integral_cheap(NL, form_factor);
#endif
}
/* disk_points are WS vectors from the shading point to the disk "bounding domain" */
@ -307,13 +311,12 @@ float ltc_evaluate_disk(vec3 N, vec3 V, mat3 Minv, vec3 disk_points[3])
float L2 = sqrt(-e2/e1);
/* Find the sphere and compute lighting. */
float form_factor = L1 * L2 * inversesqrt((1.0 + L1 * L1) * (1.0 + L2 * L2));
float form_factor = max(0.0, L1 * L2 * inversesqrt((1.0 + L1 * L1) * (1.0 + L2 * L2)));
/* use tabulated horizon-clipped sphere */
float sphere_cosine_integral = form_factor * diffuse_sphere_integral_lut(avg_dir, form_factor);
/* Less accurate version, a bit cheaper. */
//float sphere_cosine_integral = form_factor * diffuse_sphere_integral_cheap(avg_dir, form_factor);
return max(0.0, sphere_cosine_integral);
#if 1 /* use tabulated horizon-clipped sphere */
return form_factor * diffuse_sphere_integral_lut(avg_dir.z, form_factor);
#else /* Less accurate version, a bit cheaper. */
return form_factor * diffuse_sphere_integral_cheap(avg_dir.z, form_factor);
#endif
}