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;
}