improved bump and normal mapping support

metadata
Wenzel Jakob 2013-11-15 16:47:35 +01:00
parent 38fced0f53
commit b68a38ed9a
20 changed files with 506 additions and 81 deletions

View File

@ -0,0 +1,25 @@
<?xml version='1.0' encoding='utf-8'?>
<!-- Stylesheet to upgrade from Mitsuba version 0.4.x to 0.5.0 scenes -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:preserve-space elements="*"/>
<!-- Update the scene version -->
<xsl:template match="scene/@version">
<xsl:attribute name="version">0.5.0</xsl:attribute>
</xsl:template>
<!-- Update the name of the bump plugin -->
<xsl:template match="integrator[@type='bump']/@type">
<xsl:attribute name="type">bumpmap</xsl:attribute>
</xsl:template>
<!-- Default copy rule -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

View File

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

View File

@ -453,6 +453,15 @@ public:
*/ */
virtual Float getRoughness(const Intersection &its, int index) const; 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 <tt>its.shFrame</tt>
*/
virtual Frame getFrame(const Intersection &its) const;
// ============================================================= // =============================================================
//! @{ \name ConfigurableObject interface //! @{ \name ConfigurableObject interface
// ============================================================= // =============================================================

View File

@ -52,12 +52,14 @@ namespace stats {
/// Specifies the desired antialiasing filter /// Specifies the desired antialiasing filter
enum EMIPFilterType { enum EMIPFilterType {
/// No filtering (i.e. nearest neighbor lookups) /// No filtering, nearest neighbor lookups
ENearest = 0, ENearest = 0,
/// No filtering, only bilinear interpolation
EBilinear = 1,
/// Basic trilinear filtering /// Basic trilinear filtering
ETrilinear = 1, ETrilinear = 2,
/// Elliptically weighted average /// Elliptically weighted average
EEWA = 2, EEWA = 3
}; };
/** /**
@ -179,7 +181,7 @@ public:
/* 1. Determine the number of MIP levels. The following /* 1. Determine the number of MIP levels. The following
code also handles non-power-of-2 input. */ code also handles non-power-of-2 input. */
m_levels = 1; m_levels = 1;
if (m_filterType != ENearest) { if (m_filterType != ENearest && m_filterType != EBilinear) {
Vector2i size = bitmap_->getSize(); Vector2i size = bitmap_->getSize();
while (size.x > 1 || size.y > 1) { while (size.x > 1 || size.y > 1) {
size.x = std::max(1, (size.x + 1) / 2); size.x = std::max(1, (size.x + 1) / 2);
@ -233,7 +235,7 @@ public:
m_sizeRatio[0] = Vector2(1, 1); m_sizeRatio[0] = Vector2(1, 1);
/* 3. Progressively downsample until only a 1x1 image is left */ /* 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(); Vector2i size = bitmap_->getSize();
m_levels = 1; m_levels = 1;
while (size.x > 1 || size.y > 1) { while (size.x > 1 || size.y > 1) {
@ -344,7 +346,7 @@ public:
mmapPtr += m_pyramid[0].getBufferSize(); mmapPtr += m_pyramid[0].getBufferSize();
m_sizeRatio[0] = Vector2(1, 1); m_sizeRatio[0] = Vector2(1, 1);
if (m_filterType != ENearest) { if (m_filterType != ENearest && m_filterType != EBilinear) {
/* Map the remainder of the image pyramid */ /* Map the remainder of the image pyramid */
int level = 1; int level = 1;
while (size.x > 1 || size.y > 1) { while (size.x > 1 || size.y > 1) {
@ -425,7 +427,7 @@ public:
size_t expectedFileSize = sizeof(MIPMapHeader) + padding size_t expectedFileSize = sizeof(MIPMapHeader) + padding
+ Array2DType::bufferSize(size); + Array2DType::bufferSize(size);
if (filterType != ENearest) { if (filterType != ENearest && filterType != EBilinear) {
while (size.x > 1 || size.y > 1) { while (size.x > 1 || size.y > 1) {
size.x = std::max(1, (size.x + 1) / 2); size.x = std::max(1, (size.x + 1) / 2);
size.y = std::max(1, (size.y + 1) / 2); size.y = std::max(1, (size.y + 1) / 2);
@ -586,10 +588,42 @@ public:
+ evalTexel(level, xPos + 1, yPos + 1) * dx1 * dy1; + 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 /// \brief Perform a filtered texture lookup using the configured method
Value eval(const Point2 &uv, const Vector2 &d0, const Vector2 &d1) const { Value eval(const Point2 &uv, const Vector2 &d0, const Vector2 &d1) const {
if (m_filterType == ENearest) if (m_filterType == ENearest)
return evalBox(0, uv); return evalBox(0, uv);
else if (m_filterType == EBilinear)
return evalBilinear(0, uv);
/* Convert into texel coordinates */ /* Convert into texel coordinates */
const Vector2i &size = m_pyramid[0].getSize(); const Vector2i &size = m_pyramid[0].getSize();
@ -685,6 +719,7 @@ public:
switch (m_filterType) { switch (m_filterType) {
case ENearest: oss << "nearest," << endl; break; case ENearest: oss << "nearest," << endl; break;
case EBilinear: oss << "bilinear," << endl; break;
case ETrilinear: oss << "trilinear," << endl; break; case ETrilinear: oss << "trilinear," << endl; break;
case EEWA: oss << "ewa," << endl; break; case EEWA: oss << "ewa," << endl; break;
} }

View File

@ -41,6 +41,16 @@ public:
*/ */
virtual Spectrum eval(const Intersection &its, bool filter = true) const; 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 /// Return the component-wise average value of the texture over its domain
virtual Spectrum getAverage() const; virtual Spectrum getAverage() const;
@ -108,6 +118,16 @@ public:
*/ */
Spectrum eval(const Intersection &its, bool filter = true) const; 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 /// Serialize to a binary data stream
virtual void serialize(Stream *stream, InstanceManager *manager) const; virtual void serialize(Stream *stream, InstanceManager *manager) const;
@ -118,6 +138,9 @@ public:
virtual Spectrum eval(const Point2 &uv, const Vector2 &d0, virtual Spectrum eval(const Point2 &uv, const Vector2 &d0,
const Vector2 &d1) const = 0; 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 * \brief Return a bitmap representation of the texture
* *

View File

@ -18,7 +18,8 @@ add_bsdf(roughconductor roughconductor.cpp microfacet.h)
add_bsdf(roughplastic roughplastic.cpp microfacet.h ior.h) add_bsdf(roughplastic roughplastic.cpp microfacet.h ior.h)
# Materials that act as modifiers # 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(twosided twosided.cpp)
add_bsdf(mask mask.cpp) add_bsdf(mask mask.cpp)
add_bsdf(mixturebsdf mixturebsdf.cpp) add_bsdf(mixturebsdf mixturebsdf.cpp)

View File

@ -17,7 +17,8 @@ plugins += env.SharedLibrary('mixturebsdf', ['mixturebsdf.cpp'])
plugins += env.SharedLibrary('blendbsdf', ['blendbsdf.cpp']) plugins += env.SharedLibrary('blendbsdf', ['blendbsdf.cpp'])
plugins += env.SharedLibrary('coating', ['coating.cpp']) plugins += env.SharedLibrary('coating', ['coating.cpp'])
plugins += env.SharedLibrary('roughcoating', ['roughcoating.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 # Other materials
plugins += env.SharedLibrary('ward', ['ward.cpp']) plugins += env.SharedLibrary('ward', ['ward.cpp'])

View File

@ -21,9 +21,9 @@
MTS_NAMESPACE_BEGIN MTS_NAMESPACE_BEGIN
/*! \plugin{bump}{Bump map modifier} /*! \plugin{bumpmap}{Bump map modifier}
* \order{12} * \order{12}
* \icon{bsdf_bump} * \icon{bsdf_bumpmap}
* *
* \parameters{ * \parameters{
* \parameter{\Unnamed}{\Texture}{ * \parameter{\Unnamed}{\Texture}{
@ -35,8 +35,8 @@ MTS_NAMESPACE_BEGIN
* be affected by the bump map} * be affected by the bump map}
* } * }
* \renderings{ * \renderings{
* \rendering{Bump map based on tileable diagonal lines}{bsdf_bump_1} * \rendering{Bump map based on tileable diagonal lines}{bsdf_bumpmap_1}
* \rendering{An irregular bump map}{bsdf_bump_2} * \rendering{An irregular bump map}{bsdf_bumpmap_2}
* } * }
* *
* Bump mapping \cite{Blinn1978Simulation} is a simple technique for cheaply * 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 * texture plugin can be used to magnify or reduce the effect of a
* bump map texture. * bump map texture.
* \begin{xml}[caption=A rough metal model with a scaled image-based bump map] * \begin{xml}[caption=A rough metal model with a scaled image-based bump map]
* <bsdf type="bump"> * <bsdf type="bumpmap">
* <!-- The bump map is applied to a rough metal BRDF --> * <!-- The bump map is applied to a rough metal BRDF -->
* <bsdf type="roughconductor"/> * <bsdf type="roughconductor"/>
* *
@ -119,22 +119,12 @@ public:
} }
} }
void perturbIntersection(const Intersection &its, Intersection &target) const { Frame getFrame(const Intersection &its) const {
const Float eps = Epsilon; Spectrum grad[2];
m_displacement->evalGradient(its, grad);
/* Compute the U and V displacementment derivatives */ Float dDispDu = grad[0].getLuminance();
target = its; Float dDispDv = grad[1].getLuminance();
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;
/* Build a perturbed frame -- ignores the usually /* Build a perturbed frame -- ignores the usually
negligible normal derivative term */ negligible normal derivative term */
@ -143,21 +133,22 @@ public:
Vector dpdv = its.dpdv + its.shFrame.n * ( Vector dpdv = its.dpdv + its.shFrame.n * (
dDispDv - dot(its.shFrame.n, its.dpdv)); dDispDv - dot(its.shFrame.n, its.dpdv));
dpdu = normalize(dpdu); Frame result;
dpdv = normalize(dpdv - dpdu * dot(dpdv, dpdu)); 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; if (dot(result.n, its.geoFrame.n) < 0)
target.shFrame.t = dpdv; result.n *= -1;
target.shFrame = Frame(Normal(cross(dpdv, dpdu)));
if (dot(target.shFrame.n, target.geoFrame.n) < 0) return result;
target.shFrame.n *= -1;
} }
Spectrum eval(const BSDFSamplingRecord &bRec, EMeasure measure) const { Spectrum eval(const BSDFSamplingRecord &bRec, EMeasure measure) const {
const Intersection& its = bRec.its; const Intersection& its = bRec.its;
Intersection perturbed; Intersection perturbed(its);
perturbIntersection(its, perturbed); perturbed.shFrame = getFrame(its);
BSDFSamplingRecord perturbedQuery(perturbed, BSDFSamplingRecord perturbedQuery(perturbed,
perturbed.toLocal(its.toWorld(bRec.wi)), perturbed.toLocal(its.toWorld(bRec.wi)),
@ -172,8 +163,8 @@ public:
Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const { Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const {
const Intersection& its = bRec.its; const Intersection& its = bRec.its;
Intersection perturbed; Intersection perturbed(its);
perturbIntersection(its, perturbed); perturbed.shFrame = getFrame(its);
BSDFSamplingRecord perturbedQuery(perturbed, BSDFSamplingRecord perturbedQuery(perturbed,
perturbed.toLocal(its.toWorld(bRec.wi)), perturbed.toLocal(its.toWorld(bRec.wi)),
@ -189,8 +180,8 @@ public:
Spectrum sample(BSDFSamplingRecord &bRec, const Point2 &sample) const { Spectrum sample(BSDFSamplingRecord &bRec, const Point2 &sample) const {
const Intersection& its = bRec.its; const Intersection& its = bRec.its;
Intersection perturbed; Intersection perturbed(its);
perturbIntersection(its, perturbed); perturbed.shFrame = getFrame(its);
BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode); BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode);
perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi)); perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi));
@ -202,6 +193,7 @@ public:
bRec.sampledComponent = perturbedQuery.sampledComponent; bRec.sampledComponent = perturbedQuery.sampledComponent;
bRec.sampledType = perturbedQuery.sampledType; bRec.sampledType = perturbedQuery.sampledType;
bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo)); bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo));
bRec.eta = perturbedQuery.eta;
if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0)
return Spectrum(0.0f); return Spectrum(0.0f);
} }
@ -210,8 +202,8 @@ public:
Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &sample) const { Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &sample) const {
const Intersection& its = bRec.its; const Intersection& its = bRec.its;
Intersection perturbed; Intersection perturbed(its);
perturbIntersection(its, perturbed); perturbed.shFrame = getFrame(its);
BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode); BSDFSamplingRecord perturbedQuery(perturbed, bRec.sampler, bRec.mode);
perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi)); perturbedQuery.wi = perturbed.toLocal(its.toWorld(bRec.wi));
@ -223,6 +215,7 @@ public:
bRec.sampledComponent = perturbedQuery.sampledComponent; bRec.sampledComponent = perturbedQuery.sampledComponent;
bRec.sampledType = perturbedQuery.sampledType; bRec.sampledType = perturbedQuery.sampledType;
bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo)); bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo));
bRec.eta = perturbedQuery.eta;
if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0) if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0)
return Spectrum(0.0f); return Spectrum(0.0f);
} }
@ -285,38 +278,32 @@ public:
const std::string &evalName, const std::string &evalName,
const std::vector<std::string> &depNames) const { const std::vector<std::string> &depNames) const {
oss << "vec3 " << evalName << "(vec2 uv, vec3 wi, vec3 wo) {" << endl oss << "vec3 " << evalName << "(vec2 uv, vec3 wi, vec3 wo) {" << endl
<< " float du = abs(dFdx(uv.x)), dv = abs(dFdx(uv.y));" << endl << " float eps = 1e-4;" << endl
<< " if (du == 0.0) du = 0.001;" << endl
<< " if (dv == 0.0) dv = 0.001;" << endl
<< " float displacement = " << depNames[1] << "(uv)[0];" << endl << " float displacement = " << depNames[1] << "(uv)[0];" << endl
<< " float displacementU = " << depNames[1] << "(uv + vec2(du, 0.0))[0];" << endl << " float displacementU = " << depNames[1] << "(uv + vec2(eps, 0.0))[0];" << endl
<< " float displacementV = " << depNames[1] << "(uv + vec2(0.0, dv))[0];" << endl << " float displacementV = " << depNames[1] << "(uv + vec2(0.0, eps))[0];" << endl
<< " float dfdu = (displacementU - displacement)/du;" << endl << " float dfdu = (displacementU - displacement)*(0.5/eps);" << endl
<< " float dfdv = (displacementV - displacement)/dv;" << endl << " float dfdv = (displacementV - displacement)*(0.5/eps);" << endl
<< " vec3 dpdu = normalize(vec3(1.0, 0.0, dfdu));" << endl << " vec3 n = normalize(vec3(-dfdu, -dfdv, 1.0));" << endl
<< " vec3 dpdv = vec3(0.0, 1.0, dfdv);" << endl << " vec3 s = normalize(vec3(1.0-n.x*n.x, -n.x*n.y, -n.x*n.z)); " << endl
<< " dpdv = normalize(dpdv - dot(dpdu, dpdv)*dpdu);" << endl << " vec3 t = cross(s, n);" << endl
<< " vec3 n = cross(dpdu, dpdv);" << endl << " wi = vec3(dot(wi, s), dot(wi, t), dot(wi, n));" << endl
<< " wi = vec3(dot(wi, dpdu), dot(wi, dpdv), dot(wi, n));" << endl << " wo = vec3(dot(wo, s), dot(wo, t), dot(wo, n));" << endl
<< " wo = vec3(dot(wo, dpdu), dot(wo, dpdv), dot(wo, n));" << endl
<< " return " << depNames[0] << "(uv, wi, wo);" << endl << " return " << depNames[0] << "(uv, wi, wo);" << endl
<< "}" << endl << "}" << endl
<< endl << endl
<< "vec3 " << evalName << "_diffuse(vec2 uv, vec3 wi, vec3 wo) {" << endl << "vec3 " << evalName << "_diffuse(vec2 uv, vec3 wi, vec3 wo) {" << endl
<< " float du = abs(dFdx(uv.x)), dv = abs(dFdx(uv.y));" << endl << " float eps = 1e-4;" << endl
<< " if (du == 0.0) du = 0.001;" << endl
<< " if (dv == 0.0) dv = 0.001;" << endl
<< " float displacement = " << depNames[1] << "(uv)[0];" << endl << " float displacement = " << depNames[1] << "(uv)[0];" << endl
<< " float displacementU = " << depNames[1] << "(uv + vec2(du, 0.0))[0];" << endl << " float displacementU = " << depNames[1] << "(uv + vec2(eps, 0.0))[0];" << endl
<< " float displacementV = " << depNames[1] << "(uv + vec2(0.0, dv))[0];" << endl << " float displacementV = " << depNames[1] << "(uv + vec2(0.0, eps))[0];" << endl
<< " float dfdu = (displacementU - displacement)/du;" << endl << " float dfdu = (displacementU - displacement)*(0.5/eps);" << endl
<< " float dfdv = (displacementV - displacement)/dv;" << endl << " float dfdv = (displacementV - displacement)*(0.5/eps);" << endl
<< " vec3 dpdu = normalize(vec3(1.0, 0.0, dfdu));" << endl << " vec3 n = normalize(vec3(-dfdu, -dfdv, 1.0));" << endl
<< " vec3 dpdv = vec3(0.0, 1.0, dfdv);" << endl << " vec3 s = normalize(vec3(1.0-n.x*n.x, -n.x*n.y, -n.x*n.z)); " << endl
<< " dpdv = normalize(dpdv - dot(dpdu, dpdv)*dpdu);" << endl << " vec3 t = cross(s, n);" << endl
<< " vec3 n = cross(dpdu, dpdv);" << endl << " wi = vec3(dot(wi, s), dot(wi, t), dot(wi, n));" << endl
<< " wi = vec3(dot(wi, dpdu), dot(wi, dpdv), dot(wi, n));" << endl << " wo = vec3(dot(wo, s), dot(wo, t), dot(wo, n));" << endl
<< " wo = vec3(dot(wo, dpdu), dot(wo, dpdv), dot(wo, n));" << endl
<< " return " << depNames[0] << "_diffuse(uv, wi, wo);" << endl << " return " << depNames[0] << "_diffuse(uv, wi, wo);" << endl
<< "}" << endl; << "}" << endl;
} }
@ -335,5 +322,5 @@ Shader *BumpMap::createShader(Renderer *renderer) const {
MTS_IMPLEMENT_CLASS(BumpMapShader, false, Shader) MTS_IMPLEMENT_CLASS(BumpMapShader, false, Shader)
MTS_IMPLEMENT_CLASS_S(BumpMap, false, BSDF) MTS_IMPLEMENT_CLASS_S(BumpMap, false, BSDF)
MTS_EXPORT_PLUGIN(BumpMap, "Smooth dielectric coating"); MTS_EXPORT_PLUGIN(BumpMap, "Bump map modifier");
MTS_NAMESPACE_END MTS_NAMESPACE_END

259
src/bsdfs/normalmap.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <mitsuba/render/scene.h>
#include <mitsuba/hw/basicshader.h>
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<BSDF *>(manager->getInstance(stream));
m_normals = static_cast<Texture *>(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; i<m_nested->getComponentCount(); ++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<BSDF *>(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<Texture *>(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<Texture> m_normals;
ref<BSDF> 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<Shader *> &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<std::string> &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<const BSDF> m_nested;
ref<const Texture> m_normals;
ref<Shader> m_nestedShader;
ref<Shader> 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

View File

@ -155,7 +155,7 @@ MTS_NAMESPACE_BEGIN
* <!-- Fetch roughness values from a texture and slightly reduce them --> * <!-- Fetch roughness values from a texture and slightly reduce them -->
* <texture type="scale" name="alpha"> * <texture type="scale" name="alpha">
* <texture name="alpha" type="bitmap"> * <texture name="alpha" type="bitmap">
* <string name="filename" value="bump.png"/> * <string name="filename" value="roughness.png"/>
* </texture> * </texture>
* <float name="scale" value="0.6"/> * <float name="scale" value="0.6"/>
* </texture> * </texture>

View File

@ -632,8 +632,6 @@ void InterpolatedSpectrum::zeroExtend() {
} }
Float InterpolatedSpectrum::average(Float lambdaMin, Float lambdaMax) const { Float InterpolatedSpectrum::average(Float lambdaMin, Float lambdaMax) const {
typedef std::vector<Float>::const_iterator iterator;
if (m_wavelengths.size() < 2) if (m_wavelengths.size() < 2)
return 0.0f; return 0.0f;

View File

@ -63,6 +63,10 @@ Float BSDF::getEta() const {
return 1.0f; return 1.0f;
} }
Frame BSDF::getFrame(const Intersection &its) const {
return its.shFrame;
}
Float BSDF::getRoughness(const Intersection &its, int component) const { Float BSDF::getRoughness(const Intersection &its, int component) const {
NotImplementedError("getRoughness"); NotImplementedError("getRoughness");
} }

View File

@ -53,6 +53,24 @@ ref<Texture> Texture::expand() {
return this; 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() { } Texture::~Texture() { }
void Texture::serialize(Stream *stream, InstanceManager *manager) const { 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<Bitmap> Texture2D::getBitmap(const Vector2i &sizeHint) const { ref<Bitmap> Texture2D::getBitmap(const Vector2i &sizeHint) const {
Vector2i res(sizeHint); Vector2i res(sizeHint);
if (res.x <= 0 || res.y <= 0) if (res.x <= 0 || res.y <= 0)

View File

@ -59,6 +59,9 @@ MTS_NAMESPACE_BEGIN
* Specifies an optional linear object-to-world transformation. * Specifies an optional linear object-to-world transformation.
* \default{none (i.e. object space $=$ world space)} * \default{none (i.e. object space $=$ world space)}
* } * }
* \parameter{collapse}{\Boolean}{
* Collapse all contained meshes into a single object \default{\code{false}}
* }
* } * }
* \renderings{ * \renderings{
* \label{fig:rungholt} * \label{fig:rungholt}
@ -196,6 +199,9 @@ public:
/* Causes all normals to be flipped */ /* Causes all normals to be flipped */
m_flipNormals = props.getBoolean("flipNormals", false); 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 */ /* Causes all texture coordinates to be vertically flipped */
bool flipTexCoords = props.getBoolean("flipTexCoords", true); bool flipTexCoords = props.getBoolean("flipTexCoords", true);
@ -238,7 +244,7 @@ public:
Normal n; Normal n;
iss >> n.x >> n.y >> n.z; iss >> n.x >> n.y >> n.z;
normals.push_back(n); normals.push_back(n);
} else if (buf == "g") { } else if (buf == "g" && !m_collapse) {
std::string targetName; std::string targetName;
std::string newName = trim(line.substr(1, line.length()-1)); std::string newName = trim(line.substr(1, line.length()-1));
@ -816,6 +822,7 @@ private:
bool m_flipNormals, m_faceNormals; bool m_flipNormals, m_faceNormals;
std::string m_name; std::string m_name;
AABB m_aabb; AABB m_aabb;
bool m_collapse;
}; };
MTS_IMPLEMENT_CLASS_S(WavefrontOBJ, false, Shape) MTS_IMPLEMENT_CLASS_S(WavefrontOBJ, false, Shape)

View File

@ -219,6 +219,8 @@ public:
if (filterType == "ewa") if (filterType == "ewa")
m_filterType = EEWA; m_filterType = EEWA;
else if (filterType == "bilinear")
m_filterType = EBilinear;
else if (filterType == "trilinear") else if (filterType == "trilinear")
m_filterType = ETrilinear; m_filterType = ETrilinear;
else if (filterType == "nearest") else if (filterType == "nearest")
@ -451,6 +453,32 @@ public:
return result; 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<Bitmap> getBitmap(const Vector2i &/* unused */) const { ref<Bitmap> getBitmap(const Vector2i &/* unused */) const {
return m_mipmap1.get() ? m_mipmap1->toBitmap() : m_mipmap3->toBitmap(); return m_mipmap1.get() ? m_mipmap1->toBitmap() : m_mipmap3->toBitmap();
} }
@ -599,6 +627,10 @@ public:
case ENearest: case ENearest:
m_gpuTexture->setFilterType(GPUTexture::ENearest); m_gpuTexture->setFilterType(GPUTexture::ENearest);
break; break;
case EBilinear:
m_gpuTexture->setFilterType(GPUTexture::ELinear);
m_gpuTexture->setMipMapped(false);
break;
default: default:
m_gpuTexture->setFilterType(GPUTexture::EMipMapLinear); m_gpuTexture->setFilterType(GPUTexture::EMipMapLinear);
break; break;

View File

@ -86,6 +86,12 @@ public:
return m_nested->eval(its, filter) * m_scale; 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 { Spectrum getAverage() const {
return m_nested->getAverage() * m_scale; return m_nested->getAverage() * m_scale;
} }