diff --git a/lib/rendering/pbr/core/Cryptomatte.cc b/lib/rendering/pbr/core/Cryptomatte.cc index 239c35f..e2f9e10 100644 --- a/lib/rendering/pbr/core/Cryptomatte.cc +++ b/lib/rendering/pbr/core/Cryptomatte.cc @@ -3,7 +3,9 @@ #include "Cryptomatte.h" -#include // for size_t +#include +#include +#include namespace moonray { namespace pbr { @@ -50,19 +52,59 @@ void CryptomatteBuffer::clear() mFinalized = false; } -void CryptomatteBuffer::addSampleScalar(unsigned x, unsigned y, float sampleId, float weight, - const scene_rdl2::math::Vec3f& position, - const scene_rdl2::math::Vec3f& p0, - const scene_rdl2::math::Vec3f& normal, - const scene_rdl2::math::Color4& beauty, - const scene_rdl2::math::Vec3f refP, - const scene_rdl2::math::Vec3f refN, - const scene_rdl2::math::Vec2f uv, + +void computeCryptomatteResults(CryptomatteResults &results, int cryptoIdAttrIdx, int cryptoUVAttrIdx, + moonray::shading::Intersection &isect) +{ + results.mHit = true; + results.mPosition = isect.getP(); + results.mNormal = isect.getN(); + + results.mId = 0.0f; + if (cryptoIdAttrIdx >= 0) { + moonray::shading::TypedAttributeKey cryptoIdAttrKey(cryptoIdAttrIdx); + if (isect.isProvided(cryptoIdAttrKey)) { + results.mId = isect.getAttribute(cryptoIdAttrKey); + } + } + + results.mUV = isect.getSt(); + if (cryptoUVAttrIdx >= 0) { + moonray::shading::TypedAttributeKey cryptoUVAttrKey(cryptoUVAttrIdx); + if (isect.isProvided(cryptoUVAttrKey)) { + results.mUV = isect.getAttribute(cryptoUVAttrKey); + } + } + + results.mP0 = scene_rdl2::math::zero; + if (isect.isProvided(shading::StandardAttributes::sP0)) { + results.mP0 = scene_rdl2::math::transformPoint(isect.getGeometryObject()->getSceneClass().getSceneContext()->getRender2World()->inverse(), + isect.getAttribute(shading::StandardAttributes::sP0)); + } + + shading::State sstate(&isect); + sstate.getRefP(results.mRefP); + sstate.getRefN(results.mRefN); +} + + +void CryptomatteBuffer::addSampleScalar(unsigned x, unsigned y, float weight, + const CryptomatteResults &cryptomatteResults, + const scene_rdl2::math::Color &beauty, unsigned presenceDepth, int cryptoType) { PixelEntry &pixelEntry = mPixelEntries[cryptoType][y * mWidth + x]; + const float sampleId = cryptomatteResults.mId; + const scene_rdl2::math::Vec3f &position = cryptomatteResults.mPosition; + const scene_rdl2::math::Vec3f &p0 = cryptomatteResults.mP0; + const scene_rdl2::math::Vec3f &normal = cryptomatteResults.mNormal; + const scene_rdl2::math::Vec3f &refP = cryptomatteResults.mRefP; + const scene_rdl2::math::Vec3f &refN = cryptomatteResults.mRefN; + const scene_rdl2::math::Vec2f &uv = cryptomatteResults.mUV; + const scene_rdl2::math::Color4 beauty4 = scene_rdl2::math::Color4(beauty); + // Iterate over fragments stored at current pixel and see if we can merge the sample in to any of them for (Fragment &fragment : pixelEntry.mFragments) { // if multi presence is on, we treat each presence bounce as a separate cryptomatte fragment @@ -71,19 +113,19 @@ void CryptomatteBuffer::addSampleScalar(unsigned x, unsigned y, float sampleId, if (fragMatches) { fragment.mCoverage += weight; fragment.mPosition += position; - fragment.mP0 += p0; - fragment.mNormal += normal; - fragment.mBeauty += beauty; - fragment.mRefP += refP; - fragment.mRefN += refN; - fragment.mUV += uv; + fragment.mP0 += p0; + fragment.mNormal += normal; + fragment.mBeauty += beauty4; + fragment.mRefP += refP; + fragment.mRefN += refN; + fragment.mUV += uv; fragment.mNumSamples++; return; } } // No match, so add a new fragment. - pixelEntry.mFragments.push_back(Fragment(sampleId, weight, position, p0, normal, beauty, + pixelEntry.mFragments.push_back(Fragment(sampleId, weight, position, p0, normal, beauty4, refP, refN, uv, presenceDepth)); } diff --git a/lib/rendering/pbr/core/Cryptomatte.h b/lib/rendering/pbr/core/Cryptomatte.h index 6e702a5..2fa6c14 100644 --- a/lib/rendering/pbr/core/Cryptomatte.h +++ b/lib/rendering/pbr/core/Cryptomatte.h @@ -8,11 +8,15 @@ #include #include +#include #include #include #include namespace moonray { + +namespace shading { class Intersection; } + namespace pbr { /* @@ -62,6 +66,35 @@ enum CryptomatteType { NUM_CRYPTOMATTE_TYPES }; +enum CryptomatteFlags { + CRYPTOMATTE_FLAG_REGULAR = (1 << CRYPTOMATTE_TYPE_REGULAR), + CRYPTOMATTE_FLAG_REFLECTED = (1 << CRYPTOMATTE_TYPE_REFLECTED), + CRYPTOMATTE_FLAG_REFRACTED = (1 << CRYPTOMATTE_TYPE_REFRACTED), + + CRYPTOMATTE_FLAGS_ALL = (CRYPTOMATTE_FLAG_REGULAR | CRYPTOMATTE_FLAG_REFLECTED | CRYPTOMATTE_FLAG_REFRACTED) +}; + +struct CryptomatteResults +{ + // intersection results + bool mHit; + float mId; + scene_rdl2::math::Vec3f mPosition; + scene_rdl2::math::Vec3f mP0; + scene_rdl2::math::Vec3f mNormal; + scene_rdl2::math::Vec3f mRefP; + scene_rdl2::math::Vec3f mRefN; + scene_rdl2::math::Vec2f mUV; + + void init() { + memset(this, 0, sizeof(CryptomatteResults)); + } +}; + + +void computeCryptomatteResults(CryptomatteResults &results, int cryptoIdAttrIdx, int cryptoUVAttrIdx, + moonray::shading::Intersection &isect); + class CryptomatteBuffer { @@ -129,14 +162,9 @@ class CryptomatteBuffer // ----------------------------------------------------------------------------------------------------------------- - void addSampleScalar(unsigned x, unsigned y, float id, float weight, - const scene_rdl2::math::Vec3f& position, - const scene_rdl2::math::Vec3f& p0, - const scene_rdl2::math::Vec3f& normal, - const scene_rdl2::math::Color4& beauty, - const scene_rdl2::math::Vec3f refP, - const scene_rdl2::math::Vec3f refN, - const scene_rdl2::math::Vec2f uv, + void addSampleScalar(unsigned x, unsigned y, float weight, + const CryptomatteResults &cryptomatteResults, + const scene_rdl2::math::Color &beauty, unsigned presenceDepth, int cryptoType); diff --git a/lib/rendering/pbr/core/RayState.hh b/lib/rendering/pbr/core/RayState.hh index 0d396e3..2e41df1 100644 --- a/lib/rendering/pbr/core/RayState.hh +++ b/lib/rendering/pbr/core/RayState.hh @@ -20,6 +20,8 @@ HVD_MEMBER(float, mSampleClampingValue); \ HVD_MEMBER(float, mPrimaryRayDiffScale); \ HVD_MEMBER(float, mTextureDiffScale); \ + HVD_PTR(void *, mCryptomatteBuffer); \ + HVD_PTR(void *, mCryptomatteResultsArray); \ HVD_PTR(DeferredNode *, mDeferredNodesHead); \ HVD_PTR(DeferredNode **, mDeferredNodesTailPtr); \ HVD_MEMBER(int, mNumDeferredNodes); \ @@ -36,6 +38,8 @@ HVD_VALIDATE(Subpixel, mSampleClampingValue); \ HVD_VALIDATE(Subpixel, mPrimaryRayDiffScale); \ HVD_VALIDATE(Subpixel, mTextureDiffScale); \ + HVD_VALIDATE(Subpixel, mCryptomatteBuffer); \ + HVD_VALIDATE(Subpixel, mCryptomatteResultsArray); \ HVD_VALIDATE(Subpixel, mDeferredNodesHead); \ HVD_VALIDATE(Subpixel, mDeferredNodesTailPtr); \ HVD_VALIDATE(Subpixel, mNumDeferredNodes); \ @@ -116,9 +120,9 @@ //---------------------------------------------------------------------------- #if CACHE_LINE_SIZE == 128 -#define RAY_STATE_MEMBERS_PAD 92 +#define RAY_STATE_MEMBERS_PAD 76 #else -#define RAY_STATE_MEMBERS_PAD 44 +#define RAY_STATE_MEMBERS_PAD 28 #endif #define RAY_STATE_MEMBERS \ diff --git a/lib/rendering/pbr/integrator/PathIntegrator.cc b/lib/rendering/pbr/integrator/PathIntegrator.cc index 3ca9885..227c68e 100644 --- a/lib/rendering/pbr/integrator/PathIntegrator.cc +++ b/lib/rendering/pbr/integrator/PathIntegrator.cc @@ -225,6 +225,8 @@ PathIntegrator::update(const FrameState &fs, const PathIntegratorParams& params) mDeepIDAttrIdxs.push_back(deepIDAttrKey.getIndex()); } + mCryptoIdAttrIdx = mDeepIDAttrIdxs.empty() ? -1 : mDeepIDAttrIdxs[0]; + scene_rdl2::rdl2::String cryptoUVAttributeName = vars.get(scene_rdl2::rdl2::SceneVariables::sCryptoUVAttributeName); if (!cryptoUVAttributeName.empty()) { shading::TypedAttributeKey cryptoUVAttrKey(cryptoUVAttributeName); @@ -344,71 +346,18 @@ shadeMaterial(mcrt_common::ThreadLocalState *tls, const scene_rdl2::rdl2::Materi } } -// Auxilliary function to add a node in the ray tree. The members of the node will change frequently while development -// of disintegrator work is ongoing, but the basic idea is to record enough information at each hit point that any -// rendering computation that needs to be performed for that ray bounce can be deferred to a later pass and still have -// access to all the information it needs. - -void -addNode(moonray::pbr::TLState *pbrTls, Subpixel &sp, const PathVertex &pv, const shading::Intersection &isect, - const shading::Bsdf &bsdf, const shading::BsdfSlice &slice, const shading::BsdfLobe *parentLobe, - int sequenceID, float rayEpsilon, float rayDirFootprint) -{ - // Allocate new node. We use the subpixelArena so that the nodes will persist for the lifespan of the Subpixel - scene_rdl2::alloc::Arena *subpixelArena = pbrTls->mSubpixelArena; - DeferredNode *node = subpixelArena->alloc(); - - // The node needs to record the bsdf, but instead of copying the whole bsdf struct, which is large, we copy - // just the lobe pointers since on average a bsdf has considerably fewer than the max number supported (16). - const shading::BsdfLobe **lobePtrs = nullptr; - const int lobeCount = bsdf.getLobeCount(); - MNRY_ASSERT(lobeCount > 0); // Zero lobeCount would cause early-out before we ever got here - lobePtrs = subpixelArena->allocArray(lobeCount); - for (int i = 0; i < lobeCount; i++) { - lobePtrs[i] = bsdf.getLobe(i); - } - - // Set node members - node->mThroughput = pv.pathThroughput; - node->mNonMirrorDepth = pv.nonMirrorDepth; - node->mParentLobe = parentLobe; - node->mLobePtrs = lobePtrs; - node->mLobeCount = lobeCount; - node->mBsdfIsSpherical = bsdf.getIsSpherical(); - node->mSlice = slice; - node->mRayEpsilon = rayEpsilon; - node->mSequenceID = sequenceID; - node->mIntersection = isect; - node->mRayDirFootprint = rayDirFootprint; - node->mAlbedo = bsdf.albedo(slice); - - // Link new node into list - node->mNext = nullptr; - *sp.mDeferredNodesTailPtr = node; - sp.mDeferredNodesTailPtr = &node->mNext; - sp.mNumDeferredNodes++; -} // BsdfLobe is passed in so that we can track the lobe type which generated // the ray for ray debugging purposes. Other than that, it's not needed. -PathIntegrator::IndirectRadianceType -PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, +scene_rdl2::math::Color +PathIntegrator::computeRadianceRecurse0(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, Subpixel &sp, const PathVertex &prevPv, const shading::BsdfLobe *lobe, - scene_rdl2::math::Color &radiance, float &transparency, VolumeTransmittance& vt, + float &transparency, VolumeTransmittance& vt, unsigned &sequenceID, float *aovs, float *depth, - DeepParams* deepParams, CryptomatteBuffer *cryptomatteBuffer, - CryptomatteParams *reflectedCryptomatteParamsPtr, - CryptomatteParams *refractedCryptomatteParamsPtr, - bool ignoreVolumes, bool &hitVolume, + DeepParams* deepParams, uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets) const { - CHECK_CANCELLATION(pbrTls, return NONE); - - // TODO: proper depth control when recursing here from computeRadianceVolume(). - // Here we use a temporary mechanism for limiting it to a single level of recursion, - // i.e. we pass in 'true' to hitVolume (which previously wasn't used to pass - // anything in - only out). - bool wasFromAVolume = hitVolume; + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); // Turn off profiling integrator profiling for the first part of this // function. It's turned back on later. @@ -416,33 +365,25 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif const FrameState &fs = *pbrTls->mFs; const Scene *scene = MNRY_VERIFY(pbrTls->mFs->mScene); - Statistics &stats = pbrTls->mStatistics; - const AovSchema &aovSchema = *fs.mAovSchema; - - scene_rdl2::alloc::Arena *arena = pbrTls->mArena; - SCOPED_MEM(arena); - - radiance = scene_rdl2::math::sBlack; + const LightAovs &lightAovs = *fs.mLightAovs; + const MaterialAovs &materialAovs = *fs.mMaterialAovs; + scene_rdl2::math::Color radiance = scene_rdl2::math::sBlack; transparency = 0.0f; - vt.reset(); - scene_rdl2::math::Color ssAov = scene_rdl2::math::sBlack; - IndirectRadianceType indirectRadianceType = NONE; + int lobeType = (lobe == nullptr) ? 0 : lobe->getType(); + //--------------------------------------------------------------------- // Trace continuation ray - - // Find next vertex of the path. The intersectRay call doesn't intersect - // with any lights, only geometry. - // TODO: scale differentials if we want to use for geometry LOD shading::Intersection isect; - - int lobeType = (lobe == nullptr) ? 0 : lobe->getType(); bool hitGeom = scene->intersectRay(pbrTls->mTopLevelTls, ray, isect, lobeType); + + //--------------------------------------------------------------------- + // Get deep ids if (hitGeom && deepParams) { - deepParams->mHitDeep = true; + deepParams->mHitGeom = true; for (size_t i = 0; i < mDeepIDAttrIdxs.size(); i++) { shading::TypedAttributeKey deepIDAttrKey(mDeepIDAttrIdxs[i]); if (isect.isProvided(deepIDAttrKey)) { @@ -453,131 +394,82 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif } } + //--------------------------------------------------------------------- // Record ray for the path visualizer if (fs.mSimulationMode) { - if (ray.getDepth() == 0) { - fs.mScene->recordCameraRay(ray, sp.mPixel); - } else { - if (hitGeom) { - fs.mScene->recordIndirectRay(ray, sp.mPixel, lobeType); - } - } - } - - // Prevent aliasing in the visibility aov by accounting for - // primary rays that don't hit anything - if (ray.getDepth() == 0 && !hitGeom) { - - // If we're on the edge of the geometry, some rays should count as "hits", some as "misses". Here, - // we're adding light_sample_count * lights number of "misses" to the visibility aov to account for - // the light samples that couldn't be taken because the primary ray doesn't hit anything. - // This improves aliasing on the edges. - if (aovs) { - const LightAovs &lightAovs = *fs.mLightAovs; - const AovSchema &aovSchema = *fs.mAovSchema; - // predict the number of light samples that would have been taken if the ray hit geom - int totalLightSamples = mLightSamples * scene->getLightCount(); - - // Doesn't matter what the lpe is -- if there are subpixels that hit a surface that isn't included - // in the lpe, this would be black anyway. If there are subpixels that DO hit a surface that is - // included in the lpe, this addition prevents aliasing. - aovAccumVisibilityAttempts(pbrTls, aovSchema, lightAovs, totalLightSamples, aovs); - } + fs.mScene->recordCameraRay(ray, sp.mPixel); } + //--------------------------------------------------------------------- + // Set up next vertex on path PathVertex pv(prevPv); //-------------------------------------------------------------------- // Volumes - hitVolume = false; + bool hitVolume = false; - // computeRadianceVolume() increases the pv.volumeDepth. We want the presence - // continuation ray's volume depth to be unchanged, so we restore it. - // Not doing this can cause problems with presence objects inside volumes - // if the max volume depth is low (default = 1). They will appear to 'hold out' - // the volume behind them as the max volume depth is reached. + // computeRadianceVolume() increases the pv.volumeDepth. We want the presence continuation ray's volume depth + // to be unchanged, so we restore it. Not doing this can cause problems with presence objects inside volumes + // if the max volume depth is low (default = 1). They will appear to 'hold out' the volume behind them as the + // max volume depth is reached. int currentVolumeDepth = pv.volumeDepth; float volumeSurfaceT = scene_rdl2::math::sMaxValue; + // We only ignore volumes in the case where there's a deep buffer but we hit a volume, in which case we're now + // calling cRR0() again to obtain the hard surface results without the volume, to add them to the deep buffer + bool ignoreVolumes = (deepParams && deepParams->mHitVolume); if (!ignoreVolumes) { hitVolume = computeRadianceVolume(pbrTls, ray, sp, pv, lobeType, radiance, sequenceID, vt, aovs, deepParams, nullptr, &volumeSurfaceT); - if (hitVolume) { - indirectRadianceType = IndirectRadianceType(indirectRadianceType | VOLUME); - } + if (deepParams) deepParams->mHitVolume = hitVolume; pv.pathThroughput *= vt.transmittance(); } - if (!hitGeom && aovs) { - // accumuate background aovs - aovAccumBackgroundExtraAovs(pbrTls, fs, pv, aovs); - } - //--------------------------------------------------------------------- - // Code for rendering lights, only executed for primary rays since lights - // appear in deeper passes already. - if ((ray.getDepth() == 0)) { - LightIntersection hitLightIsect; - int numHits = 0; - SequenceIDIntegrator sid(0, sp.mPixel, sp.mSubpixelIndex, - SequenceType::IndexSelection, sequenceID); - IntegratorSample1D lightChoiceSamples(sid); - const Light *hitLight = scene->intersectVisibleLight(ray, - hitGeom ? ray.getEnd() : sInfiniteLightDistance, - lightChoiceSamples, hitLightIsect, numHits); - - if (hitLight != nullptr) { - // Evaluate the radiance on the selected light in camera. - // Note: we multiply the radiance contribution by the number of - // lights hit. This is because we want to compute the sum of all - // contributing lights, but we're stochastically sampling just one. - LightFilterRandomValues lightFilterR = { - scene_rdl2::math::Vec2f(0.f, 0.f), - scene_rdl2::math::Vec3f(0.f, 0.f, 0.f)}; // light filters don't apply to camera rays - scene_rdl2::math::Color lightContribution = pv.pathThroughput * numHits * - hitLight->eval(pbrTls->mTopLevelTls, - ray.getDirection(), ray.getOrigin(), lightFilterR, - ray.getTime(), hitLightIsect, true, nullptr, nullptr, ray.getDirFootprint(), nullptr, nullptr); - radiance += lightContribution; - - checkForNan(radiance, "Camera visible lights", sp, pv, ray, isect); + // Add contribution from light hit by the primary ray + radiance += computeRadianceLightsInCamera(pbrTls, ray, sp, pv, isect, sequenceID, aovs, hitGeom); + + //--------------------------------------------------------------------- + // Early out if no geometry hit + if (!hitGeom) { + if (aovs) { + // Prevent aliasing in the visibility aov by accounting for primary rays that don't hit anything. + // If we're on the edge of the geometry, some rays should count as "hits", some as "misses". Here, + // we're adding light_sample_count * lights number of "misses" to the visibility aov to account for + // the light samples that couldn't be taken because the primary ray doesn't hit anything. + // This improves aliasing on the edges. - // LPE - if (aovs) { - EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); - const LightAovs &lightAovs = *fs.mLightAovs; - // transition - int lpeStateId = pv.lpeStateId; - lpeStateId = lightAovs.lightEventTransition(pbrTls, lpeStateId, hitLight); - // accumulate matching aovs - aovAccumLightAovs(pbrTls, *fs.mAovSchema, *fs.mLightAovs, - lightContribution, nullptr, AovSchema::sLpePrefixNone, lpeStateId, aovs); - } - } - } + // predict the number of light samples that would have been taken if the ray hit geom + int totalLightSamples = mLightSamples * scene->getLightCount(); - if (!hitGeom) { - transparency = reduceTransparency(vt.mTransmittanceAlpha); + // Doesn't matter what the lpe is -- if there are subpixels that hit a surface that isn't included + // in the lpe, this would be black anyway. If there are subpixels that DO hit a surface that is + // included in the lpe, this addition prevents aliasing. + aovAccumVisibilityAttempts(pbrTls, aovSchema, lightAovs, totalLightSamples, aovs); - // Did we hit a volume and do we have volume depth/position AOVs? - if (ray.getDepth() == 0 && hitVolume && volumeSurfaceT < scene_rdl2::math::sMaxValue) { - aovSetStateVarsVolumeOnly(pbrTls, aovSchema, volumeSurfaceT, ray, - *scene, pv.pathPixelWeight, aovs); + // Accumuate background aovs + aovAccumBackgroundExtraAovs(pbrTls, fs, pv, aovs); + + // Did we hit a volumes? + if (hitVolume && volumeSurfaceT < scene_rdl2::math::sMaxValue) { + aovSetStateVarsVolumeOnly(pbrTls, aovSchema, volumeSurfaceT, ray, *scene, pv.pathPixelWeight, aovs); + } } - return indirectRadianceType; + transparency = reduceTransparency(vt.mTransmittanceAlpha); + return radiance; } - indirectRadianceType = IndirectRadianceType(indirectRadianceType | SURFACE); - CHECK_CANCELLATION(pbrTls, return NONE); + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); - // Early return with error color if isect doesn't provide all the - // required attributes shader request + //--------------------------------------------------------------------- + // Early return with error color if isect doesn't provide all the required attributes the shader requested if (!isect.hasAllRequiredAttributes()) { radiance += pv.pathThroughput * fs.mFatalColor; - return indirectRadianceType; + return radiance; } + //--------------------------------------------------------------------- // Finalize Intersection setup. geom::initIntersectionPhase2(isect, pbrTls->mTopLevelTls, @@ -593,8 +485,6 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif float tHit = ray.tfar; //--------------------------------------------------------------------- - // Run the material shader at the ray intersection point to get the Bsdf - // Transfer the ray to its intersection before we run shaders. This is // needed for texture filtering based on ray differentials. // Also scale the final differentials by a user factor. This is left until @@ -622,25 +512,11 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif shading::TLState* shadingTls = pbrTls->mTopLevelTls->mShadingTls.get(); - // Presence handling code for regular rays - // Need to continue the current ray through the partially present geometry. + //--------------------------------------------------------------------- + // Get material's presence and potentially convert it stochastically to 0 or 1 float presence = shading::presence(material, shadingTls, shading::State(&isect)); - - // Check to see if we can use stochastic presence method - if (luminance(pv.pathThroughput) < (1.0f - mPresenceQuality)) { - SequenceIDIntegrator sidPresence( sp.mPixel, - sp.mSubpixelIndex, - SequenceType::Presence, - sequenceID, - pv.presenceDepth ); // note presence depth - IntegratorSample1D prSamples(sidPresence); - float prSample; - prSamples.getSample(&prSample, pv.presenceDepth); - if (prSample < presence) { - presence = 1.f; - } else { - presence = 0.f; - } + if (presence != 1.0f && presence != 0.0f && luminance(pv.pathThroughput) < (1.0f - mPresenceQuality)) { + presence = stochasticPresence(sp, pv, sequenceID, presence); } // Some NPR materials that want to allow for completely arbitrary shading normals @@ -670,154 +546,22 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif scene_rdl2::math::Color earlyTerminatorPathThroughput = pv.pathThroughput; float earlyTerminatorPathPixelWeight = pv.pathPixelWeight; + // Presence handling: This will continue the current ray through the partially present geometry. scene_rdl2::math::Color presenceRadiance = scene_rdl2::math::sBlack; float presenceTransparency = 0.f; if (presence < 1.f - scene_rdl2::math::sEpsilon) { - float totalPresence = (1.0f - prevPv.totalPresence) * presence; - - float rayNear = rayEpsilon; - float rayFar = ray.getOrigTfar() - ray.tfar; - if (totalPresence >= mPresenceThreshold || prevPv.presenceDepth >= mMaxPresenceDepth) { - // The cleanest way to terminate presence traversal is to make it impossible for the - // presence continuation ray to hit any more geometry. This means we assume empty space - // past the last presence intersection, which will set the pixel's alpha to the - // total accumulated presence so far. This is done by setting the ray's near and far - // distances to a large value. - // The other option is to assume a solid material past the last presence intersection. - // We don't want this because that would set the pixel alpha to 1 when we really want - // the alpha to be the total accumulated presence. - // Intuitively, you would just return here but that fails to set the path throughput - // and alpha correctly. There is not a clean way to explicitly set the pixel alpha, - // especially in vector mode where it is not accessible at all from the presence code. - rayNear = scene_rdl2::math::sMaxValue * 0.5f; - rayFar = scene_rdl2::math::sMaxValue; - } - - // The origin and tfar has been moved to the geometry intersection - // point so the new tnear is just rayEpsilon. We also need to shorten the tfar - // appropriately so we don't overshoot the original ray length. - mcrt_common::RayDifferential presenceRay(ray, rayNear, rayFar); - setPriorityList(presenceRay, newPriorityList, newPriorityListCount); - - // The above constructor increments the ray depth. We want to keep the parent ray's - // depth as the presence ray is a continuation of the same parent ray. Also, if the - // ray depth is incremented, lights are not visible through presence as the depth for - // continued camera rays is no longer 0. - presenceRay.setDepth(presenceRay.getDepth() - 1); - - PathVertex newPv = pv; // new path vertex for continued ray - newPv.pathDistance += ray.getEnd(); - newPv.pathPixelWeight *= (1-presence); // weight of continued ray - newPv.aovPathPixelWeight *= (1-presence); // weight of continued ray for aov use - newPv.pathThroughput *= (1-presence); - newPv.presenceDepth++; - newPv.totalPresence = totalPresence; - newPv.volumeDepth = currentVolumeDepth; - - // LPE - // presence is a straight event - if (aovs) { - EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); - const FrameState &fs = *pbrTls->mFs; - const LightAovs &lightAovs = *fs.mLightAovs; - // transition - newPv.lpeStateId = lightAovs.straightEventTransition(pbrTls, pv.lpeStateId); - } - - // We need to weight the continued ray and the regular shading appropriately. - // Scaling the throughput and pixel weight for the path handles this properly. - // Note that we don't need to scale the bsdf lobes of the material if we do this. - pv.pathPixelWeight *= presence; // weight of regular shading - pv.aovPathPixelWeight *= presence; // weight of regular shading for aov use - pv.pathThroughput *= presence; - - // Fire continued ray and add in its radiance - VolumeTransmittance vtPresence; - unsigned presenceSequenceID = sequenceID; - bool presenceHitVolume = false; - computeRadianceRecurse(pbrTls, presenceRay, sp, newPv, lobe, - presenceRadiance, presenceTransparency, vtPresence, - presenceSequenceID, aovs, /* depth = */ nullptr, /* DeepParams = */ nullptr, - cryptomatteBuffer, - /* reflectedCryptoParamsPtr = */ nullptr, - /* refractedCryptoParamsPtr = */ nullptr, - /* ignoreVolumes = */ false, presenceHitVolume, - parentLobeLightSets); - + presenceRadiance = computeRadiancePresence(pbrTls, ray, sp, prevPv, pv, lobe, + transparency, presenceTransparency, vt, sequenceID, + aovs, cryptomatteFlags, parentLobeLightSets, + newPriorityList, newPriorityListCount, + currentVolumeDepth, rayEpsilon, presence); radiance += presenceRadiance; - vt.mTransmittanceE *= vtPresence.mTransmittanceE; - transparency = (1 - presence) * presenceTransparency; - } - - // Set the cryptomatte information - float cryptoId = 0.f; - scene_rdl2::math::Vec2f cryptoUV = isect.getSt(); - if (cryptomatteBuffer || reflectedCryptomatteParamsPtr || refractedCryptomatteParamsPtr) { - if (mDeepIDAttrIdxs.size() != 0) { - // Get the cryptomatte ID if we need it and it's specified - shading::TypedAttributeKey deepIDAttrKey(mDeepIDAttrIdxs[0]); - if (isect.isProvided(deepIDAttrKey)) { - cryptoId = isect.getAttribute(deepIDAttrKey); - } - } - shading::TypedAttributeKey cryptoUVAttrKey(mCryptoUVAttrIdx); - if (isect.isProvided(cryptoUVAttrKey)) { - cryptoUV = isect.getAttribute(cryptoUVAttrKey); - } - } - - // reflected cryptomatte PART A - if (reflectedCryptomatteParamsPtr) { - // reflectedCryptomatteParamsPtr is only non-null if we are on a primary ray path - // that is passing through reflective materials, or is a camera ray. - // See the code in PathIntegrator::addIndirectOrDirectVisibleContributions() that - // sets this pointer if we have passed through a reflective material - // (reflected cryptomatte PART B) - if (!material->getRecordReflectedCryptomatte()) { - // If we have now hit a material that is NOT set to record reflected cryptomatte - // data, we end the reflected cryptomatte path by setting the intersection data. - reflectedCryptomatteParamsPtr->mHit = true; - reflectedCryptomatteParamsPtr->mPosition = isect.getP(); - if (isect.isProvided(shading::StandardAttributes::sP0)) { - reflectedCryptomatteParamsPtr->mP0 = scene_rdl2::math::transformPoint( - isect.getGeometryObject()->getSceneClass().getSceneContext()->getRender2World()->inverse(), - isect.getAttribute(shading::StandardAttributes::sP0)); - } - reflectedCryptomatteParamsPtr->mNormal = isect.getN(); - shading::State sstate(&isect); - sstate.getRefP(reflectedCryptomatteParamsPtr->mRefP); - sstate.getRefN(reflectedCryptomatteParamsPtr->mRefN); - reflectedCryptomatteParamsPtr->mUV = cryptoUV; - reflectedCryptomatteParamsPtr->mId = cryptoId; - } - } - - // refracted cryptomatte PART A - if (refractedCryptomatteParamsPtr) { - // refractedCryptomatteParamsPtr is only non-null if we are on a primary ray path - // that is passing through transmissive materials, or is a camera ray. - // See the code in PathIntegrator::addIndirectOrDirectVisibleContributions() that - // sets this pointer if we have passed through a transmissive material - // (refracted cryptomatte PART B) - if (!material->getRecordRefractedCryptomatte()) { - // If we have now hit a material that is NOT invisible in refracted cryptomatte, - // we end the refracted cryptomatte path by setting the intersection data. - refractedCryptomatteParamsPtr->mHit = true; - refractedCryptomatteParamsPtr->mPosition = isect.getP(); - if (isect.isProvided(shading::StandardAttributes::sP0)) { - refractedCryptomatteParamsPtr->mP0 = scene_rdl2::math::transformPoint( - isect.getGeometryObject()->getSceneClass().getSceneContext()->getRender2World()->inverse(), - isect.getAttribute(shading::StandardAttributes::sP0)); - } - refractedCryptomatteParamsPtr->mNormal = isect.getN(); - shading::State sstate(&isect); - sstate.getRefP(refractedCryptomatteParamsPtr->mRefP); - sstate.getRefN(refractedCryptomatteParamsPtr->mRefN); - refractedCryptomatteParamsPtr->mUV = cryptoUV; - refractedCryptomatteParamsPtr->mId = cryptoId; - } } + //--------------------------------------------------------------------- + // Run the material shader at the ray intersection point to get the Bsdf + scene_rdl2::alloc::Arena *arena = pbrTls->mArena; + SCOPED_MEM(arena); auto bsdf = arena->allocWithCtor(); shadeMaterial(pbrTls->mTopLevelTls, material, isect, bsdf); @@ -831,79 +575,65 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif aovAccumExtraAovs(pbrTls, fs, pv, isect, material, aovs); } - CHECK_CANCELLATION(pbrTls, return NONE); + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); //--------------------------------------------------------------------- // Termination (did the shader request termination of tracing?) if (bsdf->getEarlyTermination()) { - if (ray.getDepth() == 0) { - // We override the previous transparency value when we encounter a cutout. - // If there is presence, we need to combine the transparency encountered - // during presence continuation/traversal above with the current presence - // value. - // This is a bit confusing because the presence is for the *cutout*. - // e.g. if the cutout has a presence of 1, the transparency value should - // be forced to 1 because the cutout is 100% there and completely cuts out. - // If the cutout has a presence of 0, the cutout is 0% there and thus has no - // effect on the transparency. - // Also, the alpha in the render output is 1 - transparency. - // We also multiply by the volume's transmittanceAlpha, but that's independent - // of presence. - transparency = reduceTransparency(vt.mTransmittanceAlpha) * - (presenceTransparency + (1 - presenceTransparency) * presence); - } else { - transparency = reduceTransparency(earlyTerminatorPathThroughput); - } - - // fill out the material aovs + // We override the previous transparency value when we encounter a cutout. + // If there is presence, we need to combine the transparency encountered + // during presence continuation/traversal above with the current presence + // value. + // This is a bit confusing because the presence is for the *cutout*. + // e.g. if the cutout has a presence of 1, the transparency value should + // be forced to 1 because the cutout is 100% there and completely cuts out. + // If the cutout has a presence of 0, the cutout is 0% there and thus has no + // effect on the transparency. + // Also, the alpha in the render output is 1 - transparency. + // We also multiply by the volume's transmittanceAlpha, but that's independent + // of presence. + transparency = reduceTransparency(vt.mTransmittanceAlpha) * + scene_rdl2::math::lerp(presence, 1.0f, presenceTransparency); + + // fill out the aovs if (aovs) { - aovSetMaterialAovs(pbrTls, aovSchema, *fs.mLightAovs, *fs.mMaterialAovs, + aovSetMaterialAovs(pbrTls, aovSchema, lightAovs, materialAovs, isect, ray, *scene, *bsdf, ssAov, nullptr, nullptr, earlyTerminatorPathPixelWeight, pv.lpeStateId, aovs); - } - if (aovs && ray.getDepth() == 0) { aovSetStateVars(pbrTls, aovSchema, isect, volumeSurfaceT, ray, *scene, earlyTerminatorPathPixelWeight, aovs, tHit); aovSetPrimAttrs(pbrTls, aovSchema, material->get().getAovFlags(), isect, earlyTerminatorPathPixelWeight, aovs); } - if (depth && ray.getDepth() == 0) { + if (depth) { *depth = scene->getCamera()->computeZDistance(isect.getP(), tHit, ray.getTime()); } if (deepParams) { // If we have terminated, don't output anything to the deep buffer - deepParams->mHitDeep = false; + deepParams->mHitGeom = false; } - if (reflectedCryptomatteParamsPtr) { - // If we have terminated, don't output anything to the reflected cryptomatte buffer - reflectedCryptomatteParamsPtr->mHit = false; - } - - if (refractedCryptomatteParamsPtr) { - // If we have terminated, don't output anything to the refracted cryptomatte buffer - refractedCryptomatteParamsPtr->mHit = false; - } - - return indirectRadianceType; + return radiance; } + //--------------------------------------------------------------------- if (scene_rdl2::math::isEqual(presence, 0.f)) { // Only the presence continuation ray contributes to the radiance so we can early out here. // We must process cutouts (early termination) before this or the cutout alpha will be incorrect. - return indirectRadianceType; + return radiance; } - // if this is a primary ray, fill out the intersection and primitive attribute aovs - if (aovs && ray.getDepth() == 0) { + //--------------------------------------------------------------------- + // this is a primary ray, so fill out the intersection and primitive attribute aovs + if (aovs) { aovSetStateVars(pbrTls, aovSchema, isect, volumeSurfaceT, ray, *scene, pv.pathPixelWeight, aovs, tHit); aovSetPrimAttrs(pbrTls, aovSchema, material->get().getAovFlags(), isect, pv.pathPixelWeight, aovs); } - if (depth && ray.getDepth() == 0) { + if (depth) { *depth = scene->getCamera()->computeZDistance(isect.getP(), tHit, ray.getTime()); } @@ -915,58 +645,45 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif if (aovs) { if (!isBlack(selfEmission)) { EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); - const LightAovs &lightAovs = *fs.mLightAovs; // transition int lpeStateId = pv.lpeStateId; lpeStateId = lightAovs.emissionEventTransition(pbrTls, lpeStateId, *bsdf); // accumulate matching aovs - aovAccumLightAovs(pbrTls, *fs.mAovSchema, *fs.mLightAovs, selfEmission, + aovAccumLightAovs(pbrTls, aovSchema, lightAovs, selfEmission, nullptr, AovSchema::sLpePrefixNone, lpeStateId, aovs); } } //--------------------------------------------------------------------- - // Early out if we don't have any Bsdf lobes nor Bssrdf, VolumeSubsurface + // Early out if we have neither bsdf lobes nor subsurface scattering if (bsdf->getLobeCount() == 0 && !bsdf->hasSubsurface()) { - if (aovs) { - aovSetMaterialAovs(pbrTls, aovSchema, *fs.mLightAovs, *fs.mMaterialAovs, + aovSetMaterialAovs(pbrTls, aovSchema, lightAovs, materialAovs, isect, ray, *scene, *bsdf, ssAov, nullptr, nullptr, pv.aovPathPixelWeight, pv.lpeStateId, aovs); } - - return indirectRadianceType; + return radiance; } // Starting the integrator accumulator here is roughly equivalent of what // the bundled code does. //--------------------------------------------------------------------- - // Have we reached the maximum number of bounces for each lobe types / overall - // Note: hair lobes are also glossy lobes. So the max depth for hair lobes - // would be max(mMaxGlossyDepth, mMaxHairDepth) - shading::BsdfLobe::Type indirectFlags = shading::BsdfLobe::NONE; - bool doIndirect = mBsdfSamples > 0 && - ray.getDepth() < mMaxDepth && - !wasFromAVolume; - if (doIndirect) { - setFlag(indirectFlags, (pv.diffuseDepth < mMaxDiffuseDepth ? - shading::BsdfLobe::DIFFUSE : shading::BsdfLobe::NONE)); - setFlag(indirectFlags, (pv.glossyDepth < mMaxGlossyDepth ? - shading::BsdfLobe::GLOSSY : shading::BsdfLobe::NONE)); - setFlag(indirectFlags, (pv.mirrorDepth < mMaxMirrorDepth ? - shading::BsdfLobe::MIRROR : shading::BsdfLobe::NONE)); - doIndirect = (indirectFlags != shading::BsdfLobe::NONE) || - (pv.hairDepth < mMaxHairDepth); - // If doIndirect is true due to hairDepth only, then only side type bits - // are set in indirectFlags. - setFlag(indirectFlags, (doIndirect ? - shading::BsdfLobe::ALL_SURFACE_SIDES : shading::BsdfLobe::NONE)); - } - - CHECK_CANCELLATION(pbrTls, return NONE); + // Set flags to control which indirect bounce types we'll recurse into, as + // determined by comparing the various depths to their max allowed values. + // Note: hair lobes are also glossy lobes. So their max depth is max(mMaxGlossyDepth, mMaxHairDepth). + shading::BsdfLobe::Type indirectFlags = shading::BsdfLobe::Type::NONE; + if ((mBsdfSamples > 0) && (mMaxDepth > 0)) { + if (pv.diffuseDepth < mMaxDiffuseDepth) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_DIFFUSE); + if (pv.glossyDepth < mMaxGlossyDepth ) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_GLOSSY); + if (pv.mirrorDepth < mMaxMirrorDepth ) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_MIRROR); + if (pv.hairDepth < mMaxHairDepth ) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_SURFACE_SIDES); + } + bool doIndirect = (indirectFlags != shading::BsdfLobe::Type::NONE); + + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); //--------------------------------------------------------------------- // For bssrdf/VolumeSubsurface or bsdfs which contain both @@ -980,12 +697,13 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif normalPtr = &normal; } + //--------------------------------------------------------------------- // Gather up all lights which can affect the intersection point/normal. LightSet activeLightSet; - bool hasRayTerminatorLights; computeActiveLights(arena, scene, isect, normalPtr, *bsdf, &pv, ray.getTime(), - activeLightSet, hasRayTerminatorLights, parentLobeLightSets); + activeLightSet, parentLobeLightSets); + //--------------------------------------------------------------------- // Setup a slice, which handles selecting the lobe types and setup // evaluations to include the cosine term. // Note: Even though we may not be doing indirect for certain lobe types @@ -996,110 +714,403 @@ PathIntegrator::computeRadianceRecurse(pbr::TLState *pbrTls, mcrt_common::RayDif //--------------------------------------------------------------------- // Estimate subsurface scattering - // Option 1: diffusion profile (bssrdf) approach + if (bsdf->hasSubsurface()) { + radiance += computeRadianceSubsurface(pbrTls, *bsdf, sp, pv, ray, isect, slice, + activeLightSet, doIndirect, rayEpsilon, shadowRayEpsilon, + sequenceID, ssAov, aovs, parentLobeLightSets); + // Early out if no bsdf lobes. + // No need to check for zero lobe count in the non-subsurface case - an early-out already handled that. + if (bsdf->getLobeCount() == 0) { + if (aovs) { + aovSetMaterialAovs(pbrTls, aovSchema, lightAovs, materialAovs, + isect, ray, *scene, *bsdf, ssAov, + nullptr, nullptr, pv.aovPathPixelWeight, pv.lpeStateId, aovs); + } + return radiance; + } + } + + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); + + //--------------------------------------------------------------------- + // Estimate emissive volume region energy contribution + radiance += computeRadianceEmissiveRegionsScalar(pbrTls, sp, pv, ray, isect, + *bsdf, slice, rayEpsilon, sequenceID, aovs); + + //--------------------------------------------------------------------- + // Do bsdf sampling and light sampling + radiance += computeRadianceBsdfMultiSampler(pbrTls, sp, pv, ray, isect, *bsdf, slice, + doIndirect, indirectFlags, newPriorityList, newPriorityListCount, activeLightSet, normalPtr, + rayEpsilon, shadowRayEpsilon, ssAov, sequenceID, aovs, cryptomatteFlags, + parentLobeLightSets); + + // ------------------------------------------------------------------- + // Cryptomatte handling + bool storeRegular = ((cryptomatteFlags & CRYPTOMATTE_FLAG_REGULAR ) != 0); + bool storeReflected = ((cryptomatteFlags & CRYPTOMATTE_FLAG_REFLECTED) != 0) && !material->getRecordReflectedCryptomatte(); + bool storeRefracted = ((cryptomatteFlags & CRYPTOMATTE_FLAG_REFRACTED) != 0) && !material->getRecordRefractedCryptomatte(); + if (storeRegular || storeReflected || storeRefracted) { + CryptomatteResults cryptomatteResults; + computeCryptomatteResults(cryptomatteResults, mCryptoIdAttrIdx, mCryptoUVAttrIdx, isect); + + // Regular cryptomatte + if (storeRegular) { + unsigned px, py; + uint32ToPixelLocation(sp.mPixel, &px, &py); + + // We divide by pathPixelWeight to compute Cryptomatte beauty. This can cause fireflies if + // the value is small, so we clamp at 0.01. + float reciprocalWeight = 1.0f / scene_rdl2::math::max(pv.pathPixelWeight, 0.01f); + scene_rdl2::math::Color beauty = (radiance - presenceRadiance) * reciprocalWeight; + + ((CryptomatteBuffer *)sp.mCryptomatteBuffer)->addSampleScalar(px, py, pv.pathPixelWeight, + cryptomatteResults, + beauty, + pv.presenceDepth, + moonray::pbr::CRYPTOMATTE_TYPE_REGULAR); + } + + // Ref{le|ra}cted cryptomatte PART A (see PART B in PathIntegratorMultiSampler.cc for details) + if (storeReflected) { + ((CryptomatteResults *)sp.mCryptomatteResultsArray)[CRYPTOMATTE_TYPE_REFLECTED] = cryptomatteResults; + } + if (storeRefracted) { + ((CryptomatteResults *)sp.mCryptomatteResultsArray)[CRYPTOMATTE_TYPE_REFRACTED] = cryptomatteResults; + } + } + + float minTransparency = reduceTransparency(vt.mTransmittanceMin); + transparency = scene_rdl2::math::lerp(minTransparency, 1.0f, transparency); + + return radiance; +} + + +scene_rdl2::math::Color +PathIntegrator::computeRadianceRecurse1(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, + Subpixel &sp, const PathVertex &prevPv, const shading::BsdfLobe *lobe, + float &transparency, VolumeTransmittance& vt, + unsigned &sequenceID, float *aovs, + uint32_t cryptomatteFlags, + const Rdl2LightSetList& parentLobeLightSets) const +{ + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); + + // Turn off profiling integrator profiling for the first part of this + // function. It's turned back on later. + MNRY_ASSERT(pbrTls->isIntegratorAccumulatorRunning()); + + const FrameState &fs = *pbrTls->mFs; + const Scene *scene = MNRY_VERIFY(pbrTls->mFs->mScene); + Statistics &stats = pbrTls->mStatistics; + const AovSchema &aovSchema = *fs.mAovSchema; + const LightAovs &lightAovs = *fs.mLightAovs; + const MaterialAovs &materialAovs = *fs.mMaterialAovs; + scene_rdl2::math::Color radiance = scene_rdl2::math::sBlack; + transparency = 0.0f; + vt.reset(); + scene_rdl2::math::Color ssAov = scene_rdl2::math::sBlack; + int lobeType = (lobe == nullptr) ? 0 : lobe->getType(); + + //--------------------------------------------------------------------- + // Trace continuation ray + shading::Intersection isect; + bool hitGeom = scene->intersectRay(pbrTls->mTopLevelTls, ray, isect, lobeType); - if (bsdf->getBssrdfCount() > 0) { - // increment subsurface depth - pv.subsurfaceDepth += 1; + //--------------------------------------------------------------------- + // Record ray for the path visualizer + if (fs.mSimulationMode && hitGeom) { + fs.mScene->recordIndirectRay(ray, sp.mPixel, lobeType); } - for (int bssrdfIdx = 0; bssrdfIdx < bsdf->getBssrdfCount(); ++bssrdfIdx) { - const shading::Bssrdf *bssrdf = bsdf->getBssrdf(bssrdfIdx); - // Stop the accumulator here since the subsurface accumulator will be - // started up inside of computeRadianceSubsurface as needed. - radiance += computeRadianceDiffusionSubsurface(pbrTls, *bsdf, sp, pv, ray, - isect, slice, *bssrdf, activeLightSet, doIndirect, rayEpsilon, shadowRayEpsilon, - sequenceID, ssAov, aovs, parentLobeLightSets); + //-------------------------------------------------------------------- + // Set up next vertex on path. + PathVertex pv(prevPv); + + //-------------------------------------------------------------------- + // Volumes + // computeRadianceVolume() increases the pv.volumeDepth. We want the presence + // continuation ray's volume depth to be unchanged, so we restore it. + // Not doing this can cause problems with presence objects inside volumes + // if the max volume depth is low (default = 1). They will appear to 'hold out' + // the volume behind them as the max volume depth is reached. + int currentVolumeDepth = pv.volumeDepth; + + // only recurse into volumes if we came from a lobe (i.e. not from a volume) + if (lobe) { + float volumeSurfaceT = scene_rdl2::math::sMaxValue; + computeRadianceVolume(pbrTls, ray, sp, pv, lobeType, radiance, sequenceID, vt, + aovs, nullptr, nullptr, &volumeSurfaceT); + pv.pathThroughput *= vt.transmittance(); } - // Option 2: path trace volumetric approach - const shading::VolumeSubsurface *volumeSubsurface = bsdf->getVolumeSubsurface(); - if (volumeSubsurface != nullptr) { - // increment subsurface depth - pv.subsurfaceDepth += 1; - radiance += computeRadiancePathTraceSubsurface(pbrTls, *bsdf, sp, pv, ray, - isect, *volumeSubsurface, activeLightSet, doIndirect, rayEpsilon, shadowRayEpsilon, - sequenceID, ssAov, aovs); + //-------------------------------------------------------------------- + // Early return if no geometry hit + if (!hitGeom) { + if (aovs) { + aovAccumBackgroundExtraAovs(pbrTls, fs, pv, aovs); + } + transparency = reduceTransparency(vt.mTransmittanceAlpha); + return radiance; } - checkForNan(radiance, "Subsurface scattering", sp, pv, ray, isect); + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); + + //-------------------------------------------------------------------- + // Early return with error color if isect doesn't provide all the required attributes the shader requested + if (!isect.hasAllRequiredAttributes()) { + radiance += pv.pathThroughput * fs.mFatalColor; + return radiance; + } + //-------------------------------------------------------------------- + // Finalize Intersection setup. + geom::initIntersectionPhase2(isect, + pbrTls->mTopLevelTls, + pv.mirrorDepth, + pv.glossyDepth, + pv.diffuseDepth, + isSubsurfaceAllowed(pv.subsurfaceDepth), + pv.minRoughness, + -ray.getDirection()); //--------------------------------------------------------------------- - // Early out if we don't have any Bsdf lobes - if (bsdf->getLobeCount() == 0) { + // Transfer the ray to its intersection before we run shaders. This is + // needed for texture filtering based on ray differentials. + // Also scale the final differentials by a user factor. This is left until + // the very end and not baked into the ray differentials since the factor + // will typically be > 1, and would cause the ray differentials to be larger + // than necessary. The mip selector is computed in this call also. + isect.transferAndComputeDerivatives(pbrTls->mTopLevelTls, &ray, + sp.mTextureDiffScale); + + float rayEpsilon = isect.getEpsilonHint(); + if (rayEpsilon <= 0.0f) { + // Compute automatic ray-tracing bias + float pathDistance = pv.pathDistance + ray.getEnd(); + rayEpsilon = sHitEpsilonStart * scene_rdl2::math::max(pathDistance, 1.0f); + } + float shadowRayEpsilon = isect.getShadowEpsilonHint(); + + const scene_rdl2::rdl2::Material* material = isect.getMaterial()->asA(); + MNRY_ASSERT(material != NULL); + + // perform material substitution if needed + scene_rdl2::rdl2::RaySwitchContext switchCtx; + switchCtx.mRayType = lobeTypeToRayType(pv.lobeType); + material = material->raySwitch(switchCtx); + + shading::TLState* shadingTls = pbrTls->mTopLevelTls->mShadingTls.get(); + + //--------------------------------------------------------------------- + // Get material's presence and potentially convert it stochastically to 0 or 1 + float presence = shading::presence(material, shadingTls, shading::State(&isect)); + if (presence != 1.0f && presence != 0.0f && luminance(pv.pathThroughput) < (1.0f - mPresenceQuality)) { + presence = stochasticPresence(sp, pv, sequenceID, presence); + } + + // Some NPR materials that want to allow for completely arbitrary shading normals + // can request that the integrator does not perform any light culling based on the + // normal. In those cases, we also want to prevent our call to adaptNormal() in the + // Intersection when the material evaluates its normal map bindings. + if (shading::preventLightCulling(material, shading::State(&isect))) { + isect.setUseAdaptNormal(false); + } + + // Nested dielectric handling. Uses presence code for skipping false intersections. + // See "Simple Nested Dielectrics in Ray Traced Images". + // This also enables automatic removal of self-overlapping geometry that's assigned to the + // same material. + int materialPriority = material->priority(); + const scene_rdl2::rdl2::Camera* camera = scene->getCamera()->getRdlCamera(); + + const scene_rdl2::rdl2::Material* newPriorityList[4]; + int newPriorityListCount[4]; + float mediumIor = updateMaterialPriorities(ray, scene, camera, shadingTls, isect, material, &presence, + materialPriority, newPriorityList, newPriorityListCount, + pv.presenceDepth); + isect.setMediumIor(mediumIor); + + // If we terminate early, we do not want the contribution of the presence + // value in the pathThroughput or the pathPixelWeight. + scene_rdl2::math::Color earlyTerminatorPathThroughput = pv.pathThroughput; + float earlyTerminatorPathPixelWeight = pv.pathPixelWeight; + + // Presence handling: This will continue the current ray through the partially present geometry. + if (presence < 1.f - scene_rdl2::math::sEpsilon) { + float presenceTransparency; + radiance += computeRadiancePresence(pbrTls, ray, sp, prevPv, pv, lobe, + transparency, presenceTransparency, vt, sequenceID, + aovs, cryptomatteFlags, parentLobeLightSets, + newPriorityList, newPriorityListCount, + currentVolumeDepth, rayEpsilon, presence); + } + + //--------------------------------------------------------------------- + // Run the material shader at the ray intersection point to get the Bsdf + scene_rdl2::alloc::Arena *arena = pbrTls->mArena; + SCOPED_MEM(arena); + auto bsdf = arena->allocWithCtor(); + shadeMaterial(pbrTls->mTopLevelTls, material, isect, bsdf); + if (fs.mPrintBsdf) { + bsdf->show(material->getSceneClass().getName(), material->getName(), std::cout); + } + stats.incCounter(STATS_SHADER_EVALS); + + // Evaluate any extra aovs on this material + if (aovs) { + aovAccumExtraAovs(pbrTls, fs, pv, isect, material, aovs); + } + + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); + + //--------------------------------------------------------------------- + // Early out if the shader requested termination of tracing + if (bsdf->getEarlyTermination()) { if (aovs) { - aovSetMaterialAovs(pbrTls, aovSchema, *fs.mLightAovs, *fs.mMaterialAovs, + aovSetMaterialAovs(pbrTls, aovSchema, lightAovs, materialAovs, + isect, ray, *scene, *bsdf, ssAov, + nullptr, nullptr, earlyTerminatorPathPixelWeight, pv.lpeStateId, aovs); + } + transparency = reduceTransparency(earlyTerminatorPathThroughput); + return radiance; + } + + //--------------------------------------------------------------------- + // Cryptomatte handling + bool storeReflected = ((cryptomatteFlags & CRYPTOMATTE_FLAG_REFLECTED) != 0) && !material->getRecordReflectedCryptomatte(); + bool storeRefracted = ((cryptomatteFlags & CRYPTOMATTE_FLAG_REFRACTED) != 0) && !material->getRecordRefractedCryptomatte(); + if (storeReflected || storeRefracted) { + CryptomatteResults cryptomatteResults; + computeCryptomatteResults(cryptomatteResults, mCryptoIdAttrIdx, mCryptoUVAttrIdx, isect); + + // Ref{le|ra}cted cryptomatte PART A (see PART B in PathIntegratorMultiSampler.cc for details) + if (storeReflected) { + ((CryptomatteResults *)sp.mCryptomatteResultsArray)[CRYPTOMATTE_TYPE_REFLECTED] = cryptomatteResults; + } + if (storeRefracted) { + ((CryptomatteResults *)sp.mCryptomatteResultsArray)[CRYPTOMATTE_TYPE_REFRACTED] = cryptomatteResults; + } + } + + //--------------------------------------------------------------------- + if (scene_rdl2::math::isEqual(presence, 0.f)) { + // Only the presence continuation ray contributes to the radiance so we can early out here. + // We must process cutouts (early termination) before this or the cutout alpha will be incorrect. + return radiance; + } + + //--------------------------------------------------------------------- + // Self-emission + scene_rdl2::math::Color selfEmission = pv.pathThroughput * bsdf->getSelfEmission(); + radiance += selfEmission; + // LPE + if (aovs && !isBlack(selfEmission)) { + EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); + + // transition + int lpeStateId = pv.lpeStateId; + lpeStateId = lightAovs.emissionEventTransition(pbrTls, lpeStateId, *bsdf); + + // accumulate matching aovs + aovAccumLightAovs(pbrTls, aovSchema, lightAovs, selfEmission, + nullptr, AovSchema::sLpePrefixNone, lpeStateId, aovs); + } + + //--------------------------------------------------------------------- + // Early out if we have neither bsdf lobes nor subsurface scattering + if (bsdf->getLobeCount() == 0 && !bsdf->hasSubsurface()) { + if (aovs) { + aovSetMaterialAovs(pbrTls, aovSchema, lightAovs, materialAovs, isect, ray, *scene, *bsdf, ssAov, nullptr, nullptr, pv.aovPathPixelWeight, pv.lpeStateId, aovs); } + return radiance; + } + + // Starting the integrator accumulator here is roughly equivalent of what + // the bundled code does. + + //--------------------------------------------------------------------- + // Set flags to control which indirect bounce types we'll recurse into, as + // determined by comparing the various depths to their max allowed values. + // Note: hair lobes are also glossy lobes. So their max depth is max(mMaxGlossyDepth, mMaxHairDepth). + // Also note we check there's a lobe, to limit indirect lighting on volumes to a single bounce + shading::BsdfLobe::Type indirectFlags = shading::BsdfLobe::Type::NONE; + if ((mBsdfSamples > 0) && (ray.getDepth() < mMaxDepth) && lobe) { + if (pv.diffuseDepth < mMaxDiffuseDepth) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_DIFFUSE); + if (pv.glossyDepth < mMaxGlossyDepth ) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_GLOSSY); + if (pv.mirrorDepth < mMaxMirrorDepth ) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_MIRROR); + if (pv.hairDepth < mMaxHairDepth ) setFlag(indirectFlags, shading::BsdfLobe::Type::ALL_SURFACE_SIDES); + } + bool doIndirect = (indirectFlags != shading::BsdfLobe::Type::NONE); + + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); + + //--------------------------------------------------------------------- + // For bssrdf/VolumeSubsurface or bsdfs which contain both + // reflection and transmission lobes or is spherical, + // a single normal can't be used for culling so skip normal culling. + scene_rdl2::math::Vec3f normal(scene_rdl2::math::zero); + scene_rdl2::math::Vec3f *normalPtr = nullptr; + if (!bsdf->hasSubsurface() && !bsdf->getIsSpherical() && + ((bsdf->getType() & shading::BsdfLobe::ALL_SURFACE_SIDES) != shading::BsdfLobe::ALL_SURFACE_SIDES)) { + normal = (bsdf->getType() & shading::BsdfLobe::REFLECTION) ? isect.getNg() : -isect.getNg(); + normalPtr = &normal; + } + + //--------------------------------------------------------------------- + // Gather up all lights which can affect the intersection point/normal. + LightSet activeLightSet; + computeActiveLights(arena, scene, isect, normalPtr, *bsdf, &pv, ray.getTime(), + activeLightSet, parentLobeLightSets); + + //--------------------------------------------------------------------- + // Setup a slice, which handles selecting the lobe types and setup + // evaluations to include the cosine term. + // Note: Even though we may not be doing indirect for certain lobe types + // (according to indirectFlags), we still want to draw samples according + // to all lobes for direct lighting MIS. + shading::BsdfSlice slice(isect.getNg(), -ray.getDirection(), true, + isect.isEntering(), fs.mShadowTerminatorFix, shading::BsdfLobe::ALL); - return indirectRadianceType; + //--------------------------------------------------------------------- + // Estimate subsurface scattering + if (bsdf->hasSubsurface()) { + radiance += computeRadianceSubsurface(pbrTls, *bsdf, sp, pv, ray, isect, slice, + activeLightSet, doIndirect, rayEpsilon, shadowRayEpsilon, + sequenceID, ssAov, aovs, parentLobeLightSets); + // Early out if no bsdf lobes. + // No need to check for zero lobe count in the non-subsurface case - an early-out already handled that. + if (bsdf->getLobeCount() == 0) { + if (aovs) { + aovSetMaterialAovs(pbrTls, aovSchema, lightAovs, materialAovs, + isect, ray, *scene, *bsdf, ssAov, + nullptr, nullptr, pv.aovPathPixelWeight, pv.lpeStateId, aovs); + } + return radiance; + } } - CHECK_CANCELLATION(pbrTls, return NONE ); + CHECK_CANCELLATION(pbrTls, return scene_rdl2::math::sBlack); //--------------------------------------------------------------------- // Estimate emissive volume region energy contribution radiance += computeRadianceEmissiveRegionsScalar(pbrTls, sp, pv, ray, isect, *bsdf, slice, rayEpsilon, sequenceID, aovs); - // At this point we have all the information about the current hard surface bounce, so we add a node for it - // to capture the data we'll need for deferred rendering. Note that immediately below there's a call to - // computeRadianceBsdfMultiSampler(), which recursively adds the nodes for any subtrees. - // Vector and xpu modes don't make use of the nodes, but they can call here under some circumstances, so we - // supress node addition for all but scalar mode. - if (fs.mExecutionMode == mcrt_common::ExecutionMode::SCALAR) { - addNode(pbrTls, sp, pv, isect, *bsdf, slice, lobe, sequenceID, rayEpsilon, ray.getDirFootprint()); - } - //--------------------------------------------------------------------- - // Setup bsdf and light samples + // Do bsdf sampling and light sampling radiance += computeRadianceBsdfMultiSampler(pbrTls, sp, pv, ray, isect, *bsdf, slice, doIndirect, indirectFlags, newPriorityList, newPriorityListCount, activeLightSet, normalPtr, - rayEpsilon, shadowRayEpsilon, ssAov, sequenceID, aovs, reflectedCryptomatteParamsPtr, refractedCryptomatteParamsPtr, + rayEpsilon, shadowRayEpsilon, ssAov, sequenceID, aovs, cryptomatteFlags, parentLobeLightSets); - // ------------------------------------------------------------------- - if (cryptomatteBuffer) { - scene_rdl2::math::Vec3f P0(0.0f); - if (isect.isProvided(shading::StandardAttributes::sP0)) { - P0 = scene_rdl2::math::transformPoint( - isect.getGeometryObject()->getSceneClass().getSceneContext()->getRender2World()->inverse(), - isect.getAttribute(shading::StandardAttributes::sP0)); - } - - scene_rdl2::math::Vec3f refP(0.0f), refN(0.0f); - shading::State sstate(&isect); - sstate.getRefP(refP); - sstate.getRefN(refN); - - unsigned px, py; - uint32ToPixelLocation(sp.mPixel, &px, &py); - - // We divide by pathPixelWeight to compute Cryptomatte beauty. This can cause fireflies if - // the value is small, so we clamp at 0.01. - float reciprocalWeight = 1.0f / scene_rdl2::math::max(pv.pathPixelWeight, 0.01f); - scene_rdl2::math::Color beauty = (radiance - presenceRadiance) * reciprocalWeight; - - cryptomatteBuffer->addSampleScalar(px, py, cryptoId, - pv.pathPixelWeight, - isect.getP(), - P0, - isect.getN(), - scene_rdl2::math::Color4(beauty), - refP, - refN, - cryptoUV, - pv.presenceDepth, - moonray::pbr::CRYPTOMATTE_TYPE_REGULAR); - } - float minTransparency = reduceTransparency(vt.mTransmittanceMin); - transparency = transparency + (1 - transparency) * minTransparency; + transparency = scene_rdl2::math::lerp(minTransparency, 1.0f, transparency); - return indirectRadianceType; + return radiance; } @@ -1134,9 +1145,6 @@ PathIntegrator::initPrimaryRay(pbr::TLState *pbrTls, sp.mSubpixelX = sample.pixelX; sp.mSubpixelY = sample.pixelY; sp.mPixelSamples = pixelSamples; - sp.mDeferredNodesHead = nullptr; - sp.mDeferredNodesTailPtr = &sp.mDeferredNodesHead; - sp.mNumDeferredNodes = 0; // Make sure our clamping is look-invariant with changes in bsdf sample counts sp.mSampleClampingValue = mSampleClampingValue / mBsdfSamples; @@ -1279,6 +1287,9 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, const FrameState &fs = *pbrTls->mFs; + const AovSchema &aovSchema = *fs.mAovSchema; + const LightAovs &lightAovs = *fs.mLightAovs; + // Create primary ray. const Scene *scene = MNRY_VERIFY(fs.mScene); const bool validRay = initPrimaryRay(pbrTls, scene->getCamera(), pixelX, pixelY, subpixelIndex, @@ -1293,8 +1304,6 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, if (aovs) { EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); - const LightAovs &lightAovs = *fs.mLightAovs; - // transition pv.lpeStateId = lightAovs.cameraEventTransition(pbrTls); } @@ -1316,15 +1325,19 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, // the volume attenuation along this ray to the first hit (or infinity) VolumeTransmittance vt; + sp.mCryptomatteBuffer = (void *)cryptomatteBuffer; + CryptomatteResults cryptomatteResultsArray[NUM_CRYPTOMATTE_TYPES]; + sp.mCryptomatteResultsArray = (void *)cryptomatteResultsArray; + // We maintain an independent set of cryptomatte data for reflections - CryptomatteParams reflectedCryptomatteParams; - reflectedCryptomatteParams.init(cryptomatteBuffer); - CryptomatteParams *reflectedCryptomatteParamsPtr = cryptomatteBuffer ? &reflectedCryptomatteParams : nullptr; + CryptomatteResults &reflectedCryptomatteResults = cryptomatteResultsArray[CRYPTOMATTE_TYPE_REFLECTED]; + reflectedCryptomatteResults.init(); // We maintain another independent set of cryptomatte data for refractions/transmission - CryptomatteParams refractedCryptomatteParams; - refractedCryptomatteParams.init(cryptomatteBuffer); - CryptomatteParams *refractedCryptomatteParamsPtr = cryptomatteBuffer ? &refractedCryptomatteParams : nullptr; + CryptomatteResults &refractedCryptomatteResults = cryptomatteResultsArray[CRYPTOMATTE_TYPE_REFRACTED]; + refractedCryptomatteResults.init(); + + uint32_t cryptomatteFlags = cryptomatteBuffer ? CRYPTOMATTE_FLAGS_ALL : 0; if (deepBuffer) { @@ -1337,7 +1350,8 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, deepParams.mSampleX = sample.pixelX; deepParams.mSampleY = sample.pixelY; deepParams.mPixelSamples = pixelSamples; - deepParams.mHitDeep = false; + deepParams.mHitGeom = false; + deepParams.mHitVolume = false; deepParams.mVolumeAovs = deepVolumeAovs; // This is the only place we pass a non-null deepParams as we only want to @@ -1345,14 +1359,12 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, // First render normally, capturing the deep volume segments (if any) and // the flat radiance + aovs. Check if we hit any volumes with the primary ray. - bool hitVolume = false; - scene_rdl2::math::Color deepRadiance; float deepTransparency; - computeRadianceRecurse(pbrTls, ray, sp, pv, /* lobe = */ nullptr, deepRadiance, - deepTransparency, vt, sequenceID, aovs, depth, - &deepParams, cryptomatteBuffer, - reflectedCryptomatteParamsPtr, refractedCryptomatteParamsPtr, - /* ignoreVolumes = */ false, hitVolume, Rdl2LightSetList()); + scene_rdl2::math::Color deepRadiance = computeRadianceRecurse0(pbrTls, ray, sp, pv, /* lobe = */ nullptr, + deepTransparency, vt, + sequenceID, aovs, depth, + &deepParams, cryptomatteFlags, + Rdl2LightSetList()); float deepAlpha = 1.f - deepTransparency; @@ -1360,7 +1372,7 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, alpha = deepAlpha; pathPixelWeight = pv.pathPixelWeight; - if (hitVolume) { + if (deepParams.mHitVolume) { // We hit a volume. Render again with the volume disabled to get the // correct hard surface radiance without volume attenuation applied. // Note that we can't alter the radiance or aovs computed above. We need @@ -1371,29 +1383,29 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, initPrimaryRay(pbrTls, scene->getCamera(), pixelX, pixelY, subpixelIndex, pixelSamples, sample, hsRay, hsSp, hsPv); + // Copy the cryptomatte addresses for cases where both deep and cryptomatte are used + hsSp.mCryptomatteBuffer = sp.mCryptomatteBuffer; + hsSp.mCryptomatteResultsArray = sp.mCryptomatteResultsArray; + // LPE if (aovs) { EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); - const LightAovs &lightAovs = *fs.mLightAovs; - // transition hsPv.lpeStateId = lightAovs.cameraEventTransition(pbrTls); } - scene_rdl2::math::Color hsRadiance; float hsTransparency; float *hsAovs = aovParams.mDeepAovs; - hitVolume = false; - computeRadianceRecurse(pbrTls, hsRay, hsSp, hsPv, /* lobe = */ nullptr, hsRadiance, - hsTransparency, vt, hsSequenceID, hsAovs, depth, - &deepParams, cryptomatteBuffer, - reflectedCryptomatteParamsPtr, refractedCryptomatteParamsPtr, - /* ignoreVolumes = */ true, hitVolume, Rdl2LightSetList()); + scene_rdl2::math::Color hsRadiance = computeRadianceRecurse0(pbrTls, hsRay, hsSp, hsPv, /* lobe = */ nullptr, + hsTransparency, vt, + hsSequenceID, hsAovs, depth, + &deepParams, cryptomatteFlags, + Rdl2LightSetList()); float hsAlpha = 1.f; - if (deepParams.mHitDeep) { + if (deepParams.mHitGeom) { // put the hard surface deep intersection into the deep buffer scene_rdl2::math::Vec3f hsNormal = hsRay.getNg(); deepBuffer->addSample(pbrTls, deepParams.mPixelX, deepParams.mPixelY, @@ -1405,7 +1417,7 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, } else { // We didn't hit a volume. Fill in the hard surface data with the existing // radiance+aov values. - if (deepParams.mHitDeep) { + if (deepParams.mHitGeom) { // put the hard surface deep intersection into the deep buffer scene_rdl2::math::Vec3f deepNormal = ray.getNg(); deepBuffer->addSample(pbrTls, deepParams.mPixelX, deepParams.mPixelY, @@ -1425,11 +1437,11 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, // not deep render float transparency; - bool hitVolume = false; - computeRadianceRecurse(pbrTls, ray, sp, pv, nullptr, radiance, - transparency, vt, sequenceID, aovs, depth, nullptr, cryptomatteBuffer, - reflectedCryptomatteParamsPtr, refractedCryptomatteParamsPtr, - /* ignoreVolumes = */ false, hitVolume, Rdl2LightSetList()); + radiance = computeRadianceRecurse0(pbrTls, ray, sp, pv, /* lobe = */ nullptr, + transparency, vt, + sequenceID, aovs, depth, + /* deepParams = */ nullptr, cryptomatteFlags, + Rdl2LightSetList()); alpha = 1.f - transparency; pathPixelWeight = pv.pathPixelWeight; @@ -1441,32 +1453,20 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, } } - if (cryptomatteBuffer && reflectedCryptomatteParams.mHit) { - cryptomatteBuffer->addSampleScalar(pixelX, pixelY, reflectedCryptomatteParams.mId, - 1.0f, - reflectedCryptomatteParams.mPosition, - reflectedCryptomatteParams.mP0, - reflectedCryptomatteParams.mNormal, - scene_rdl2::math::Color4(radiance), - reflectedCryptomatteParams.mRefP, - reflectedCryptomatteParams.mRefN, - reflectedCryptomatteParams.mUV, - pv.presenceDepth, - moonray::pbr::CRYPTOMATTE_TYPE_REFLECTED); - } - - if (cryptomatteBuffer && refractedCryptomatteParams.mHit) { - cryptomatteBuffer->addSampleScalar(pixelX, pixelY, refractedCryptomatteParams.mId, - 1.0f, - refractedCryptomatteParams.mPosition, - refractedCryptomatteParams.mP0, - refractedCryptomatteParams.mNormal, - scene_rdl2::math::Color4(radiance), - refractedCryptomatteParams.mRefP, - refractedCryptomatteParams.mRefN, - refractedCryptomatteParams.mUV, - pv.presenceDepth, - moonray::pbr::CRYPTOMATTE_TYPE_REFRACTED); + if (cryptomatteBuffer && reflectedCryptomatteResults.mHit) { + cryptomatteBuffer->addSampleScalar(pixelX, pixelY, 1.0f, + reflectedCryptomatteResults, + radiance, + pv.presenceDepth, + moonray::pbr::CRYPTOMATTE_TYPE_REFLECTED); + } + + if (cryptomatteBuffer && refractedCryptomatteResults.mHit) { + cryptomatteBuffer->addSampleScalar(pixelX, pixelY, 1.0f, + refractedCryptomatteResults, + radiance, + pv.presenceDepth, + moonray::pbr::CRYPTOMATTE_TYPE_REFRACTED); } #ifdef DO_AOV_RADIANCE_CLAMPING @@ -1477,11 +1477,142 @@ PathIntegrator::computeRadiance(pbr::TLState *pbrTls, int pixelX, int pixelY, alpha = std::max(alpha, 0.f); #endif - aovSetBeautyAndAlpha(pbrTls, *fs.mAovSchema, radiance, alpha, pathPixelWeight, aovs); + aovSetBeautyAndAlpha(pbrTls, aovSchema, radiance, alpha, pathPixelWeight, aovs); return radiance; } +scene_rdl2::math::Color +PathIntegrator::computeRadiancePresence(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, + Subpixel &sp, const PathVertex &prevPv, PathVertex &pv, const shading::BsdfLobe *lobe, + float &transparency, float &presenceTransparency, VolumeTransmittance& vt, unsigned sequenceID, float *aovs, + uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets, + const scene_rdl2::rdl2::Material* newPriorityList[4], int newPriorityListCount[4], + int currentVolumeDepth, float rayEpsilon, float presence) const +{ + float totalPresence = (1.0f - prevPv.totalPresence) * presence; + + float rayNear = rayEpsilon; + float rayFar = ray.getOrigTfar() - ray.tfar; + if (totalPresence >= mPresenceThreshold || prevPv.presenceDepth >= mMaxPresenceDepth) { + // The cleanest way to terminate presence traversal is to make it impossible for the + // presence continuation ray to hit any more geometry. This means we assume empty space + // past the last presence intersection, which will set the pixel's alpha to the + // total accumulated presence so far. This is done by setting the ray's near and far + // distances to a large value. + // The other option is to assume a solid material past the last presence intersection. + // We don't want this because that would set the pixel alpha to 1 when we really want + // the alpha to be the total accumulated presence. + // Intuitively, you would just return here but that fails to set the path throughput + // and alpha correctly. There is not a clean way to explicitly set the pixel alpha, + // especially in vector mode where it is not accessible at all from the presence code. + rayNear = scene_rdl2::math::sMaxValue * 0.5f; + rayFar = scene_rdl2::math::sMaxValue; + } + + // The origin and tfar has been moved to the geometry intersection + // point so the new tnear is just rayEpsilon. We also need to shorten the tfar + // appropriately so we don't overshoot the original ray length. + mcrt_common::RayDifferential presenceRay(ray, rayNear, rayFar); + setPriorityList(presenceRay, newPriorityList, newPriorityListCount); + + // The above constructor increments the ray depth. We want to keep the parent ray's + // depth as the presence ray is a continuation of the same parent ray. Also, if the + // ray depth is incremented, lights are not visible through presence as the depth for + // continued camera rays is no longer 0. + presenceRay.setDepth(ray.getDepth()); + + PathVertex newPv = pv; // new path vertex for continued ray + newPv.pathDistance += ray.getEnd(); + newPv.pathPixelWeight *= (1-presence); // weight of continued ray + newPv.aovPathPixelWeight *= (1-presence); // weight of continued ray for aov use + newPv.pathThroughput *= (1-presence); + newPv.presenceDepth++; + newPv.totalPresence = totalPresence; + newPv.volumeDepth = currentVolumeDepth; + + // LPE + // presence is a straight event + if (aovs) { + EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); + // transition + newPv.lpeStateId = pbrTls->mFs->mLightAovs->straightEventTransition(pbrTls, pv.lpeStateId); + } + + // We need to weight the continued ray and the regular shading appropriately. + // Scaling the throughput and pixel weight for the path handles this properly. + // Note that we don't need to scale the bsdf lobes of the material if we do this. + pv.pathPixelWeight *= presence; // weight of regular shading + pv.aovPathPixelWeight *= presence; // weight of regular shading for aov use + pv.pathThroughput *= presence; + + // Fire continued ray and add in its radiance + VolumeTransmittance vtPresence; + scene_rdl2::math::Color presenceRadiance; + if (ray.getDepth() == 0) { + presenceRadiance = computeRadianceRecurse0(pbrTls, presenceRay, sp, newPv, lobe, presenceTransparency, + vtPresence, sequenceID, aovs, /* depth = */ nullptr, /* DeepParams = */ nullptr, + cryptomatteFlags, parentLobeLightSets); + } else { + presenceRadiance = computeRadianceRecurse1(pbrTls, presenceRay, sp, newPv, lobe, presenceTransparency, + vtPresence, sequenceID, aovs, /* cryptomatteFlags = */ 0, parentLobeLightSets); + } + + vt.mTransmittanceE *= vtPresence.mTransmittanceE; + transparency = (1 - presence) * presenceTransparency; + return presenceRadiance; +} + + +scene_rdl2::math::Color +PathIntegrator::computeRadianceLightsInCamera(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, + Subpixel &sp, const PathVertex &pv, const shading::Intersection &isect, + unsigned &sequenceID, float *aovs, bool hitGeom) const +{ + LightIntersection hitLightIsect; + int numHits = 0; + SequenceIDIntegrator sid(0, sp.mPixel, sp.mSubpixelIndex, SequenceType::IndexSelection, sequenceID); + IntegratorSample1D lightChoiceSamples(sid); + + const FrameState &fs = *pbrTls->mFs; + const Scene *scene = MNRY_VERIFY(pbrTls->mFs->mScene); + const AovSchema &aovSchema = *fs.mAovSchema; + const LightAovs &lightAovs = *fs.mLightAovs; + + // Stochastically choose one light hit by the ray + const Light *hitLight = scene->intersectVisibleLight(ray, hitGeom ? ray.getEnd() : sInfiniteLightDistance, + lightChoiceSamples, hitLightIsect, numHits); + scene_rdl2::math::Color radiance(0.f); + if (hitLight != nullptr) { + // Evaluate the radiance on the selected light in camera. + LightFilterRandomValues lightFilterR = { + scene_rdl2::math::Vec2f(0.f, 0.f), + scene_rdl2::math::Vec3f(0.f, 0.f, 0.f)}; // light filters don't apply to camera rays + // We multiply the radiance contribution by the number of lights hit. + // This is equivalent to dividing by its selection probability. + scene_rdl2::math::Color lightValue = + hitLight->eval(pbrTls->mTopLevelTls, ray.getDirection(), ray.getOrigin(), lightFilterR, ray.getTime(), + hitLightIsect, true, nullptr, nullptr, ray.getDirFootprint(), nullptr, nullptr); + radiance = pv.pathThroughput * numHits * lightValue; + + // LPE + if (aovs) { + EXCL_ACCUMULATOR_PROFILE(pbrTls, EXCL_ACCUM_AOVS); + // transition + int lpeStateId = pv.lpeStateId; + lpeStateId = lightAovs.lightEventTransition(pbrTls, lpeStateId, hitLight); + // accumulate matching aovs + aovAccumLightAovs(pbrTls, aovSchema, lightAovs, + radiance, nullptr, AovSchema::sLpePrefixNone, lpeStateId, aovs); + } + } + + checkForNan(radiance, "Camera visible lights", sp, pv, ray, isect); + + return radiance; +} + + scene_rdl2::math::Color PathIntegrator::computeColorFromIntersection(pbr::TLState *pbrTls, int pixelX, int pixelY, int subpixelIndex, int pixelSamples, const Sample& sample, diff --git a/lib/rendering/pbr/integrator/PathIntegrator.h b/lib/rendering/pbr/integrator/PathIntegrator.h index 9ac70f7..e9659c2 100644 --- a/lib/rendering/pbr/integrator/PathIntegrator.h +++ b/lib/rendering/pbr/integrator/PathIntegrator.h @@ -143,7 +143,8 @@ class PathIntegrator float *mVolumeAovs; // intersection results - bool mHitDeep; + bool mHitGeom; + bool mHitVolume; float mDeepIDs[7]; // Also see types.hh DEEP_DATA_MEMBERS }; @@ -191,6 +192,15 @@ class PathIntegrator int subpixelIndex, int pixelSamples, const Sample& sample, ComputeRadianceAovParams &aovParams, rndr::FastRenderMode fastMode) const; + scene_rdl2::math::Color computeRadianceSubsurface(pbr::TLState *pbrTls, + const shading::Bsdf &bsdf, Subpixel &sp, PathVertex &pv, + const mcrt_common::RayDifferential &ray, const shading::Intersection &isect, + const shading::BsdfSlice &slice, + const LightSet &lightSet, bool doIndirect, + float rayEpsilon, float shadowRayEpsilon, + unsigned &sequenceID, scene_rdl2::math::Color &ssAov, float *aovs, + const Rdl2LightSetList& parentLobeLightSets) const; + scene_rdl2::math::Color computeRadianceDiffusionSubsurface(pbr::TLState *pbrTls, const shading::Bsdf &bsdf, Subpixel &sp, const PathVertex &pv, const mcrt_common::RayDifferential &ray, const shading::Intersection &isect, @@ -268,43 +278,6 @@ class PathIntegrator } private: - enum IndirectRadianceType - { - NONE = 0, - SURFACE = 1 << 0, - VOLUME = 1 << 1, - ALL = SURFACE | VOLUME - }; - - - struct CryptomatteParams - { - // intersection results - bool mHit; - float mId; - scene_rdl2::math::Vec3f mPosition; - scene_rdl2::math::Vec3f mP0; - scene_rdl2::math::Vec3f mNormal; - scene_rdl2::math::Color4 mBeauty; - scene_rdl2::math::Vec3f mRefP; - scene_rdl2::math::Vec3f mRefN; - scene_rdl2::math::Vec2f mUV; - CryptomatteBuffer* mCryptomatteBuffer; - - void init(CryptomatteBuffer* buffer) { - mHit = false; - mId = 0.f; - mP0 = scene_rdl2::math::Vec3f(0.f); - mPosition = scene_rdl2::math::Vec3f(0.f); - mNormal = scene_rdl2::math::Vec3f(0.f); - mBeauty = scene_rdl2::math::Color4(0.f); - mRefP = scene_rdl2::math::Vec3f(0.f); - mRefN = scene_rdl2::math::Vec3f(0.f); - mUV = scene_rdl2::math::Vec2f(0.f); - mCryptomatteBuffer = buffer; - } - }; - /// Copy is disabled PathIntegrator(const PathIntegrator&) = delete; PathIntegrator &operator=(const PathIntegrator&) = delete; @@ -347,8 +320,7 @@ class PathIntegrator const scene_rdl2::rdl2::Material* newPriorityList[4], int newPriorityListCount[4], scene_rdl2::math::Color &radiance, unsigned &sequenceID, float *aovs, - CryptomatteParams *reflectedCryptomatteParams, - CryptomatteParams *refractedCryptomatteParams, + uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets) const; // compute volume emission line integral along the ray @@ -371,8 +343,7 @@ class PathIntegrator int newPriorityListCount[4], const LightSet &activeLightSet, const scene_rdl2::math::Vec3f *cullingNormal, float rayEpsilon, float shadowRayEpsilon, const scene_rdl2::math::Color &ssAov, unsigned &sequenceID, float *aovs, - CryptomatteParams *reflectedCryptomatteParams, - CryptomatteParams *refractedCryptomatteParams, + uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets) const; // compute the emission contribution from volumes emitting energy @@ -471,18 +442,22 @@ class PathIntegrator bool& reachTransmittanceThreshold, DeepParams* deepParams) const; - // return the type of indirect radiance contribution along ray - // (it can be a surface intersection that contributes bounce lighting or - // volume emission/in-scattering accumulated along the ray) - IndirectRadianceType computeRadianceRecurse(pbr::TLState *pbrTls, + // Streamlined version of the original computeRadianceRecurse() function, specialized to ray depth 0 + scene_rdl2::math::Color computeRadianceRecurse0(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, Subpixel &sp, const PathVertex &pv, const shading::BsdfLobe *lobe, - scene_rdl2::math::Color &radiance, float &transparency, VolumeTransmittance& vt, + float &transparency, VolumeTransmittance& vt, unsigned &sequenceID, float *aovs, float *depth, - DeepParams* deepParams, CryptomatteBuffer *cryptomatteBuffer, - CryptomatteParams *reflectedCryptomatteParams, - CryptomatteParams *refractedCryptomatteParams, - bool ignoreVolumes, bool &hitVolume, + DeepParams* deepParams, uint32_t cryptomatteFlags, + const Rdl2LightSetList& parentLobeLightSets) const; + + // Streamlined version of the original computeRadianceRecurse() function, specialized to ray depth 1+ + scene_rdl2::math::Color computeRadianceRecurse1(pbr::TLState *pbrTls, + mcrt_common::RayDifferential &ray, + Subpixel &sp, const PathVertex &pv, const shading::BsdfLobe *lobe, + float &transparency, VolumeTransmittance& vt, + unsigned &sequenceID, float *aovs, + uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets) const; scene_rdl2::math::Color computeRadianceSubsurfaceSample(pbr::TLState *pbrTls, @@ -525,6 +500,21 @@ class PathIntegrator const Subpixel &sp, unsigned sequenceID, const scene_rdl2::math::Color& throughput, float& presence, int receiverId, bool isVolume = false) const; + // Auxiliary function to compute the radiance gathered along a presence ray (the ray which continues on + // through a surface with non-binary presence), appropriately attenuated by the presence value. + scene_rdl2::math::Color computeRadiancePresence(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, + Subpixel &sp, const PathVertex &prevPv, PathVertex &pv, const shading::BsdfLobe *lobe, + float &transparency, float &presenceTransparency, VolumeTransmittance& vt, unsigned sequenceID, float *aovs, + uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets, + const scene_rdl2::rdl2::Material* newPriorityList[4], int newPriorityListCount[4], + int currentVolumeDepth, float rayEpsilon, float presence) const; + + // Auxiliary function to compute the in-camera radiance of a light hit directly by a primary ray. Not called + // for non-primary rays since those contributions are already gathered by light & bsdf sampling. + scene_rdl2::math::Color computeRadianceLightsInCamera(pbr::TLState *pbrTls, mcrt_common::RayDifferential &ray, + Subpixel &sp, const PathVertex &pv, const shading::Intersection &isect, unsigned &sequenceID, float *aovs, + bool hitGeom) const; + PATH_INTEGRATOR_MEMBERS; }; diff --git a/lib/rendering/pbr/integrator/PathIntegrator.hh b/lib/rendering/pbr/integrator/PathIntegrator.hh index b3bf196..3f340ae 100644 --- a/lib/rendering/pbr/integrator/PathIntegrator.hh +++ b/lib/rendering/pbr/integrator/PathIntegrator.hh @@ -44,8 +44,8 @@ HUD_MEMBER(int, mVolumeIndirectSamples); \ HUD_MEMBER(int, mPad2); \ HUD_CPP_MEMBER(std::vector, mDeepIDAttrIdxs, 24); \ - HUD_MEMBER(int, mCryptoUVAttrIdx); \ - HUD_MEMBER(int, mPad3) + HUD_MEMBER(int, mCryptoIdAttrIdx); \ + HUD_MEMBER(int, mCryptoUVAttrIdx) #define PATH_INTEGRATOR_VALIDATION \ @@ -86,8 +86,7 @@ HUD_VALIDATE(PathIntegrator, mVolumeIndirectSamples); \ HUD_VALIDATE(PathIntegrator, mPad2); \ HUD_VALIDATE(PathIntegrator, mDeepIDAttrIdxs); \ + HUD_VALIDATE(PathIntegrator, mCryptoIdAttrIdx); \ HUD_VALIDATE(PathIntegrator, mCryptoUVAttrIdx); \ - HUD_VALIDATE(PathIntegrator, mDeepIDAttrIdxs); \ - HUD_VALIDATE(PathIntegrator, mPad3); \ HUD_END_VALIDATION diff --git a/lib/rendering/pbr/integrator/PathIntegratorMultiSampler.cc b/lib/rendering/pbr/integrator/PathIntegratorMultiSampler.cc index a7e47aa..6c7d5e7 100644 --- a/lib/rendering/pbr/integrator/PathIntegratorMultiSampler.cc +++ b/lib/rendering/pbr/integrator/PathIntegratorMultiSampler.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -410,8 +411,7 @@ PathIntegrator::addIndirectAndDirectVisibleContributions( const scene_rdl2::rdl2::Material* newPriorityList[4], int newPriorityListCount[4], scene_rdl2::math::Color &radiance, unsigned& sequenceID, float *aovs, - CryptomatteParams *reflectedCryptomatteParams, - CryptomatteParams *refractedCryptomatteParams, + uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets) const { MNRY_ASSERT(pbrTls->isIntegratorAccumulatorRunning()); @@ -532,12 +532,10 @@ PathIntegrator::addIndirectAndDirectVisibleContributions( CHECK_CANCELLATION(pbrTls, return); // Recurse - scene_rdl2::math::Color radianceIndirect; float transparencyIndirect; // the volume attenuation along this ray to the first hit (or infinity) VolumeTransmittance vtIndirect; ++sequenceID; - bool hitVolume = false; // reflected/refracted cryptomatte PART B @@ -553,7 +551,7 @@ PathIntegrator::addIndirectAndDirectVisibleContributions( // cryptomatte coverage for a pixel. // // We must be on an existing reflected/refracted cryptomatte path - // ( reflectedCryptomatteParams != nullptr or refractedCryptomatteParams != nullptr). + // ( reflectedCryptomatteResults != nullptr or refractedCryptomatteResults != nullptr). // // The material must also be set to be "record_reflected_cryptomatte" or "record_refracted_cryptomatte". // Materials must be explicitly tagged because just examining the lobe's flags is not 100% @@ -561,7 +559,7 @@ PathIntegrator::addIndirectAndDirectVisibleContributions( // materials and special cases such as hair that have counterintuitive flags. // // See the other cryptomatte logic in "reflected/refracted cryptomatte PART A" that - // uses the newRefractedCryptomatteParams set up here. + // uses the newRefractedCryptomatteResults set up here. const bool glossyOrMirrorReflection = ((lobe->getType() & shading::BsdfLobe::REFLECTION) && ((lobe->getType() & shading::BsdfLobe::GLOSSY) || @@ -575,19 +573,15 @@ PathIntegrator::addIndirectAndDirectVisibleContributions( const scene_rdl2::rdl2::Material* material = isect.getMaterial()->asA(); - CryptomatteParams *newReflectedCryptomatteParams = - (reflectedCryptomatteParams && - i == 0 && - glossyOrMirrorReflection && - material->getRecordReflectedCryptomatte()) ? - reflectedCryptomatteParams : nullptr; - - CryptomatteParams *newRefractedCryptomatteParams = - (refractedCryptomatteParams && - i == 0 && - glossyOrMirrorTransmission && - material->getRecordRefractedCryptomatte()) ? - refractedCryptomatteParams : nullptr; + uint32_t newCryptomatteFlags = 0; + if (i==0) { + if (material->getRecordReflectedCryptomatte() && glossyOrMirrorReflection) { + newCryptomatteFlags |= (cryptomatteFlags & CRYPTOMATTE_FLAG_REFLECTED); + } + if (material->getRecordRefractedCryptomatte() && glossyOrMirrorTransmission) { + newCryptomatteFlags |= (cryptomatteFlags & CRYPTOMATTE_FLAG_REFRACTED); + } + } // Append this lobe's lightset to the parent lobes' lists const scene_rdl2::rdl2::LightSet* lobeLightSet = lobe->getLightSet(); @@ -596,20 +590,12 @@ PathIntegrator::addIndirectAndDirectVisibleContributions( newParentLobeLightSets.append(lobeLightSet); } - IndirectRadianceType indirectRadianceType = computeRadianceRecurse( - pbrTls, ray, sp, - pv, lobe, radianceIndirect, transparencyIndirect, - vtIndirect, sequenceID, aovs, nullptr, nullptr, nullptr, - newReflectedCryptomatteParams, newRefractedCryptomatteParams, false, hitVolume, - newParentLobeLightSets); - - if (indirectRadianceType != NONE) { - // Accumulate indirect lighting contribution - // In the bundled version, this contribution gets accounted for by - // queueing a BundledRadiance for the direct radiance and/or emission, - // and spawning one or more new rays for the indirect radiance. - radiance += radianceIndirect; - } + radiance += computeRadianceRecurse1(pbrTls, ray, sp, + pv, lobe, transparencyIndirect, + vtIndirect, sequenceID, aovs, + newCryptomatteFlags, + newParentLobeLightSets); + if (!bsmp[s].didHitLight()) { const FrameState &fs = *pbrTls->mFs; const AovSchema &aovSchema = *fs.mAovSchema; @@ -636,8 +622,7 @@ PathIntegrator::computeRadianceBsdfMultiSampler(pbr::TLState *pbrTls, int newPriorityListCount[4], const LightSet &activeLightSet, const scene_rdl2::math::Vec3f *cullingNormal, float rayEpsilon, float shadowRayEpsilon, const scene_rdl2::math::Color &ssAov, unsigned &sequenceID, float *aovs, - CryptomatteParams *reflectedCryptomatteParams, - CryptomatteParams *refractedCryptomatteParams, + uint32_t cryptomatteFlags, const Rdl2LightSetList& parentLobeLightSets) const { // TODO: @@ -738,7 +723,7 @@ PathIntegrator::computeRadianceBsdfMultiSampler(pbr::TLState *pbrTls, // Note: This will recurse addIndirectAndDirectVisibleContributions(pbrTls, sp, pv, bSampler, bsmp, ray, rayEpsilon, shadowRayEpsilon, isect, indirectFlags, newPriorityList, newPriorityListCount, - radiance, sequenceID, aovs, reflectedCryptomatteParams, refractedCryptomatteParams, parentLobeLightSets); + radiance, sequenceID, aovs, cryptomatteFlags, parentLobeLightSets); checkForNan(radiance, "Indirect and direct contributions", sp, pv, ray, isect); } else { diff --git a/lib/rendering/pbr/integrator/PathIntegratorSubsurface.cc b/lib/rendering/pbr/integrator/PathIntegratorSubsurface.cc index 17b540e..5a7870c 100644 --- a/lib/rendering/pbr/integrator/PathIntegratorSubsurface.cc +++ b/lib/rendering/pbr/integrator/PathIntegratorSubsurface.cc @@ -463,10 +463,8 @@ PathIntegrator::computeRadianceSubsurfaceSample(pbr::TLState *pbrTls, ray); // Recurse - scene_rdl2::math::Color contribution; float transparency; ++sequenceID; - bool hitVolume = false; // Append this lobe's lightset to the parent lobes' lists Rdl2LightSetList newParentLobeLightSets = parentLobeLightSets; @@ -474,15 +472,9 @@ PathIntegrator::computeRadianceSubsurfaceSample(pbr::TLState *pbrTls, newParentLobeLightSets.append(lobeLightSet); } - IndirectRadianceType indirectRadianceType = computeRadianceRecurse( - pbrTls, ray, sp, nextPv, &lobe, - contribution, transparency, vt, - sequenceID, aovs, nullptr, nullptr, nullptr, nullptr, nullptr, false, hitVolume, newParentLobeLightSets); - if (indirectRadianceType != NONE) { - // Accumulate radiance, but only accumulate indirect or direct - // contribution - radiance += contribution; - } + radiance += computeRadianceRecurse1(pbrTls, ray, sp, nextPv, &lobe, + transparency, vt, + sequenceID, aovs, /* cryptomatteFlags = */ 0, newParentLobeLightSets); } //------------------------------ // Compute direct lighting contribution with MIS if needed. @@ -564,6 +556,45 @@ PathIntegrator::computeRadianceSubsurfaceSample(pbr::TLState *pbrTls, return radiance; } + +scene_rdl2::math::Color +PathIntegrator::computeRadianceSubsurface(pbr::TLState *pbrTls, + const Bsdf &bsdf, Subpixel &sp, PathVertex &pv, + const RayDifferential &ray, const Intersection &isect, + const BsdfSlice &slice, + const LightSet &lightSet, bool doIndirect, + float rayEpsilon, float shadowRayEpsilon, + unsigned &sequenceID, scene_rdl2::math::Color &ssAov, float *aovs, + const Rdl2LightSetList& parentLobeLightSets) const +{ + pv.subsurfaceDepth += 1; + + scene_rdl2::math::Color radiance(0.0f); + + if (bsdf.getBssrdfCount() > 0) { + // Option 1: diffusion profile (bssrdf) approach + for (int bssrdfIdx = 0; bssrdfIdx < bsdf.getBssrdfCount(); ++bssrdfIdx) { + const shading::Bssrdf *bssrdf = bsdf.getBssrdf(bssrdfIdx); + // Stop the accumulator here since the subsurface accumulator will be + // started up inside of computeRadianceSubsurface as needed. + radiance += computeRadianceDiffusionSubsurface(pbrTls, bsdf, sp, pv, ray, + isect, slice, *bssrdf, lightSet, doIndirect, rayEpsilon, shadowRayEpsilon, + sequenceID, ssAov, aovs, parentLobeLightSets); + } + } else { + MNRY_ASSERT(bsdf.getVolumeSubsurface() != nullptr); + // Option 2: path trace volumetric approach (a.k.a. random walk) + radiance += computeRadiancePathTraceSubsurface(pbrTls, bsdf, sp, pv, ray, isect, + *bsdf.getVolumeSubsurface(), lightSet, doIndirect, rayEpsilon, shadowRayEpsilon, + sequenceID, ssAov, aovs); + } + + checkForNan(radiance, "Subsurface scattering", sp, pv, ray, isect); + + return radiance; +} + + scene_rdl2::math::Color PathIntegrator::computeRadianceDiffusionSubsurface(pbr::TLState *pbrTls, const Bsdf &bsdf, Subpixel &sp, const PathVertex &pv, diff --git a/lib/rendering/pbr/integrator/PathIntegratorUtil.cc b/lib/rendering/pbr/integrator/PathIntegratorUtil.cc index aa30441..642a901 100644 --- a/lib/rendering/pbr/integrator/PathIntegratorUtil.cc +++ b/lib/rendering/pbr/integrator/PathIntegratorUtil.cc @@ -663,6 +663,20 @@ applyRussianRoulette(const LightSetSampler &lSampler, LightSample *lsmp, } } +float +stochasticPresence(Subpixel &sp, const PathVertex &pv, unsigned &sequenceID, float presence) +{ + SequenceIDIntegrator sidPresence( sp.mPixel, + sp.mSubpixelIndex, + SequenceType::Presence, + sequenceID, + pv.presenceDepth ); + IntegratorSample1D prSamples(sidPresence); + float prSample; + prSamples.getSample(&prSample, pv.presenceDepth); + return (prSample < presence) ? 1.f : 0.f; +} + void accumulateRayPresence(pbr::TLState *pbrTls, const Light* light, diff --git a/lib/rendering/pbr/integrator/PathIntegratorUtil.h b/lib/rendering/pbr/integrator/PathIntegratorUtil.h index 6d948f1..0f4e294 100644 --- a/lib/rendering/pbr/integrator/PathIntegratorUtil.h +++ b/lib/rendering/pbr/integrator/PathIntegratorUtil.h @@ -279,6 +279,8 @@ void applyRussianRoulette(const LightSetSampler &lSampler, LightSample *lsmp, const Subpixel &sp, const PathVertex &pv, unsigned sequenceID, float threshold, IntegratorSample1D& rrSamples); +float stochasticPresence(Subpixel &sp, const PathVertex &pv, unsigned &sequenceID, float presence); + void accumulateRayPresence(pbr::TLState *pbrTls, const Light* light, const mcrt_common::Ray& shadowRay, diff --git a/lib/rendering/pbr/integrator/PathIntegratorVolume.cc b/lib/rendering/pbr/integrator/PathIntegratorVolume.cc index 2625da8..2626266 100644 --- a/lib/rendering/pbr/integrator/PathIntegratorVolume.cc +++ b/lib/rendering/pbr/integrator/PathIntegratorVolume.cc @@ -503,15 +503,11 @@ PathIntegrator::integrateVolumeScatteringIndirect(pbr::TLState *pbrTls, const mc newPv.nonMirrorDepth += 1; newPv.volumeDepth += 1; - scene_rdl2::math::Color Ls(0.0f); float transparency = 0.0f; VolumeTransmittance vt; - bool hitVolume = true; - computeRadianceRecurse(pbrTls, rayIndirect, sp, newPv, nullptr, Ls, transparency, vt, sequenceID, aovs, - nullptr, nullptr, nullptr, nullptr, nullptr, true, hitVolume, Rdl2LightSetList()); - - LIndirect += Ls; + LIndirect += computeRadianceRecurse1(pbrTls, rayIndirect, sp, newPv, /* lobe = */ nullptr, transparency, vt, + sequenceID, aovs, /* cryptomatteFlags = */ 0, Rdl2LightSetList()); } return LIndirect; @@ -1427,6 +1423,9 @@ PathIntegrator::computeRadianceVolume(pbr::TLState *pbrTls, const mcrt_common::R // it's a primary ray from camera. // Otherwise, we figure out the ray mask based on the lobe type. // for ray mask propagation, we simply merge with existing ray mask. + // TODO: this is dangerous. lobeType will be zero not just for primary rays but also if we got here via + // calling computeRadianceRecurse() from a volume. The only reason this currently doesn't happen is that + // we artificially restrict the ray paths to prevent this happening, for efficiency reasons. int rayMask = lobeType == 0 ? scene_rdl2::rdl2::CAMERA : lobeTypeToRayMask(lobeType); size_t intervalCount = collectVolumeIntervals(pbrTls, ray, rayMask); diff --git a/lib/rendering/pbr/integrator/Picking.cc b/lib/rendering/pbr/integrator/Picking.cc index 7118bcc..fa87e0b 100644 --- a/lib/rendering/pbr/integrator/Picking.cc +++ b/lib/rendering/pbr/integrator/Picking.cc @@ -176,10 +176,9 @@ computeLightContributions(mcrt_common::ThreadLocalState *tls, const Scene* scene // Get the lights contributing to this pixel LightSet lightSet; - bool hasRayTerminatorLights; Rdl2LightSetList lightSetList; computeActiveLights(&tls->mArena, scene, isect, normalPtr, bsdf, /* PathVertex = */ nullptr, - /* rayTime = */ 0.f, lightSet, hasRayTerminatorLights, lightSetList); + /* rayTime = */ 0.f, lightSet, lightSetList); // Populate the array with the light and contribution data to be returned. int count = lightSet.getLightCount(); diff --git a/lib/rendering/pbr/light/LightUtil.cc b/lib/rendering/pbr/light/LightUtil.cc index 30249db..58c12b1 100644 --- a/lib/rendering/pbr/light/LightUtil.cc +++ b/lib/rendering/pbr/light/LightUtil.cc @@ -43,7 +43,6 @@ computeActiveLights(scene_rdl2::alloc::Arena *arena, const PathVertex * const pv, float rayTime, LightSet &lightSet, - bool &hasRayTerminatorLights, const Rdl2LightSetList& parentLobeLightSets) { MNRY_ASSERT(arena); @@ -74,7 +73,6 @@ computeActiveLights(scene_rdl2::alloc::Arena *arena, } int activeLightCount = 0; - hasRayTerminatorLights = false; if (lightList) { size_t upperBound = lightList->size(); @@ -103,7 +101,6 @@ computeActiveLights(scene_rdl2::alloc::Arena *arena, light->canIlluminate(pos, normal, rayTime, radius, (*lightFilterLists)[i], pv)) { lightIdMap[i] = activeLightCount; activeLightId[activeLightCount++] = i; - hasRayTerminatorLights |= light->getIsRayTerminator(); } else { lightIdMap[i] = -1; } diff --git a/lib/rendering/pbr/light/LightUtil.h b/lib/rendering/pbr/light/LightUtil.h index 9f3535f..ae66fc8 100644 --- a/lib/rendering/pbr/light/LightUtil.h +++ b/lib/rendering/pbr/light/LightUtil.h @@ -329,7 +329,6 @@ computeActiveLights(scene_rdl2::alloc::Arena *arena, float rayTime, // outputs LightSet &lightSet, - bool &hasRayTerminatorLights, const Rdl2LightSetList& parentLobeLightSets); // Randomly choose a light based on how many lights have been hit so far