// we force to connect eye path to light path Color3 BidirPathTracing::connectVertices(BidirPathState& lightState , BSDF& cameraBsdf , const Vector3& hitPos , BidirPathState& cameraState) { Vector3 dir = lightState.pos - hitPos; Real dist2 = dir.sqrLength(); Real dist = std::sqrt(dist2); dir = dir / dist; Color3 res(0); Real cosAtCamera , cameraBsdfDirPdf , cameraBsdfRevPdf; Color3 cameraBsdfFactor = cameraBsdf.f(scene , dir , cosAtCamera , &cameraBsdfDirPdf , &cameraBsdfRevPdf); if (cameraBsdfFactor.isBlack()) return res; Real cameraContProb = cameraBsdf.continueProb; cameraBsdfDirPdf *= cameraContProb; cameraBsdfRevPdf *= cameraContProb; Real cosAtLight , lightBsdfDirPdf , lightBsdfRevPdf; Color3 lightBsdfFactor = lightState.bsdf.f(scene , -dir , cosAtLight , &lightBsdfDirPdf , &lightBsdfRevPdf); if (lightBsdfFactor.isBlack()) return res; Real lightContProb = lightState.bsdf.continueProb; lightBsdfDirPdf *= lightContProb; lightBsdfRevPdf *= lightContProb; Real geometryTerm = cosAtLight * cosAtCamera / dist2; if (cmp(geometryTerm) < 0) return res; Real cameraBsdfDirPdfArea = pdfWtoA(cameraBsdfDirPdf , dist , cosAtLight); Real lightBsdfDirPdfArea = pdfWtoA(lightBsdfDirPdf , dist , cosAtCamera); res = (cameraBsdfFactor | lightBsdfFactor) * geometryTerm; if (res.isBlack() || scene.occluded(hitPos , dir , hitPos + dir * dist)) return Color3(0); Real wLight = mis(cameraBsdfDirPdfArea) * (lightState.dVCM + mis(lightBsdfRevPdf) * lightState.dVC); Real wCamera = mis(lightBsdfDirPdfArea) * (cameraState.dVCM + mis(cameraBsdfRevPdf) * cameraState.dVC); Real weight = 1.f / (wLight + 1.f + wCamera); return res * weight; }
static Color3 finalGathering(KdTree<Photon> *map , Scene& scene , Intersection& inter , RNG& rng , const Vector3& wo , int gatherSamples , int knn , Real maxSqrDis) { Color3 res = Color3(0.0 , 0.0 , 0.0); for (int i = 0; i < gatherSamples; i++) { Real pdf; Vector3 wi = sampleCosHemisphere(rng.randVector3() , &pdf); Ray ray = Ray(inter.p + wi * EPS , wi); Intersection _inter; Geometry *_g = scene.intersect(ray , _inter); if (_g == NULL) continue; Color3 tmp = estimate(map , 0 , knn , scene , _inter , -wi , maxSqrDis); BSDF bsdf(wi , _inter , scene); Real cosine , bsdfPdf; Color3 brdf = bsdf.f(scene , wo , cosine , &bsdfPdf); if (brdf.isBlack()) continue; pdf *= bsdfPdf; res = res + (tmp | brdf) * (cosine / pdf); } res = res / gatherSamples; return res; }
Color3 BidirPathTracing::getLightRadiance(AbstractLight *light , BidirPathState& cameraState , const Vector3& hitPos , const Vector3& rayDir) { int lightCount = scene.lights.size(); Real lightPickProb = 1.f / lightCount; Real directPdfArea , emissionPdf; Color3 radiance = light->getRadiance(scene.sceneSphere , rayDir , hitPos , &directPdfArea , &emissionPdf); Color3 res(0); if (radiance.isBlack()) return res; if (cameraState.pathLength == 1) return radiance; directPdfArea *= lightPickProb; emissionPdf *= lightPickProb; Real wCamera = mis(directPdfArea) * cameraState.dVCM + mis(emissionPdf) * cameraState.dVC; Real weight = 1.f / (1.f + wCamera); return radiance * weight; }
Color3 BidirPathTracing::connectToCamera(BidirPathState& lightState , const Vector3& hitPos , BSDF& bsdf) { Color3 res(0); Camera& camera = scene.camera; Vector3 dirToCamera = camera.pos - hitPos; if (((-dirToCamera) ^ camera.forward) <= 0) return res; Real distEye2 = dirToCamera.sqrLength(); Real dist = std::sqrt(distEye2); dirToCamera = dirToCamera / dist; Real cosToCamera , bsdfDirPdf , bsdfRevPdf; Color3 bsdfFactor = bsdf.f(scene , dirToCamera , cosToCamera , &bsdfDirPdf , &bsdfRevPdf); if (bsdfFactor.isBlack()) return res; bsdfRevPdf *= bsdf.continueProb; Real cosAtCamera = ((-dirToCamera) ^ camera.forward); Real imagePointToCameraDist = camera.imagePlaneDist / cosAtCamera; Real imageToSolidAngleFactor = SQR(imagePointToCameraDist) / cosAtCamera; Real imageToSurfaceFactor = imageToSolidAngleFactor * std::abs(cosToCamera) / distEye2; Real cameraPdfA = imageToSurfaceFactor /* * 1.f */; // pixel area is 1 Real surfaceToImageFactor = 1.f / imageToSurfaceFactor; // We divide the contribution by surfaceToImageFactor to convert the (already // divided) pdf from surface area to image plane area, w.r.t. which the // pixel integral is actually defined. We also divide by the number of samples // this technique makes res = (lightState.throughput | bsdfFactor) / (lightPathNum * surfaceToImageFactor); if (res.isBlack()) return res; if (scene.occluded(hitPos , dirToCamera , camera.pos)) return Color3(0); Real wLight = mis(cameraPdfA / lightPathNum) * (lightState.dVCM + mis(bsdfRevPdf) * lightState.dVC); Real weight = 1.f / (wLight + 1.f); //fprintf(fp , "weight = %.6f\n" , weight); return res * weight; }
bool BidirPathTracing::sampleScattering(BSDF& bsdf , const Vector3& hitPos , BidirPathState& pathState) { Real bsdfDirPdf , cosWo; int sampledBSDFType; Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() , pathState.dir , bsdfDirPdf , cosWo , &sampledBSDFType); if (bsdfFactor.isBlack()) return 0; Real bsdfRevPdf = bsdfDirPdf; if ((sampledBSDFType & BSDF_SPECULAR) == 0) bsdfRevPdf = bsdf.pdf(scene , pathState.dir , 1); Real contProb = bsdf.continueProb; if (rng.randFloat() > contProb) return 0; bsdfDirPdf *= contProb; bsdfRevPdf *= contProb; // Partial sub-path MIS quantities // the evaluation is completed when the actual hit point is known! // i.e. after tracing the ray, out of the procedure if (sampledBSDFType & BSDF_SPECULAR) { pathState.specularVertexNum++; pathState.dVCM = 0.f; pathState.dVC *= mis(cosWo); } else { pathState.specularPath &= 0; pathState.dVC = mis(1.f / bsdfDirPdf) * (pathState.dVCM + pathState.dVC * mis(bsdfRevPdf)); pathState.dVCM = mis(1.f / bsdfDirPdf); } pathState.origin = hitPos; pathState.throughput = (pathState.throughput | bsdfFactor) * (cosWo / bsdfDirPdf); return 1; }
static Color3 directIllumination(Scene& scene , Intersection& inter , RNG& rng , const Vector3& visionDir) { Color3 res = Color3(0.0 , 0.0 , 0.0); Vector3 lightDir; Real cosine; Color3 brdf; Ray ray; BSDF bsdf(visionDir , inter , scene); int N = 1; for (int i = 0; i < N; i++) { int k = rand() % scene.lights.size(); AbstractLight *l = scene.lights[k]; Vector3 dirToLight; Real dist , directPdf; Color3 illu = l->illuminance(scene.sceneSphere , inter.p , rng.randVector3() , dirToLight , dist , directPdf); illu = illu / (directPdf / scene.lights.size()); if (scene.occluded(inter.p + dirToLight * EPS , dirToLight , inter.p + dirToLight * (inter.t - EPS))) continue; Real bsdfPdf; brdf = bsdf.f(scene , dirToLight , cosine , &bsdfPdf); if (brdf.isBlack()) continue; res = res + (illu | brdf) * (cosine / bsdfPdf); } res = res / N; return res; }
void PhotonIntegrator::buildPhotonMap(Scene& scene) { if (scene.lights.size() <= 0) return; causticPhotons.clear(); indirectPhotons.clear(); bool causticDone = 0 , indirectDone = 0; int nShot = 0; bool specularPath; int nIntersections; Intersection inter; Real lightPickProb = 1.f / scene.lights.size(); while (!causticDone || !indirectDone) { nShot++; /* generate initial photon ray */ int k = (int)(rng.randFloat() * scene.lights.size()); Vector3 lightPos , lightDir; Real emissionPdf , directPdfArea , cosAtLight; Color3 alpha; AbstractLight *l = scene.lights[k]; alpha = l->emit(scene.sceneSphere , rng.randVector3() , rng.randVector3() , lightPos , lightDir , emissionPdf , &directPdfArea , &cosAtLight); alpha = alpha * cosAtLight / (emissionPdf * lightPickProb); Ray photonRay(lightPos , lightDir); if (!alpha.isBlack()) { specularPath = 1; nIntersections = 0; Geometry *g = scene.intersect(photonRay , inter); while (g != NULL && inter.matId > 0) { BSDF bsdf(-photonRay.dir , inter , scene); nIntersections++; bool hasNonSpecular = (cmp(bsdf.componentProb.diffuseProb) > 0 || cmp(bsdf.componentProb.glossyProb) > 0); if (hasNonSpecular) { Photon photon(inter.p , -photonRay.dir , alpha); if (specularPath && nIntersections > 1) { if (!causticDone) { causticPhotons.push_back(photon); if (causticPhotons.size() == nCausticPhotons) { causticDone = 1; nCausticPaths = nShot; causticMap = new KdTree<Photon>(causticPhotons); } } } else { if (nIntersections > 1 && !indirectDone) { indirectPhotons.push_back(photon); if (indirectPhotons.size() == nIndirectPhotons) { indirectDone = 1; nIndirectPaths = nShot; indirectMap = new KdTree<Photon>(indirectPhotons); } } } } if (nIntersections > maxPathLength) break; /* find new photon ray direction */ /* handle specular reflection and transmission first */ Real pdf , cosWo; int sampledType; Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() , photonRay.dir , pdf , cosWo , &sampledType); if (bsdfFactor.isBlack()) break; if (sampledType & BSDF_NON_SPECULAR) specularPath = 0; // Russian Roulette Real contProb = bsdf.continueProb; if (cmp(contProb - 1.f) < 0) { if (cmp(rng.randFloat() - contProb) > 0) break; pdf *= contProb; } alpha = (alpha | bsdfFactor) * (cosWo / pdf); photonRay.origin = inter.p + photonRay.dir * EPS; photonRay.tmin = 0.f; photonRay.tmax = INF; g = scene.intersect(photonRay , inter); } } } }
Color3 PhotonIntegrator::raytracing(const Ray& ray , int dep) { Color3 res = Color3(0.0 , 0.0 , 0.0); if (dep > 2) return res; Geometry *g = NULL; Intersection inter; Ray reflectRay , transRay; g = scene.intersect(ray , inter); if (g == NULL) return res; if (inter.matId < 0) { AbstractLight *l = scene.lights[-inter.matId - 1]; return l->getIntensity() * 100.f; } res = res + directIllumination(scene , inter , rng , -ray.dir); res = res + estimate(indirectMap , nIndirectPaths , knnPhotons , scene , inter , -ray.dir , maxSqrDis); // final gathering is too slow! //res = res + finalGathering(indirectMap , scene , inter , rng , -ray.dir , 50 , knnPhotons , maxSqrDis); res = res + estimate(causticMap , nCausticPaths , knnPhotons , scene , inter , -ray.dir , maxSqrDis); BSDF bsdf(-ray.dir , inter , scene); Real pdf , cosWo; int sampledType; Ray newRay; Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() , newRay.dir , pdf , cosWo , &sampledType); if (bsdfFactor.isBlack()) return res; // Russian Roulette Real contProb = bsdf.continueProb; if (cmp(contProb - 1.f) < 0) { if (cmp(rng.randFloat() - contProb) > 0) return res; pdf *= contProb; } newRay.origin = inter.p + newRay.dir * EPS; newRay.tmin = 0; newRay.tmax = INF; Color3 contrib = raytracing(newRay , dep + 1); res = res + (contrib | bsdfFactor) * (cosWo / pdf); return res; }
static Color3 estimate(KdTree<Photon> *map , int nPaths , int knn , Scene& scene , Intersection& inter , const Vector3& wo , Real maxSqrDis) { Color3 res = Color3(0.0 , 0.0 , 0.0); if (map == NULL) return res; Photon photon; photon.pos = inter.p; ClosePhotonQuery query(knn , 0); Real searchSqrDis = maxSqrDis; Real msd; /* max square distance */ while (query.kPhotons.size() < knn) { msd = searchSqrDis; query.init(msd); map->searchKnn(0 , photon.pos , query); searchSqrDis *= 2.0; } int nFoundPhotons = query.kPhotons.size(); if (nFoundPhotons == 0) return res; Vector3 nv; if (cmp(wo ^ inter.n) < 0) nv = -inter.n; else nv = inter.n; Real scale = 1.0 / (PI * msd * nFoundPhotons); BSDF bsdf(wo , inter , scene); Real cosTerm , pdf; for (int i = 0; i < nFoundPhotons; i++) { /* Real k = kernel(kPhotons[i].photon , p , msd); k = 1.0 / PI; Real scale = k / (nPaths * msd); */ if (cmp(nv ^ query.kPhotons[i].photon->wi) > 0) { Color3 brdf = bsdf.f(scene , query.kPhotons[i].photon->wi , cosTerm , &pdf); if (brdf.isBlack()) continue; res = res + (brdf | query.kPhotons[i].photon->alpha) * (cosTerm * scale / pdf); } else { Vector3 wi(query.kPhotons[i].photon->wi); wi.z *= -1.f; Color3 brdf = bsdf.f(scene , wi , cosTerm , &pdf); if (brdf.isBlack()) continue; res = res + (brdf | query.kPhotons[i].photon->alpha) * (cosTerm * scale / pdf); } } return res; }
Color3 BidirPathTracing::getDirectIllumination(BidirPathState& cameraState , const Vector3& hitPos , BSDF& bsdf) { Color3 res(0); Real weight = 0.f; int lightCount = scene.lights.size(); Real lightPickProb = 1.f / lightCount; int lightId = (int)(rng.randFloat() * lightCount); AbstractLight *light = scene.lights[lightId]; Vector3 dirToLight; Real dist , directPdf , emissionPdf , cosAtLight , cosAtSurface; int sampledBSDFType; Color3 illu = light->illuminance(scene.sceneSphere , hitPos , rng.randVector3() , dirToLight , dist , directPdf , &emissionPdf , &cosAtLight); Real bsdfDirPdf , bsdfRevPdf , cosToLight; if (!illu.isBlack() && directPdf > 0) { Color3 bsdfFactor = bsdf.f(scene , dirToLight , cosToLight , &bsdfDirPdf , &bsdfRevPdf); Color3 tmp; if (!bsdfFactor.isBlack()) { Real contProb = bsdf.continueProb; bsdfDirPdf *= light->isDelta() ? 0.f : contProb; bsdfRevPdf *= contProb; tmp = (illu | bsdfFactor) * cosToLight / (directPdf * lightPickProb); if (!tmp.isBlack() && !scene.occluded(hitPos , dirToLight , hitPos + dirToLight * dist)) { Real wLight = mis(bsdfDirPdf) / mis(directPdf * lightPickProb); Real wCamera = mis(emissionPdf * cosToLight / (directPdf * cosAtLight)) * (cameraState.dVCM + mis(bsdfRevPdf) * cameraState.dVC); weight = 1.f / (wLight + 1.f + wCamera); //fprintf(fp , "weight = %.6f\n" , weight); if (light->isDelta()) { res = res + tmp; } else { Real _weight = mis(directPdf) / (mis(directPdf) + mis(bsdfDirPdf)); res = res + tmp * _weight; } } } } if (!light->isDelta()) { Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() , dirToLight , directPdf , cosAtSurface , &sampledBSDFType); if (!bsdfFactor.isBlack() && directPdf > 0) { Real weight = 1.f; Real lightPdf; if (!(sampledBSDFType & BSDF_SPECULAR)) { illu = light->getRadiance(scene.sceneSphere , dirToLight , hitPos , &lightPdf , &emissionPdf); if (cmp(lightPdf) == 0) return res; weight = mis(directPdf) / (mis(directPdf) + mis(lightPdf)); } Intersection lightInter; Color3 tmp(0.f); Ray ray(hitPos + dirToLight * EPS , dirToLight); if (scene.intersect(ray , lightInter) != NULL) { if (lightInter.matId < 0) { if (light != scene.lights[-lightInter.matId - 1]) { illu = Color3(0.f); } } else { illu = Color3(0.f); } } else { illu = Color3(0.f); if (scene.background != NULL) { illu = getLightRadiance(scene.background , cameraState , Vector3(0) , ray.dir); } } if (!illu.isBlack()) { tmp = (illu | bsdfFactor) * cosAtSurface / (directPdf); res = res + tmp * weight; } } } return res * weight; }