diff --git a/data/schema/upgrade_0.3.0.xsl b/data/schema/upgrade_0.3.0.xsl index 2503929d..3606aeba 100644 --- a/data/schema/upgrade_0.3.0.xsl +++ b/data/schema/upgrade_0.3.0.xsl @@ -48,7 +48,7 @@ @@ -66,7 +66,7 @@ - + 1.0 @@ -74,7 +74,7 @@ - + 1.0 @@ -211,7 +211,7 @@ densityMultiplier - + conductor @@ -246,7 +246,7 @@ mixturebsdf - + bitmap diff --git a/data/schema/upgrade_0.4.0.xsl b/data/schema/upgrade_0.4.0.xsl index 4c073b79..ef1102b1 100644 --- a/data/schema/upgrade_0.4.0.xsl +++ b/data/schema/upgrade_0.4.0.xsl @@ -91,7 +91,7 @@ ldrfilm - + focusDistance diff --git a/data/schema/upgrade_0.5.0.xsl b/data/schema/upgrade_0.5.0.xsl new file mode 100644 index 00000000..64d70137 --- /dev/null +++ b/data/schema/upgrade_0.5.0.xsl @@ -0,0 +1,25 @@ + + + + + + + + + + + 0.5.0 + + + + + bumpmap + + + + + + + + + diff --git a/doc/images/bsdf_bump.pdf b/doc/images/bsdf_bumpmap.pdf similarity index 100% rename from doc/images/bsdf_bump.pdf rename to doc/images/bsdf_bumpmap.pdf diff --git a/doc/images/bsdf_bump_1.jpg b/doc/images/bsdf_bumpmap_1.jpg similarity index 100% rename from doc/images/bsdf_bump_1.jpg rename to doc/images/bsdf_bumpmap_1.jpg diff --git a/doc/images/bsdf_bump_2.jpg b/doc/images/bsdf_bumpmap_2.jpg similarity index 100% rename from doc/images/bsdf_bump_2.jpg rename to doc/images/bsdf_bumpmap_2.jpg diff --git a/include/mitsuba/render/bsdf.h b/include/mitsuba/render/bsdf.h index 9e4f1190..de16a1de 100644 --- a/include/mitsuba/render/bsdf.h +++ b/include/mitsuba/render/bsdf.h @@ -453,6 +453,15 @@ public: */ virtual Float getRoughness(const Intersection &its, int index) const; + /** + * \brief Sometimes, BSDF models make use of a perturbed frame for + * internal shading computations (e.g. bump maps). This function + * exposes this internal frame. + * + * By default, it returns its.shFrame + */ + virtual Frame getFrame(const Intersection &its) const; + // ============================================================= //! @{ \name ConfigurableObject interface // ============================================================= diff --git a/include/mitsuba/render/mipmap.h b/include/mitsuba/render/mipmap.h index 16deb898..e43bb90f 100644 --- a/include/mitsuba/render/mipmap.h +++ b/include/mitsuba/render/mipmap.h @@ -52,12 +52,14 @@ namespace stats { /// Specifies the desired antialiasing filter enum EMIPFilterType { - /// No filtering (i.e. nearest neighbor lookups) + /// No filtering, nearest neighbor lookups ENearest = 0, + /// No filtering, only bilinear interpolation + EBilinear = 1, /// Basic trilinear filtering - ETrilinear = 1, + ETrilinear = 2, /// Elliptically weighted average - EEWA = 2, + EEWA = 3 }; /** @@ -179,7 +181,7 @@ public: /* 1. Determine the number of MIP levels. The following code also handles non-power-of-2 input. */ m_levels = 1; - if (m_filterType != ENearest) { + if (m_filterType != ENearest && m_filterType != EBilinear) { Vector2i size = bitmap_->getSize(); while (size.x > 1 || size.y > 1) { size.x = std::max(1, (size.x + 1) / 2); @@ -233,7 +235,7 @@ public: m_sizeRatio[0] = Vector2(1, 1); /* 3. Progressively downsample until only a 1x1 image is left */ - if (m_filterType != ENearest) { + if (m_filterType != ENearest && m_filterType != EBilinear) { Vector2i size = bitmap_->getSize(); m_levels = 1; while (size.x > 1 || size.y > 1) { @@ -344,7 +346,7 @@ public: mmapPtr += m_pyramid[0].getBufferSize(); m_sizeRatio[0] = Vector2(1, 1); - if (m_filterType != ENearest) { + if (m_filterType != ENearest && m_filterType != EBilinear) { /* Map the remainder of the image pyramid */ int level = 1; while (size.x > 1 || size.y > 1) { @@ -425,7 +427,7 @@ public: size_t expectedFileSize = sizeof(MIPMapHeader) + padding + Array2DType::bufferSize(size); - if (filterType != ENearest) { + if (filterType != ENearest && filterType != EBilinear) { while (size.x > 1 || size.y > 1) { size.x = std::max(1, (size.x + 1) / 2); size.y = std::max(1, (size.y + 1) / 2); @@ -586,10 +588,42 @@ public: + evalTexel(level, xPos + 1, yPos + 1) * dx1 * dy1; } + /** + * \brief Evaluate the gradient of the texture at the given MIP level + */ + inline void evalGradientBilinear(int level, const Point2 &uv, Value *gradient) const { + if (EXPECT_NOT_TAKEN(!std::isfinite(uv.x) || !std::isfinite(uv.y))) { + Log(EWarn, "evalGradientBilinear(): encountered a NaN!"); + gradient[0] = gradient[1] = Value(0.0f); + return; + } else if (EXPECT_NOT_TAKEN(level >= m_levels)) { + evalGradientBilinear(m_levels-1, uv, gradient); + return; + } + + /* Convert to fractional pixel coordinates on the specified level */ + const Vector2i &size = m_pyramid[level].getSize(); + Float u = uv.x * size.x - 0.5f, v = uv.y * size.y - 0.5f; + + int xPos = floorToInt(u), yPos = floorToInt(v); + Float dx = u - xPos, dy = v - yPos; + + const Value p00 = evalTexel(level, xPos, yPos); + const Value p10 = evalTexel(level, xPos+1, yPos); + const Value p01 = evalTexel(level, xPos, yPos+1); + const Value p11 = evalTexel(level, xPos+1, yPos+1); + Value tmp = p01 + p10 - p11; + + gradient[0] = (p10 + p00*(dy-1) - tmp*dy) * size.x; + gradient[1] = (p01 + p00*(dx-1) - tmp*dx) * size.y; + } + /// \brief Perform a filtered texture lookup using the configured method Value eval(const Point2 &uv, const Vector2 &d0, const Vector2 &d1) const { if (m_filterType == ENearest) return evalBox(0, uv); + else if (m_filterType == EBilinear) + return evalBilinear(0, uv); /* Convert into texel coordinates */ const Vector2i &size = m_pyramid[0].getSize(); @@ -685,6 +719,7 @@ public: switch (m_filterType) { case ENearest: oss << "nearest," << endl; break; + case EBilinear: oss << "bilinear," << endl; break; case ETrilinear: oss << "trilinear," << endl; break; case EEWA: oss << "ewa," << endl; break; } diff --git a/include/mitsuba/render/texture.h b/include/mitsuba/render/texture.h index 6b27cd70..4b981330 100644 --- a/include/mitsuba/render/texture.h +++ b/include/mitsuba/render/texture.h @@ -41,6 +41,16 @@ public: */ virtual Spectrum eval(const Intersection &its, bool filter = true) const; + /** + * \brief Return the texture gradient at \c its + * + * The parameter \c gradient should point to an array with space for + * two \ref Spectrum data structures corresponding to the U and V derivative. + * + * \remark This function is usually implemented pointwise without any kind of filtering. + */ + virtual void evalGradient(const Intersection &its, Spectrum *gradient) const; + /// Return the component-wise average value of the texture over its domain virtual Spectrum getAverage() const; @@ -108,6 +118,16 @@ public: */ Spectrum eval(const Intersection &its, bool filter = true) const; + /** + * \brief Return the texture gradient at \c its + * + * The parameter \c gradient should point to an array with space for + * two \ref Spectrum data structures corresponding to the U and V derivative. + * + * \remark This function is usually implemented pointwise without any kind of filtering. + */ + void evalGradient(const Intersection &its, Spectrum *gradient) const; + /// Serialize to a binary data stream virtual void serialize(Stream *stream, InstanceManager *manager) const; @@ -118,6 +138,9 @@ public: virtual Spectrum eval(const Point2 &uv, const Vector2 &d0, const Vector2 &d1) const = 0; + /// Unfiltered radient lookup lookup -- Texture2D subclasses can optionally provide this function + virtual void evalGradient(const Point2 &uv, Spectrum *gradient) const; + /** * \brief Return a bitmap representation of the texture * diff --git a/src/bsdfs/CMakeLists.txt b/src/bsdfs/CMakeLists.txt index d962ea7b..c0d6357d 100644 --- a/src/bsdfs/CMakeLists.txt +++ b/src/bsdfs/CMakeLists.txt @@ -18,7 +18,8 @@ add_bsdf(roughconductor roughconductor.cpp microfacet.h) add_bsdf(roughplastic roughplastic.cpp microfacet.h ior.h) # Materials that act as modifiers -add_bsdf(bump bump.cpp) +add_bsdf(bumpmap bumpmap.cpp) +add_bsdf(normalmap normalmap.cpp) add_bsdf(twosided twosided.cpp) add_bsdf(mask mask.cpp) add_bsdf(mixturebsdf mixturebsdf.cpp) diff --git a/src/bsdfs/SConscript b/src/bsdfs/SConscript index 4154cac5..3b5b724c 100644 --- a/src/bsdfs/SConscript +++ b/src/bsdfs/SConscript @@ -17,7 +17,8 @@ plugins += env.SharedLibrary('mixturebsdf', ['mixturebsdf.cpp']) plugins += env.SharedLibrary('blendbsdf', ['blendbsdf.cpp']) plugins += env.SharedLibrary('coating', ['coating.cpp']) plugins += env.SharedLibrary('roughcoating', ['roughcoating.cpp']) -plugins += env.SharedLibrary('bump', ['bump.cpp']) +plugins += env.SharedLibrary('bumpmap', ['bumpmap.cpp']) +plugins += env.SharedLibrary('normalmap', ['normalmap.cpp']) # Other materials plugins += env.SharedLibrary('ward', ['ward.cpp']) diff --git a/src/bsdfs/bump.cpp b/src/bsdfs/bumpmap.cpp similarity index 77% rename from src/bsdfs/bump.cpp rename to src/bsdfs/bumpmap.cpp index c7a81c7a..8de46dc6 100644 --- a/src/bsdfs/bump.cpp +++ b/src/bsdfs/bumpmap.cpp @@ -21,9 +21,9 @@ MTS_NAMESPACE_BEGIN -/*! \plugin{bump}{Bump map modifier} +/*! \plugin{bumpmap}{Bump map modifier} * \order{12} - * \icon{bsdf_bump} + * \icon{bsdf_bumpmap} * * \parameters{ * \parameter{\Unnamed}{\Texture}{ @@ -35,8 +35,8 @@ MTS_NAMESPACE_BEGIN * be affected by the bump map} * } * \renderings{ - * \rendering{Bump map based on tileable diagonal lines}{bsdf_bump_1} - * \rendering{An irregular bump map}{bsdf_bump_2} + * \rendering{Bump map based on tileable diagonal lines}{bsdf_bumpmap_1} + * \rendering{An irregular bump map}{bsdf_bumpmap_2} * } * * Bump mapping \cite{Blinn1978Simulation} is a simple technique for cheaply @@ -57,7 +57,7 @@ MTS_NAMESPACE_BEGIN * texture plugin can be used to magnify or reduce the effect of a * bump map texture. * \begin{xml}[caption=A rough metal model with a scaled image-based bump map] - * + * * * * @@ -119,22 +119,12 @@ public: } } - void perturbIntersection(const Intersection &its, Intersection &target) const { - const Float eps = Epsilon; + Frame getFrame(const Intersection &its) const { + Spectrum grad[2]; + m_displacement->evalGradient(its, grad); - /* Compute the U and V displacementment derivatives */ - target = its; - Float disp = m_displacement->eval(target, false).getLuminance(); - target.p = its.p + its.dpdu * eps; - target.uv = its.uv + Point2(eps, 0); - Float dispU = m_displacement->eval(target, false).getLuminance(); - target.p = its.p + its.dpdv * eps; - target.uv = its.uv + Point2(0, eps); - Float dispV = m_displacement->eval(target, false).getLuminance(); - target.p = its.p; - target.uv = its.uv; - Float dDispDu = (dispU - disp) / eps; - Float dDispDv = (dispV - disp) / eps; + Float dDispDu = grad[0].getLuminance(); + Float dDispDv = grad[1].getLuminance(); /* Build a perturbed frame -- ignores the usually negligible normal derivative term */ @@ -143,21 +133,22 @@ public: Vector dpdv = its.dpdv + its.shFrame.n * ( dDispDv - dot(its.shFrame.n, its.dpdv)); - dpdu = normalize(dpdu); - dpdv = normalize(dpdv - dpdu * dot(dpdv, dpdu)); + Frame result; + result.n = normalize(cross(dpdu, dpdv)); + result.s = normalize(dpdu - result.n + * dot(result.n, dpdu)); + result.t = cross(result.n, result.s); - target.shFrame.s = dpdu; - target.shFrame.t = dpdv; - target.shFrame = Frame(Normal(cross(dpdv, dpdu))); + if (dot(result.n, its.geoFrame.n) < 0) + result.n *= -1; - if (dot(target.shFrame.n, target.geoFrame.n) < 0) - target.shFrame.n *= -1; + return result; } Spectrum eval(const BSDFSamplingRecord &bRec, EMeasure measure) const { const Intersection& its = bRec.its; - Intersection perturbed; - perturbIntersection(its, perturbed); + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); BSDFSamplingRecord perturbedQuery(perturbed, perturbed.toLocal(its.toWorld(bRec.wi)), @@ -172,8 +163,8 @@ public: Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const { const Intersection& its = bRec.its; - Intersection perturbed; - perturbIntersection(its, perturbed); + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); BSDFSamplingRecord perturbedQuery(perturbed, perturbed.toLocal(its.toWorld(bRec.wi)), @@ -189,8 +180,8 @@ public: Spectrum sample(BSDFSamplingRecord &bRec, const Point2 &sample) const { const Intersection& its = bRec.its; - Intersection perturbed; - perturbIntersection(its, perturbed); + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode); perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi)); @@ -202,6 +193,7 @@ public: bRec.sampledComponent = perturbedQuery.sampledComponent; bRec.sampledType = perturbedQuery.sampledType; bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo)); + bRec.eta = perturbedQuery.eta; if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) return Spectrum(0.0f); } @@ -210,8 +202,8 @@ public: Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &sample) const { const Intersection& its = bRec.its; - Intersection perturbed; - perturbIntersection(its, perturbed); + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode); perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi)); @@ -223,6 +215,7 @@ public: bRec.sampledComponent = perturbedQuery.sampledComponent; bRec.sampledType = perturbedQuery.sampledType; bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo)); + bRec.eta = perturbedQuery.eta; if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) return Spectrum(0.0f); } @@ -285,38 +278,32 @@ public: const std::string &evalName, const std::vector &depNames) const { oss << "vec3 " << evalName << "(vec2 uv, vec3 wi, vec3 wo) {" << endl - << " float du = abs(dFdx(uv.x)), dv = abs(dFdx(uv.y));" << endl - << " if (du == 0.0) du = 0.001;" << endl - << " if (dv == 0.0) dv = 0.001;" << endl + << " float eps = 1e-4;" << endl << " float displacement = " << depNames[1] << "(uv)[0];" << endl - << " float displacementU = " << depNames[1] << "(uv + vec2(du, 0.0))[0];" << endl - << " float displacementV = " << depNames[1] << "(uv + vec2(0.0, dv))[0];" << endl - << " float dfdu = (displacementU - displacement)/du;" << endl - << " float dfdv = (displacementV - displacement)/dv;" << endl - << " vec3 dpdu = normalize(vec3(1.0, 0.0, dfdu));" << endl - << " vec3 dpdv = vec3(0.0, 1.0, dfdv);" << endl - << " dpdv = normalize(dpdv - dot(dpdu, dpdv)*dpdu);" << endl - << " vec3 n = cross(dpdu, dpdv);" << endl - << " wi = vec3(dot(wi, dpdu), dot(wi, dpdv), dot(wi, n));" << endl - << " wo = vec3(dot(wo, dpdu), dot(wo, dpdv), dot(wo, n));" << endl + << " float displacementU = " << depNames[1] << "(uv + vec2(eps, 0.0))[0];" << endl + << " float displacementV = " << depNames[1] << "(uv + vec2(0.0, eps))[0];" << endl + << " float dfdu = (displacementU - displacement)*(0.5/eps);" << endl + << " float dfdv = (displacementV - displacement)*(0.5/eps);" << endl + << " vec3 n = normalize(vec3(-dfdu, -dfdv, 1.0));" << endl + << " vec3 s = normalize(vec3(1.0-n.x*n.x, -n.x*n.y, -n.x*n.z)); " << endl + << " vec3 t = cross(s, n);" << endl + << " wi = vec3(dot(wi, s), dot(wi, t), dot(wi, n));" << endl + << " wo = vec3(dot(wo, s), dot(wo, t), dot(wo, n));" << endl << " return " << depNames[0] << "(uv, wi, wo);" << endl << "}" << endl << endl << "vec3 " << evalName << "_diffuse(vec2 uv, vec3 wi, vec3 wo) {" << endl - << " float du = abs(dFdx(uv.x)), dv = abs(dFdx(uv.y));" << endl - << " if (du == 0.0) du = 0.001;" << endl - << " if (dv == 0.0) dv = 0.001;" << endl + << " float eps = 1e-4;" << endl << " float displacement = " << depNames[1] << "(uv)[0];" << endl - << " float displacementU = " << depNames[1] << "(uv + vec2(du, 0.0))[0];" << endl - << " float displacementV = " << depNames[1] << "(uv + vec2(0.0, dv))[0];" << endl - << " float dfdu = (displacementU - displacement)/du;" << endl - << " float dfdv = (displacementV - displacement)/dv;" << endl - << " vec3 dpdu = normalize(vec3(1.0, 0.0, dfdu));" << endl - << " vec3 dpdv = vec3(0.0, 1.0, dfdv);" << endl - << " dpdv = normalize(dpdv - dot(dpdu, dpdv)*dpdu);" << endl - << " vec3 n = cross(dpdu, dpdv);" << endl - << " wi = vec3(dot(wi, dpdu), dot(wi, dpdv), dot(wi, n));" << endl - << " wo = vec3(dot(wo, dpdu), dot(wo, dpdv), dot(wo, n));" << endl + << " float displacementU = " << depNames[1] << "(uv + vec2(eps, 0.0))[0];" << endl + << " float displacementV = " << depNames[1] << "(uv + vec2(0.0, eps))[0];" << endl + << " float dfdu = (displacementU - displacement)*(0.5/eps);" << endl + << " float dfdv = (displacementV - displacement)*(0.5/eps);" << endl + << " vec3 n = normalize(vec3(-dfdu, -dfdv, 1.0));" << endl + << " vec3 s = normalize(vec3(1.0-n.x*n.x, -n.x*n.y, -n.x*n.z)); " << endl + << " vec3 t = cross(s, n);" << endl + << " wi = vec3(dot(wi, s), dot(wi, t), dot(wi, n));" << endl + << " wo = vec3(dot(wo, s), dot(wo, t), dot(wo, n));" << endl << " return " << depNames[0] << "_diffuse(uv, wi, wo);" << endl << "}" << endl; } @@ -335,5 +322,5 @@ Shader *BumpMap::createShader(Renderer *renderer) const { MTS_IMPLEMENT_CLASS(BumpMapShader, false, Shader) MTS_IMPLEMENT_CLASS_S(BumpMap, false, BSDF) -MTS_EXPORT_PLUGIN(BumpMap, "Smooth dielectric coating"); +MTS_EXPORT_PLUGIN(BumpMap, "Bump map modifier"); MTS_NAMESPACE_END diff --git a/src/bsdfs/normalmap.cpp b/src/bsdfs/normalmap.cpp new file mode 100644 index 00000000..4b46f0ab --- /dev/null +++ b/src/bsdfs/normalmap.cpp @@ -0,0 +1,259 @@ +/* + 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 + +MTS_NAMESPACE_BEGIN + +class NormalMap : public BSDF { +public: + NormalMap(const Properties &props) : BSDF(props) { } + + NormalMap(Stream *stream, InstanceManager *manager) + : BSDF(stream, manager) { + m_nested = static_cast(manager->getInstance(stream)); + m_normals = static_cast(manager->getInstance(stream)); + configure(); + } + + void configure() { + if (!m_nested) + Log(EError, "A child BSDF instance is required"); + if (!m_normals) + Log(EError, "A normal map texture must be specified"); + + m_components.clear(); + for (int i=0; igetComponentCount(); ++i) + m_components.push_back(m_nested->getType(i) | ESpatiallyVarying | EAnisotropic); + + m_usesRayDifferentials = true; + + BSDF::configure(); + } + + void serialize(Stream *stream, InstanceManager *manager) const { + BSDF::serialize(stream, manager); + + manager->serialize(stream, m_nested.get()); + manager->serialize(stream, m_normals.get()); + } + + void addChild(const std::string &name, ConfigurableObject *child) { + if (child->getClass()->derivesFrom(MTS_CLASS(BSDF))) { + if (m_nested != NULL) + Log(EError, "Only a single nested BSDF can be added!"); + m_nested = static_cast(child); + } else if (child->getClass()->derivesFrom(MTS_CLASS(Texture))) { + if (m_normals != NULL) + Log(EError, "Only a single normals texture can be specified!"); + m_normals = static_cast(child); + } else { + BSDF::addChild(name, child); + } + } + + Frame getFrame(const Intersection &its) const { + Normal n; + m_normals->eval(its, false).toLinearRGB(n.x, n.y, n.z); + for (int i=0; i<3; ++i) + n[i] = 2 * n[i] - 1; + + Frame result; + result.n = normalize(its.shFrame.toWorld(n)); + result.s = normalize(its.dpdu - result.n + * dot(result.n, its.dpdu)); + result.t = cross(result.n, result.s); + + if (dot(result.n, its.geoFrame.n) < 0) + result.n *= -1; + + return result; + } + + Spectrum eval(const BSDFSamplingRecord &bRec, EMeasure measure) const { + const Intersection& its = bRec.its; + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); + + BSDFSamplingRecord perturbedQuery(perturbed, + perturbed.toLocal(its.toWorld(bRec.wi)), + perturbed.toLocal(its.toWorld(bRec.wo)), bRec.mode); + + if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) + return Spectrum(0.0f); + + perturbedQuery.sampler = bRec.sampler; + perturbedQuery.typeMask = bRec.typeMask; + perturbedQuery.component = bRec.component; + + return m_nested->eval(perturbedQuery, measure); + } + + Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const { + const Intersection& its = bRec.its; + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); + + BSDFSamplingRecord perturbedQuery(perturbed, + perturbed.toLocal(its.toWorld(bRec.wi)), + perturbed.toLocal(its.toWorld(bRec.wo)), bRec.mode); + if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) + return 0; + perturbedQuery.mode = bRec.mode; + perturbedQuery.sampler = bRec.sampler; + perturbedQuery.typeMask = bRec.typeMask; + perturbedQuery.component = bRec.component; + return m_nested->pdf(perturbedQuery, measure); + } + + Spectrum sample(BSDFSamplingRecord &bRec, const Point2 &sample) const { + const Intersection& its = bRec.its; + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); + + BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode); + perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi)); + perturbedQuery.sampler = bRec.sampler; + perturbedQuery.typeMask = bRec.typeMask; + perturbedQuery.component = bRec.component; + Spectrum result = m_nested->sample(perturbedQuery, sample); + if (!result.isZero()) { + bRec.sampledComponent = perturbedQuery.sampledComponent; + bRec.sampledType = perturbedQuery.sampledType; + bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo)); + bRec.eta = perturbedQuery.eta; + if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) + return Spectrum(0.0f); + } + return result; + } + + Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &sample) const { + const Intersection& its = bRec.its; + Intersection perturbed(its); + perturbed.shFrame = getFrame(its); + + BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode); + perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi)); + perturbedQuery.typeMask = bRec.typeMask; + perturbedQuery.component = bRec.component; + Spectrum result = m_nested->sample(perturbedQuery, pdf, sample); + + if (!result.isZero()) { + bRec.sampledComponent = perturbedQuery.sampledComponent; + bRec.sampledType = perturbedQuery.sampledType; + bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo)); + bRec.eta = perturbedQuery.eta; + if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) + return Spectrum(0.0f); + } + + return result; + } + + Float getRoughness(const Intersection &its, int component) const { + return m_nested->getRoughness(its, component); + } + + std::string toString() const { + std::ostringstream oss; + oss << "NormalMap[" << endl + << " id = \"" << getID() << "\"," << endl + << " normals = " << indent(m_normals->toString()) << endl + << " nested = " << indent(m_nested->toString()) << endl + << "]"; + return oss.str(); + } + + Shader *createShader(Renderer *renderer) const; + + MTS_DECLARE_CLASS() +protected: + ref m_normals; + ref m_nested; +}; + +// ================ Hardware shader implementation ================ + +/** + * This is a quite approximate version of the bump map model -- it likely + * won't match the reference exactly, but it should be good enough for + * preview purposes + */ +class NormalMapShader : public Shader { +public: + NormalMapShader(Renderer *renderer, const BSDF *nested, const Texture *normals) + : Shader(renderer, EBSDFShader), m_nested(nested), m_normals(normals) { + m_nestedShader = renderer->registerShaderForResource(m_nested.get()); + m_normalShader = renderer->registerShaderForResource(m_normals.get()); + } + + bool isComplete() const { + return m_nestedShader.get() != NULL; + } + + void cleanup(Renderer *renderer) { + renderer->unregisterShaderForResource(m_nested.get()); + renderer->unregisterShaderForResource(m_normals.get()); + } + + void putDependencies(std::vector &deps) { + deps.push_back(m_nestedShader.get()); + deps.push_back(m_normalShader.get()); + } + + void generateCode(std::ostringstream &oss, + const std::string &evalName, + const std::vector &depNames) const { + oss << "vec3 " << evalName << "(vec2 uv, vec3 wi, vec3 wo) {" << endl + << " vec3 n = normalize(2.0*" << depNames[1] << "(uv) - vec3(1.0));" << endl + << " vec3 s = normalize(vec3(1.0-n.x*n.x, -n.x*n.y, -n.x*n.z)); " << endl + << " vec3 t = cross(s, n);" << endl + << " wi = vec3(dot(wi, s), dot(wi, t), dot(wi, n));" << endl + << " wo = vec3(dot(wo, s), dot(wo, t), dot(wo, n));" << endl + << " return " << depNames[0] << "(uv, wi, wo);" << endl + << "}" << endl + << endl + << "vec3 " << evalName << "_diffuse(vec2 uv, vec3 wi, vec3 wo) {" << endl + << " vec3 n = normalize(2.0*" << depNames[1] << "(uv) - vec3(1.0));" << endl + << " vec3 s = normalize(vec3(1.0-n.x*n.x, -n.x*n.y, -n.x*n.z)); " << endl + << " vec3 t = cross(s, n);" << endl + << " wi = vec3(dot(wi, s), dot(wi, t), dot(wi, n));" << endl + << " wo = vec3(dot(wo, s), dot(wo, t), dot(wo, n));" << endl + << " return " << depNames[0] << "_diffuse(uv, wi, wo);" << endl + << "}" << endl + << endl; + } + + MTS_DECLARE_CLASS() +private: + ref m_nested; + ref m_normals; + ref m_nestedShader; + ref m_normalShader; +}; + +Shader *NormalMap::createShader(Renderer *renderer) const { + return new NormalMapShader(renderer, m_nested.get(), m_normals.get()); +} + +MTS_IMPLEMENT_CLASS(NormalMapShader, false, Shader) +MTS_IMPLEMENT_CLASS_S(NormalMap, false, BSDF) +MTS_EXPORT_PLUGIN(NormalMap, "Smooth dielectric coating"); +MTS_NAMESPACE_END diff --git a/src/bsdfs/roughplastic.cpp b/src/bsdfs/roughplastic.cpp index 97cb4753..cc8f933d 100644 --- a/src/bsdfs/roughplastic.cpp +++ b/src/bsdfs/roughplastic.cpp @@ -155,7 +155,7 @@ MTS_NAMESPACE_BEGIN * * * - * + * * * * diff --git a/src/libcore/spectrum.cpp b/src/libcore/spectrum.cpp index aa3f82bb..9f14399e 100644 --- a/src/libcore/spectrum.cpp +++ b/src/libcore/spectrum.cpp @@ -632,8 +632,6 @@ void InterpolatedSpectrum::zeroExtend() { } Float InterpolatedSpectrum::average(Float lambdaMin, Float lambdaMax) const { - typedef std::vector::const_iterator iterator; - if (m_wavelengths.size() < 2) return 0.0f; diff --git a/src/librender/bsdf.cpp b/src/librender/bsdf.cpp index da5ea1b5..b6717190 100644 --- a/src/librender/bsdf.cpp +++ b/src/librender/bsdf.cpp @@ -63,6 +63,10 @@ Float BSDF::getEta() const { return 1.0f; } +Frame BSDF::getFrame(const Intersection &its) const { + return its.shFrame; +} + Float BSDF::getRoughness(const Intersection &its, int component) const { NotImplementedError("getRoughness"); } diff --git a/src/librender/texture.cpp b/src/librender/texture.cpp index 4c203831..15f804a7 100644 --- a/src/librender/texture.cpp +++ b/src/librender/texture.cpp @@ -53,6 +53,24 @@ ref Texture::expand() { return this; } +void Texture::evalGradient(const Intersection &_its, Spectrum *gradient) const { + const Float eps = Epsilon; + Intersection its(_its); + + Spectrum value = eval(its, false); + + its.p = _its.p + its.dpdu * eps; + its.uv = _its.uv + Point2(eps, 0); + Spectrum valueU = eval(its, false); + + its.p = _its.p + its.dpdv * eps; + its.uv = _its.uv + Point2(0, eps); + Spectrum valueV = eval(its, false); + + gradient[0] = (valueU - value)*(1/eps); + gradient[1] = (valueV - value)*(1/eps); +} + Texture::~Texture() { } void Texture::serialize(Stream *stream, InstanceManager *manager) const { @@ -101,6 +119,26 @@ Spectrum Texture2D::eval(const Intersection &its, bool filter) const { } } +void Texture2D::evalGradient(const Intersection &its, Spectrum *gradient) const { + Point2 uv = Point2(its.uv.x * m_uvScale.x, its.uv.y * m_uvScale.y) + m_uvOffset; + + evalGradient(uv, gradient); + + gradient[0] *= m_uvScale.x; + gradient[1] *= m_uvScale.y; +} + +void Texture2D::evalGradient(const Point2 &uv, Spectrum *gradient) const { + const Float eps = Epsilon; + + Spectrum value = eval(uv); + Spectrum valueU = eval(uv + Vector2(eps, 0)); + Spectrum valueV = eval(uv + Vector2(0, eps)); + + gradient[0] = (valueU - value)*(1/eps); + gradient[1] = (valueV - value)*(1/eps); +} + ref Texture2D::getBitmap(const Vector2i &sizeHint) const { Vector2i res(sizeHint); if (res.x <= 0 || res.y <= 0) diff --git a/src/shapes/obj.cpp b/src/shapes/obj.cpp index 2e1130e6..f0a1244e 100644 --- a/src/shapes/obj.cpp +++ b/src/shapes/obj.cpp @@ -59,6 +59,9 @@ MTS_NAMESPACE_BEGIN * Specifies an optional linear object-to-world transformation. * \default{none (i.e. object space $=$ world space)} * } + * \parameter{collapse}{\Boolean}{ + * Collapse all contained meshes into a single object \default{\code{false}} + * } * } * \renderings{ * \label{fig:rungholt} @@ -196,6 +199,9 @@ public: /* Causes all normals to be flipped */ m_flipNormals = props.getBoolean("flipNormals", false); + /* Collapse all contained shapes / groups into a single object? */ + m_collapse = props.getBoolean("collapse", false); + /* Causes all texture coordinates to be vertically flipped */ bool flipTexCoords = props.getBoolean("flipTexCoords", true); @@ -238,7 +244,7 @@ public: Normal n; iss >> n.x >> n.y >> n.z; normals.push_back(n); - } else if (buf == "g") { + } else if (buf == "g" && !m_collapse) { std::string targetName; std::string newName = trim(line.substr(1, line.length()-1)); @@ -816,6 +822,7 @@ private: bool m_flipNormals, m_faceNormals; std::string m_name; AABB m_aabb; + bool m_collapse; }; MTS_IMPLEMENT_CLASS_S(WavefrontOBJ, false, Shape) diff --git a/src/textures/bitmap.cpp b/src/textures/bitmap.cpp index ee8deb58..d9d87a9e 100644 --- a/src/textures/bitmap.cpp +++ b/src/textures/bitmap.cpp @@ -219,6 +219,8 @@ public: if (filterType == "ewa") m_filterType = EEWA; + else if (filterType == "bilinear") + m_filterType = EBilinear; else if (filterType == "trilinear") m_filterType = ETrilinear; else if (filterType == "nearest") @@ -451,6 +453,32 @@ public: return result; } + void evalGradient(const Point2 &uv, Spectrum *gradient) const { + /* There are no ray differentials to do any kind of + prefiltering. Evaluate the full-resolution texture */ + + if (m_mipmap3.get()) { + Color3 result[2]; + if (m_mipmap3->getFilterType() != ENearest) { + m_mipmap3->evalGradientBilinear(0, uv, result); + gradient[0].fromLinearRGB(result[0][0], result[0][1], result[0][2]); + gradient[1].fromLinearRGB(result[1][0], result[1][1], result[1][2]); + } else { + gradient[0] = gradient[1] = Spectrum(0.0f); + } + } else { + Color1 result[2]; + if (m_mipmap1->getFilterType() != ENearest) { + m_mipmap1->evalGradientBilinear(0, uv, result); + gradient[0] = Spectrum(result[0][0]); + gradient[1] = Spectrum(result[1][0]); + } else { + gradient[0] = gradient[1] = Spectrum(0.0f); + } + } + stats::filteredLookups.incrementBase(); + } + ref getBitmap(const Vector2i &/* unused */) const { return m_mipmap1.get() ? m_mipmap1->toBitmap() : m_mipmap3->toBitmap(); } @@ -599,6 +627,10 @@ public: case ENearest: m_gpuTexture->setFilterType(GPUTexture::ENearest); break; + case EBilinear: + m_gpuTexture->setFilterType(GPUTexture::ELinear); + m_gpuTexture->setMipMapped(false); + break; default: m_gpuTexture->setFilterType(GPUTexture::EMipMapLinear); break; diff --git a/src/textures/scale.cpp b/src/textures/scale.cpp index 94270396..a7713bdd 100644 --- a/src/textures/scale.cpp +++ b/src/textures/scale.cpp @@ -86,6 +86,12 @@ public: return m_nested->eval(its, filter) * m_scale; } + void evalGradient(const Intersection &its, Spectrum *gradient) const { + m_nested->evalGradient(its, gradient); + gradient[0] *= m_scale; + gradient[1] *= m_scale; + } + Spectrum getAverage() const { return m_nested->getAverage() * m_scale; }