EEVEE: Cleanup: light power / radiance code and document it

All thanks to @weizhen for spoting the inconsistencies.
This commit is contained in:
Clément Foucault 2023-02-06 18:23:03 +01:00
parent 9c14039a8f
commit 81f8d74f6d
3 changed files with 103 additions and 78 deletions

@ -66,58 +66,79 @@ static void light_shape_parameters_set(EEVEE_Light *evli, const Light *la, const
}
}
static float light_shape_power_get(const Light *la, const EEVEE_Light *evli)
static float light_shape_radiance_get(const Light *la, const EEVEE_Light *evli)
{
float power;
/* Make illumination power constant */
if (la->type == LA_AREA) {
power = 1.0f / (evli->sizex * evli->sizey * 4.0f * M_PI) * /* 1/(w*h*Pi) */
0.8f; /* XXX: Empirical, Fit cycles power. */
if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) {
/* Scale power to account for the lower area of the ellipse compared to the surrounding
* rectangle. */
power *= 4.0f / M_PI;
/* Make illumination power constant. */
switch (la->type) {
case LA_AREA: {
/* Rectangle area. */
float area = (evli->sizex * 2.0f) * (evli->sizey * 2.0f);
/* Scale for the lower area of the ellipse compared to the surrounding rectangle. */
if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) {
area *= M_PI / 4.0f;
}
/* NOTE: The 4 factor is from Cycles definition of power. */
/* NOTE: Missing a factor of PI here to match Cycles. */
return 1.0f / (4.0f * area);
}
case LA_SPOT:
case LA_LOCAL: {
/* Sphere area. */
float area = 4.0f * (float)M_PI * square_f(evli->radius);
/* NOTE: Presence of a factor of PI here to match Cycles. But it should be missing to be
* consistent with the other cases. */
return 1.0f / (area * (float)M_PI);
}
default:
case LA_SUN: {
/* Disk area. */
float area = (float)M_PI * square_f(evli->radius);
/* Make illumination power closer to cycles for bigger radii. Cycles uses a cos^3 term that
* we cannot reproduce so we account for that by scaling the light power. This function is
* the result of a rough manual fitting. */
float sun_scaling = 1.0f + square_f(evli->radius) / 2.0f;
/* NOTE: Missing a factor of PI here to match Cycles. */
return sun_scaling / area;
}
}
else if (ELEM(la->type, LA_SPOT, LA_LOCAL)) {
power = 1.0f / (4.0f * evli->radius * evli->radius * M_PI * M_PI); /* `1/(4*(r^2)*(Pi^2))` */
/* for point lights (a.k.a radius == 0.0) */
// power = M_PI * M_PI * 0.78; /* XXX: Empirical, Fit cycles power. */
}
else { /* LA_SUN */
power = 1.0f / (evli->radius * evli->radius * M_PI);
/* Make illumination power closer to cycles for bigger radii. Cycles uses a cos^3 term that we
* cannot reproduce so we account for that by scaling the light power. This function is the
* result of a rough manual fitting. */
power += 1.0f / (2.0f * M_PI); /* `power *= 1 + (r^2)/2` */
}
return power;
}
static float light_shape_power_volume_get(const Light *la,
const EEVEE_Light *evli,
float area_power)
/* Returns a factor to apply to light power to get a point light radiance instead of a shape
* radiance. */
static float light_volume_radiance_factor_get(const Light *la,
const EEVEE_Light *evli,
float area_power)
{
/* Volume light is evaluated as point lights. Remove the shape power. */
float power = 1.0f / area_power;
if (la->type == LA_AREA) {
/* Match cycles. Empirical fit... must correspond to some constant. */
power *= 0.0792f * M_PI;
/* This corrects for area light most representative point trick. The fit was found by
* reducing the average error compared to cycles. */
float area = evli->sizex * evli->sizey;
float tmp = M_PI_2 / (M_PI_2 + sqrtf(area));
/* Lerp between 1.0 and the limit (1 / pi). */
power *= tmp + (1.0f - tmp) * M_1_PI;
}
else if (ELEM(la->type, LA_SPOT, LA_LOCAL)) {
/* Match cycles. Empirical fit... must correspond to some constant. */
power *= 0.0792f;
}
else { /* LA_SUN */
/* Nothing to do. */
switch (la->type) {
case LA_AREA: {
/* This corrects for area light most representative point trick. The fit was found by
* reducing the average error compared to cycles. */
float area = (evli->sizex * 2.0f) * (evli->sizey * 2.0f);
float tmp = M_PI_2 / (M_PI_2 + sqrtf(area));
/* Lerp between 1.0 and the limit (1 / pi). */
float mrp_scaling = tmp + (1.0f - tmp) * M_1_PI;
/* NOTE: The 4 factor is from Cycles definition of power. */
/* NOTE: Missing a factor of PI here to match Cycles. */
power *= mrp_scaling / 4.0f;
break;
}
case LA_SPOT:
case LA_LOCAL: {
/* Sphere solid angle. */
float area = 4.0f * (float)M_PI;
/* NOTE: Missing a factor of PI here to match Cycles. */
power *= 1.0f / area;
break;
}
default:
case LA_SUN: {
/* NOTE: Missing a factor of PI here to match Cycles. */
/* Do nothing. */
break;
}
}
return power;
}
@ -180,10 +201,10 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli)
evli->light_type = LAMPTYPE_AREA_ELLIPSE;
}
float shape_power = light_shape_power_get(la, evli);
float shape_power = light_shape_radiance_get(la, evli);
mul_v3_fl(evli->color, shape_power * la->energy);
evli->volume *= light_shape_power_volume_get(la, evli, shape_power);
evli->volume *= light_volume_radiance_factor_get(la, evli, shape_power);
/* No shadow by default */
evli->shadow_id = -1.0f;

@ -67,8 +67,8 @@ void Light::sync(/* ShadowModule &shadows , */ const Object *ob, float threshold
shape_parameters_set(la, scale);
float shape_power = shape_power_get(la);
float point_power = point_power_get(la);
float shape_power = shape_radiance_get(la);
float point_power = point_radiance_get(la);
this->diffuse_power = la->diff_fac * shape_power;
this->transmit_power = la->diff_fac * point_power;
this->specular_power = la->spec_fac * shape_power;
@ -185,64 +185,68 @@ void Light::shape_parameters_set(const ::Light *la, const float scale[3])
}
}
float Light::shape_power_get(const ::Light *la)
float Light::shape_radiance_get(const ::Light *la)
{
/* Make illumination power constant */
/* Make illumination power constant. */
switch (la->type) {
case LA_AREA: {
float area = _area_size_x * _area_size_y;
float power = 1.0f / (area * 4.0f * float(M_PI));
/* FIXME : Empirical, Fit cycles power */
power *= 0.8f;
/* Rectangle area. */
float area = (_area_size_x * 2.0f) * (_area_size_y * 2.0f);
/* Scale for the lower area of the ellipse compared to the surrounding rectangle. */
if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) {
/* Scale power to account for the lower area of the ellipse compared to the surrounding
* rectangle. */
power *= 4.0f / M_PI;
area *= M_PI / 4.0f;
}
return power;
/* NOTE: The 4 factor is from Cycles definition of power. */
/* NOTE: Missing a factor of PI here to match Cycles. */
return 1.0f / (4.0f * area);
}
case LA_SPOT:
case LA_LOCAL: {
return 1.0f / (4.0f * square_f(_radius) * float(M_PI * M_PI));
/* Sphere area. */
float area = 4.0f * float(M_PI) * square_f(_radius);
/* NOTE: Presence of a factor of PI here to match Cycles. But it should be missing to be
* consistent with the other cases. */
return 1.0f / (area * float(M_PI));
}
default:
case LA_SUN: {
float power = 1.0f / (square_f(_radius) * float(M_PI));
/* Disk area. */
float area = float(M_PI) * square_f(_radius);
/* Make illumination power closer to cycles for bigger radii. Cycles uses a cos^3 term that
* we cannot reproduce so we account for that by scaling the light power. This function is
* the result of a rough manual fitting. */
/* Simplification of: power *= 1 + r²/2 */
power += 1.0f / (2.0f * M_PI);
return power;
float sun_scaling = 1.0f + square_f(_radius) / 2.0f;
/* NOTE: Missing a factor of PI here to match Cycles. */
return sun_scaling / area;
}
}
}
float Light::point_power_get(const ::Light *la)
float Light::point_radiance_get(const ::Light *la)
{
/* Volume light is evaluated as point lights. Remove the shape power. */
/* Volume light is evaluated as point lights. */
switch (la->type) {
case LA_AREA: {
/* Match cycles. Empirical fit... must correspond to some constant. */
float power = 0.0792f * M_PI;
/* This corrects for area light most representative point trick. The fit was found by
* reducing the average error compared to cycles. */
float area = _area_size_x * _area_size_y;
/* This corrects for area light most representative point trick.
* The fit was found by reducing the average error compared to cycles. */
float area = (_area_size_x * 2.0) * (_area_size_y * 2.0f);
float tmp = M_PI_2 / (M_PI_2 + sqrtf(area));
/* Lerp between 1.0 and the limit (1 / pi). */
power *= tmp + (1.0f - tmp) * M_1_PI;
return power;
float mrp_scaling = tmp + (1.0f - tmp) * M_1_PI;
/* NOTE: The 4 factor is from Cycles definition of power. */
/* NOTE: Missing a factor of PI here to match Cycles. */
return mrp_scaling / 4.0f;
}
case LA_SPOT:
case LA_LOCAL: {
/* Match cycles. Empirical fit... must correspond to some constant. */
return 0.0792f;
/* Sphere solid angle. */
float area = 4.0f * float(M_PI);
/* NOTE: Missing a factor of PI here to match Cycles. */
return 1.0f / area;
}
default:
case LA_SUN: {
/* NOTE: Missing a factor of PI here to match Cycles. */
return 1.0f;
}
}

@ -59,8 +59,8 @@ struct Light : public LightData {
private:
float attenuation_radius_get(const ::Light *la, float light_threshold, float light_power);
void shape_parameters_set(const ::Light *la, const float scale[3]);
float shape_power_get(const ::Light *la);
float point_power_get(const ::Light *la);
float shape_radiance_get(const ::Light *la);
float point_radiance_get(const ::Light *la);
};
/** \} */