Color pathTrace(const Ray& ray, ShapeSet& scene, std::list<Shape*>& lights, Rng& rng, size_t lightSamplesHint, size_t maxRayDepth, size_t pixelSampleIndex, Sampler **bounceSamplers) { // Accumulate total incoming radiance in 'result' Color result = Color(0.0f, 0.0f, 0.0f); // As we get through more and more bounces, we track how much the light is // diminished through each bounce Color throughput = Color(1.0f, 1.0f, 1.0f); // Start with the initial ray from the camera Ray currentRay = ray; // While we have bounces left we can still take... size_t numBounces = 0; while (numBounces < maxRayDepth) { // Trace the ray to see if we hit anything Intersection intersection(currentRay); if (!scene.intersect(intersection)) { // No hit, return black (background) break; } // Add in emission when directly visible or via a specular bounce if (numBounces == 0) { result += throughput * intersection.m_pMaterial->emittance(); } // Evaluate the material and intersection information at this bounce Point position = intersection.position(); Vector normal = intersection.m_normal; Vector outgoing = -currentRay.m_direction; Brdf* pBrdf = NULL; float brdfWeight = 1.0f; Color matColor = intersection.m_pMaterial->evaluate(position, normal, outgoing, pBrdf, brdfWeight); // No BRDF? We can't evaluate lighting, so bail. if (pBrdf == NULL) { return result; } // Evaluate direct lighting at this bounce. // For each light... for (std::list<Shape*>::iterator iter = lights.begin(); iter != lights.end(); ++iter) { // Set up samplers for evaluating this light StratifiedRandomSampler lightSampler(lightSamplesHint, lightSamplesHint, rng); StratifiedRandomSampler brdfSampler(lightSamplesHint, lightSamplesHint, rng); // Sample the light (with stratified random sampling to reduce noise) Color lightResult = Color(0.0f, 0.0f, 0.0f); size_t numLightSamples = lightSampler.total2DSamplesAvailable(); for (size_t lightSampleIndex = 0; lightSampleIndex < numLightSamples; ++lightSampleIndex) { // Sample the light using MIS between the light and the BRDF. // This means we ask the light for a direction, and the likelihood // of having sampled that direction (the PDF). Then we ask the // BRDF what it thinks of that direction (its PDF), and weight // the light sample with MIS. // // Then, we ask the BRDF for a direction, and the likelihood of // having sampled that direction (the PDF). Then we ask the // light what it thinks of that direction (its PDF, and whether // that direction even runs into the light at all), and weight // the BRDF sample with MIS. // // By doing both samples and asking both the BRDF and light for // their PDF for each one, we can combine the strengths of both // sampling methods and get the best of both worlds. It does // cost an extra shadow ray and evaluation, though, but it is // generally such an improvement in quality that it is very much // worth the overhead. // Ask the light for a random position/normal we can use for lighting Light *pLightShape = dynamic_cast<Light*>(*iter); float lsu, lsv; lightSampler.sample2D(lightSampleIndex, lsu, lsv); Point lightPoint; Vector lightNormal; float lightPdf = 0.0f; pLightShape->sampleSurface(position, normal, lsu, lsv, lightPoint, lightNormal, lightPdf); if (lightPdf > 0.0f) { // Ask the BRDF what it thinks of this light position (for MIS) Vector lightIncoming = position - lightPoint; float lightDistance = lightIncoming.normalize(); float brdfPdf = 0.0f; float brdfResult = pBrdf->evaluateSA(lightIncoming, outgoing, normal, brdfPdf); if (brdfResult > 0.0f && brdfPdf > 0.0f) { // Fire a shadow ray to make sure we can actually see the light position Ray shadowRay(position, -lightIncoming, lightDistance - kRayTMin); if (!scene.doesIntersect(shadowRay)) { // The light point is visible, so let's add that // contribution (mixed by MIS) float misWeightLight = powerHeuristic(1, lightPdf, 1, brdfPdf); lightResult += pLightShape->emitted() * intersection.m_colorModifier * matColor * brdfResult * std::fabs(dot(-lightIncoming, normal)) * misWeightLight / (lightPdf * brdfWeight); } } } // Ask the BRDF for a sample direction float bsu, bsv; brdfSampler.sample2D(lightSampleIndex, bsu, bsv); Vector brdfIncoming; float brdfPdf = 0.0f; float brdfResult = pBrdf->sampleSA(brdfIncoming, outgoing, normal, bsu, bsv, brdfPdf); if (brdfPdf > 0.0f && brdfResult > 0.0f) { Intersection shadowIntersection(Ray(position, -brdfIncoming)); bool intersected = scene.intersect(shadowIntersection); if (intersected && shadowIntersection.m_pShape == pLightShape) { // Ask the light what it thinks of this direction (for MIS) lightPdf = pLightShape->intersectPdf(shadowIntersection); if (lightPdf > 0.0f) { // BRDF chose the light, so let's add that // contribution (mixed by MIS) float misWeightBrdf = powerHeuristic(1, brdfPdf, 1, lightPdf); lightResult += pLightShape->emitted() * intersection.m_colorModifier * matColor * brdfResult * std::fabs(dot(-brdfIncoming, normal)) * misWeightBrdf / (brdfPdf * brdfWeight); } } } } // Average light samples if (numLightSamples) { lightResult /= numLightSamples; } // Add direct lighting at this bounce (modified by how much the // previous bounces have dimmed it) result += throughput * lightResult; } // Sample the BRDF to find the direction the next leg of the path goes in float brdfSampleU, brdfSampleV; bounceSamplers[numBounces]->sample2D(pixelSampleIndex, brdfSampleU, brdfSampleV); Vector incoming; float incomingBrdfPdf = 0.0f; float incomingBrdfResult = pBrdf->sampleSA(incoming, outgoing, normal, brdfSampleU, brdfSampleV, incomingBrdfPdf); if (incomingBrdfPdf > 0.0f) { currentRay.m_origin = position; currentRay.m_direction = -incoming; currentRay.m_tMax = kRayTMax; // Reduce lighting effect for the next bounce based on this bounce's BRDF throughput *= intersection.m_colorModifier * matColor * incomingBrdfResult * (std::fabs(dot(-incoming, normal)) / (incomingBrdfPdf * brdfWeight)); } else { break; // BRDF is zero, stop bouncing } numBounces++; } // This represents an estimate of the total light coming in along the path return result; }
Spectrum Path::estimateDirectLighting( const RayIntersection i_intersection, RenderablePtr i_light, const Sampler &i_sampler, const SceneData &i_scene ) { // Direct contribution Spectrum Ld = Spectrum::black(); float lightPdf = 0.0f; float surfacePdf = 0.0f; vec3f wiWorld; const vec3f &woWorld = -i_intersection.dir; const vec3f &woLocal = i_intersection.worldToSurface( woWorld ); const vec3f &normal = i_intersection.getSurfacePoint().normal; const shading::BSDF &bsdf = i_intersection.getBSDF(); /* ==================== */ /* Sample Light */ /* ==================== */ { VisibilityTester visibilityTester; const vec3f &uLight = i_sampler.sample3D(); Spectrum Li = i_light->sampleIncidenceRadiance( i_intersection, uLight, &wiWorld, &lightPdf, &visibilityTester ); // Convert to local (surface) coords const vec3f &wiLocal = i_intersection.worldToSurface( wiWorld ); // Sample light if ( !Li.isBlack() && lightPdf > 0.0f ) { const Spectrum &f = bsdf.computeF( woLocal, wiLocal ) * absDot( wiWorld, normal ); surfacePdf = bsdf.pdf( woLocal, wiLocal ); if ( !f.isBlack() ) { // Ray is in shadow if ( visibilityTester.isOccluded( i_scene ) ) { Li = Spectrum::black(); } if ( !Li.isBlack() ) { // Weight with MIS float misWeight = powerHeuristic( lightPdf, surfacePdf ); Ld += ( Li * f * misWeight ) / lightPdf; } } } } /* ==================== */ /* Sample BSDF */ /* ==================== */ { const vec2f &uSurface = i_sampler.sample2D(); vec3f wiLocal; const Spectrum &f = bsdf.sampleF( woLocal, &wiLocal, uSurface, &surfacePdf ) * absDot( wiWorld, normal ); if ( !f.isBlack() && surfacePdf > 0.0f ) { // TODO computer light PDF lightPdf = 0.0; // No contribution, return if ( lightPdf == 0.0 ) { return Ld; } // Convert to local (surface) coords vec3f wiWorld = i_intersection.surfaceToWorld( wiLocal ); const Ray bsdfRay = Ray( i_intersection.getSurfacePoint().pos, wiWorld ); RayIntersection lightIntersection; bool foundIntersection = i_scene.intersect( bsdfRay, lightIntersection ); Spectrum Li = Spectrum::black(); if ( foundIntersection ) { if ( lightIntersection.m_shapeId == i_light->getIdentifier() ) { Li = i_light->computeRadiance( lightIntersection.getSurfacePoint(), wiWorld ); } } if ( !Li.isBlack() ) { // Weight with MIS float misWeight = powerHeuristic( lightPdf, surfacePdf ); Ld += ( Li * f * misWeight ) / surfacePdf; } } } return Ld; }