/* 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 . */ #include #include #include #define EIGEN_DONT_PARALLELIZE #define EIGEN_NO_DEBUG #include #include MTS_NAMESPACE_BEGIN /* Some statistics counters */ static StatsCounter statsStepFailed( "Specular manifold", "Retries (step failed)"); static StatsCounter statsStepTooFar( "Specular manifold", "Retries (step increased distance)"); static StatsCounter statsStepSuccess( "Specular manifold", "Successful steps"); static StatsCounter statsAvgIterations( "Specular manifold", "Avg. iterations per walk", EAverage); static StatsCounter statsAvgIterationsSuccess( "Specular manifold", "Avg. iterations per successful walk", EAverage); static StatsCounter statsAvgManifoldSize( "Specular manifold", "Avg. manifold size", EAverage); static StatsCounter statsSuccessfulWalks( "Specular manifold", "Successful walks", EPercentage); static StatsCounter statsMediumSuccess( "Specular manifold", "Successful walks w/ media", EPercentage); static StatsCounter statsNonManifold( "Specular manifold", "Non-manifold", EPercentage); static StatsCounter statsUpdateFailed( "Specular manifold", "Update failed"); static StatsCounter statsMaxManifold( "Specular manifold", "Max. manifold size", EMaximumValue); SpecularManifold::SpecularManifold(const Scene *scene, int maxIterations) : m_scene(scene) { m_maxIterations = maxIterations > 0 ? maxIterations : MTS_MANIFOLD_MAX_ITERATIONS; } bool SpecularManifold::init(const Path &path, int start, int end) { int step = start < end ? 1 : -1; if (path.vertex(start)->isSupernode()) start += step; if (path.vertex(end)->isSupernode()) end -= step; const PathVertex *vs = path.vertex(start), *ve = path.vertex(end); /* Create the initial vertex that is pinned in position by default */ SimpleVertex v(EPinnedPosition, vs->getPosition()); /* When the endpoint is on an orthographic camera or directional light source, switch to a directionally pinned vertex instead */ if (vs->getType() & (PathVertex::ESensorSample | PathVertex::EEmitterSample)) { const PositionSamplingRecord &pRec = vs->getPositionSamplingRecord(); uint32_t type = static_cast(pRec.object)->getType() & (AbstractEmitter::EDeltaDirection | AbstractEmitter::EDeltaPosition); if (type == AbstractEmitter::EDeltaDirection) { v.type = EPinnedDirection; v.gn = v.n = pRec.n; coordinateSystem(pRec.n, v.dpdu, v.dpdv); } } m_time = vs->getTime(); m_vertices.clear(); m_vertices.push_back(v); for (int i=start + step; i != end; i += step) { const PathVertex *pred = path.vertex(i-step), *vertex = path.vertex(i), *succ = path.vertex(i+step); if (vertex->isSurfaceInteraction()) { const Intersection &its = vertex->getIntersection(); const BSDF *bsdf = its.getBSDF(); v.p = its.p; v.gn = its.geoFrame.n; v.n = its.shFrame.n; v.dpdu = its.dpdu; v.dpdv = its.dpdv; v.object = bsdf; v.degenerate = !vertex->isConnectable(); const Shape *shape = its.instance != NULL ? its.instance : its.shape; shape->getNormalDerivative(its, v.dndu, v.dndv); /* Turn into an orthonormal parameterization at 'p' */ Float invLen = 1 / v.dpdu.length(); v.dpdu *= invLen; v.dndu *= invLen; Float dp = dot(v.dpdu, v.dpdv); Vector dpdv = v.dpdv - dp * v.dpdu; Vector dndv = v.dndv - dp * v.dndu; invLen = 1 / dpdv.length(); v.dpdv = dpdv * invLen; v.dndv = dndv * invLen; Vector wPred = pred->getPosition() - v.p; Vector wSucc = succ->getPosition() - v.p; if (dot(v.gn, wPred) * dot(v.gn, wSucc) < 0) { v.type = ERefraction; v.eta = bsdf->getEta(); } else { v.type = EReflection; v.eta = 1.0f; } } else if (vertex->isMediumInteraction()) { const MediumSamplingRecord &mRec = vertex->getMediumSamplingRecord(); Vector wi = pred->getPosition() - mRec.p; Float invLength = 1.0f / wi.length(); wi *= invLength; v.p = mRec.p; v.gn = v.n = Normal(0.0f); Vector s, t; coordinateSystem(wi, s, t); v.dpdu = s; v.dpdv = t; v.dndu = s * invLength; v.dndv = t * invLength; v.object = mRec.getPhaseFunction(); v.eta = 1.0f; v.degenerate = false; v.type = EMedium; } else { Log(EError, "Unknown vertex type! : %s", vertex->toString().c_str()); } m_vertices.push_back(v); } v = SimpleVertex(EMovable, ve->getPosition()); m_vertices.push_back(v); #if MTS_MANIFOLD_DEBUG == 1 cout << "==========================================" << endl; cout << "Initialized specular manifold: " << toString() << endl; #endif return true; } bool SpecularManifold::computeTangents() { const int n = static_cast(m_vertices.size() - 1); m_vertices[0].Tp.setZero(); m_vertices[m_vertices.size()-1].Tp.setIdentity(); if (m_vertices.size() == 2) /* Nothing to do */ return true; /* Matrix assembly stage */ for (int i=0; i std::abs(wi.y)) { Float tl = 1.0f / std::sqrt(wi.x * wi.x + wi.z * wi.z); t = Vector(wi.z * tl, 0.0f, -wi.x * tl); dt_dpred_u = Vector(dwi_dpred_u.z*tl, 0.0f, -dwi_dpred_u.x*tl); dt_dpred_v = Vector(dwi_dpred_v.z*tl, 0.0f, -dwi_dpred_v.x*tl); dt_dcur_u = Vector(dwi_dcur_u.z*tl, 0.0f, -dwi_dcur_u.x*tl); dt_dcur_v = Vector(dwi_dcur_v.z*tl, 0.0f, -dwi_dcur_v.x*tl); } else { Float tl = 1.0f / std::sqrt(wi.y * wi.y + wi.z * wi.z); t = Vector(0.0f, wi.z * tl, -wi.y * tl); dt_dpred_u = Vector(0.0f, dwi_dpred_u.z*tl, -dwi_dpred_u.y*tl); dt_dpred_v = Vector(0.0f, dwi_dpred_v.z*tl, -dwi_dpred_v.y*tl); dt_dcur_u = Vector(0.0f, dwi_dcur_u.z*tl, -dwi_dcur_u.y*tl); dt_dcur_v = Vector(0.0f, dwi_dcur_v.z*tl, -dwi_dcur_v.y*tl); } dt_dpred_u -= t * dot(t, dt_dpred_u); dt_dpred_v -= t * dot(t, dt_dpred_v); dt_dcur_u -= t * dot(t, dt_dcur_u); dt_dcur_v -= t * dot(t, dt_dcur_v); Vector s = cross(t, wi); Vector ds_dpred_u = cross(dt_dpred_u, wi) + cross(t, dwi_dpred_u); Vector ds_dpred_v = cross(dt_dpred_v, wi) + cross(t, dwi_dpred_v); Vector ds_dcur_u = cross(dt_dcur_u, wi) + cross(t, dwi_dcur_u); Vector ds_dcur_v = cross(dt_dcur_v, wi) + cross(t, dwi_dcur_v); /* Some tangential projections */ Vector2 t_cur_dpdu (dot(v[ 0].dpdu, s), dot(v[ 0].dpdu, t)), t_cur_dpdv (dot(v[ 0].dpdv, s), dot(v[ 0].dpdv, t)), t_next_dpdu(dot(v[ 1].dpdu, s), dot(v[ 1].dpdu, t)), t_next_dpdv(dot(v[ 1].dpdv, s), dot(v[ 1].dpdv, t)), t_wo = Vector2(dot(wo, s), dot(wo, t)); v[0].a = Matrix2x2( Vector2(dot(ds_dpred_u, wo), dot(dt_dpred_u, wo)), Vector2(dot(ds_dpred_v, wo), dot(dt_dpred_v, wo)) ); v[0].b = Matrix2x2( (t_wo * dot(wo, v[0].dpdu) - t_cur_dpdu) * ilo + Vector2(dot(ds_dcur_u, wo), dot(dt_dcur_u, wo)), (t_wo * dot(wo, v[0].dpdv) - t_cur_dpdv) * ilo + Vector2(dot(ds_dcur_v, wo), dot(dt_dcur_v, wo))); v[0].c = Matrix2x2( (t_next_dpdu - t_wo * dot(wo, v[1].dpdu)) * ilo, (t_next_dpdv - t_wo * dot(wo, v[1].dpdv)) * ilo); v[0].m = Vector(dot(s, wo), dot(t, wo), dot(wi, wo)); } else { Log(EError, "Unknown vertex type!"); } } /* Find the tangent space with respect to translation of the last vertex. For this, we must solve a tridiagonal system. The following is simplified version of the block tridiagonal LU factorization algorithm for this specific problem */ Matrix2x2 Li; if (!m_vertices[0].b.invert(Li)) return false; for (int i=0; i < n - 1; ++i) { m_vertices[i].u = Li * m_vertices[i].c; Matrix2x2 temp = m_vertices[i+1].b - m_vertices[i+1].a * m_vertices[i].u; if (!temp.invert(Li)) return false; } m_vertices[n-1].Tp = -Li * m_vertices[n-1].c; for (int i=n-2; i>=0; --i) m_vertices[i].Tp = -m_vertices[i].u * m_vertices[i+1].Tp; return true; } bool SpecularManifold::project(const Vector &d) { const SimpleVertex &last = m_vertices[m_vertices.size()-1]; Float du = dot(d, last.dpdu), dv = dot(d, last.dpdv); Ray ray(Point(0.0f), Vector(1.0f), 0); // make gcc happy Intersection its; m_proposal.clear(); for (size_t i=0; irayIntersect(ray, its)) return false; Vector n = its.shFrame.n, s = its.dpdu, t; s = normalize(s - n * dot(n, s)); t = cross(n, s); Vector m = s * vertex.m[0] + t * vertex.m[1] + n * vertex.m[2]; ray.setOrigin(its.p); ray.setDirection(reflect(-ray.d, m)); } else if (vertex.type == ERefraction) { if (!m_scene->rayIntersect(ray, its)) return false; Vector n = its.shFrame.n, s = its.dpdu, t; s = normalize(s - n * dot(n, s)); t = cross(n, s); Vector m = s * vertex.m[0] + t * vertex.m[1] + n * vertex.m[2]; Vector refracted = refract(-ray.d, m, its.shape->getBSDF()->getEta()); if (refracted.isZero()) return false; ray.setOrigin(its.p); ray.setDirection(refracted); } else if (vertex.type == EMedium) { Float length = (m_vertices[i].p - m_vertices[i-1].p).length(), invLength = 1.0f / length; /* Check for occlusion */ if (m_scene->rayIntersect(Ray(ray, Epsilon, length))) return false; vertex.p = ray(length); vertex.n = Vector(0.0f); Vector wi = -ray.d, s, t; coordinateSystem(wi, s, t); vertex.dpdu = s; vertex.dpdv = t; vertex.dndu = s * invLength; vertex.dndv = t * invLength; ray.setOrigin(vertex.p); ray.setDirection(s * vertex.m[0] + t * vertex.m[1] + wi * vertex.m[2]); } else { Log(EError, "Unsupported vertex type!"); } if (vertex.type != EMedium) { if (vertex.object != its.shape->getBSDF()) return false; vertex.p = its.p; vertex.n = its.shFrame.n; vertex.gn = its.geoFrame.n; vertex.dpdu = its.dpdu; vertex.dpdv = its.dpdv; const Shape *shape = its.instance != NULL ? its.instance : its.shape; shape->getNormalDerivative(its, vertex.dndu, vertex.dndv); /* Turn into an orthonormal parameterization at 'p' */ Float invLen = 1 / vertex.dpdu.length(); vertex.dpdu *= invLen; vertex.dndu *= invLen; Float dp = dot(vertex.dpdu, vertex.dpdv); Vector dpdv = vertex.dpdv - dp * vertex.dpdu; Vector dndv = vertex.dndv - dp * vertex.dndu; invLen = 1 / dpdv.length(); vertex.dpdv = dpdv * invLen; vertex.dndv = dndv * invLen; } } return true; } bool SpecularManifold::move(const Point &target, const Normal &n) { SimpleVertex &last = m_vertices[m_vertices.size()-1]; #if MTS_MANIFOLD_DEBUG == 1 cout << "moveTo(" << last.p.toString() << " => " << target.toString() << ", n=" << n.toString() << ")" << endl; #endif if (m_vertices.size() == 2 && m_vertices[0].type == EPinnedPosition) { /* Nothing to do */ return true; } bool medium = false; for (size_t i=0; i " << newDist << endl; #endif if (newDist > dist) { ++statsStepTooFar; #if MTS_MANIFOLD_DEBUG == 1 cout << "-> Rejecting!" << endl; #endif goto failure; } #if MTS_MANIFOLD_DEBUG == 1 cout << "-> Accepting!" << endl; #endif ++statsStepSuccess; m_proposal.swap(m_vertices); /* Increase the step size */ stepSize = std::min((Float) 1.0f, stepSize * 2.0f); continue; failure: /* Reduce the step size */ stepSize /= 2.0f; } #if MTS_MANIFOLD_DEBUG == 1 cout << "Exceeded the max. iteration count!" << endl; #endif return false; } bool SpecularManifold::update(Path &path, int start, int end) { int step; ETransportMode mode; if (start < end) { step = 1; mode = EImportance; } else { step = -1; mode = ERadiance; } int last = (int) m_vertices.size() - 2; if (m_vertices[0].type == EPinnedDirection) last = std::max(last, 1); for (int j=0, i=start; j < last; ++j, i += step) { const SimpleVertex &v = m_vertices[j], &vn = m_vertices[j+1]; PathVertex *pred = path.vertexOrNull(i-step), *vertex = path.vertex(i), *succ = path.vertex(i+step); int predEdgeIdx = (mode == EImportance) ? i-step : i-step-1; PathEdge *predEdge = path.edgeOrNull(predEdgeIdx), *succEdge = path.edge(predEdgeIdx + step); Vector d = vn.p - v.p; Float length = d.length(); d /= length; PathVertex::EVertexType desiredType = vn.type == EMedium ? PathVertex::EMediumInteraction : PathVertex::ESurfaceInteraction; if (v.type == EPinnedDirection) { /* Create a fake vertex and use it to call sampleDirect(). This is kind of terrible -- a nicer API is needed to cleanly support this */ PathVertex temp; temp.type = PathVertex::EMediumInteraction; temp.degenerate = false; temp.measure = EArea; MediumSamplingRecord &mRec = temp.getMediumSamplingRecord(); mRec.time = m_time; mRec.p = vn.p; if (temp.sampleDirect(m_scene, NULL, vertex, succEdge, succ, mode).isZero()) { #if MTS_MANIFOLD_DEBUG == 1 cout << "update(): failed in sampleDirect()!" << endl; #endif ++statsUpdateFailed; return false; } if (m_vertices.size() >= 3) { PathVertex *succ2 = path.vertex(i+2*step); PathEdge *succ2Edge = path.edge(predEdgeIdx + 2*step); if (!succ->sampleNext(m_scene, NULL, vertex, succEdge, succ2Edge, succ2, mode)) { #if MTS_MANIFOLD_DEBUG == 1 cout << "update(): failed in sampleNext() / pinned direction!" << endl; #endif ++statsUpdateFailed; return false; } } i += step; } else if (!v.degenerate) { if (!vertex->perturbDirection(m_scene, pred, predEdge, succEdge, succ, d, length, desiredType, mode)) { #if MTS_MANIFOLD_DEBUG == 1 cout << "update(): failed in perturbDirection()" << endl; #endif ++statsUpdateFailed; return false; } Float relerr = (vn.p - succ->getPosition()).length() / std::max(std::max(std::abs(vn.p.x), std::abs(vn.p.y)), std::abs(vn.p.z)); if (relerr > 1e-3f) { // be extra-cautious #if MTS_MANIFOLD_DEBUG == 1 cout << "update(): failed, relative error of perturbDirection() too high:" << relerr << endl; #endif ++statsUpdateFailed; return false; } } else { unsigned int compType; if (v.type == ERefraction) compType = v.eta != 1 ? BSDF::EDeltaTransmission : (BSDF::ENull | BSDF::EDeltaTransmission); else compType = BSDF::EDeltaReflection; if (!vertex->propagatePerturbation(m_scene, pred, predEdge, succEdge, succ, compType, length, desiredType, mode)) { #if MTS_MANIFOLD_DEBUG == 1 cout << "update(): failed in propagatePerturbation()" << endl; #endif ++statsUpdateFailed; return false; } Float relerr = (vn.p - succ->getPosition()).length() / std::max(std::max(std::abs(vn.p.x), std::abs(vn.p.y)), std::abs(vn.p.z)); if (relerr > 1e-3f) { // be extra-cautious #if MTS_MANIFOLD_DEBUG == 1 cout << "update(): failed, relative error of propagatePerturbation() too high:" << relerr << endl; #endif ++statsUpdateFailed; return false; } } } return true; } Float SpecularManifold::det(const Path &path, int a, int b, int c) { int k = path.length(); if (a == 0 || a == k) std::swap(a, c); int step = b > a ? 1 : -1, nGlossy = 0, nSpecular = 0; for (int i=a + step; i != c; i += step) { if (path.vertex(i)->isConnectable()) ++nGlossy; else ++nSpecular; } if (nGlossy <= 1) /* No glossy materials -- we don't need this derivative */ return 1.0f; bool success = init(path, a, c); BDAssert(success); int b_idx = std::abs(b-a); SimpleVertex &vb = m_vertices[b_idx]; const PathVertex *pb = path.vertex(b); if (pb->isMediumInteraction()) { vb.n = Vector(path.edge(a < b ? (b-1) : b)->d); } else { vb.n = pb->getShadingNormal(); } coordinateSystem(vb.n, vb.dpdu, vb.dpdv); if (!computeTangents()) { Log(EWarn, "Could not compute tangents!"); return 0.0f; } m_vertices[b_idx].a.setZero(); m_vertices[b_idx].b.setIdentity(); m_vertices[b_idx].c.setZero(); if (nSpecular == 0) { /* The chain only consists of glossy vertices -- simply compute the determinant of the block tridiagonal matrix A. See D.K. Salkuyeh, Comments on "A note on a three-term recurrence for a tridiagonal matrix", Appl. Math. Comput. 176 (2006) 442-444. */ Matrix2x2 Di(0.0f), D = m_vertices[1].b; Float det = D.det(); for (size_t i=2; i A(2*(nGlossy + nSpecular), 2*(nGlossy + nSpecular)); A.setZero(); for (int j=0, i=0; j= 0) { A(2*i, 2*(j-1)) = m_vertices[j+1].a(0,0); A(2*i, 2*(j-1)+1) = m_vertices[j+1].a(0,1); A(2*i+1, 2*(j-1)) = m_vertices[j+1].a(1,0); A(2*i+1, 2*(j-1)+1) = m_vertices[j+1].a(1,1); } A(2*i, 2*j) = m_vertices[j+1].b(0,0); A(2*i, 2*j+1) = m_vertices[j+1].b(0,1); A(2*i+1, 2*j) = m_vertices[j+1].b(1,0); A(2*i+1, 2*j+1) = m_vertices[j+1].b(1,1); if (j+1 < nGlossy + nSpecular) { A(2*i, 2*(j+1)) = m_vertices[j+1].c(0,0); A(2*i, 2*(j+1)+1) = m_vertices[j+1].c(0,1); A(2*i+1, 2*(j+1)) = m_vertices[j+1].c(1,0); A(2*i+1, 2*(j+1)+1) = m_vertices[j+1].c(1,1); } ++i; } /* Compute the inverse and "cross out" irrelevant columns and rows */ Eigen::Matrix Ai = A.inverse(); for (int i=0; i(2*i, 2*i).setIdentity(); } return std::abs(Ai.determinant()); } } Float SpecularManifold::multiG(const Path &path, int a, int b) { if (a == 0) ++a; else if (a == path.length()) --a; if (b == 0) ++b; else if (b == path.length()) --b; int step = b > a ? 1 : -1; while (!path.vertex(b)->isConnectable()) b -= step; while (!path.vertex(a)->isConnectable()) a += step; Float result = 1; BDAssert(path.vertex(a)->isConnectable() && path.vertex(b)->isConnectable()); for (int i = a + step, start = a; i != b + step; i += step) { if (path.vertex(i)->isConnectable()) { result *= G(path, start, i); start = i; } } return result; } Float SpecularManifold::G(const Path &path, int a, int b) { if (std::abs(a-b) == 1) { if (a > b) std::swap(a, b); return path.edge(a)->evalCached(path.vertex(a), path.vertex(b), PathEdge::EGeometricTerm)[0]; } Assert(path.vertex(a)->isConnectable()); Assert(path.vertex(b)->isConnectable()); int step = b > a ? 1 : -1; bool success = init(path, a, b); BDAssert(success); SimpleVertex &last = m_vertices[m_vertices.size()-1]; const PathVertex *vb = path.vertex(b); if (!vb->isOnSurface()) { last.n = Vector(path.edge(a < b ? (b-1) : b)->d); } else { last.n = vb->getShadingNormal(); } coordinateSystem(last.n, last.dpdu, last.dpdv); statsNonManifold.incrementBase(); if (!computeTangents()) { ++statsNonManifold; Log(EWarn, "SpecularManifold::evalG(): non-manifold configuration!"); return 0; } Float result; if (m_vertices[0].type == EPinnedDirection) { result = cross(m_vertices[0].map(1, 0), m_vertices[0].map(0, 1)).length(); } else if (m_vertices[0].type == EPinnedPosition) { Vector d = m_vertices[1].p - m_vertices[0].p; Float lengthSqr = d.lengthSquared(), invLength = 1/std::sqrt(lengthSqr); result = cross(m_vertices[1].map(1, 0), m_vertices[1].map(0, 1)).length() / lengthSqr; if (path.vertex(a)->isOnSurface()) result *= absDot(d, path.vertex(a)->getShadingNormal()) * invLength; if (path.vertex(a+step)->isOnSurface()) result *= absDot(d, path.vertex(a+step)->getShadingNormal()) * invLength; } else { Log(EError, "Invalid vertex type!"); return 0; } return result; } std::string SpecularManifold::SimpleVertex::toString() const { std::ostringstream oss; oss << "SimpleVertex[" << endl << " type = "; switch (type) { case EPinnedPosition: oss << "pinnedPosition"; break; case EPinnedDirection: oss << "pinnedDirection"; break; case EReflection: oss << "reflection"; break; case ERefraction: oss << "refraction"; break; case EMedium: oss << "medium"; break; case EMovable: oss << "movable"; break; default: SLog(EError, "Unknown vertex type!"); } oss << "," << endl << " p = " << p.toString() << "," << endl << " n = " << n.toString() << "," << endl << " m = " << m.toString() << "," << endl << " dpdu = " << dpdu.toString() << "," << endl << " dpdv = " << dpdv.toString() << "," << endl << " dndu = " << dndu.toString() << "," << endl << " dndv = " << dndv.toString() << "," << endl << " eta = " << eta << "," << endl << " object = " << (object ? indent(object->toString()).c_str() : "null") << endl << "]"; return oss.str(); } std::string SpecularManifold::toString() const { std::ostringstream oss; oss << "SpecularManifold[" << endl; for (size_t i=0; i " << indent(m_vertices[i].toString()); if (i+1 < m_vertices.size()) oss << ","; oss << endl; } oss << "]"; return oss.str(); } MTS_IMPLEMENT_CLASS(SpecularManifold, false, Object) MTS_NAMESPACE_END