Cycles: replace spot light disk sampling with sphere sampling #109329

Merged
Weizhen Huang merged 12 commits from weizhen/blender:spot_light_to_sphere_light_with_cone into main 2023-07-07 17:15:24 +02:00
8 changed files with 217 additions and 100 deletions

View File

@ -147,7 +147,7 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
ls->eval_fac = 1.0f;
}
else if (type == LIGHT_SPOT) {
if (!spot_light_sample<in_volume_segment>(klight, rand, P, ls)) {
if (!spot_light_sample<in_volume_segment>(klight, rand, P, N, shader_flags, ls)) {
return false;
}
}
@ -469,7 +469,7 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
ls->group = lamp_lightgroup(kg, lamp);
if (type == LIGHT_SPOT) {
if (!spot_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
if (!spot_light_sample_from_intersection(klight, isect, ray_P, ray_D, N, path_flag, ls)) {
return false;
}
}

View File

@ -44,7 +44,7 @@ ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight,
ls->P = P + ls->D * ls->t;
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
ls->eval_fac = klight->spot.eval_fac;
if (r_sq == 0) {
/* Use intensity instead of radiance for point light. */
ls->eval_fac /= sqr(ls->t);
@ -100,7 +100,7 @@ ccl_device_forceinline void point_light_mnee_sample_update(const ccl_global Kern
ls->Ng = normalize(ls->P - klight->co);
}
else {
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
ls->eval_fac = klight->spot.eval_fac;
ls->Ng = -ls->D;
@ -136,7 +136,7 @@ ccl_device_inline bool point_light_sample_from_intersection(
const uint32_t path_flag,
ccl_private LightSample *ccl_restrict ls)
{
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
ls->eval_fac = klight->spot.eval_fac;
const float radius = klight->spot.radius;

View File

@ -381,7 +381,7 @@ ccl_device_forceinline void light_sample_update(KernelGlobals kg,
point_light_mnee_sample_update(klight, ls, P, N, path_flag);
}
else if (ls->type == LIGHT_SPOT) {
spot_light_mnee_sample_update(klight, ls, P);
spot_light_mnee_sample_update(klight, ls, P, N, path_flag);
}
else if (ls->type == LIGHT_AREA) {
area_light_mnee_sample_update(klight, ls, P);

View File

@ -8,96 +8,187 @@
CCL_NAMESPACE_BEGIN
ccl_device float spot_light_attenuation(const ccl_global KernelSpotLight *spot, float3 ray)
/* Transform vector to spot light's local coordinate system. */
ccl_device float3 spot_light_to_local(const ccl_global KernelSpotLight *spot, const float3 ray)
{
const float3 scaled_ray = safe_normalize(make_float3(dot(ray, spot->scaled_axis_u),
dot(ray, spot->scaled_axis_v),
dot(ray, spot->dir * spot->inv_len_z)));
return safe_normalize(make_float3(dot(ray, spot->scaled_axis_u),
dot(ray, spot->scaled_axis_v),
dot(ray, spot->dir * spot->inv_len_z)));
}
return smoothstepf((scaled_ray.z - spot->cos_half_spot_angle) * spot->spot_smooth);
/* Compute spot light attenuation of a ray given in local coordinate system. */
ccl_device float spot_light_attenuation(const ccl_global KernelSpotLight *spot, const float3 ray)
{
return smoothstepf((ray.z - spot->cos_half_spot_angle) * spot->spot_smooth);
}
ccl_device void spot_light_uv(const float3 ray,
const float half_cot_half_spot_angle,
ccl_private float *u,
ccl_private float *v)
{
/* Ensures that the spot light projects the full image regarless of the spot angle. */
const float factor = half_cot_half_spot_angle / ray.z;
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
*u = ray.y * factor + 0.5f;
*v = -(ray.x + ray.y) * factor;
}
template<bool in_volume_segment>
ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight,
const float2 rand,
const float3 P,
const float3 N,
const int shader_flags,
ccl_private LightSample *ls)
{
ls->P = klight->co;
const float radius = klight->spot.radius;
const float r_sq = sqr(klight->spot.radius);
const float3 center = klight->co;
const float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f) {
/* disk light */
ls->P += disk_light_sample(lightN, rand) * radius;
float3 lightN = P - center;
const float d_sq = len_squared(lightN);
const float d = sqrtf(d_sq);
lightN /= d;
float cos_theta;
ls->t = FLT_MAX;
if (d_sq > r_sq) {
const float one_minus_cos_half_spot_spread = 1.0f - klight->spot.cos_half_spot_angle;
const float one_minus_cos_half_angle = sin_sqr_to_one_minus_cos(r_sq / d_sq);
if (in_volume_segment || one_minus_cos_half_angle < one_minus_cos_half_spot_spread) {
/* Sample visible part of the sphere. */
sample_uniform_cone_concentric(
-lightN, one_minus_cos_half_angle, rand, &cos_theta, &ls->D, &ls->pdf);
}
else {
/* Sample spread cone. */
sample_uniform_cone_concentric(
-klight->spot.dir, one_minus_cos_half_spot_spread, rand, &cos_theta, &ls->D, &ls->pdf);
if (!ray_sphere_intersect(P, ls->D, 0.0f, FLT_MAX, center, radius, &ls->P, &ls->t)) {
/* Sampled direction does not intersect with the light. */
return false;
}
}
}
else {
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
if (has_transmission) {
ls->D = sample_uniform_sphere(rand);
ls->pdf = M_1_2PI_F * 0.5f;
}
else {
sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf);
}
cos_theta = -dot(ls->D, lightN);
}
const float invarea = klight->spot.invarea;
ls->pdf = invarea;
if (ls->t == FLT_MAX) {
/* Law of cosines. */
ls->t = d * cos_theta -
copysignf(safe_sqrtf(r_sq - d_sq + d_sq * sqr(cos_theta)), d_sq - r_sq);
ls->P = P + ls->D * ls->t;
}
else {
/* Already computed when sampling the spread cone. */
}
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D);
ls->eval_fac = klight->spot.eval_fac;
if (d_sq > r_sq) {
ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
}
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(&klight->spot, -ls->D);
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
if (r_sq == 0) {
/* Use intensity instead of radiance when the radius is zero. */
ls->eval_fac /= sqr(ls->t);
/* `ls->Ng` is not well-defined when the radius is zero, use the incoming direction instead. */
ls->Ng = -ls->D;
}
else {
ls->Ng = normalize(ls->P - center);
/* Remap sampled point onto the sphere to prevent precision issues with small radius. */
ls->P = ls->Ng * radius + center;
}
spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v);
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
return true;
}
ccl_device_forceinline float spot_light_pdf(const float cos_half_spread,
const float d_sq,
const float r_sq,
const float3 N,
const float3 D,
const uint32_t path_flag)
{
if (d_sq > r_sq) {
return M_1_2PI_F / min(sin_sqr_to_one_minus_cos(r_sq / d_sq), 1.0f - cos_half_spread);
}
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
return has_transmission ? M_1_2PI_F * 0.5f : pdf_cos_hemisphere(N, D);
}
ccl_device_forceinline void spot_light_mnee_sample_update(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
const float3 P,
const float3 N,
const uint32_t path_flag)
{
ls->D = normalize_len(ls->P - P, &ls->t);
ls->Ng = -ls->D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D);
ls->eval_fac = klight->spot.eval_fac;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
/* NOTE : preserve pdf in area measure. */
ls->pdf = invarea;
const float radius = klight->spot.radius;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(&klight->spot, ls->Ng);
if (radius > 0) {
const float d_sq = len_squared(P - klight->co);
const float r_sq = sqr(radius);
const float t_sq = sqr(ls->t);
ls->pdf = spot_light_pdf(klight->spot.cos_half_spot_angle, d_sq, r_sq, N, ls->D, path_flag);
/* NOTE : preserve pdf in area measure. */
ls->pdf *= 0.5f * fabsf(d_sq - r_sq - t_sq) / (radius * ls->t * t_sq);
ls->Ng = normalize(ls->P - klight->co);
if (d_sq > r_sq) {
ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
}
}
else {
ls->Ng = -ls->D;
ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
/* PDF does not change. */
}
spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v);
}
ccl_device_inline bool spot_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t)
{
/* Spot/Disk light. */
const float3 lightP = klight->co;
const float radius = klight->spot.radius;
if (radius == 0.0f) {
return false;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
/* One sided. */
if (dot(ray->D, lightN) >= 0.0f) {
if (dot(ray->D, ray->P - klight->co) >= 0.0f) {
return false;
}
float3 P;
return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t);
return point_light_intersect(klight, ray, t);
}
ccl_device_inline bool spot_light_sample_from_intersection(
@ -105,35 +196,27 @@ ccl_device_inline bool spot_light_sample_from_intersection(
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
const float3 N,
const uint32_t path_flag,
ccl_private LightSample *ccl_restrict ls)
{
/* the normal of the oriented disk */
const float3 lightN = normalize(ray_P - klight->co);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
const float d_sq = len_squared(ray_P - klight->co);
const float r_sq = sqr(klight->spot.radius);
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
ls->pdf = spot_light_pdf(klight->spot.cos_half_spot_angle, d_sq, r_sq, N, ray_D, path_flag);
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(&klight->spot, -ls->D);
if (ls->eval_fac == 0.0f) {
const float3 local_ray = spot_light_to_local(&klight->spot, -ray_D);
ls->eval_fac = klight->spot.eval_fac;
if (d_sq > r_sq) {
ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
}
if (ls->eval_fac == 0) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->Ng = r_sq > 0 ? normalize(ls->P - klight->co) : -ray_D;
/* compute pdf */
if (ls->t != FLT_MAX) {
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
}
else {
ls->pdf = 0.f;
}
spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v);
return true;
}
@ -146,18 +229,20 @@ ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLi
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
float min_distance;
const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &min_distance);
float dist_point_to_centroid;
const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &dist_point_to_centroid);
const float radius = klight->spot.radius;
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
cos_theta_u = min_distance / hypotenus;
cos_theta_u = (dist_point_to_centroid > radius) ? cos_from_sin(radius / dist_point_to_centroid) :
-1.0f;
if (in_volume_segment) {
return true;
}
distance = make_float2(hypotenus, min_distance);
distance = (dist_point_to_centroid > radius) ?
dist_point_to_centroid * make_float2(1.0f / cos_theta_u, 1.0f) :
one_float2() * radius / M_SQRT2_F;
point_to_centroid = point_to_centroid_;
return true;

View File

@ -1353,12 +1353,13 @@ typedef struct KernelSpotLight {
packed_float3 scaled_axis_u;
float radius;
packed_float3 scaled_axis_v;
float invarea;
float eval_fac;
packed_float3 dir;
float cos_half_spot_angle;
float half_cot_half_spot_angle;
float inv_len_z;
float spot_smooth;
float pad[2];
float pad;
} KernelSpotLight;
/* PointLight is SpotLight with only radius and invarea being used. */

View File

@ -1210,22 +1210,22 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
klights[light_index].strength[1] = light->strength.y;
klights[light_index].strength[2] = light->strength.z;
if (light->light_type == LIGHT_POINT) {
if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
shader_id &= ~SHADER_AREA_LIGHT;
float radius = light->size;
/* TODO: `invarea` was used for disk sampling, with the current solid angle sampling this
* becomes unnecessary. We could store `eval_fac` instead, but currently it shares the same
* #KernelSpotLight type with #LIGHT_SPOT, so keep it know until refactor for spot light. */
float invarea = (light->normalize && radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) :
float invarea = (light->normalize && radius > 0.0f) ? 1.0f / (M_4PI_F * radius * radius) :
1.0f;
/* Convert radiant flux to radiance or radiant intensity. */
float eval_fac = (radius > 0) ? invarea * M_1_PI_F : 0.25f * M_1_PI_F;
if (light->use_mis && radius > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co = co;
klights[light_index].spot.radius = radius;
klights[light_index].spot.invarea = invarea;
klights[light_index].spot.eval_fac = eval_fac;
}
else if (light->light_type == LIGHT_DISTANT) {
shader_id &= ~SHADER_AREA_LIGHT;
@ -1306,9 +1306,7 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
klights[light_index].area.tan_half_spread = tan_half_spread;
klights[light_index].area.normalize_spread = normalize_spread;
}
else if (light->light_type == LIGHT_SPOT) {
shader_id &= ~SHADER_AREA_LIGHT;
if (light->light_type == LIGHT_SPOT) {
/* Scale axes to accommodate non-uniform scaling. */
float3 scaled_axis_u = light->axisu / len_squared(light->axisu);
float3 scaled_axis_v = light->axisv / len_squared(light->axisv);
@ -1316,22 +1314,14 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
/* Keep direction normalized. */
float3 dir = safe_normalize_len(light->dir, &len_z);
float radius = light->size;
float invarea = (light->normalize && radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) :
1.0f;
float cos_half_spot_angle = cosf(light->spot_angle * 0.5f);
float spot_smooth = 1.0f / ((1.0f - cos_half_spot_angle) * light->spot_smooth);
if (light->use_mis && radius > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co = co;
klights[light_index].spot.scaled_axis_u = scaled_axis_u;
klights[light_index].spot.radius = radius;
klights[light_index].spot.scaled_axis_v = scaled_axis_v;
klights[light_index].spot.invarea = invarea;
klights[light_index].spot.dir = dir;
klights[light_index].spot.cos_half_spot_angle = cos_half_spot_angle;
klights[light_index].spot.half_cot_half_spot_angle = 0.5f / tanf(light->spot_angle * 0.5f);
klights[light_index].spot.inv_len_z = 1.0f / len_z;
klights[light_index].spot.spot_smooth = spot_smooth;
}

View File

@ -76,6 +76,7 @@ class Light : public Node {
NODE_SOCKET_API(uint64_t, light_set_membership);
NODE_SOCKET_API(uint64_t, shadow_set_membership);
/* Normalize power by the surface area of the light. */
NODE_SOCKET_API(bool, normalize)
void tag_update(Scene *scene);

View File

@ -213,6 +213,37 @@ static void versioning_remove_microfacet_sharp_distribution(bNodeTree *ntree)
}
}
static void version_replace_texcoord_normal_socket(bNodeTree *ntree)
{
/* The normal of a spot light was set to the incoming light direction, replace with the
* `Incoming` socket from the Geometry shader node. */
bNode *geometry_node = nullptr;
bNode *transform_node = nullptr;
bNodeSocket *incoming_socket = nullptr;
bNodeSocket *vec_in_socket = nullptr;
bNodeSocket *vec_out_socket = nullptr;
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) {
if (link->fromnode->type == SH_NODE_TEX_COORD && STREQ(link->fromsock->identifier, "Normal")) {
if (geometry_node == nullptr) {
geometry_node = nodeAddStaticNode(nullptr, ntree, SH_NODE_NEW_GEOMETRY);
incoming_socket = nodeFindSocket(geometry_node, SOCK_OUT, "Incoming");
transform_node = nodeAddStaticNode(nullptr, ntree, SH_NODE_VECT_TRANSFORM);
vec_in_socket = nodeFindSocket(transform_node, SOCK_IN, "Vector");
vec_out_socket = nodeFindSocket(transform_node, SOCK_OUT, "Vector");
NodeShaderVectTransform *nodeprop = (NodeShaderVectTransform *)transform_node->storage;
nodeprop->type = SHD_VECT_TRANSFORM_TYPE_NORMAL;
nodeAddLink(ntree, geometry_node, incoming_socket, transform_node, vec_in_socket);
}
nodeAddLink(ntree, transform_node, vec_out_socket, link->tonode, link->tosock);
nodeRemLink(ntree, link);
}
}
}
void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
{
if (!MAIN_VERSION_ATLEAST(bmain, 400, 1)) {
@ -277,6 +308,15 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
act->frame_end = min_ff(act->frame_end, MAXFRAMEF);
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 400, 9)) {
LISTBASE_FOREACH (Light *, light, &bmain->lights) {
if (light->type == LA_SPOT && light->nodetree) {
version_replace_texcoord_normal_socket(light->nodetree);
}
}
}
/**
* Versioning code until next subversion bump goes here.
*