Skip to content

Commit

Permalink
Fixed a banding problem when using sphere light sources with very sma…
Browse files Browse the repository at this point in the history
…ll radius (issue LuxCoreRender/BlendLuxCore#477)
  • Loading branch information
Dade916 committed Jun 4, 2020
1 parent 68e1ae4 commit e15e45c
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 43 deletions.
48 changes: 27 additions & 21 deletions include/slg/lights/light_funcs.cl
Original file line number Diff line number Diff line change
Expand Up @@ -626,34 +626,40 @@ OPENCL_FORCE_INLINE float3 SphereLight_Illuminate(__global const LightSource *sp

// The point isn't inside the sphere

// Build a local coordinate system
const float3 localZ = toLight * (1.f / centerDistance);
Frame localFrame;
Frame_SetFromZ_Private(&localFrame, localZ);

// Sample sphere uniformly inside subtended cone
const float cosThetaMax = sqrt(max(0.f, 1.f - radiusSquared / centerDistanceSquared));
if (cosThetaMax > 1.f - DEFAULT_EPSILON_STATIC) {
// If the subtended angle is too small, I sample the light source like
// if it was a point light source in order to avoiding banding due to
// (lack of) numerical precision.
return PointLight_Illuminate(sphereLight, bsdf, time, shadowRay, directPdfW);
} else {
// Build a local coordinate system
const float3 localZ = toLight * (1.f / centerDistance);
Frame localFrame;
Frame_SetFromZ_Private(&localFrame, localZ);

// Sample sphere uniformly inside subtended cone
const float3 localShadowRayDir = UniformSampleConeLocal(u0, u1, cosThetaMax);
if (CosTheta(localShadowRayDir) < DEFAULT_COS_EPSILON_STATIC)
return BLACK;

const float3 localShadowRayDir = UniformSampleConeLocal(u0, u1, cosThetaMax);
if (CosTheta(localShadowRayDir) < DEFAULT_COS_EPSILON_STATIC)
return BLACK;

const float3 shadowRayDir = Frame_ToWorld_Private(&localFrame, localShadowRayDir);
const float3 shadowRayDir = Frame_ToWorld_Private(&localFrame, localShadowRayDir);

// Check the intersection with the sphere
const float3 shadowRayOrig = BSDF_GetRayOrigin(bsdf, shadowRayDir);
float shadowRayDistance;
if (!SphereLight_SphereIntersect(absolutePos, radiusSquared, shadowRayOrig, shadowRayDir, &shadowRayDistance))
shadowRayDistance = dot(toLight, shadowRayDir);
// Check the intersection with the sphere
const float3 shadowRayOrig = BSDF_GetRayOrigin(bsdf, shadowRayDir);
float shadowRayDistance;
if (!SphereLight_SphereIntersect(absolutePos, radiusSquared, shadowRayOrig, shadowRayDir, &shadowRayDistance))
shadowRayDistance = dot(toLight, shadowRayDir);

*directPdfW = UniformConePdf(cosThetaMax);
*directPdfW = UniformConePdf(cosThetaMax);

// Setup the shadow ray
Ray_Init4(shadowRay, shadowRayOrig, shadowRayDir, 0.f, shadowRayDistance, time);
// Setup the shadow ray
Ray_Init4(shadowRay, shadowRayOrig, shadowRayDir, 0.f, shadowRayDistance, time);

const float invArea = 1.f / (4.f * M_PI_F * radiusSquared);
const float invArea = 1.f / (4.f * M_PI_F * radiusSquared);

return VLOAD3F(sphereLight->notIntersectable.sphere.emittedFactor.c) * invArea * M_1_PI_F;
return VLOAD3F(sphereLight->notIntersectable.sphere.emittedFactor.c) * invArea * M_1_PI_F;
}
}

//------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions release-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
* Fixed a problem with BlendLuxCore when parsing more than 9 image pipelines (#336)
* Fixed a bug causing fireflies when using light tracing for caustics in some case (#329)
* Fixed a crash when using RemoveUnusedTextures() with Volumes (issue #377)
* Fixed a banding problem when using sphere light sources with very small radius (issue LuxCoreRender/BlendLuxCore#477)

Note: due to Glossycoating updated support for bump mapping on GPUs, some old scene using this kind of material/bump map combination may require some fix.

Expand Down
50 changes: 28 additions & 22 deletions src/slg/lights/spherelight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,38 +116,44 @@ Spectrum SphereLight::Illuminate(const Scene &scene, const BSDF &bsdf,

// The point isn't inside the sphere

// Build a local coordinate system
const Vector localZ = toLight / centerDistance;
Frame localFrame(localZ);

// Sample sphere uniformly inside subtended cone
const float cosThetaMax = sqrtf(Max(0.f, 1.f - radiusSquared / centerDistanceSquared));
if (cosThetaMax > 1.f - DEFAULT_EPSILON_STATIC) {
// If the subtended angle is too small, I sample the light source like
// if it was a point light source in order to avoiding banding due to
// (lack of) numerical precision.
return PointLight::Illuminate(scene, bsdf, time, u0, u1, passThroughEvent, shadowRay, directPdfW, emissionPdfW, cosThetaAtLight);
} else {
// Build a local coordinate system
const Vector localZ = toLight / centerDistance;
Frame localFrame(localZ);

const Vector localRayDir = UniformSampleCone(u0, u1, cosThetaMax);
// Sample sphere uniformly inside subtended cone
const Vector localRayDir = UniformSampleCone(u0, u1, cosThetaMax);

if (CosTheta(localRayDir) < DEFAULT_COS_EPSILON_STATIC)
return Spectrum();
if (CosTheta(localRayDir) < DEFAULT_COS_EPSILON_STATIC)
return Spectrum();

const Vector shadowRayDir = localFrame.ToWorld(localRayDir);
const Point shadowRayOrig = bsdf.GetRayOrigin(shadowRayDir);
const Ray ray(shadowRayOrig, shadowRayDir);
const Vector shadowRayDir = localFrame.ToWorld(localRayDir);
const Point shadowRayOrig = bsdf.GetRayOrigin(shadowRayDir);
const Ray ray(shadowRayOrig, shadowRayDir);

// Check the intersection with the sphere
float shadowRayDistance;
if (!SphereIntersect(ray, shadowRayDistance))
shadowRayDistance = Dot(toLight, shadowRayDir);
// Check the intersection with the sphere
float shadowRayDistance;
if (!SphereIntersect(ray, shadowRayDistance))
shadowRayDistance = Dot(toLight, shadowRayDir);

if (cosThetaAtLight)
*cosThetaAtLight = CosTheta(localRayDir);
if (cosThetaAtLight)
*cosThetaAtLight = CosTheta(localRayDir);

directPdfW = UniformConePdf(cosThetaMax);
directPdfW = UniformConePdf(cosThetaMax);

if (emissionPdfW)
*emissionPdfW = invArea * CosTheta(localRayDir) * INV_PI;
if (emissionPdfW)
*emissionPdfW = invArea * CosTheta(localRayDir) * INV_PI;

shadowRay = Ray(shadowRayOrig, shadowRayDir, 0.f, shadowRayDistance, time);
shadowRay = Ray(shadowRayOrig, shadowRayDir, 0.f, shadowRayDistance, time);

return emittedFactor * invArea * INV_PI;
return emittedFactor * invArea * INV_PI;
}
}

Properties SphereLight::ToProperties(const ImageMapCache &imgMapCache, const bool useRealFileName) const {
Expand Down

0 comments on commit e15e45c

Please sign in to comment.