/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 2007-2014 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/bsdf.h>
#include <mitsuba/hw/basicshader.h>
#include <mitsuba/core/warp.h>
#include "microfacet.h"
#include "rtrans.h"
#include "ior.h"

MTS_NAMESPACE_BEGIN

/*!\plugin{roughplastic}{Rough plastic material}
 * \order{9}
 * \icon{bsdf_roughplastic}
 * \parameters{
 *     \parameter{distribution}{\String}{
 *          Specifies the type of microfacet normal distribution
 *          used to model the surface roughness.
 *          \vspace{-1mm}
 *       \begin{enumerate}[(i)]
 *           \item \code{beckmann}: Physically-based distribution derived from
 *               Gaussian random surfaces. This is the default.\vspace{-1.5mm}
 *           \item \code{ggx}: The GGX \cite{Walter07Microfacet} distribution (also known as
 *               Trowbridge-Reitz \cite{Trowbridge19975Average} distribution)
 *               was designed to better approximate the long tails observed in measurements
 *               of ground surfaces, which are not modeled by the Beckmann distribution.
 *           \vspace{-1.5mm}
 *           \item \code{phong}: Classical Phong distribution.
 *              In most cases, the \code{ggx} and \code{beckmann} distributions
 *              should be preferred, since they provide better importance sampling
 *              and accurate shadowing/masking computations.
 *              \vspace{-4mm}
 *       \end{enumerate}
 *     }
 *     \parameter{alpha}{\Float\Or\Texture}{
 *         Specifies the roughness of the unresolved surface micro-geometry.
 *         When the Beckmann distribution is used, this parameter is equal to the
 *         \emph{root mean square} (RMS) slope of the microfacets.
 *         \default{0.1}.
 *     }
 *
 *     \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified
 *      numerically or using a known material name. \default{\texttt{polypropylene} / 1.49}}
 *     \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified
 *      numerically or using a known material name. \default{\texttt{air} / 1.000277}}
 *     \parameter{sampleVisible}{\Boolean}{
 *         Enables an improved importance sampling technique. Refer to
 *         pages \pageref{plg:roughconductor} and \pageref{sec:visiblenormal-sampling}
 *         for details. \default{\code{true}}
 *     }
 *     \parameter{specular\showbreak Reflectance}{\Spectrum\Or\Texture}{Optional
 *         factor that can be used to modulate the specular reflection component. Note
 *         that for physical realism, this parameter should never be touched. \default{1.0}}
 *     \parameter{diffuse\showbreak Reflectance}{\Spectrum\Or\Texture}{Optional
 *         factor used to modulate the diffuse reflection component\default{0.5}}
 *     \parameter{nonlinear}{\Boolean}{
 *         Account for nonlinear color shifts due to internal scattering? See the
 *         \pluginref{plastic} plugin for details.\default{Don't account for them and
 *         preserve the texture colors, i.e. \code{false}}
 *     }
 * }
 *
 * \vspace{3mm}
 * This plugin implements a realistic microfacet scattering model for rendering
 * rough dielectric materials with internal scattering, such as plastic. It can
 * be interpreted as a fancy version of the Cook-Torrance model and should be
 * preferred over heuristic models like \pluginref{phong} and \pluginref{ward}
 * when possible.
 *
 * Microfacet theory describes rough surfaces as an arrangement of
 * unresolved and ideally specular facets, whose normal directions are given by
 * a specially chosen \emph{microfacet distribution}. By accounting for shadowing
 * and masking effects between these facets, it is possible to reproduce the important
 * off-specular reflections peaks observed in real-world measurements of such
 * materials.
 *
 * \renderings{
 *     \rendering{Beckmann, $\alpha=0.1$}{bsdf_roughplastic_beckmann}
 *     \rendering{GGX, $\alpha=0.3$}{bsdf_roughplastic_ggx}
 * }
 *
 * This plugin is essentially the ``roughened'' equivalent of the (smooth) plugin
 * \pluginref{plastic}. For very low values of $\alpha$, the two will
 * be identical, though scenes using this plugin will take longer to render
 * due to the additional computational burden of tracking surface roughness.
 *
 * For convenience, this model allows to specify IOR values either numerically,
 * or based on a list of known materials (see \tblref{dielectric-iors} on
 * \tblpage{dielectric-iors} for an overview).
 * When no parameters are given, the plugin activates the defaults,
 * which describe a white polypropylene plastic material with a light amount
 * of roughness modeled using the Beckmann distribution.
 *
 * Like the \pluginref{plastic} material, this model internally simulates the
 * interaction of light with a diffuse base surface coated by a thin dielectric
 * layer (where the coating layer is now \emph{rough}). This is a convenient
 * abstraction rather than a restriction. In other words, there are many
 * materials that can be rendered with this model, even if they might not
 * fit this description perfectly well.
 *
 * The simplicity of this setup makes it possible to account for interesting
 * nonlinear effects due to internal scattering, which is controlled by
 * the \texttt{nonlinear} parameter. For more details, please refer to the description
 * of this parameter given in the \pluginref{plastic} plugin section
 * on \pluginpage{plastic}.
 *
 * To get an intuition about the effect of the surface roughness parameter
 * $\alpha$, consider the following approximate classification: a value of
 * $\alpha=0.001-0.01$ corresponds to a material with slight imperfections
 * on an otherwise smooth surface finish, $\alpha=0.1$ is relatively rough,
 * and $\alpha=0.3-0.7$ is \emph{extremely} rough (e.g. an etched or ground
 * finish). Values significantly above that are probably not too realistic.
 *
 * \renderings{
 *     \medrendering{Diffuse textured rendering}{bsdf_plastic_diffuse}
 *     \medrendering{Textured rough plastic model and \code{nonlinear=false}}{bsdf_roughplastic_preserve}
 *     \medrendering{Textured rough plastic model and \code{nonlinear=true}}{bsdf_roughplastic_nopreserve}
 *     \caption{
 *        When asked to do so, this model can account for subtle nonlinear color shifts due
 *        to internal scattering processes. The above images show a textured
 *        object first rendered using \pluginref{diffuse}, then
 *        \pluginref{roughplastic} with the default parameters, and finally using
 *        \pluginref{roughplastic} and support for nonlinear color shifts.
 *     }
 * }
 * \renderings{
 *     \rendering{Wood material with smooth horizontal stripes}{bsdf_roughplastic_roughtex1}
 *     \rendering{A material with imperfections at a much smaller scale than what
 *       is modeled e.g. using a bump map.}{bsdf_roughplastic_roughtex2}\vspace{-3mm}
 *     \caption{
 *         The ability to texture the roughness parameter makes it possible
 *         to render materials with a structured finish, as well as
 *         ``smudgy'' objects.
 *     }
 * }
 * \vspace{2mm}
 * \begin{xml}[caption={A material definition for black plastic material with
 *    a spatially varying roughness.},
 *    label=lst:roughplastic-varyingalpha]
 * <bsdf type="roughplastic">
 *     <string name="distribution" value="beckmann"/>
 *     <float name="intIOR" value="1.61"/>
 *     <spectrum name="diffuseReflectance" value="0"/>
 *     <!-- Fetch roughness values from a texture and slightly reduce them -->
 *     <texture type="scale" name="alpha">
 *         <texture name="alpha" type="bitmap">
 *             <string name="filename" value="roughness.png"/>
 *         </texture>
 *         <float name="scale" value="0.6"/>
 *     </texture>
 * </bsdf>
 * \end{xml}
 *
 * \subsubsection*{Technical details}
 * The implementation of this model is partly based on the paper ``Microfacet
 * Models for Refraction through Rough Surfaces'' by Walter et al.
 * \cite{Walter07Microfacet}. Several different types of microfacet
 * distributions are supported. Note that the choices are slightly more
 * restricted here---in comparison to other rough scattering models in
 * Mitsuba, anisotropic distributions are not allowed.
 *
 * The implementation of this model makes heavy use of a \emph{rough
 * Fresnel transmittance} function, which is a generalization of the
 * usual Fresnel transmittion coefficient to microfacet surfaces. Unfortunately,
 * this function is normally prohibitively expensive, since each
 * evaluation involves a numerical integration over the sphere.
 *
 * To avoid this performance issue, Mitsuba ships with data files
 * (contained in the \code{data/microfacet} directory) containing precomputed
 * values of this function over a large range of parameter values. At runtime,
 * the relevant parts are extracted using tricubic interpolation.
 *
 * When rendering with the Phong microfacet distribution, a conversion is
 * used to turn the specified Beckmann-equivalent $\alpha$ roughness value
 * into the exponent parameter of this distribution. This is done in a way,
 * such that the same value $\alpha$ will produce a similar appearance across
 * different microfacet distributions.
 */
class RoughPlastic : public BSDF {
public:
    RoughPlastic(const Properties &props) : BSDF(props) {
        m_specularReflectance = new ConstantSpectrumTexture(
            props.getSpectrum("specularReflectance", Spectrum(1.0f)));
        m_diffuseReflectance = new ConstantSpectrumTexture(
            props.getSpectrum("diffuseReflectance", Spectrum(0.5f)));

        /* Specifies the internal index of refraction at the interface */
        Float intIOR = lookupIOR(props, "intIOR", "polypropylene");

        /* Specifies the external index of refraction at the interface */
        Float extIOR = lookupIOR(props, "extIOR", "air");

        if (intIOR < 0 || extIOR < 0 || intIOR == extIOR)
            Log(EError, "The interior and exterior indices of "
                "refraction must be positive and differ!");

        m_eta = intIOR / extIOR;

        m_nonlinear = props.getBoolean("nonlinear", false);

        MicrofacetDistribution distr(props);
        m_type = distr.getType();
        m_sampleVisible = distr.getSampleVisible();

        if (distr.isAnisotropic())
            Log(EError, "The 'roughplastic' plugin currently does not support "
                "anisotropic microfacet distributions!");

        m_alpha = new ConstantFloatTexture(distr.getAlpha());

        m_specularSamplingWeight = 0.0f;
    }

    RoughPlastic(Stream *stream, InstanceManager *manager)
     : BSDF(stream, manager) {
        m_type = (MicrofacetDistribution::EType) stream->readUInt();
        m_sampleVisible = stream->readBool();
        m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream));
        m_diffuseReflectance = static_cast<Texture *>(manager->getInstance(stream));
        m_alpha = static_cast<Texture *>(manager->getInstance(stream));
        m_eta = stream->readFloat();
        m_nonlinear = stream->readBool();

        configure();
    }

    void serialize(Stream *stream, InstanceManager *manager) const {
        BSDF::serialize(stream, manager);

        stream->writeUInt((uint32_t) m_type);
        stream->writeBool(m_sampleVisible);
        manager->serialize(stream, m_specularReflectance.get());
        manager->serialize(stream, m_diffuseReflectance.get());
        manager->serialize(stream, m_alpha.get());
        stream->writeFloat(m_eta);
        stream->writeBool(m_nonlinear);
    }

    void configure() {
        bool constAlpha = m_alpha->isConstant();

        m_components.clear();

        m_components.push_back(EGlossyReflection | EFrontSide
            | ((constAlpha && m_specularReflectance->isConstant())
                ? 0 : ESpatiallyVarying));
        m_components.push_back(EDiffuseReflection | EFrontSide
            | ((constAlpha && m_diffuseReflectance->isConstant())
                ? 0 : ESpatiallyVarying));

        /* Verify the input parameters and fix them if necessary */
        m_specularReflectance = ensureEnergyConservation(
            m_specularReflectance, "specularReflectance", 1.0f);
        m_diffuseReflectance = ensureEnergyConservation(
            m_diffuseReflectance, "diffuseReflectance", 1.0f);

        /* Compute weights that further steer samples towards
           the specular or diffuse components */
        Float dAvg = m_diffuseReflectance->getAverage().getLuminance(),
              sAvg = m_specularReflectance->getAverage().getLuminance();
        m_specularSamplingWeight = sAvg / (dAvg + sAvg);

        m_invEta2 = 1.0f / (m_eta*m_eta);

        if (!m_externalRoughTransmittance.get()) {
            /* Load precomputed data used to compute the rough
               transmittance through the dielectric interface */
            m_externalRoughTransmittance = new RoughTransmittance(m_type);

            m_externalRoughTransmittance->checkEta(m_eta);
            m_externalRoughTransmittance->checkAlpha(m_alpha->getMinimum().average());
            m_externalRoughTransmittance->checkAlpha(m_alpha->getMaximum().average());

            /* Reduce the rough transmittance data to a 2D slice */
            m_internalRoughTransmittance = m_externalRoughTransmittance->clone();
            m_externalRoughTransmittance->setEta(m_eta);
            m_internalRoughTransmittance->setEta(1/m_eta);

            /* If possible, even reduce it to a 1D slice */
            if (constAlpha)
                m_externalRoughTransmittance->setAlpha(
                    m_alpha->eval(Intersection()).average());
        }

        m_usesRayDifferentials =
            m_specularReflectance->usesRayDifferentials() ||
            m_diffuseReflectance->usesRayDifferentials() ||
            m_alpha->usesRayDifferentials();

        BSDF::configure();
    }

    Spectrum getDiffuseReflectance(const Intersection &its) const {
        /* Evaluate the roughness texture */
        Float alpha = m_alpha->eval(its).average();
        Float Ftr = m_externalRoughTransmittance->evalDiffuse(alpha);

        return m_diffuseReflectance->eval(its) * Ftr;
    }

    Spectrum getSpecularReflectance(const Intersection &its) const {
        return m_specularReflectance->eval(its);
    }

    /// Helper function: reflect \c wi with respect to a given surface normal
    inline Vector reflect(const Vector &wi, const Normal &m) const {
        return 2 * dot(wi, m) * Vector(m) - wi;
    }
    
    virtual CoherenceProp lightMatterInteraction(const BSDFSamplingRecord &bRec) const override {
        Assert(!!bRec.csd && "Not implemented");
        
        const auto &f = Frame(bRec.wo);
        const auto &Q = Matrix3x3(f.s,f.t,f.n);
        const auto r  = .1f;
        
        auto csd = make_csd_spherical_source(Q,r,sqrt(bRec.csd.Theta(2,2)));
        ForEachHGCoeff(n,m)
            csd.hg[n][m] *= bRec.csd.eval(n,m,{0,0,0});

        return csd;
    }
    
    Spectrum evalHG(const BSDFSamplingRecord &bRec) const override {
        bool hasSpecular = (bRec.typeMask & EGlossyReflection) &&
            (bRec.component == -1 || bRec.component == 0);
        bool hasDiffuse = (bRec.typeMask & EDiffuseReflection) &&
            (bRec.component == -1 || bRec.component == 1);
        if (Frame::cosTheta(bRec.wi) <= 0 ||
            Frame::cosTheta(bRec.wo) <= 0 ||
            (!hasSpecular && !hasDiffuse))
            return Spectrum(0.0f);

        /* Construct the microfacet distribution matching the
           roughness values at the current surface position. */
        MicrofacetDistribution distr(
            m_type,
            m_alpha->eval(bRec.its).average(),
            m_sampleVisible
        );

        Spectrum result(0.0f);
        if (hasSpecular) {
            /* Calculate the reflection half-vector */
            const Vector H = normalize(bRec.wo+bRec.wi);

            /* Evaluate the microfacet normal distribution */
            const Float D = distr.eval(H);

            /* Fresnel term */
            const Float F = fresnelDielectricExt(dot(bRec.wi, H), m_eta);

            /* Smith's shadow-masking function */
            const Float G = distr.G(bRec.wi, bRec.wo, H);

            /* Calculate the specular reflection component */
            Float value = F * D * G /
                (4.0f * Frame::cosTheta(bRec.wi));

            result += m_specularReflectance->eval(bRec.its) * value;
        }

        if (hasDiffuse) {
            Spectrum diff = m_diffuseReflectance->eval(bRec.its);
            Float T12 = m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), distr.getAlpha());
            Float T21 = m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wo), distr.getAlpha());
            Float Fdr = 1-m_internalRoughTransmittance->evalDiffuse(distr.getAlpha());

            if (m_nonlinear)
                diff /= Spectrum(1.0f) - diff * Fdr;
            else
                diff /= 1-Fdr;

            result += diff * (INV_PI * Frame::cosTheta(bRec.wo) * T12 * T21 * m_invEta2);
        }

        return result;
    }

    Spectrum eval(const BSDFSamplingRecord &bRec, EMeasure measure) const {
        if (measure != ESolidAngle)
            return Spectrum(.0f);

        return evalHG(bRec);
    }

    Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const {
        bool hasSpecular = (bRec.typeMask & EGlossyReflection) &&
            (bRec.component == -1 || bRec.component == 0);
        bool hasDiffuse = (bRec.typeMask & EDiffuseReflection) &&
            (bRec.component == -1 || bRec.component == 1);

        if (measure != ESolidAngle ||
            Frame::cosTheta(bRec.wi) <= 0 ||
            Frame::cosTheta(bRec.wo) <= 0 ||
            (!hasSpecular && !hasDiffuse))
            return 0.0f;

        /* Construct the microfacet distribution matching the
           roughness values at the current surface position. */
        MicrofacetDistribution distr(
            m_type,
            m_alpha->eval(bRec.its).average(),
            m_sampleVisible
        );

        /* Calculate the reflection half-vector */
        const Vector H = normalize(bRec.wo+bRec.wi);

        Float probDiffuse, probSpecular;
        if (hasSpecular && hasDiffuse) {
            /* Find the probability of sampling the specular component */
            probSpecular = 1-m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), distr.getAlpha());

            /* Reallocate samples */
            probSpecular = (probSpecular*m_specularSamplingWeight) /
                (probSpecular*m_specularSamplingWeight +
                (1-probSpecular) * (1-m_specularSamplingWeight));

            probDiffuse = 1 - probSpecular;
        } else {
            probDiffuse = probSpecular = 1.0f;
        }

        Float result = 0.0f;
        if (hasSpecular) {
            /* Jacobian of the half-direction mapping */
            const Float dwh_dwo = 1.0f / (4.0f * dot(bRec.wo, H));

            /* Evaluate the microfacet model sampling density function */
            const Float prob = distr.pdf(bRec.wi, H);

            result = prob * dwh_dwo * probSpecular;
        }

        if (hasDiffuse)
            result += probDiffuse * warp::squareToCosineHemispherePdf(bRec.wo);

        return result;
    }

    inline Spectrum sample(BSDFSamplingRecord &bRec, Float &_pdf, const Point2 &_sample) const {
        bool hasSpecular = (bRec.typeMask & EGlossyReflection) &&
            (bRec.component == -1 || bRec.component == 0);
        bool hasDiffuse = (bRec.typeMask & EDiffuseReflection) &&
            (bRec.component == -1 || bRec.component == 1);

        if (Frame::cosTheta(bRec.wi) <= 0 || (!hasSpecular && !hasDiffuse))
            return Spectrum(0.0f);

        bool choseSpecular = hasSpecular;
        Point2 sample(_sample);

        /* Construct the microfacet distribution matching the
           roughness values at the current surface position. */
        MicrofacetDistribution distr(
            m_type,
            m_alpha->eval(bRec.its).average(),
            m_sampleVisible
        );

        Float probSpecular;
        if (hasSpecular && hasDiffuse) {
            /* Find the probability of sampling the specular component */
            probSpecular = 1 - m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), distr.getAlpha());

            /* Reallocate samples */
            probSpecular = (probSpecular*m_specularSamplingWeight) /
                (probSpecular*m_specularSamplingWeight +
                (1-probSpecular) * (1-m_specularSamplingWeight));

            if (sample.y < probSpecular) {
                sample.y /= probSpecular;
            } else {
                sample.y = (sample.y - probSpecular) / (1 - probSpecular);
                choseSpecular = false;
            }
        }

        if (choseSpecular) {
            /* Perfect specular reflection based on the microfacet normal */
            Normal m = distr.sample(bRec.wi, sample);
            bRec.wo = reflect(bRec.wi, m);
            bRec.sampledComponent = 0;
            bRec.sampledType = EGlossyReflection;

            /* Side check */
            if (Frame::cosTheta(bRec.wo) <= 0)
                return Spectrum(0.0f);
        } else {
            bRec.sampledComponent = 1;
            bRec.sampledType = EDiffuseReflection;
            bRec.wo = warp::squareToCosineHemisphere(sample);
        }
        bRec.eta = 1.0f;

        /* Guard against numerical imprecisions */
        _pdf = pdf(bRec, ESolidAngle);

        if (_pdf == 0)
            return Spectrum(0.0f);
        else
            return eval(bRec, ESolidAngle) / _pdf;
    }

    Spectrum sample(BSDFSamplingRecord &bRec, const Point2 &sample) const {
        Float pdf;
        return RoughPlastic::sample(bRec, pdf, sample);
    }

    void addChild(const std::string &name, ConfigurableObject *child) {
        if (child->getClass()->derivesFrom(MTS_CLASS(Texture))) {
            if (name == "alpha")
                m_alpha = static_cast<Texture *>(child);
            else if (name == "specularReflectance")
                m_specularReflectance = static_cast<Texture *>(child);
            else if (name == "diffuseReflectance")
                m_diffuseReflectance = static_cast<Texture *>(child);
            else
                BSDF::addChild(name, child);
        } else {
            BSDF::addChild(name, child);
        }
    }

    Float getRoughness(const Intersection &its, int component) const {
        Assert(component == 0 || component == 1);

        if (component == 0)
            return m_alpha->eval(its).average();
        else
            return std::numeric_limits<Float>::infinity();
    }

    std::string toString() const {
        std::ostringstream oss;
        oss << "RoughPlastic[" << endl
            << "  id = \"" << getID() << "\"," << endl
            << "  distribution = " << MicrofacetDistribution::distributionName(m_type) << "," << endl
            << "  sampleVisible = " << m_sampleVisible << "," << endl
            << "  alpha = " << indent(m_alpha->toString()) << "," << endl
            << "  specularReflectance = " << indent(m_specularReflectance->toString()) << "," << endl
            << "  diffuseReflectance = " << indent(m_diffuseReflectance->toString()) << "," << endl
            << "  specularSamplingWeight = " << m_specularSamplingWeight << "," << endl
            << "  diffuseSamplingWeight = " << (1-m_specularSamplingWeight) << "," << endl
            << "  eta = " << m_eta << "," << endl
            << "  nonlinear = " << m_nonlinear << endl
            << "]";
        return oss.str();
    }

    Shader *createShader(Renderer *renderer) const;

    MTS_DECLARE_CLASS()
private:
    MicrofacetDistribution::EType m_type;
    ref<RoughTransmittance> m_externalRoughTransmittance;
    ref<RoughTransmittance> m_internalRoughTransmittance;
    ref<Texture> m_diffuseReflectance;
    ref<Texture> m_specularReflectance;
    ref<Texture> m_alpha;
    Float m_eta, m_invEta2;
    Float m_specularSamplingWeight;
    bool m_nonlinear;
    bool m_sampleVisible;
};

/**
 * GLSL port of the rough plastic shader. This version is much more
 * approximate -- it only supports the Beckmann distribution,
 * does everything in RGB, uses a cheaper shadowing-masking term, and
 * it also makes use of the Schlick approximation to the Fresnel
 * reflectance of dielectrics. When the roughness is lower than
 * \alpha < 0.2, the shader clamps it to 0.2 so that it will still perform
 * reasonably well in a VPL-based preview. There is no support for
 * non-linear effects due to internal scattering.
 */
class RoughPlasticShader : public Shader {
public:
    RoughPlasticShader(Renderer *renderer, const Texture *specularReflectance,
            const Texture *diffuseReflectance, const Texture *alpha, Float eta)
        : Shader(renderer, EBSDFShader),
            m_specularReflectance(specularReflectance),
            m_diffuseReflectance(diffuseReflectance),
            m_alpha(alpha) {
        m_specularReflectanceShader = renderer->registerShaderForResource(m_specularReflectance.get());
        m_diffuseReflectanceShader = renderer->registerShaderForResource(m_diffuseReflectance.get());
        m_alphaShader = renderer->registerShaderForResource(m_alpha.get());
        m_R0 = fresnelDielectricExt(1.0f, eta);
    }

    bool isComplete() const {
        return m_specularReflectanceShader.get() != NULL &&
            m_diffuseReflectanceShader.get() != NULL &&
            m_alphaShader.get() != NULL;
    }

    void putDependencies(std::vector<Shader *> &deps) {
        deps.push_back(m_specularReflectanceShader.get());
        deps.push_back(m_diffuseReflectanceShader.get());
        deps.push_back(m_alphaShader.get());
    }

    void cleanup(Renderer *renderer) {
        renderer->unregisterShaderForResource(m_specularReflectance.get());
        renderer->unregisterShaderForResource(m_diffuseReflectance.get());
        renderer->unregisterShaderForResource(m_alpha.get());
    }

    void resolve(const GPUProgram *program, const std::string &evalName, std::vector<int> &parameterIDs) const {
        parameterIDs.push_back(program->getParameterID(evalName + "_R0", false));
    }

    void bind(GPUProgram *program, const std::vector<int> &parameterIDs, int &textureUnitOffset) const {
        program->setParameter(parameterIDs[0], m_R0);
    }

    void generateCode(std::ostringstream &oss,
            const std::string &evalName,
            const std::vector<std::string> &depNames) const {
        oss << "uniform float " << evalName << "_R0;" << endl
            << endl
            << "float " << evalName << "_D(vec3 m, float alpha) {" << endl
            << "    float ct = cosTheta(m);" << endl
            << "    if (cosTheta(m) <= 0.0)" << endl
            << "        return 0.0;" << endl
            << "    float ex = tanTheta(m) / alpha;" << endl
            << "    return exp(-(ex*ex)) / (pi * alpha * alpha *" << endl
            << "               pow(cosTheta(m), 4.0));" << endl
            << "}" << endl
            << endl
            << "float " << evalName << "_G(vec3 m, vec3 wi, vec3 wo) {" << endl
            << "    if ((dot(wi, m) * cosTheta(wi)) <= 0 || " << endl
            << "        (dot(wo, m) * cosTheta(wo)) <= 0)" << endl
            << "        return 0.0;" << endl
            << "    float nDotM = cosTheta(m);" << endl
            << "    return min(1.0, min(" << endl
            << "        abs(2 * nDotM * cosTheta(wo) / dot(wo, m))," << endl
            << "        abs(2 * nDotM * cosTheta(wi) / dot(wi, m))));" << endl
            << "}" << endl
            << endl
            << endl
            << "float " << evalName << "_schlick(float ct) {" << endl
            << "    float ctSqr = ct*ct, ct5 = ctSqr*ctSqr*ct;" << endl
            << "    return " << evalName << "_R0 + (1.0 - " << evalName << "_R0) * ct5;" << endl
            << "}" << endl
            << endl
            << "vec3 " << evalName << "(vec2 uv, vec3 wi, vec3 wo) {" << endl
            << "    if (cosTheta(wi) <= 0 || cosTheta(wo) <= 0)" << endl
            << "        return vec3(0.0);" << endl
            << "    vec3 H = normalize(wi + wo);" << endl
            << "    vec3 specRef = " << depNames[0] << "(uv);" << endl
            << "    vec3 diffuseRef = " << depNames[1] << "(uv);" << endl
            << "    float alpha = max(0.2, " << depNames[2] << "(uv)[0]);" << endl
            << "    float D = " << evalName << "_D(H, alpha)" << ";" << endl
            << "    float G = " << evalName << "_G(H, wi, wo);" << endl
            << "    float F = " << evalName << "_schlick(1-dot(wi, H));" << endl
            << "    return specRef    * (F * D * G / (4*cosTheta(wi))) + " << endl
            << "           diffuseRef * ((1-F) * cosTheta(wo) * inv_pi);" << endl
            << "}" << endl
            << endl
            << "vec3 " << evalName << "_diffuse(vec2 uv, vec3 wi, vec3 wo) {" << endl
            << "    if (cosTheta(wi) < 0.0 || cosTheta(wo) < 0.0)" << endl
            << "        return vec3(0.0);" << endl
            << "    vec3 diffuseRef = " << depNames[1] << "(uv);" << endl
            << "    return diffuseRef * inv_pi * cosTheta(wo);"<< endl
            << "}" << endl;
    }
    MTS_DECLARE_CLASS()
private:
    ref<const Texture> m_specularReflectance;
    ref<const Texture> m_diffuseReflectance;
    ref<const Texture> m_alpha;
    ref<Shader> m_specularReflectanceShader;
    ref<Shader> m_diffuseReflectanceShader;
    ref<Shader> m_alphaShader;
    Float m_R0;
};

Shader *RoughPlastic::createShader(Renderer *renderer) const {
    return new RoughPlasticShader(renderer,
        m_specularReflectance.get(), m_diffuseReflectance.get(),
        m_alpha.get(), m_eta);
}

MTS_IMPLEMENT_CLASS(RoughPlasticShader, false, Shader)
MTS_IMPLEMENT_CLASS_S(RoughPlastic, false, BSDF)
MTS_EXPORT_PLUGIN(RoughPlastic, "Rough plastic BRDF");
MTS_NAMESPACE_END
