mitsuba/src/librender/irrcache.cpp

389 lines
12 KiB
C++

/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2014 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 <http://www.gnu.org/licenses/>.
*/
#include <mitsuba/render/irrcache.h>
#include <mitsuba/core/statistics.h>
MTS_NAMESPACE_BEGIN
HemisphereSampler::HemisphereSampler(uint32_t M, uint32_t N) : m_M(M), m_N(N) {
m_entries = new SampleEntry[m_M*m_N];
m_uk = new Vector[m_N];
m_vk = new Vector[m_N];
m_vkMinus = new Vector[m_N];
m_random = new Random();
}
HemisphereSampler::~HemisphereSampler() {
delete[] m_entries;
delete[] m_uk;
delete[] m_vk;
delete[] m_vkMinus;
}
void HemisphereSampler::generateDirections(const Intersection &its) {
for (uint32_t j=0; j<m_M; j++) {
for (uint32_t k=0; k<m_N; k++) {
SampleEntry &entry = m_entries[j*m_N + k];
Point2 sample(m_random->nextFloat(), m_random->nextFloat());
/* Sample uniformly wrt. projected solid angles */
Float sinTheta2 = (j+sample.x)/m_M,
cosTheta = math::safe_sqrt(1 - sinTheta2),
sinTheta = std::sqrt(sinTheta2),
sinPhi, cosPhi;
math::sincos(2*M_PI*(k+sample.y)/m_N, &sinPhi, &cosPhi);
entry.d = its.shFrame.toWorld(Vector(
sinTheta*cosPhi, sinTheta*sinPhi, cosTheta));
entry.cosTheta = cosTheta;
entry.sinTheta = sinTheta;
entry.dist = -1;
}
}
/* Precompute planar vectors - see "Practical Global Illumination" by Jaroslav Krivanek
and Pascal Gautron for more details on this notation */
for (uint32_t k=0; k<m_N; k++) {
Float phi = 2*M_PI*(k+.5f)/m_N,
vk = phi - M_PI/2,
vkMinus = (2*M_PI*k)/m_N + M_PI/2;
/* v_k plane vectors (centered) */
m_vk[k] = its.shFrame.toWorld(Vector(std::cos(vk), std::sin(vk), 0));
/* v_{k-} (positioned at the start of the cell intervals) */
m_vkMinus[k] = its.shFrame.toWorld(Vector(std::cos(vkMinus), std::sin(vkMinus), 0));
/* u_k plane vectors (centered) */
m_uk[k] = its.shFrame.toWorld(Vector(std::cos(phi), std::sin(phi), 0));
}
}
void HemisphereSampler::process(const Intersection &its) {
for (int i=0; i<3; ++i) {
m_rGrad[i] = Spectrum(0.0f);
m_tGrad[i] = Spectrum(0.0f);
}
m_E = Spectrum(0.0f);
m_hMean = 0;
m_hMin = std::numeric_limits<Float>::infinity();
m_hMinRestricted = std::numeric_limits<Float>::infinity();
Float invDists = 0;
for (uint32_t j=0; j<m_M; j++) {
const Float cosThetaMinus = std::sqrt(1-j/(Float)m_M),
sinThetaMinus = std::sqrt(j/(Float)m_M),
cosTheta = std::sqrt(1-(j+.5f)/m_M),
sinTheta = std::sqrt((j+.5f)/m_M),
cosThetaPlus = std::sqrt(1-(j+1)/(Float)m_M),
cosThetaDiff = cosThetaMinus - cosThetaPlus,
tanTheta = sinTheta / cosTheta;
for (uint32_t k=0; k<m_N; k++) {
const SampleEntry &entry = m_entries[j*m_N + k];
/* Rotational gradient - \pi/(MN) * \sum_{k=0}^{N-1}(v_k \sum_{j=0}^{M-1}) \tan\theta_j * L_{jk}) */
for (int i=0; i<3; ++i)
m_rGrad[i] += entry.L * (-tanTheta * m_vk[k][i]);
if (j>1) {
/* Gradient in the u_k-direction */
const SampleEntry &other = m_entries[(j-1)*m_N + k];
const Float minDist = std::min(entry.dist, other.dist);
if (minDist > 0) {
const Float factor = (2*M_PI*cosThetaMinus*cosThetaMinus*sinThetaMinus)/(m_N*minDist);
const Spectrum spec = (entry.L - other.L) * factor;
for (int i=0; i<3; ++i)
m_tGrad[i] += spec * m_uk[k][i];
}
}
int kPrev = k-1;
if (kPrev < 0)
kPrev = m_N-1;
/* Gradient in the v_k-direction */
const SampleEntry &other = m_entries[j*m_N + kPrev];
const Float minDist = std::min(entry.dist, other.dist);
if (minDist > 0) {
const Spectrum spec = (entry.L - other.L) * (cosTheta * cosThetaDiff
/ (minDist * sinTheta));
for (int i=0; i<3; ++i)
m_tGrad[i] += spec * m_vkMinus[k][i];
}
if (entry.dist > 0) {
invDists += 1.0f / entry.dist;
m_hMin = std::min(m_hMin, entry.dist);
/* Discard rays close to the tangent plane */
if (entry.cosTheta > 0.173f) // at least 10deg
m_hMinRestricted = std::min(m_hMinRestricted, entry.dist);
}
m_E += entry.L;
}
}
if (invDists > 0)
m_hMean = (m_M*m_N) / invDists;
for (int i=0; i<3; ++i)
m_rGrad[i] *= M_PI/(m_M*m_N);
m_E *= M_PI / (m_M*m_N);
}
/* First pass of neighbor clamping */
struct clamp_self_functor {
clamp_self_functor(const Point &p, Float &R0) : p(p), R0(R0) {
}
void operator()(IrradianceCache::Record *sample) {
Float distance = (p - sample->p).length();
R0 = std::min(R0, sample->originalR0 + distance);
}
Point p;
Float &R0;
};
/* Second pass of neighbor clamping */
struct clamp_neighbors_functor {
clamp_neighbors_functor(const Point &p, Float R0) : p(p), R0(R0) {
}
void operator()(IrradianceCache::Record *sample) {
Float distance = (p - sample->p).length();
Float distanceLimit = R0 + distance;
if (sample->originalR0 > distanceLimit) {
/* Update valid range and clamp back into the
permitted interval */
sample->originalR0 = distanceLimit;
sample->R0 = std::min(sample->R0_max,
std::max(sample->R0_min, sample->originalR0));
}
}
Point p;
Float R0;
};
/* Irradiance interpolation functor */
struct irr_interp_functor {
irr_interp_functor(const Intersection &its, Float kappa, bool gradients) : its(its),
kappa(kappa), weightSum(0), gradients(gradients), E(0.0f) {
}
void operator()(const IrradianceCache::Record *sample) {
Float weight = sample->getWeight(its.p, its.shFrame.n, kappa);
if (weight == 0)
return;
Spectrum extrapolated = sample->E;
if (gradients) {
Vector crossN = cross(sample->n, its.shFrame.n);
Vector diff = its.p - sample->p;
for (int i=0; i<SPECTRUM_SAMPLES; ++i) {
for (int j=0; j<3; ++j) {
extrapolated[i] +=
crossN[j] * sample->rGrad[j][i]
+ diff[j] * sample->tGrad[j][i];
}
extrapolated[i] = std::max(extrapolated[i], (Float) 0);
}
}
E += weight * extrapolated;
weightSum += weight;
}
const Intersection &its;
Float kappa, weightSum;
bool gradients;
Spectrum E;
};
IrradianceCache::IrradianceCache(const AABB &aabb)
: m_octree(aabb) {
/* Use the longest AABB axis as an estimate of the scene dimensions */
m_sceneSize = (aabb.max-aabb.min)[aabb.getLargestAxis()];
m_mutex = new Mutex();
/* Reasonable default settings */
setQuality(1.0f);
useGradients(true);
clampNeighbor(true);
clampScreen(true);
}
IrradianceCache::IrradianceCache(Stream *stream, InstanceManager *manager) :
m_octree(AABB(stream)) {
m_mutex = new Mutex();
m_kappa = stream->readFloat();
m_sceneSize = stream->readFloat();
m_clampScreen = stream->readBool();
m_clampNeighbor = stream->readBool();
m_useGradients = stream->readBool();
size_t recordCount = stream->readSize();
m_records.reserve(recordCount);
for (size_t i=0; i<recordCount; ++i) {
Record *sample = new Record(stream);
Float validRadius = sample->R0 / (2*m_kappa);
m_octree.insert(sample, AABB(
sample->p-Vector(1,1,1)*validRadius,
sample->p+Vector(1,1,1)*validRadius
));
m_records.push_back(sample);
}
}
IrradianceCache::~IrradianceCache() {
for (size_t i=0; i<m_records.size(); ++i)
delete m_records[i];
}
void IrradianceCache::serialize(Stream *stream, InstanceManager *manager) const {
m_octree.getAABB().serialize(stream);
stream->writeFloat(m_kappa);
stream->writeFloat(m_sceneSize);
stream->writeBool(m_clampScreen);
stream->writeBool(m_clampNeighbor);
stream->writeBool(m_useGradients);
stream->writeSize(m_records.size());
for (uint32_t i=0; i<m_records.size(); ++i)
m_records[i]->serialize(stream);
}
IrradianceCache::Record *IrradianceCache::put(const RayDifferential &ray, const Intersection &its,
const HemisphereSampler &hs) {
const Spectrum &E = hs.getIrradiance();
TranslationalGradient tGrad;
for (int i=0; i<3; ++i)
tGrad[i] = hs.getTranslationalGradient()[i];
Float R0 = hs.getMinimumDistanceRestricted();
if (!E.isValid()) {
Log(EWarn, "Invalid irradiance cache sample: %s", E.toString().c_str());
return NULL;
}
Float R0_min = 0, R0_max = std::numeric_limits<Float>::infinity();
/* Clamping recommended by Tabellion and Lamourlette ("An Approximate Global
Illumination System for Computer Generated Films") */
if (m_clampScreen && ray.hasDifferentials) {
const Float d = -dot(its.geoFrame.n, Vector(its.p));
const Float txRecip = dot(its.geoFrame.n, ray.rxDirection),
tyRecip = dot(its.geoFrame.n, ray.ryDirection);
if (txRecip != 0 && tyRecip != 0) {
// Ray distances traveled
const Float tx = -(dot(its.geoFrame.n, Vector(ray.rxOrigin)) + d) /
txRecip;
const Float ty = -(dot(its.geoFrame.n, Vector(ray.ryOrigin)) + d) /
tyRecip;
Point px = ray.rxOrigin + ray.rxDirection * tx,
py = ray.ryOrigin + ray.ryDirection * ty;
Float sqrtArea = std::sqrt(cross(px-its.p, py-its.p).length())*2;
R0_min = 3.0f*sqrtArea;
R0_max = 20.0f*sqrtArea;
}
}
if (m_useGradients) {
/* Limit R0 by the gradient magnitude [Krivanek et al.] */
for (int i=0; i<SPECTRUM_SAMPLES; ++i) {
Vector grad(tGrad[0][i], tGrad[1][i], tGrad[2][i]);
Float length = grad.length();
if (length > Epsilon)
R0 = std::min(R0, E[i]/grad.length());
}
/* Limit the translational gradient magnitude [Krivanek et al.] */
for (int i=0; i<3; ++i)
tGrad[i] = tGrad[i] *
std::min((Float) 1, hs.getMinimumDistance() / R0_min);
}
if (m_clampNeighbor) {
/* Perform neighbor clamping [Krivanek et al.] to distribute
geometric feature information amongst neighboring hss */
clamp_self_functor clampSelf(its.p, R0);
m_octree.searchSphere(BSphere(its.p, R0), clampSelf);
clamp_neighbors_functor clampNeighbors(its.p, R0);
m_octree.searchSphere(BSphere(its.p, R0), clampNeighbors);
}
Record *record = new Record();
record->p = its.p;
record->n = its.shFrame.n;
record->E = E;
record->R0 = std::min(R0_max, std::max(R0_min, R0));
record->originalR0 = R0;
record->R0_min = R0_min;
record->R0_max = R0_max;
for (int i=0; i<3; ++i) {
record->rGrad[i] = hs.getRotationalGradient()[i];
record->tGrad[i] = tGrad[i];
}
insert(record);
return record;
}
void IrradianceCache::insert(Record *record) {
Float validRadius = record->R0 / (2*m_kappa);
m_octree.insert(record, AABB(
record->p-Vector(1,1,1)*validRadius,
record->p+Vector(1,1,1)*validRadius
));
LockGuard lock(m_mutex);
m_records.push_back(record);
}
static StatsCounter irradHits("Irradiance cache", "Hits");
static StatsCounter irradMisses("Irradiance cache", "Misses");
bool IrradianceCache::get(const Intersection &its, Spectrum &E) const {
irr_interp_functor functor(its, m_kappa, m_useGradients);
m_octree.lookup(its.p, functor);
if (functor.weightSum > 0) {
E = functor.E / functor.weightSum;
++irradHits;
Assert(!E.isNaN());
return true;
}
++irradMisses;
return false;
}
std::string IrradianceCache::toString() const {
std::ostringstream oss;
oss << "IrradianceCache[" << endl
<< " records = " << m_records.size() << "," << endl
<< " quality = " << m_kappa << "," << endl
<< " sceneSize = " << m_sceneSize << "," << endl
<< " clampScreen = " << m_clampScreen << "," << endl
<< " clampNeighbor = " << m_clampNeighbor << "," << endl
<< " useGradients = " << m_useGradients << endl
<< "]";
return oss.str();
}
MTS_IMPLEMENT_CLASS(HemisphereSampler, false, Object)
MTS_IMPLEMENT_CLASS_S(IrradianceCache, false, Object)
MTS_NAMESPACE_END