/* photonmap.C: implementation of the photonmap */ #include "photonmap.H" #include "statistics.h" #include "error.h" /**** Utility functions ****/ bool ZeroAlbedo(BSDF *bsdf, HITREC *hit, BSDFFLAGS flags) { COLOR col; col = BsdfScatteredPower(bsdf, hit, &hit->gnormal, flags); return (COLORAVERAGE(col) < EPSILON); } float GetFalseMonochrome(float val) { float tmp; // r=0,g=0,b=0; float max = pmapstate.falseColMax; if(pmapstate.falseColLog) { max = log(1.0 + max); val = log(1.0 + val); } //printf("log 10 = %g\n", log(10)); tmp = MIN(val,max); tmp = (tmp/max); return tmp; } COLOR GetFalseColor(float val) { RGB rgb; COLOR col; float max, tmp, r=0,g=0,b=0; if(pmapstate.falseColMono) { tmp = GetFalseMonochrome(val); RGBSET(rgb, tmp, tmp, tmp); RGBToColor(rgb, &col); return col; } max = pmapstate.falseColMax; if(pmapstate.falseColLog) { max = log(1.0 + max); val = log(1.0 + val); } //printf("log 10 = %g\n", log(10)); tmp = MIN(val,max); // Do some log scale ? tmp = 3.0 * (tmp/max); if(tmp <= 1.0) { b = tmp; } else if(tmp < 2.0) { g = tmp - 1.0; b = 1.0 - g; } else { r = tmp - 2.0; g = 1.0 - r; } RGBSET(rgb, r, g, b); RGBToColor(rgb, &col); return col; } /**** Photonmap methods ****/ // Constructor CPhotonMap::CPhotonMap(int *estimate_nrp, bool doPrecomputeIrradiance) { m_balanced = true; m_doBalancing = false; m_estimate_nrp = estimate_nrp; m_precomputeIrradiance = doPrecomputeIrradiance; m_irradianceComputed = false; if(doPrecomputeIrradiance) { //fprintf(stderr, "Irradiance photon kdtree\n"); m_kdtree = new CPhotonkdtree(3, sizeof(CIrrPhoton), true); // Data is copied } else { //fprintf(stderr, "Normal photon kdtree\n"); m_kdtree = new CPhotonkdtree(3, sizeof(CPhoton), true); // Data is copied } m_totalPaths = 0; m_nrPhotons = 0; m_totalPhotons = 0; m_grid = new CSampleGrid2D(2,4); VECTORSET(m_sampleLastPos, HUGE, HUGE, HUGE); m_photons = new CPhoton*[MAXRECONPHOTONS]; m_distances = new float[MAXRECONPHOTONS]; m_cosines = new float[MAXRECONPHOTONS]; m_nrpFound = 0; // no valid photons in array m_cosinesOk = true; } // Destructor CPhotonMap::~CPhotonMap(void) { delete m_kdtree; delete[] m_photons; delete[] m_distances; delete[] m_cosines; } // Initialize void CPhotonMap::Init(void) { // ??? } void CPhotonMap::ComputeCosines(VECTOR &normal) { VECTOR dir; if(!m_cosinesOk) { m_nrpCosinePos = 0; for(int i = 0; i < m_nrpFound; i++) { dir = m_photons[i]->Dir(); m_cosines[i] = VECTORDOTPRODUCT(dir, normal); if(m_cosines[i] > 0) m_nrpCosinePos++; } m_cosinesOk = true; } } // Adding photons void CPhotonMap::DoAddPhoton(CPhoton &photon, VECTOR &normal, short flags) { if(m_precomputeIrradiance) { CIrrPhoton irrPhoton; irrPhoton.Copy(photon); irrPhoton.SetNormal(normal); m_kdtree->AddPoint(&irrPhoton, flags); } else { m_kdtree->AddPoint(&photon, flags); } } bool CPhotonMap::AddPhoton(CPhoton &photon, VECTOR &normal, short flags) { drand48(); // Just to keep in sync with density controlled storage DoAddPhoton(photon, normal, flags); //m_kdtree->AddPoint(&photon, flags); m_nrPhotons++; m_totalPhotons++; m_balanced = false; m_irradianceComputed = false; return true; } double ComputeAcceptProb(float currentD, float requiredD) { // Step function if(pmapstate.acceptPdfType == STEP) { if(currentD > requiredD) return 0.0; else return 1.0; } else if(pmapstate.acceptPdfType == TRANSCOSINE) { // Translated cosine double ratio = MIN(1.0, currentD / requiredD); // in [0,1] return (0.5 * (1.0 + cos(ratio * M_PI))); } else { Error("ComputeAcceptProb", "Unknown accept pdf type"); return 0.0; } } void CPhotonMap::Redistribute(CPhoton &photon, float acceptProb, short flags) { // Redistribute this photon over the nearest neighbours // m_distances, m_photons and m_cosines should be filled correctly! // only photons are used for which direction * normal > 0 // -- Check the flags // -- normal weighted average? COLOR deltaPower; COLOR pow; float factor = 1.0 / m_nrpCosinePos; pow = photon.Power(); COLORSCALE(factor, pow, deltaPower); for(int i = 0; i < m_nrpFound; i++) { if(m_cosines[i] > 0.0) { m_photons[i]->AddPower(deltaPower); } } } bool CPhotonMap::DC_AddPhoton(CPhoton &photon, HITREC &hit, float requiredD, short flags) { // Get current density // VECTOR pos = photon.Pos(); bool stored; float currentD = GetCurrentDensity(hit, pmapstate.distribPhotons); // m_photons and m_distances is valid now !! // Compute acceptance probability double acceptProb = ComputeAcceptProb(currentD, requiredD); // printf("A prob %g, CD %g RD %g\n", acceptProb, currentD, requiredD); // Roulette if(drand48() < acceptProb) { // Store //photon.SetDCWeight(1.0 / acceptProb); DoAddPhoton(photon, hit.normal, flags); //m_kdtree->AddPoint(&photon, flags); m_nrPhotons++; m_balanced = false; m_irradianceComputed = false; stored = true; } else { // Redistribute power over neighbours or ignore stored = false; Redistribute(photon, acceptProb, flags); } m_totalPhotons++; // All photons including non stored photons return stored; } // End adding photons // void CPhotonMap::StopAddingMode(void) // { // m_addingMode = false; // // // Balance tree... // } /* Get a maximum radius^2 to be used when locating photons */ double CPhotonMap::GetMaxR2(void) { /* A maximum radius^2 is chosen as follows: * The radiance of the reconstruction must be larger * than a fraction of the radiance contribution when * taking into account all the stored photons (N_all) (some kind * of ambient radiance). R_all is the radius including all photons. * R_all^2 / N_all is approximated by total_area / M_PI * m_totalPaths * (This is an over estimation, which is ok for a maxr estimate) * BRDF evals are approximated by 1. */ const double radfraction = 0.03; double maxr2 = (*m_estimate_nrp * total_area / (M_PI * m_totalPaths * radfraction)); return maxr2; } // Precompute Irradiance void CPhotonMap::PhotonPrecomputeIrradiance(CIrrPhoton *photon) { COLOR irradiance, power; COLORCLEAR(irradiance); // locate nearest photons using a max radius limit VECTOR pos = photon->Pos(); m_nrpFound = DoQuery(&pos); if(m_nrpFound > 3) { // Construct irradiance estimate float maxDistance = m_distances[0]; for(int i = 0; i < m_nrpFound; i++) { if((photon->Normal() & m_photons[i]->Dir()) > 0) { power = m_photons[i]->Power(); COLORADD(irradiance, power, irradiance); } } // Now we have incoming radiance integrated over area estimate, // so we convert it to irradiance, maxDistance is already squared // An extra factor PI is added, that accounts for Albedo -> diffuse brdf... float factor = 1.0 / (M_PI * M_PI * maxDistance * m_totalPaths); COLORSCALE(factor, irradiance, irradiance); } photon->SetIrradiance(irradiance); } static void PrecomputeIrradianceCallback(CPhotonMap *map, CIrrPhoton *photon) { map->PhotonPrecomputeIrradiance(photon); } void CPhotonMap::PrecomputeIrradiance(void) { fprintf(stderr, "CPhotonMap::PrecomputeIrradiance\n"); if(m_precomputeIrradiance && !m_irradianceComputed) { m_kdtree->IterateNodes((void (*)(void *, void *))PrecomputeIrradianceCallback, this); m_irradianceComputed = true; } } // Reconstruct // bool CPhotonMap::IrradianceReconstruct(HITREC *hit, VECTOR &outDir, // BSDF *bsdf, BSDF *inBsdf, // BSDF *outBsdf, // COLOR *result) bool CPhotonMap::IrradianceReconstruct(HITREC *hit, VECTOR &outDir, COLOR& diffuseAlbedo, COLOR *result) { if(!m_irradianceComputed) PrecomputeIrradiance(); CIrrPhoton *photon = DoIrradianceQuery(&hit->point, &hit->normal); if(photon) { //float factor = 1.0 / (M_PI * m_totalPaths); //COLORPRODSCALED(photon->m_irradiance, factor, diffuseAlbedo, *result); COLORPROD(photon->m_irradiance, diffuseAlbedo, *result); // COLOR eval = BsdfEval(bsdf, hit, inBsdf, outBsdf, &outDir, // &hit->normal, BRDF_DIFFUSE_COMPONENT); // COLORPROD(photon->m_irradiance, eval, *result); return true; } else { // No appropriate photon found return false; } } COLOR CPhotonMap::Reconstruct(HITREC *hit, VECTOR &outDir, BSDF *bsdf, BSDF* inBsdf, BSDF *outBsdf) { // Find nearest photons float maxDistance = 0.0; COLOR result, eval, power, col; float factor; COLORCLEAR(result); COLOR diffuseAlbedo, glossyAlbedo; diffuseAlbedo = BsdfScatteredPower(bsdf, hit, &hit->gnormal, BRDF_DIFFUSE_COMPONENT); // -- TODO Irradiance precomputation for diffuse transmission glossyAlbedo = BsdfScatteredPower(bsdf, hit, &hit->gnormal, BTDF_DIFFUSE_COMPONENT | BSDF_GLOSSY_COMPONENT); //if(ZeroAlbedo(bsdf, hit, BSDF_DIFFUSE_COMPONENT | BSDF_GLOSSY_COMPONENT)) // return result; CheckNBalance(); if(COLORAVERAGE(glossyAlbedo) < EPSILON) { if(COLORAVERAGE(diffuseAlbedo) < EPSILON) return result; // No reflectance else { if(m_precomputeIrradiance) { // if(IrradianceReconstruct(hit, outDir, bsdf, inBsdf, outBsdf, &result)) if(IrradianceReconstruct(hit, outDir, diffuseAlbedo, &result)) return result; else // no appropriate irradiance photon -> do normal reconstruction { // fprintf(stderr, "No irradiance photon found\n"); } } } } // Normal reconstruct... // locate nearest photons using a max radius limit m_nrpFound = DoQuery(&hit->point); if(m_nrpFound < 3) return result; // Construct radiance estimate maxDistance = m_distances[0]; for(int i = 0; i < m_nrpFound; i++) { VECTOR dir = m_photons[i]->Dir(); eval = BsdfEval(bsdf, hit, inBsdf, outBsdf, &outDir, &dir, BSDF_DIFFUSE_COMPONENT|BSDF_GLOSSY_COMPONENT); power = m_photons[i]->Power(); COLORPROD(eval, power, col); COLORADD(result, col, result); } // Now we have a radiance integrated over area estimate, // so we convert it to radiance, maxDistance is already squared factor = 1.0 / (M_PI * maxDistance * m_totalPaths); COLORSCALE(factor, result, result); #ifdef PMAP_DEBUG printf("\nReconstruct: NRP %i MAXD %f\n", m_nrpFound, maxDistance); printf("POS: "); VectorPrint(stdout, pos); printf("\n"); printf("OUT: "); VectorPrint(stdout, outDir); printf("\n"); printf("N: "); VectorPrint(stdout, normal); printf("\n"); printf("RES: "); ColorPrint(stdout, result); printf("\n"); printf("-------------------------------------------------\n"); #endif return result; } float CPhotonMap::GetCurrentDensity(HITREC &hit, int nrPhotons) { // Find nearest photons if(nrPhotons == 0) nrPhotons=*m_estimate_nrp; float maxDistance = 0.0; if(nrPhotons == 0) return 0.0; m_nrpFound = DoQuery(&hit.point, nrPhotons, GetMaxR2()); if(m_nrpFound < 3) return 0.0; // Construct density estimate maxDistance = m_distances[0]; // Only valid since max heap is used in kdtree ComputeCosines(hit.gnormal); // -- shading normal ? if(m_nrpCosinePos <= 3) return 0.0; return(m_nrpCosinePos / (M_PI * maxDistance)); } // Return a color coded density of the photonmap COLOR CPhotonMap::GetDensityColor(HITREC &hit) { float density; COLOR result; density = GetCurrentDensity(hit); result = GetFalseColor(density); return result; } double CPhotonMap::Sample(VECTOR &pos, double *r, double *s, COORDSYS *coord, BSDFFLAGS flag, float n) { COLOR col; // printf("Sample\n"); // PrintCoordSys(stdout, coord); // -- Epsilon in as a function of scene/camera measure ?? if(!VECTOREQUAL(m_sampleLastPos, pos, 0.0001)) { // Need a new grid m_grid->Init(); // Find nearest photons m_nrpFound = DoQuery(&pos, m_sample_nrp, KDMAXRADIUS, NO_IMPSAMP_PHOTON); //printf("m_nrpFound %i\n", m_nrpFound); double pr,ps; for(int i = 0; i < m_nrpFound; i++) { m_photons[i]->FindRS(&pr, &ps, coord, flag, n); col = m_photons[i]->Power(); //printf("Photon %2i, D: ", i); VectorPrint(stdout, m_photons[i]->Dir()); //printf(" P: "); VectorPrint(stdout, m_photons[i]->Pos()); //printf("\n"); //printf("Ph %i, pr %g, ps %g, pow %g\n", i, pr, ps, // COLORAVERAGE(col)/m_nrPhotons); m_grid->Add(pr, ps, COLORAVERAGE(col)/m_nrPhotons); } m_grid->EnsureNonZeroEntries(); m_sampleLastPos = pos; // Caching } // Sample double pdf; m_grid->Sample(r,s, &pdf); return pdf; // printf("CPhotonMap::Sample nrp %i r %g s %g\n", m_nrpFound, *r, *s); } #include "hemirenderer.H" float CPhotonMap::GetGridValue(double phi, double theta) { double s = phi / (2 * M_PI); double tmp = cos(theta); double r = - tmp*tmp + 1; return m_grid->GetValue(r,s); } static void CPhotonMapGetGrid(VECTOR *pos, COORDSYS *coord, double phi, double theta, void *map, RGB *color, double *distance) { CPhotonMap *pm = (CPhotonMap *)map; COLOR colg,colr,colb; RGBToColor(Green, &colg); RGBToColor(Red, &colr); RGBToColor(Blue, &colb); COLOR dest=colb; COLORADDSCALED(colb, phi / (2*M_PI), colr, dest); COLORADDSCALED(dest, theta / (M_PI), colg, dest); ColorToRGB(dest, color); *distance = 0.1 * pm->GetGridValue(phi, theta); } double CPhotonMap::DebugSample(VECTOR &pos, double *r, double *s, COORDSYS *coord, BSDFFLAGS flag, float n) { double pdf = Sample(pos, r, s, coord, flag, n); // Now m_grid has the correct data m_grid->Print(); CHemisphereRenderer *hemi = new CHemisphereRenderer; hemi->Initialize(32,8, pos, coord, CPhotonMapGetGrid, (void *)this); hemi->EnableRendering(true); return pdf; }