mitsuba/src/libbidir/path.cpp

635 lines
20 KiB
C++

/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2012 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/bidir/path.h>
MTS_NAMESPACE_BEGIN
void Path::initialize(const Scene *scene, Float time,
ETransportMode mode, MemoryPool &pool) {
release(pool);
m_vertices.push_back(pool.allocVertex());
m_vertices[0]->makeEndpoint(scene, time, mode);
}
int Path::randomWalk(const Scene *scene, Sampler *sampler,
int nSteps, int rrStart, ETransportMode mode,
MemoryPool &pool) {
/* Determine the relevant edge and vertex to start the random walk */
PathVertex *curVertex = m_vertices[m_vertices.size()-1],
*predVertex = m_vertices.size() < 2 ? NULL :
m_vertices[m_vertices.size()-2];
PathEdge *predEdge = m_edges.empty() ? NULL :
m_edges[m_edges.size()-1];
Spectrum throughput(1.0f);
for (int i=0; i<nSteps || nSteps == -1; ++i) {
PathVertex *succVertex = pool.allocVertex();
PathEdge *succEdge = pool.allocEdge();
if (!curVertex->sampleNext(scene, sampler, predVertex, predEdge, succEdge,
succVertex, mode, rrStart != -1 && i >= rrStart, &throughput)) {
pool.release(succVertex);
pool.release(succEdge);
return i;
}
append(succEdge, succVertex);
predVertex = curVertex;
curVertex = succVertex;
predEdge = succEdge;
}
return nSteps;
}
int Path::randomWalkFromPixel(const Scene *scene, Sampler *sampler,
int nSteps, const Point2i &pixelPosition, int rrStart, MemoryPool &pool) {
PathVertex *v1 = pool.allocVertex(), *v2 = pool.allocVertex();
PathEdge *e0 = pool.allocEdge(), *e1 = pool.allocEdge();
/* Use a special sampling routine for the first two sensor vertices so that
the resulting subpath passes through the specified pixel position */
int t = vertex(0)->sampleSensor(scene,
sampler, pixelPosition, e0, v1, e1, v2);
if (t < 1) {
pool.release(e0);
pool.release(v1);
return 0;
}
append(e0, v1);
if (t < 2) {
pool.release(e1);
pool.release(v2);
return 1;
}
append(e1, v2);
PathVertex *predVertex = v1, *curVertex = v2;
PathEdge *predEdge = e1;
Spectrum throughput(1.0f);
for (; t<nSteps || nSteps == -1; ++t) {
PathVertex *succVertex = pool.allocVertex();
PathEdge *succEdge = pool.allocEdge();
if (!curVertex->sampleNext(scene, sampler, predVertex, predEdge, succEdge,
succVertex, ERadiance, rrStart != -1 && t >= rrStart, &throughput)) {
pool.release(succVertex);
pool.release(succEdge);
return t;
}
append(succEdge, succVertex);
predVertex = curVertex;
curVertex = succVertex;
predEdge = succEdge;
}
return nSteps;
}
std::pair<int, int> Path::alternatingRandomWalkFromPixel(const Scene *scene, Sampler *sampler,
Path &emitterPath, int nEmitterSteps, Path &sensorPath, int nSensorSteps,
const Point2i &pixelPosition, int rrStart, MemoryPool &pool) {
/* Determine the relevant edges and vertices to start the random walk */
PathVertex *curVertexS = emitterPath.vertex(0),
*curVertexT = sensorPath.vertex(0),
*predVertexS = NULL, *predVertexT = NULL;
PathEdge *predEdgeS = NULL, *predEdgeT = NULL;
PathVertex *v1 = pool.allocVertex(), *v2 = pool.allocVertex();
PathEdge *e0 = pool.allocEdge(), *e1 = pool.allocEdge();
/* Use a special sampling routine for the first two sensor vertices so that
the resulting subpath passes through the specified pixel position */
int t = curVertexT->sampleSensor(scene,
sampler, pixelPosition, e0, v1, e1, v2);
if (t >= 1) {
sensorPath.append(e0, v1);
} else {
pool.release(e0);
pool.release(v1);
}
if (t == 2) {
sensorPath.append(e1, v2);
predVertexT = v1;
curVertexT = v2;
predEdgeT = e1;
} else {
pool.release(e1);
pool.release(v2);
curVertexT = NULL;
}
Spectrum throughputS(1.0f), throughputT(1.0f);
int s = 0;
do {
if (curVertexT && (t < nSensorSteps || nSensorSteps == -1)) {
PathVertex *succVertexT = pool.allocVertex();
PathEdge *succEdgeT = pool.allocEdge();
if (curVertexT->sampleNext(scene, sampler, predVertexT,
predEdgeT, succEdgeT, succVertexT, ERadiance,
rrStart != -1 && t >= rrStart, &throughputT)) {
sensorPath.append(succEdgeT, succVertexT);
predVertexT = curVertexT;
curVertexT = succVertexT;
predEdgeT = succEdgeT;
t++;
} else {
pool.release(succVertexT);
pool.release(succEdgeT);
curVertexT = NULL;
}
} else {
curVertexT = NULL;
}
if (curVertexS && (s < nEmitterSteps || nEmitterSteps == -1)) {
PathVertex *succVertexS = pool.allocVertex();
PathEdge *succEdgeS = pool.allocEdge();
if (curVertexS->sampleNext(scene, sampler, predVertexS,
predEdgeS, succEdgeS, succVertexS, EImportance,
rrStart != -1 && s >= rrStart, &throughputS)) {
emitterPath.append(succEdgeS, succVertexS);
predVertexS = curVertexS;
curVertexS = succVertexS;
predEdgeS = succEdgeS;
s++;
} else {
pool.release(succVertexS);
pool.release(succEdgeS);
curVertexS = NULL;
}
} else {
curVertexS = NULL;
}
} while (curVertexS || curVertexT);
return std::make_pair(s, t);
}
void Path::reverse() {
std::reverse(m_vertices.begin(), m_vertices.end());
std::reverse(m_edges.begin(), m_edges.end());
}
void Path::release(MemoryPool &pool) {
for (size_t i=0; i<m_vertices.size(); ++i)
pool.release(m_vertices[i]);
for (size_t i=0; i<m_edges.size(); ++i)
pool.release(m_edges[i]);
m_vertices.clear();
m_edges.clear();
}
void Path::clone(Path &target, MemoryPool &pool) const {
target.release(pool);
for (size_t i=0; i<m_vertices.size(); ++i)
target.append(m_vertices[i]->clone(pool));
for (size_t i=0; i<m_edges.size(); ++i)
target.append(m_edges[i]->clone(pool));
}
void Path::append(const Path &path) {
for (size_t i=0; i<path.vertexCount(); ++i)
m_vertices.push_back(path.vertex(i));
for (size_t i=0; i<path.edgeCount(); ++i)
m_edges.push_back(path.edge(i));
}
void Path::append(const Path &path, size_t start, size_t end, bool reverse) {
size_t count = end - start;
for (size_t i=start; i<end; ++i) {
m_vertices.push_back(path.vertex(i));
if (i+1 < end)
m_edges.push_back(path.edge(i));
}
if (reverse) {
std::reverse(m_vertices.end() - count, m_vertices.end());
std::reverse(m_edges.end() - (count - 1), m_edges.end());
}
}
void Path::release(size_t start, size_t end, MemoryPool &pool) {
for (size_t i=start; i<end; ++i) {
pool.release(m_vertices[i]);
if (i+1 < end)
pool.release(m_edges[i]);
}
}
bool Path::operator==(const Path &path) const {
if (m_vertices.size() != path.vertexCount() ||
m_edges.size() != path.edgeCount())
return false;
for (size_t i=0; i<m_vertices.size(); ++i)
if (*path.vertex(i) != *m_vertices[i])
return false;
for (size_t i=0; i<m_edges.size(); ++i)
if (*path.edge(i) != *m_edges[i])
return false;
return true;
}
Float Path::miWeight(const Scene *scene, const Path &emitterSubpath,
const PathEdge *connectionEdge, const Path &sensorSubpath,
int s, int t, bool sampleDirect, bool lightImage) {
int k = s+t+1, n = k+1;
const PathVertex
*vsPred = emitterSubpath.vertexOrNull(s-1),
*vtPred = sensorSubpath.vertexOrNull(t-1),
*vs = emitterSubpath.vertex(s),
*vt = sensorSubpath.vertex(t);
/* pdfImp[i] and pdfRad[i] store the area/volume density of vertex
'i' when sampled from the adjacent vertex in the emitter
and sensor direction, respectively. */
Float ratioEmitterDirect = 0.0f, ratioSensorDirect = 0.0f;
Float *pdfImp = (Float *) alloca(n * sizeof(Float)),
*pdfRad = (Float *) alloca(n * sizeof(Float));
bool *connectable = (bool *) alloca(n * sizeof(bool)),
*isNull = (bool *) alloca(n * sizeof(bool));
/* Keep track of which vertices are connectable / null interactions */
int pos = 0;
for (int i=0; i<=s; ++i) {
const PathVertex *v = emitterSubpath.vertex(i);
connectable[pos] = v->isConnectable();
isNull[pos] = v->isNullInteraction() && !connectable[pos];
pos++;
}
for (int i=t; i>=0; --i) {
const PathVertex *v = sensorSubpath.vertex(i);
connectable[pos] = v->isConnectable();
isNull[pos] = v->isNullInteraction() && !connectable[pos];
pos++;
}
if (k <= 3)
sampleDirect = false;
EMeasure vsMeasure = EArea, vtMeasure = EArea;
if (sampleDirect) {
/* When direct sampling is enabled, we may be able to create certain
connections that otherwise would have failed (e.g. to an
orthographic camera or a directional light source) */
const AbstractEmitter *emitter = (s > 0 ? emitterSubpath.vertex(1) : vt)->getAbstractEmitter();
const AbstractEmitter *sensor = (t > 0 ? sensorSubpath.vertex(1) : vs)->getAbstractEmitter();
EMeasure emitterDirectMeasure = emitter->getDirectMeasure();
EMeasure sensorDirectMeasure = sensor->getDirectMeasure();
connectable[0] = emitterDirectMeasure != EDiscrete && emitterDirectMeasure != EInvalidMeasure;
connectable[1] = emitterDirectMeasure != EInvalidMeasure;
connectable[k-1] = sensorDirectMeasure != EInvalidMeasure;
connectable[k] = sensorDirectMeasure != EDiscrete && sensorDirectMeasure != EInvalidMeasure;
/* The following is needed to handle orthographic cameras &
directional light sources together with direct sampling */
if (t == 1)
vtMeasure = sensor->needsDirectionSample() ? EArea : EDiscrete;
else if (s == 1)
vsMeasure = emitter->needsDirectionSample() ? EArea : EDiscrete;
}
/* Collect importance transfer area/volume densities from vertices */
pos = 0;
pdfImp[pos++] = 1.0;
for (int i=0; i<s; ++i)
pdfImp[pos++] = emitterSubpath.vertex(i)->pdf[EImportance]
* emitterSubpath.edge(i)->pdf[EImportance];
pdfImp[pos++] = vs->evalPdf(scene, vsPred, vt, EImportance, vsMeasure)
* connectionEdge->pdf[EImportance];
if (t > 0) {
pdfImp[pos++] = vt->evalPdf(scene, vs, vtPred, EImportance, vtMeasure)
* sensorSubpath.edge(t-1)->pdf[EImportance];
for (int i=t-1; i>0; --i)
pdfImp[pos++] = sensorSubpath.vertex(i)->pdf[EImportance]
* sensorSubpath.edge(i-1)->pdf[EImportance];
}
/* Collect radiance transfer area/volume densities from vertices */
pos = 0;
if (s > 0) {
for (int i=0; i<s-1; ++i)
pdfRad[pos++] = emitterSubpath.vertex(i+1)->pdf[ERadiance]
* emitterSubpath.edge(i)->pdf[ERadiance];
pdfRad[pos++] = vs->evalPdf(scene, vt, vsPred, ERadiance, vsMeasure)
* emitterSubpath.edge(s-1)->pdf[ERadiance];
}
pdfRad[pos++] = vt->evalPdf(scene, vtPred, vs, ERadiance, vtMeasure)
* connectionEdge->pdf[ERadiance];
for (int i=t; i>0; --i)
pdfRad[pos++] = sensorSubpath.vertex(i-1)->pdf[ERadiance]
* sensorSubpath.edge(i-1)->pdf[ERadiance];
pdfRad[pos++] = 1.0;
/* When the path contains specular surface interactions, it is possible
to compute the correct MI weights even without going through all the
trouble of computing the proper generalized geometric terms (described
in the SIGGRAPH 2012 specular manifolds paper). The reason is that these
all cancel out. But to make sure that that's actually true, we need to
convert some of the area densities in the 'pdfRad' and 'pdfImp' arrays
into the projected solid angle measure */
for (int i=1; i <= k-3; ++i) {
if (i == s || !(connectable[i] && !connectable[i+1]))
continue;
const PathVertex *cur = i <= s ? emitterSubpath.vertex(i) : sensorSubpath.vertex(k-i);
const PathVertex *succ = i+1 <= s ? emitterSubpath.vertex(i+1) : sensorSubpath.vertex(k-i-1);
const PathEdge *edge = i < s ? emitterSubpath.edge(i) : sensorSubpath.edge(k-i-1);
pdfImp[i+1] *= edge->length * edge->length / std::abs(
(succ->isOnSurface() ? dot(edge->d, succ->getGeometricNormal()) : 1) *
(cur->isOnSurface() ? dot(edge->d, cur->getGeometricNormal()) : 1));
}
for (int i=k-1; i >= 3; --i) {
if (i-1 == s || !(connectable[i] && !connectable[i-1]))
continue;
const PathVertex *cur = i <= s ? emitterSubpath.vertex(i) : sensorSubpath.vertex(k-i);
const PathVertex *succ = i-1 <= s ? emitterSubpath.vertex(i-1) : sensorSubpath.vertex(k-i+1);
const PathEdge *edge = i <= s ? emitterSubpath.edge(i-1) : sensorSubpath.edge(k-i);
pdfRad[i-1] *= edge->length * edge->length / std::abs(
(succ->isOnSurface() ? dot(edge->d, succ->getGeometricNormal()) : 1) *
(cur->isOnSurface() ? dot(edge->d, cur->getGeometricNormal()) : 1));
}
int emitterRefIndirection = 2, sensorRefIndirection = k-2;
/* One more array sweep before the actual useful work starts -- phew! :)
"Collapse" edges/vertices that were caused by BSDF::ENull interactions.
The BDPT implementation is smart enough to connect straight through those,
so they shouldn't be treated as Dirac delta events in what follows */
for (int i=1; i <= k-3; ++i) {
if (!connectable[i] || !isNull[i+1])
continue;
int start = i+1, end = start;
while (isNull[end+1])
++end;
if (!connectable[end+1]) {
/// The chain contains a non-ENull interaction
isNull[start] = false;
continue;
}
const PathVertex *before = i <= s ? emitterSubpath.vertex(i) : sensorSubpath.vertex(k-i);
const PathVertex *after = end+1 <= s ? emitterSubpath.vertex(end+1) : sensorSubpath.vertex(k-end-1);
Vector d = before->getPosition() - after->getPosition();
Float lengthSquared = d.lengthSquared();
d /= std::sqrt(lengthSquared);
Float geoTerm = std::abs(
(before->isOnSurface() ? dot(before->getGeometricNormal(), d) : 1) *
(after->isOnSurface() ? dot(after->getGeometricNormal(), d) : 1)) / lengthSquared;
pdfRad[start-1] *= pdfRad[end] * geoTerm;
pdfRad[end] = 1;
pdfImp[start] *= pdfImp[end+1] * geoTerm;
pdfImp[end+1] = 1;
/* When an ENull chain starts right after the emitter / before the sensor,
we must keep track of the reference vertex for direct sampling strategies. */
if (start == 2)
emitterRefIndirection = end + 1;
else if (end == k-2)
sensorRefIndirection = start - 1;
i = end;
}
double initial = 1.0f;
/* When direct sampling strategies are enabled, we must
account for them here as well */
if (sampleDirect) {
/* Direct connection probability of the emitter */
const PathVertex *sample = s>0 ? emitterSubpath.vertex(1) : vt;
const PathVertex *ref = emitterRefIndirection <= s
? emitterSubpath.vertex(emitterRefIndirection) : sensorSubpath.vertex(k-emitterRefIndirection);
EMeasure measure = sample->getAbstractEmitter()->getDirectMeasure();
if (connectable[1] && connectable[emitterRefIndirection])
ratioEmitterDirect = ref->evalPdfDirect(scene, sample, EImportance,
measure == ESolidAngle ? EArea : measure) / pdfImp[1];
/* Direct connection probability of the sensor */
sample = t>0 ? sensorSubpath.vertex(1) : vs;
ref = sensorRefIndirection <= s ? emitterSubpath.vertex(sensorRefIndirection)
: sensorSubpath.vertex(k-sensorRefIndirection);
measure = sample->getAbstractEmitter()->getDirectMeasure();
if (connectable[k-1] && connectable[sensorRefIndirection])
ratioSensorDirect = ref->evalPdfDirect(scene, sample, ERadiance,
measure == ESolidAngle ? EArea : measure) / pdfRad[k-1];
if (s == 1)
initial /= ratioEmitterDirect;
else if (t == 1)
initial /= ratioSensorDirect;
}
double weight = 1, pdf = initial;
/* With all of the above information, the MI weight can now be computed.
Since the goal is to evaluate the power heuristic, the absolute area
product density of each strategy is interestingly not required. Instead,
an incremental scheme can be used that only finds the densities relative
to the (s,t) strategy, which can be done using a linear sweep. For
details, refer to the Veach thesis, p.306. */
for (int i=s+1; i<k; ++i) {
double next = pdf * (double) pdfImp[i] / (double) pdfRad[i],
value = next;
if (sampleDirect) {
if (i == 1)
value *= ratioEmitterDirect;
else if (i == sensorRefIndirection)
value *= ratioSensorDirect;
}
int tPrime = k-i-1;
if (connectable[i] && (connectable[i+1] || isNull[i+1]) && (lightImage || tPrime > 1))
weight += value*value;
pdf = next;
}
/* As above, but now compute pdf[i] with i<s (this is done by
evaluating the inverse of the previous expressions). */
pdf = initial;
for (int i=s-1; i>=0; --i) {
double next = pdf * (double) pdfRad[i+1] / (double) pdfImp[i+1],
value = next;
if (sampleDirect) {
if (i == 1)
value *= ratioEmitterDirect;
else if (i == sensorRefIndirection)
value *= ratioSensorDirect;
}
int tPrime = k-i-1;
if (connectable[i] && (connectable[i+1] || isNull[i+1]) && (lightImage || tPrime > 1))
weight += value*value;
pdf = next;
}
return (Float) (1.0 / weight);
}
void Path::collapseTo(PathEdge &target) const {
BDAssert(m_edges.size() > 0);
target.pdf[ERadiance] = 1.0f;
target.pdf[EImportance] = 1.0f;
target.weight[ERadiance] = Spectrum(1.0f);
target.weight[EImportance] = Spectrum(1.0f);
target.d = m_edges[0]->d;
target.medium = m_edges[0]->medium;
target.length = 0;
for (size_t i=0; i<m_edges.size(); ++i) {
const PathEdge *edge = m_edges[i];
target.weight[ERadiance] *= edge->weight[ERadiance];
target.weight[EImportance] *= edge->weight[EImportance];
target.pdf[ERadiance] *= edge->pdf[ERadiance];
target.pdf[EImportance] *= edge->pdf[EImportance];
target.length += edge->length;
if (target.medium != edge->medium)
target.medium = NULL;
}
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex *vertex = m_vertices[i];
BDAssert(vertex->isSurfaceInteraction() &&
vertex->componentType == BSDF::ENull);
target.weight[ERadiance] *= vertex->weight[ERadiance];
target.weight[EImportance] *= vertex->weight[EImportance];
target.pdf[ERadiance] *= vertex->pdf[ERadiance];
target.pdf[EImportance] *= vertex->pdf[EImportance];
}
}
std::string Path::toString() const {
std::ostringstream oss;
oss << "Path[" << endl;
if (m_vertices.size() == m_edges.size() + 1) {
for (size_t i=0; i<m_vertices.size(); ++i) {
oss << " Vertex " << i << " => " << indent(m_vertices[i]->toString());
if (i<m_edges.size()) {
oss << "," << endl;
oss << " Edge " << i << " => " << indent(m_edges[i]->toString());
}
if (i+1 < m_vertices.size())
oss << ",";
oss << endl;
}
} else if (m_edges.size() == m_vertices.size() + 1) {
for (size_t i=0; i<m_edges.size(); ++i) {
oss << " Edge " << i << " => " << indent(m_edges[i]->toString());
if (i<m_vertices.size()) {
oss << "," << endl;
oss << " Vertex " << i << " => " << indent(m_vertices[i]->toString());
}
if (i+1 < m_edges.size())
oss << ",";
oss << endl;
}
} else {
SLog(EError, "Unknown path configuration!");
}
oss << "]";
return oss.str();
}
std::string Path::summarize() const {
std::ostringstream oss;
BDAssert(m_vertices.size() == m_edges.size() + 1);
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex *vertex = m_vertices[i];
switch (vertex->type) {
case PathVertex::EEmitterSupernode: oss << "E"; break;
case PathVertex::ESensorSupernode: oss << "C"; break;
case PathVertex::EEmitterSample: oss << "e"; break;
case PathVertex::ESensorSample: oss << "c"; break;
case PathVertex::ESurfaceInteraction: oss << "S"; break;
case PathVertex::EMediumInteraction: oss << "M"; break;
default:
SLog(EError, "Unknown vertex typ!");
}
if (!vertex->isConnectable())
oss << "d";
if (i<m_edges.size()) {
const PathEdge *edge = m_edges[i];
if (edge->medium == NULL)
oss << " - ";
else
oss << " = ";
}
}
return oss.str();
}
MTS_NAMESPACE_END