From e15e45ceffe7bbd31e8be78c2e37a6d8acef7c47 Mon Sep 17 00:00:00 2001 From: David Bucciarelli Date: Thu, 4 Jun 2020 16:44:02 +0200 Subject: [PATCH] Fixed a banding problem when using sphere light sources with very small radius (issue LuxCoreRender/BlendLuxCore#477) --- include/slg/lights/light_funcs.cl | 48 ++++++++++++++++------------- release-notes.txt | 1 + src/slg/lights/spherelight.cpp | 50 +++++++++++++++++-------------- 3 files changed, 56 insertions(+), 43 deletions(-) diff --git a/include/slg/lights/light_funcs.cl b/include/slg/lights/light_funcs.cl index d3c5b7936..e3ca90967 100644 --- a/include/slg/lights/light_funcs.cl +++ b/include/slg/lights/light_funcs.cl @@ -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; + } } //------------------------------------------------------------------------------ diff --git a/release-notes.txt b/release-notes.txt index 3ddb0d50c..e5d40d990 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -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. diff --git a/src/slg/lights/spherelight.cpp b/src/slg/lights/spherelight.cpp index 2faf77e3d..6f770a2cc 100644 --- a/src/slg/lights/spherelight.cpp +++ b/src/slg/lights/spherelight.cpp @@ -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 {