diff --git a/intern/cycles/kernel/light/distant.h b/intern/cycles/kernel/light/distant.h index 2dd02a58e2f..0f1b2ea0179 100644 --- a/intern/cycles/kernel/light/distant.h +++ b/intern/cycles/kernel/light/distant.h @@ -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; } diff --git a/intern/cycles/kernel/sample/mapping.h b/intern/cycles/kernel/sample/mapping.h index 6ae8fb9f95a..91e12f5f625 100644 --- a/intern/cycles/kernel/sample/mapping.h +++ b/intern/cycles/kernel/sample/mapping.h @@ -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; diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index b0ecb34a98a..dcd84fa96b2 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -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 { diff --git a/intern/cycles/scene/light.cpp b/intern/cycles/scene/light.cpp index f6728f02366..6311b4ec00b 100644 --- a/intern/cycles/scene/light.cpp +++ b/intern/cycles/scene/light.cpp @@ -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(); diff --git a/intern/cycles/util/math_fast.h b/intern/cycles/util/math_fast.h index dbd932409e3..52f17f57418 100644 --- a/intern/cycles/util/math_fast.h +++ b/intern/cycles/util/math_fast.h @@ -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