EmissionVertex sampleLightPath( BDPTPathVertex* pLightPath, uint32_t maxLightPathDepth, const Scene& scene, const PowerBasedLightSampler& lightSampler, uint32_t misPathCount, // The number of paths used to estimate an integral MisFunctor&& mis, RandomGenerator&& rng) { std::for_each(pLightPath, pLightPath + maxLightPathDepth, [&](BDPTPathVertex& vertex) { vertex.m_fPathPdf = 0.f; }); if(maxLightPathDepth > 0u) { const Light* pLight = nullptr; float lightPdf; auto positionSample = getFloat2(rng); pLightPath[0] = BDPTPathVertex(scene, lightSampler, getFloat(rng), positionSample, getFloat2(rng), pLight, lightPdf, misPathCount, mis); if(pLightPath->m_fPathPdf) { while(pLightPath->m_nDepth < maxLightPathDepth && pLightPath->extend(*(pLightPath + 1), scene, misPathCount, true, rng, mis)) { ++pLightPath; } if(!pLightPath->m_Intersection) { pLightPath->m_fPathPdf = 0.f; // If the last vertex is outside the scene, invalidate it } } return { pLight, lightPdf, positionSample }; } return { nullptr, 0.f, Vec2f(0.f) }; }
SensorVertex sampleEyePath( BDPTPathVertex* pEyePath, uint32_t maxEyePathDepth, const Scene& scene, const Sensor& sensor, uint32_t misPathCount, // The number of paths used to estimate an integral MisFunctor&& mis, RandomGenerator&& rng) { std::for_each(pEyePath, pEyePath + maxEyePathDepth, [&](BDPTPathVertex& vertex) { vertex.m_fPathPdf = 0.f; }); if(maxEyePathDepth > 0u) { auto positionSample = getFloat2(rng); pEyePath[0] = BDPTPathVertex(scene, sensor, getFloat(rng), positionSample, getFloat2(rng), misPathCount, mis); if(pEyePath->m_Intersection && pEyePath->m_fPathPdf) { while(pEyePath->m_nDepth < maxEyePathDepth && pEyePath->extend(*(pEyePath + 1), scene, misPathCount, false, rng, mis)) { ++pEyePath; } } return { &sensor, positionSample }; } return { nullptr, 0.f, Vec2f(0.f) }; }
void InstantRadiosityRenderer::processPixel(uint32_t threadID, uint32_t tileID, const Vec2u& pixel) const { PixelSensor pixelSensor(getSensor(), pixel, getFramebufferSize()); RaySample raySample; float positionPdf, directionPdf; auto We = pixelSensor.sampleExitantRay(getScene(), getFloat2(threadID), getFloat2(threadID), raySample, positionPdf, directionPdf); auto L = zero<Vec3f>(); if(We != zero<Vec3f>() && raySample.pdf) { auto I = getScene().intersect(raySample.value); BSDF bsdf(-raySample.value.dir, I, getScene()); if(I) { // Direct illumination for(auto& vpl: m_EmissionVPLBuffer) { RaySample shadowRay; auto Le = vpl.pLight->sampleDirectIllumination(getScene(), vpl.emissionVertexSample, I, shadowRay); if(shadowRay.pdf) { auto contrib = We * Le * bsdf.eval(shadowRay.value.dir) * abs(dot(I.Ns, shadowRay.value.dir)) / (vpl.lightPdf * shadowRay.pdf * m_nLightPathCount * raySample.pdf); if(contrib != zero<Vec3f>()) { if(!getScene().occluded(shadowRay.value)) { L += contrib; } } } } // Indirect illumination for(auto& vpl: m_SurfaceVPLBuffer) { auto dirToVPL = vpl.intersection.P - I.P; auto dist = length(dirToVPL); if(dist > 0.f) { dirToVPL /= dist; auto fs1 = bsdf.eval(dirToVPL); auto fs2 = vpl.bsdf.eval(-dirToVPL); auto geometricFactor = abs(dot(I.Ns, dirToVPL)) * abs(dot(vpl.intersection.Ns, -dirToVPL)) / sqr(dist); auto contrib = We * vpl.power * fs1 * fs2 * geometricFactor / raySample.pdf; if(contrib != zero<Vec3f>()) { Ray shadowRay(I, vpl.intersection, dirToVPL, dist); if(!getScene().occluded(shadowRay)) { L += contrib; } } } } } } accumulate(0, getPixelIndex(pixel.x, pixel.y), Vec4f(L, 1.f)); }
float2 Random::getFloat2(float maxRadius) { float2 result; do { result = getFloat2(-float2::ONE, float2::ONE); } while (lengthSquared(result) >= 1.0f); return result * maxRadius; }
void InstantRadiosityRenderer::beginFrame() { auto lightPathDepth = m_nMaxDepth - 2; m_EmissionVPLBuffer.clear(); m_EmissionVPLBuffer.reserve(m_nLightPathCount); m_SurfaceVPLBuffer.clear(); m_SurfaceVPLBuffer.reserve(m_nLightPathCount * lightPathDepth); for(auto i: range(m_nLightPathCount)) { auto power = Vec3f(1.f / m_nLightPathCount); EmissionVPL vpl; vpl.pLight = m_Sampler.sample(getScene(), getFloat(0u), vpl.lightPdf); if(vpl.pLight && vpl.lightPdf) { float emissionVertexPdf; RaySample exitantRaySample; vpl.emissionVertexSample = getFloat2(0u); auto Le = vpl.pLight->sampleExitantRay(getScene(), vpl.emissionVertexSample, getFloat2(0u), exitantRaySample, emissionVertexPdf); m_EmissionVPLBuffer.emplace_back(vpl); if(Le != zero<Vec3f>() && exitantRaySample.pdf) { auto intersection = getScene().intersect(exitantRaySample.value); if(intersection) { power *= Le / (vpl.lightPdf * exitantRaySample.pdf); m_SurfaceVPLBuffer.emplace_back(intersection, -exitantRaySample.value.dir, getScene(), power); for(auto depth = 1u; depth < lightPathDepth; ++depth) { Sample3f outgoingDir; float cosThetaOutDir; auto fs = m_SurfaceVPLBuffer.back().bsdf.sample(getFloat3(0u), outgoingDir, cosThetaOutDir, nullptr, true); if(fs != zero<Vec3f>() && outgoingDir.pdf) { Ray ray(m_SurfaceVPLBuffer.back().intersection, outgoingDir.value); auto intersection = getScene().intersect(ray); if(intersection) { power *= fs * abs(cosThetaOutDir) / outgoingDir.pdf; m_SurfaceVPLBuffer.emplace_back(intersection, -ray.dir, getScene(), power); } } } } } } } }
bool extend(const Scene& scene, const RandomGenerator& rng) { if(!m_Intersection) { // Cannot extend from infinity invalidate(); return false; } Sample3f woSample; float cosThetaOutDir; uint32_t sampledEvent; auto fs = m_BSDF.sample(Vec3f(getFloat(rng), getFloat2(rng)), woSample, cosThetaOutDir, &sampledEvent, m_bSampleAdjoint); if(woSample.pdf == 0.f || fs == zero<Vec3f>()) { invalidate(); return false; } m_nSampledEvent = sampledEvent; m_Power *= abs(cosThetaOutDir) * fs / woSample.pdf; ++m_nLength; auto nextI = scene.intersect(Ray(m_Intersection, woSample.value)); if(!nextI) { m_Intersection = nextI; return true; } auto incidentDirection = -woSample.value; auto sqrLength = sqr(nextI.distance); if(sqrLength == 0.f) { invalidate(); return false; } auto pdfWrtArea = woSample.pdf * abs(dot(nextI.Ns, incidentDirection)) / sqrLength; if(pdfWrtArea == 0.f) { invalidate(); return false; } m_Intersection = nextI; m_BSDF.init(incidentDirection, nextI, scene); return true; }
void processSample(uint32_t threadID, uint32_t pixelID, uint32_t sampleID, uint32_t x, uint32_t y) const { PixelSensor sensor(getSensor(), Vec2u(x, y), getFramebufferSize()); auto pixelSample = getPixelSample(threadID, sampleID); auto lensSample = getFloat2(threadID); RaySample raySample; Intersection I; sampleExitantRay( sensor, getScene(), lensSample, pixelSample, raySample, I); auto ray = raySample.value; accumulate(0, pixelID, Vec4f(ray.dir, 1.f)); }
float2 Random::getFloat2(float minRadius, float maxRadius) { float2 v = getFloat2(1.0f); float ratio = minRadius / maxRadius; return lerp(v, normalize(v), ratio) * maxRadius; }
bool extend(BDPTPathVertex& next, const Scene& scene, uint32_t pathCount, bool sampleAdjoint, // Number of paths sampled with the strategies s/t = m_nDepth + 1, t/s = * RandomGenerator&& rng, MisFunctor&& mis) { if(!m_Intersection) { return false; // Can't sample from infinity } Sample3f woSample; float cosThetaOutDir; uint32_t sampledEvent; auto fs = m_BSDF.sample(Vec3f(getFloat(rng), getFloat2(rng)), woSample, cosThetaOutDir, &sampledEvent, sampleAdjoint); if(woSample.pdf == 0.f || fs == zero<Vec3f>()) { return false; } float reversePdf = m_BSDF.pdf(woSample.value, true); auto nextI = scene.intersect(Ray(m_Intersection, woSample.value)); if(!nextI) { next.m_Intersection = nextI; next.m_BSDF.init(-woSample.value, scene); next.m_fPdfWrtArea = woSample.pdf; next.m_fPathPdf = m_fPathPdf * woSample.pdf; next.m_Power = m_Power * abs(cosThetaOutDir) * fs / woSample.pdf; next.m_nDepth = m_nDepth + 1; auto previousPointToIncidentDirectionJacobian = abs(cosThetaOutDir); // No division by squared distance since the point is at infinity if((sampledEvent & ScatteringEvent::Specular) && m_BSDF.isDelta()) { next.m_fdVC = mis(previousPointToIncidentDirectionJacobian) * m_fdVC; next.m_fdVCM = 0.f; } else { // We set dVC first in case next == *this, so that the dVCM is unchanged until next line next.m_fdVC = mis(previousPointToIncidentDirectionJacobian / next.m_fPdfWrtArea) * (m_fdVCM + mis(reversePdf) * m_fdVC); next.m_fdVCM = mis(pathCount / next.m_fPdfWrtArea); } } else { auto incidentDirection = -woSample.value; auto sqrLength = sqr(nextI.distance); if(sqrLength == 0.f) { return false; } auto pdfWrtArea = woSample.pdf * abs(dot(nextI.Ns, incidentDirection)) / sqrLength; if(pdfWrtArea == 0.f) { return false; } next.m_Intersection = nextI; next.m_BSDF.init(incidentDirection, nextI, scene); next.m_fPdfWrtArea = pdfWrtArea; next.m_fPathPdf = m_fPathPdf * pdfWrtArea; next.m_Power = m_Power * abs(cosThetaOutDir) * fs / woSample.pdf; next.m_nDepth = m_nDepth + 1; auto previousPointToIncidentDirectionJacobian = abs(cosThetaOutDir) / sqrLength; if((sampledEvent & ScatteringEvent::Specular) && m_BSDF.isDelta()) { next.m_fdVC = mis(previousPointToIncidentDirectionJacobian) * m_fdVC; next.m_fdVCM = 0.f; } else { // We set dVC first in case next == *this, so that the dVCM is unchanged until next line next.m_fdVC = mis(previousPointToIncidentDirectionJacobian / next.m_fPdfWrtArea) * (m_fdVCM + mis(reversePdf) * m_fdVC); next.m_fdVCM = mis(pathCount / next.m_fPdfWrtArea); } } return true; }
void UniformResamplingRecursiveMISBPTRenderer::processSample(uint32_t threadID, uint32_t tileID, uint32_t pixelID, uint32_t sampleID, uint32_t x, uint32_t y) const { auto mis = [&](float v) { return Mis(v); }; ThreadRNG rng(*this, threadID); PixelSensor pixelSensor(getSensor(), Vec2u(x, y), getFramebufferSize()); auto pixelSample = getPixelSample(threadID, sampleID); auto sensorSample = getFloat2(threadID); auto fImportanceScale = 1.f / getSppCount(); BDPTPathVertex eyeVertex(getScene(), pixelSensor, sensorSample, pixelSample, m_nLightPathCount, mis); if(eyeVertex.m_Power == zero<Vec3f>() || !eyeVertex.m_fPathPdf) { return; } // Sample the light path to connect with the eye path auto lightPathIdx = clamp(std::size_t(getFloat(threadID) * m_nLightPathCount), std::size_t(0), m_nLightPathCount - 1); auto pLightPath = m_LightPathBuffer.getSlicePtr(lightPathIdx); auto maxEyePathDepth = getMaxEyePathDepth(); auto maxLightPathDepth = getMaxLightPathDepth(); auto extendEyePath = [&]() -> bool { return eyeVertex.m_nDepth < maxEyePathDepth && eyeVertex.extend(eyeVertex, getScene(), getSppCount(), false, rng, mis); }; // Iterate over an eye path do { // Intersection with light source auto totalLength = eyeVertex.m_nDepth; if(acceptPathDepth(totalLength) && totalLength <= m_nMaxDepth) { auto contrib = fImportanceScale * computeEmittedRadiance(eyeVertex, getScene(), m_LightSampler, getSppCount(), mis); if(isInvalidMeasurementEstimate(contrib)) { reportInvalidContrib(threadID, tileID, pixelID, [&]() { debugLog() << "s = " << 0 << ", t = " << (eyeVertex.m_nDepth + 1) << std::endl; }); } accumulate(FINAL_RENDER, pixelID, Vec4f(contrib, 0)); accumulate(FINAL_RENDER_DEPTH1 + totalLength - 1u, pixelID, Vec4f(contrib, 0)); auto strategyOffset = computeBPTStrategyOffset(totalLength + 1, 0u); accumulate(BPT_STRATEGY_s0_t2 + strategyOffset, pixelID, Vec4f(contrib, 0)); } // Connections if(eyeVertex.m_Intersection) { // Direct illumination auto totalLength = eyeVertex.m_nDepth + 1; if(acceptPathDepth(totalLength) && totalLength <= m_nMaxDepth) { float lightPdf; auto pLight = m_LightSampler.sample(getScene(), getFloat(threadID), lightPdf); auto s2D = getFloat2(threadID); auto contrib = fImportanceScale * connectVertices(eyeVertex, EmissionVertex(pLight, lightPdf, s2D), getScene(), getSppCount(), mis); if(isInvalidMeasurementEstimate(contrib)) { reportInvalidContrib(threadID, tileID, pixelID, [&]() { debugLog() << "s = " << 1 << ", t = " << (eyeVertex.m_nDepth + 1) << std::endl; }); } accumulate(FINAL_RENDER, pixelID, Vec4f(contrib, 0)); accumulate(FINAL_RENDER_DEPTH1 + totalLength - 1u, pixelID, Vec4f(contrib, 0)); auto strategyOffset = computeBPTStrategyOffset(totalLength + 1, 1); accumulate(BPT_STRATEGY_s0_t2 + strategyOffset, pixelID, Vec4f(contrib, 0)); } // Connection with each light vertex for(auto j = 0u; j < maxLightPathDepth; ++j) { auto pLightVertex = pLightPath + j; auto totalLength = eyeVertex.m_nDepth + pLightVertex->m_nDepth + 1; if(pLightVertex->m_fPathPdf > 0.f && acceptPathDepth(totalLength) && totalLength <= m_nMaxDepth) { auto contrib = fImportanceScale * connectVertices(eyeVertex, *pLightVertex, getScene(), getSppCount(), mis); if(isInvalidMeasurementEstimate(contrib)) { reportInvalidContrib(threadID, tileID, pixelID, [&]() { debugLog() << "s = " << (pLightVertex->m_nDepth + 1) << ", t = " << (eyeVertex.m_nDepth + 1) << std::endl; }); } accumulate(FINAL_RENDER, pixelID, Vec4f(contrib, 0)); accumulate(FINAL_RENDER_DEPTH1 + totalLength - 1u, pixelID, Vec4f(contrib, 0)); auto strategyOffset = computeBPTStrategyOffset(totalLength + 1, pLightVertex->m_nDepth + 1); accumulate(BPT_STRATEGY_s0_t2 + strategyOffset, pixelID, Vec4f(contrib, 0)); } } } } while(extendEyePath()); }