/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2010 by Wenzel Jakob and others. Mitsuba is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. Mitsuba is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include MTS_NAMESPACE_BEGIN /* Precompute cosine/sine values for quick conversions from quantized spherical coordinates to floating point vectors. (Precomputation idea based on Jensen's implementation.) */ Float PhotonMap::m_cosTheta[256]; Float PhotonMap::m_sinTheta[256]; Float PhotonMap::m_cosPhi[256]; Float PhotonMap::m_sinPhi[256]; Float PhotonMap::m_expTable[256]; bool PhotonMap::createPrecompTables() { // Compiler sanity check #if !defined(DOUBLE_PRECISION) && SPECTRUM_SAMPLES == 3 if (sizeof(Photon) != 24) { cerr << "Internal error - incorrect photon storage size" << endl; exit(-1); } #endif for (int i=0; i<256; i++) { Float angle = (Float) i * ((Float) M_PI / 256.0f); m_cosPhi[i] = std::cos(2.0f * angle); m_sinPhi[i] = std::sin(2.0f * angle); /* Theta has twice the angular resolution */ m_cosTheta[i] = std::cos(angle); m_sinTheta[i] = std::sin(angle); m_expTable[i] = std::ldexp((Float) 1, i - (128+8)); } m_expTable[0] = 0; return true; } bool PhotonMap::m_precompTableReady = PhotonMap::createPrecompTables(); PhotonMap::Photon::Photon(const Point &p, const Normal &normal, const Vector &dir, const Spectrum &P, uint16_t _depth) { if (P.isNaN()) Log(EWarn, "Creating an invalid photon with power: %s", P.toString().c_str()); /* Possibly convert to single precision floating point (if Mitsuba is configured to use double precision) */ pos[0] = (float) p.x; pos[1] = (float) p.y; pos[2] = (float) p.z; depth = _depth; unused = 0; axis = -1; /* Convert the direction into an approximate spherical coordinate format to reduce storage requirements */ theta = (unsigned char) std::min(255, (int) (std::acos(dir.z) * (256.0 / M_PI))); int tmp = std::min(255, (int) (std::atan2(dir.y, dir.x) * (256.0 / (2.0 * M_PI)))); if (tmp < 0) phi = (unsigned char) (tmp + 256); else phi = (unsigned char) tmp; if (normal.isZero()) { thetaN = phiN = 0; } else { thetaN = (unsigned char) std::min(255, (int) (std::acos(normal.z) * (256.0 / M_PI))); tmp = std::min(255, (int) (std::atan2(normal.y, normal.x) * (256.0 / (2.0 * M_PI)))); if (tmp < 0) phiN = (unsigned char) (tmp + 256); else phiN = (unsigned char) tmp; } #if defined(DOUBLE_PRECISION) || SPECTRUM_SAMPLES > 3 power = P; #else /* Pack the photon power into Greg Ward's RGBE format */ P.toRGBE(power); #endif } PhotonMap::PhotonMap(size_t maxPhotons) : m_photonCount(0), m_maxPhotons(maxPhotons), m_minPhotons(8), m_numThreads(-1), m_balanced(false), m_scale(1.0f), m_context(NULL) { Assert(m_precompTableReady); /* For convenient heap addressing, the the photon list entries start with number 1 */ m_photons = new Photon[maxPhotons + 1]; } PhotonMap::PhotonMap(Stream *stream, InstanceManager *manager) : m_numThreads(-1), m_context(NULL) { m_aabb = AABB(stream); m_balanced = stream->readBool(); m_maxPhotons = (size_t) stream->readULong(); m_minPhotons = (size_t) stream->readULong(); m_lastInnerNode = (size_t) stream->readULong(); m_lastRChildNode = (size_t) stream->readULong(); m_scale = (Float) stream->readFloat(); m_photonCount = (size_t) stream->readULong(); m_photons = new Photon[m_maxPhotons + 1]; for (size_t i=1; i<=m_maxPhotons; ++i) m_photons[i] = Photon(stream); } PhotonMap::~PhotonMap() { if (m_context) delete[] m_context; delete[] m_photons; } std::string PhotonMap::toString() const { std::ostringstream oss; oss << "PhotonMap[" << endl << " aabb = " << m_aabb.toString() << "," << endl << " photonCount = " << m_photonCount << "," << endl << " maxPhotons = " << m_maxPhotons << "," << endl << " minPhotons = " << m_minPhotons << "," << endl << " balanced = " << m_balanced << "," << endl << " scale = " << m_scale << endl << "]"; return oss.str(); } void PhotonMap::serialize(Stream *stream, InstanceManager *manager) const { Log(EDebug, "Serializing a photon map (%.2f KB)", m_photonCount * 20.0f / 1024.0f); m_aabb.serialize(stream); stream->writeBool(m_balanced); stream->writeULong(m_maxPhotons); stream->writeULong(m_minPhotons); stream->writeULong(m_lastInnerNode); stream->writeULong(m_lastRChildNode); stream->writeFloat(m_scale); stream->writeULong(m_photonCount); for (size_t i=1; i<=m_maxPhotons; ++i) m_photons[i].serialize(stream); } bool PhotonMap::storePhoton(const Point &pos, const Normal &normal, const Vector &dir, const Spectrum &power, uint16_t depth) { Assert(!m_balanced); /* Overflow check */ if (m_photonCount >= m_maxPhotons) return false; /* Keep track of the volume covered by all stored photons */ m_aabb.expandBy(pos); m_photons[++m_photonCount] = Photon(pos, normal, dir, power, depth); return true; } bool PhotonMap::storePhoton(const Photon &photon) { Assert(!m_balanced); /* Overflow check */ if (m_photonCount >= m_maxPhotons) return false; /* Keep track of the volume covered by all stored photons */ m_aabb.expandBy(Point(photon.pos[0], photon.pos[1], photon.pos[2])); m_photons[++m_photonCount] = photon; return true; } void PhotonMap::prepareSMP(int numThreads) { int photonsPerThread = m_maxPhotons / numThreads; int remainder = m_maxPhotons - photonsPerThread * numThreads; m_numThreads = numThreads; m_context = new ThreadContext[numThreads]; for (int i=0; i= m_context[thread].maxPhotons) return false; /* Keep track of the volume covered by all stored photons */ m_context[thread].aabb.expandBy(pos); int idx = m_context[thread].photonCount++; m_photons[m_context[thread].photonOffset + idx] = Photon(pos, normal, dir, power, depth); return true; } /** * Relaxed partitioning algorithm based on code in stl_algo.h and Jensen's * reference implementation. This is *much* faster than std::partition * by relaxing the partitioning rules, making use of random access iterators * and assuming that there is a guard element following the right boundary. * * This function accepts *two* predicates and assumes that * pred1(x) == !pred2(x) for all given elements except for a set S, * where both predicates return false. After the algorithm finishes, * elements in S may have been placed on either side. However, all elements * for which pred1(x) is true are guaranteed to be on the left side, and * all elements for which pred2(x) is true will have been moved to the * right side. * * Having two predicates is important for an efficient photon map * balancing implementation. If pred2 is simply the opposite of pred1, * the balancing routine will slow down to a crawl when it is applied to * an array containing lots of elements with the same value as the pivot. * (In the context of photon mapping, this can for example happen when the * scene contains an axis-aligned surface). Since it does not matter on * which side of the split these elements end up, the relaxed method * enables a much better separation into two sets. */ template inline _RandomAccessIterator guarded_partition( _RandomAccessIterator start, _RandomAccessIterator end, _Predicate1 pred1, _Predicate2 pred2) { end--; while (true) { while (pred1(*start)) /* Guarded */ start++; while (pred2(*end) && end > start) end--; if (start >= end) break; std::iter_swap(start++, end--); } return start; } /** * This algorithm works similarly to QUICKSORT, although it does not sort * the array. Instead, it creates a partition such that there is an ordering * of all entries with respect to the value at 'pivotIndex'. It does this by * repetetively partitioning according to some arbitrarily chosen pivot * (currently the rightmost one) and then recursing either into the left or * right halves. */ void PhotonMap::quickPartition(photon_iterator left, photon_iterator right, photon_iterator pivot, int axis) const { right--; while (right > left) { const float pivotValue = (*right)->pos[axis]; /* Relaxed quicksort-style partitioning routine. (See above for an explanation). */ photon_iterator mid = guarded_partition(left, right, comparePhotonLess(axis, pivotValue), comparePhotonGreater(axis, pivotValue) ); /* Move the pivot in between the two sets */ std::swap(*mid, *right); /* Is the requested pivot index located inside the left half? */ if (mid > pivot) right = mid - 1; /* Is the requested pivot index located inside the right half? */ else if (mid < pivot) left = mid + 1; else return; } } /** * This algorithm is based on Donald Knuth's book * "The Art of Computer Programming, Volume 3: Sorting and Searching" * (1st edition, section 5.2, page 595) * * Given a permutation and an array of values, it applies the permutation * in linear time without requiring additional memory. This is based on * the fact that each permutation can be decomposed into a disjoint set * of permutations, which can then be applied individually. */ template void permute_inplace(T *values, std::vector &perm) { for (size_t i=0; i 1. */ size_t PhotonMap::leftSubtreeSize(size_t treeSize) const { /* Layer 0 contains one node */ size_t p = 1; /* Traverse downwards until the first incompletely filled tree level is encountered */ while (2*p <= treeSize) p *= 2; /* Calculate the number of filled slots in the last level */ size_t remaining = treeSize - p + 1; if (2*remaining < p) { /* Case 2: The last level contains too few nodes. Remove overestimate from the left subtree node count and add the remaining nodes */ p = (p >> 1) + remaining; } return p - 1; } void PhotonMap::balance() { Assert(!m_balanced); /* Unify the results from all gather threads */ if (m_context != NULL) { for (int i=0; i photonPointers(m_photonCount + 1); /* Destination for the final heap permutation. Indexed starting at 1 */ std::vector heapPermutation(m_photonCount + 1); heapPermutation[0] = 0; for (size_t i=0; i<=m_photonCount; i++) photonPointers[i] = &m_photons[i]; Log(EInfo, "Photon map: balancing %i photons ..", m_photonCount); balanceRecursive(photonPointers.begin(), photonPointers.begin()+1, photonPointers.end(), heapPermutation, m_aabb, 1); /* 'heapPointers' now contains a permutation representing the properly left-balanced photon map. Apply this permutation to the photon array. */ permute_inplace(m_photons, heapPermutation); /* We want to quickly be able to determine whether a node at a given index is an inner node (e.g. it has left or right children) and specifically, if it has a right child */ m_lastInnerNode = m_photonCount/2; m_lastRChildNode = (m_photonCount-1)/2; m_balanced = true; } void PhotonMap::balanceRecursive(photon_iterator basePtr, photon_iterator sortStart, photon_iterator sortEnd, std::vector &heapPermutation, AABB &aabb, size_t heapIndex) const { /* A fully left-balanced binary tree has this many nodes on its left subtree */ size_t leftSize = leftSubtreeSize(sortEnd - sortStart); /* We choose a pivot index such that the resulting tree satisfies this property */ photon_iterator pivot = sortStart + leftSize; /* Splitting along the axis with the widest spread works well in practice and is cheap to compute (p.71) */ int splitAxis = aabb.getLargestAxis(); /* QUICKSORT-like partitioning iterations until the entry referenced by 'pivot' imposes an ordering wrt. all other photons in the range */ quickPartition(sortStart, sortEnd, pivot, splitAxis); Float splitPos = (*pivot)->pos[splitAxis]; /* Update the heap permutation and record the splitting axis */ heapPermutation[heapIndex] = *pivot - *basePtr; (*pivot)->axis = splitAxis; if (pivot > sortStart) { if (pivot > sortStart + 1) { /* There are more then two elements on the left subtree. Balance them recursively */ std::swap(aabb.max[splitAxis], splitPos); balanceRecursive(basePtr, sortStart, pivot, heapPermutation, aabb, leftChild(heapIndex)); std::swap(aabb.max[splitAxis], splitPos); } else { /* Leaf node - just copy. */ heapPermutation[leftChild(heapIndex)] = *sortStart - *basePtr; } } if (pivot < sortEnd - 1) { if (pivot < sortEnd - 2) { /* There are more then two elements on the right subtree. Balance them recursively */ std::swap(aabb.min[splitAxis], splitPos); balanceRecursive(basePtr, pivot+1, sortEnd, heapPermutation, aabb, rightChild(heapIndex)); std::swap(aabb.min[splitAxis], splitPos); } else { /* Leaf node - just copy. */ heapPermutation[rightChild(heapIndex)] = *(sortEnd-1) - *basePtr; } } } unsigned int PhotonMap::nnSearch(const Point &p, Float &searchRadiusSquared, unsigned int maxSize, search_result *results) const { const float pos[3] = { (float) p.x, (float) p.y, (float) p.z }; size_t stack[MAX_PHOTONMAP_DEPTH]; unsigned int index = 1, stackPos = 1, fill = 0; bool isPriorityQueue = false; float distSquared = (float) searchRadiusSquared; stack[0] = 0; while (index > 0) { const_photon_ptr photon = &m_photons[index]; /* Recurse on inner nodes */ if (isInnerNode(index)) { float distToPlane = pos[photon->axis] - photon->pos[photon->axis]; /* Does the search region overlap with both split half-spaces? */ bool searchBoth = (distToPlane*distToPlane <= distSquared); if (distToPlane > 0) { /* The search query is located on the right side of the split. Search this side first. */ if (hasRightChild(index)) { if (searchBoth) stack[stackPos++] = leftChild(index); index = rightChild(index); } else if (searchBoth) { index = leftChild(index); } else { index = stack[--stackPos]; } } else { /* The search query is located on the left side of the split. Search this side first. */ if (searchBoth && hasRightChild(index)) stack[stackPos++] = rightChild(index); index = leftChild(index); } } else { index = stack[--stackPos]; } /* Check if the current photon is within the query's search radius */ const float photonDistSquared = photon->distSquared(pos); if (photonDistSquared < distSquared) { /* Similarly to Jensen's implementation, the search switches to a priority queue when the available search result space is exhausted */ if (fill < maxSize) { /* There is still room, just add the photon to the search result list */ results[fill++] = search_result(photonDistSquared, photon); } else { search_result *begin = results, *end = begin + maxSize + 1; if (!isPriorityQueue) { /* Establish the MAX-heap property */ std::make_heap(begin, begin + maxSize, distanceMetric()); isPriorityQueue = true; } /* Add the new photon, remove the one farthest away */ results[fill] = search_result(photonDistSquared, photon); std::push_heap(begin, end, distanceMetric()); std::pop_heap(begin, end, distanceMetric()); /* Reduce the search radius accordingly */ distSquared = results[0].first; } } } searchRadiusSquared = (Float) distSquared; return fill; } Spectrum PhotonMap::estimateIrradiance(const Point &p, const Normal &n, Float searchRadius, unsigned int maxPhotons) const { Spectrum result(0.0f); /* The photon map needs to be balanced before executing searches */ Assert(m_balanced); /* Search for photons contained within a spherical region */ Float distSquared = searchRadius*searchRadius; search_result *results = static_cast(alloca((maxPhotons+1) * sizeof(search_result))); unsigned int resultCount = nnSearch(p, distSquared, maxPhotons, results); /* Avoid very noisy estimates */ if (resultCount < m_minPhotons) return result; /* Sum over all contributions */ for (unsigned int i=0; i(alloca((maxPhotons+1) * sizeof(search_result))); unsigned int resultCount = nnSearch(p, distSquared, maxPhotons, results); /* Avoid very noisy estimates */ if (EXPECT_NOT_TAKEN(resultCount < m_minPhotons)) return result; /* Sum over all contributions */ for (unsigned int i=0; i(alloca((maxPhotons+1) * sizeof(search_result))); unsigned int resultCount = nnSearch(p, distSquared, maxPhotons, results); /* Avoid very noisy estimates */ if (EXPECT_NOT_TAKEN(resultCount < m_minPhotons)) return Spectrum(0.0f); /* Sum over all contributions */ for (unsigned int i=0; i 0) continue; const Float sqrTerm = 1.0f - photonDistanceSqr/distSquared, weight = sqrTerm*sqrTerm; const __m128 mantissa = _mm_cvtepi32_ps( _mm_set_epi32(photon.power[0], photon.power[1], photon.power[2], 0)), exponent = _mm_load1_ps(&m_expTable[photon.power[3]]), packedWeight = _mm_load1_ps(&weight), value = _mm_mul_ps(packedWeight, _mm_mul_ps(mantissa, exponent)); result.ps = _mm_add_ps(value, result.ps); } /* Based on the assumption that the surface is locally flat, the estimate is divided by the area of a disc corresponding to the projected spherical search region */ result.ps = _mm_mul_ps(result.ps, _mm_set1_ps(m_scale * 3 * INV_PI / distSquared)); Spectrum spec; spec.fromLinearRGB(result.f[3], result.f[2], result.f[1]); return spec; } #endif Spectrum PhotonMap::estimateRadianceFiltered(const Intersection &its, Float searchRadius, unsigned int maxPhotons) const { Spectrum result(0.0f); const BSDF *bsdf = its.shape->getBSDF(); /* The photon map needs to be balanced before executing searches */ Assert(m_balanced); /* Search for photons contained within a spherical region */ Float distSquared = searchRadius*searchRadius; search_result *results = static_cast(alloca((maxPhotons+1) * sizeof(search_result))); unsigned int resultCount = nnSearch(its.p, distSquared, maxPhotons, results); /* Avoid very noisy estimates */ if (EXPECT_NOT_TAKEN(resultCount < m_minPhotons)) return Spectrum(0.0f); /* Sum over all contributions */ for (unsigned int i=0; if(BSDFQueryRecord(its, wi)) * weight); } /* Based on the assumption that the surface is locally flat, the estimate is divided by the area of a disc corresponding to the projected spherical search region */ return result * (m_scale * 3 * INV_PI / distSquared); } int PhotonMap::estimateRadianceRaw(const Intersection &its, Float searchRadius, Spectrum &result, int maxDepth) const { result = Spectrum(0.0f); const BSDF *bsdf = its.shape->getBSDF(); /* The photon map needs to be balanced before executing searches */ Assert(m_balanced); const float pos[3] = { (float) its.p.x, (float) its.p.y, (float) its.p.z }; size_t stack[MAX_PHOTONMAP_DEPTH]; unsigned int index = 1, stackPos = 1, resultCount = 0; float distSquared = (float) searchRadius*searchRadius; stack[0] = 0; while (index > 0) { const_photon_ptr photon = &m_photons[index]; /* Recurse on inner nodes */ if (isInnerNode(index)) { float distToPlane = pos[photon->axis] - photon->pos[photon->axis]; /* Does the search region overlap with both split half-spaces? */ bool searchBoth = (distToPlane*distToPlane <= distSquared); if (distToPlane > 0) { /* The search query is located on the right side of the split. Search this side first. */ if (hasRightChild(index)) { if (searchBoth) stack[stackPos++] = leftChild(index); index = rightChild(index); } else if (searchBoth) { index = leftChild(index); } else { index = stack[--stackPos]; } } else { /* The search query is located on the left side of the split. Search this side first. */ if (searchBoth && hasRightChild(index)) stack[stackPos++] = rightChild(index); index = leftChild(index); } } else { index = stack[--stackPos]; } /* Check if the current photon is within the query's search radius */ const float photonDistSquared = photon->distSquared(pos); if (photonDistSquared < distSquared) { Normal photonNormal(photon->getNormal()); Vector wiWorld = -photon->getDirection(); if (photon->getDepth() > maxDepth || dot(photonNormal, its.shFrame.n) < .1 || dot(photonNormal, wiWorld) < 1e-2) continue; Vector wiLocal = its.toLocal(wiWorld); BSDFQueryRecord bRec(its, wiLocal); bRec.quantity = EImportance; std::swap(bRec.wi, bRec.wo); Spectrum weight(1.0f); /* Account for non-symmetry due to shading normals */ result += photon->getPower() * bsdf->f(bRec) * std::abs(Frame::cosTheta(wiLocal) / dot(photonNormal, wiWorld)); resultCount++; } } return resultCount; } Spectrum PhotonMap::estimateVolumeRadiance(const MediumSamplingRecord &mRec, const Ray &ray, Float searchRadius, unsigned int maxPhotons, const Medium *medium) const { Spectrum result(0.0f); /* The photon map needs to be balanced before executing searches */ Assert(m_balanced); /* Search for photons contained within a spherical region */ Float distSquared = searchRadius*searchRadius; search_result *results = static_cast(alloca((maxPhotons+1) * sizeof(search_result))); unsigned int resultCount = nnSearch(ray.o, distSquared, maxPhotons, results); /* Avoid very noisy estimates */ if (EXPECT_NOT_TAKEN(resultCount < m_minPhotons)) return Spectrum(0.0f); const PhaseFunction *phase = medium->getPhaseFunction(); Vector wo = -ray.d; /* Sum over all contributions */ for (unsigned int i=0; if(mRec, photon.getDirection(), wo)); } const Float volFactor = (4/(Float) 3) * (Float) M_PI * distSquared * std::sqrt(distSquared); return result * (m_scale / volFactor); } void PhotonMap::setScale(Float value) { m_scale = value; } void PhotonMap::setMinPhotons(int minPhotons) { m_minPhotons = minPhotons; } void PhotonMap::dumpOBJ(const std::string &filename) { std::ofstream os(filename.c_str()); os << "o Photons" << endl; for (unsigned int i=1; i<=getPhotonCount(); i++) { Point p = getPhotonPosition(i); os << "v " << p.x << " " << p.y << " " << p.z << endl; } /// Need to generate some fake geometry so that blender will import the points for (unsigned int i=3; i<=getPhotonCount(); i++) os << "f " << i << " " << i-1 << " " << i-2 << endl; os.close(); } MTS_IMPLEMENT_CLASS_S(PhotonMap, false, Object) MTS_NAMESPACE_END