Cycles: Change sun lamp to have uniform intensity at high angles #108996

Merged
Weizhen Huang merged 6 commits from LukasStockner/blender:uniform-distant-light into main 2023-07-07 17:20:28 +02:00
5 changed files with 58 additions and 64 deletions

View File

@ -14,24 +14,15 @@ ccl_device_inline bool distant_light_sample(const ccl_global KernelLight *klight
const float2 rand,
ccl_private LightSample *ls)
{
/* distant light */
float3 lightD = klight->co;
float3 D = lightD;
float radius = klight->distant.radius;
float invarea = klight->distant.invarea;
float unused;
sample_uniform_cone_concentric(
klight->co, klight->distant.one_minus_cosangle, rand, &unused, &ls->Ng, &ls->pdf);
if (radius > 0.0f) {
D = normalize(D + disk_light_sample(D, rand) * radius);
}
ls->P = D;
ls->Ng = D;
ls->D = -D;
ls->P = ls->Ng;
ls->D = -ls->Ng;
ls->t = FLT_MAX;
float costheta = dot(lightD, D);
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
ls->eval_fac = klight->distant.eval_fac;
return true;
}
@ -50,15 +41,11 @@ ccl_device bool distant_light_intersect(const ccl_global KernelLight *klight,
{
kernel_assert(klight->type == LIGHT_DISTANT);
if (klight->distant.radius == 0.0f) {
if (klight->distant.angle == 0.0f) {
return false;
}
const float3 lightD = klight->co;
const float costheta = dot(-lightD, ray->D);
const float cosangle = klight->distant.cosangle;
if (costheta < cosangle) {
if (vector_angle(-klight->co, ray->D) > klight->distant.angle) {
return false;
}
@ -76,7 +63,6 @@ ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
{
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, lamp);
const int shader = klight->shader_id;
const float radius = klight->distant.radius;
const LightType type = (LightType)klight->type;
if (type != LIGHT_DISTANT) {
@ -85,37 +71,19 @@ ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
if (!(shader & SHADER_USE_MIS)) {
return false;
}
if (radius == 0.0f) {
if (klight->distant.angle == 0.0f) {
return false;
}
/* a distant light is infinitely far away, but equivalent to a disk
* shaped light exactly 1 unit away from the current shading point.
*
* radius t^2/cos(theta)
* <----------> t = sqrt(1^2 + tan(theta)^2)
* tan(th) area = radius*radius*pi
* <----->
* \ | (1 + tan(theta)^2)/cos(theta)
* \ | (1 + tan(acos(cos(theta)))^2)/cos(theta)
* t \th| 1 simplifies to
* \-| 1/(cos(theta)^3)
* \| magic!
* P
*/
float3 lightD = klight->co;
float costheta = dot(-lightD, ray_D);
float cosangle = klight->distant.cosangle;
/* Workaround to prevent a hang in the classroom scene with AMD HIP drivers 22.10,
* Remove when a compiler fix is available. */
#ifdef __HIP__
ls->shader = klight->shader_id;
#endif
if (costheta < cosangle)
if (vector_angle(-klight->co, ray_D) > klight->distant.angle) {
return false;
}
ls->type = type;
#ifndef __HIP__
@ -133,10 +101,8 @@ ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
ls->D = ray_D;
ls->group = lamp_lightgroup(kg, lamp);
/* compute pdf */
float invarea = klight->distant.invarea;
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
ls->pdf = klight->distant.pdf;
ls->eval_fac = klight->distant.eval_fac;
return true;
}

View File

@ -133,14 +133,35 @@ ccl_device_inline void sample_uniform_cone_concentric(const float3 N,
ccl_private float *pdf)
{
if (one_minus_cos_angle > 0) {
/* Map random number from 2D to 1D. */
/* Remap radius to get a uniform distribution w.r.t. solid angle on the cone.
* The logic to derive this mapping is as follows:
*
* Sampling a cone is comparable to sampling the hemisphere, we just restrict theta. Therefore,
* the same trick of first sampling the unit disk and the projecting the result up towards the
* hemisphere by calculating the appropriate z coordinate still works.
*
* However, by itself this results in cosine-weighted hemisphere sampling, so we need some kind
* of remapping. Cosine-weighted hemisphere and uniform cone sampling have the same conditional
* PDF for phi (both are constant), so we only need to think about theta, which corresponds
* directly to the radius.
*
* To find this mapping, we consider the simplest sampling strategies for cosine-weighted
* hemispheres and uniform cones. In both, phi is chosen as 2pi * random(). For the former,
* r_disk(rand) = sqrt(rand). This is just naive disk sampling, since the projection to the
* hemisphere doesn't change the radius. For the latter, r_cone(rand) =
* sin_from_cos(mix(cos_angle, 1, rand)).
*
* So, to remap, we just invert r_disk (-> rand(r_disk) = r_disk^2) and insert it into r_cone:
* r_cone(r_disk) = r_cone(rand(r_disk)) = sin_from_cos(mix(cos_angle, 1, r_disk^2)). In
* practise, we need to replace `rand` with `1 - rand` to preserve the stratification, but
* since it's uniform, that's fine. */
float2 xy = concentric_sample_disk(rand);
const float r2 = len_squared(xy);
/* Equivalent to `mix(cos_angle, 1.0f, 1.0f - r2)` */
*cos_theta = 1.0f - r2 * one_minus_cos_angle;
/* Equivalent to `xy *= sin_theta / sqrt(r2); */
/* Remap disk radius to cone radius, equivalent to `xy *= sin_theta / sqrt(r2); */
xy *= safe_sqrtf(one_minus_cos_angle * (2.0f - one_minus_cos_angle * r2));
float3 T, B;

View File

@ -1375,10 +1375,10 @@ typedef struct KernelAreaLight {
} KernelAreaLight;
typedef struct KernelDistantLight {
float radius;
float cosangle;
float invarea;
float pad;
float angle;
float one_minus_cosangle;
float pdf;
float eval_fac;
} KernelDistantLight;
typedef struct KernelLight {

View File

@ -1230,22 +1230,23 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
else if (light->light_type == LIGHT_DISTANT) {
shader_id &= ~SHADER_AREA_LIGHT;
float3 dir = safe_normalize(light->dir);
float angle = light->angle / 2.0f;
float radius = tanf(angle);
float cosangle = cosf(angle);
float area = M_PI_F * radius * radius;
float invarea = (light->normalize && area > 0.0f) ? 1.0f / area : 1.0f;
float3 dir = light->dir;
dir = safe_normalize(dir);
if (light->use_mis && area > 0.0f)
if (light->use_mis && angle > 0.0f) {
shader_id |= SHADER_USE_MIS;
}
const float one_minus_cosangle = 2.0f * sqr(sinf(0.5f * angle));
const float pdf = (angle > 0.0f) ? (M_1_2PI_F / one_minus_cosangle) : 1.0f;
klights[light_index].co = dir;
klights[light_index].distant.invarea = invarea;
klights[light_index].distant.radius = radius;
klights[light_index].distant.cosangle = cosangle;
klights[light_index].distant.angle = angle;
klights[light_index].distant.one_minus_cosangle = one_minus_cosangle;
klights[light_index].distant.pdf = pdf;
klights[light_index].distant.eval_fac = (light->normalize && angle > 0) ?
M_1_PI_F / sqr(sinf(angle)) :
1.0f;
}
else if (light->light_type == LIGHT_BACKGROUND) {
uint visibility = scene->background->get_visibility();

View File

@ -336,6 +336,12 @@ ccl_device float fast_atan2f(float y, float x)
return copysignf(r, y);
}
/* Same as precise_angle, but using fast_atan2f. Still much better that acos(dot(a, b)). */
ccl_device_inline float vector_angle(float3 a, float3 b)
{
return 2.0f * fast_atan2f(len(a - b), len(a + b));
}
/* Based on:
*
* https://github.com/LiraNuna/glsl-sse2/blob/master/source/vec4.h