/* 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 . */ #include #include "maxexp.h" MTS_NAMESPACE_BEGIN /*!\plugin{homogeneous}{Homogeneous participating medium} * \order{1} * \parameters{ * \parameter{material}{\String}{ * Name of a material preset, see * \tblref{medium-coefficients}. \default{\texttt{skin1}} * } * \parameter{sigmaA, sigmaS}{\Spectrum}{ * Absorption and scattering * coefficients of the medium in inverse scene units. * These parameters are mutually exclusive with \code{sigmaT} and \code{albedo} * \default{configured based on \code{material}} * } * \parameter{sigmaT, albedo}{\Spectrum}{ * Extinction coefficient in inverse scene units * and a (unitless) single-scattering albedo. * These parameters are mutually exclusive with \code{sigmaA} and \code{sigmaS} * \default{configured based on \code{material}} * } * \parameter{\footnotesize{scale}}{\Float}{ * Optional scale factor that will be applied to the \code{sigma*} parameters. * It is provided for convenience when accomodating data based on different units, * or to simply tweak the density of the medium. \default{1} * } * \parameter{\Unnamed}{\Phase}{ * A nested phase function that describes the directional * scattering properties of the medium. When none is specified, * the renderer will automatically use an instance of * \pluginref{isotropic}. * } * } * * This class implements a flexible homogeneous participating * medium with support for arbitrary phase functions and various * medium sampling methods. It provides two different ways of configuring * the medium properties. One possibility is to load a material preset * using the \code{material} parameter---see \tblref{medium-coefficients} * for details. Alternatively, when specifying parameters by hand, they can either * be provided using the scattering and absorption coefficients, or * by declaring the extinction coefficient and single scattering * albedo (whichever is more convenient). Mixing these parameter * initialization methods is not allowed. * * All scattering parameters (named \code{sigma*}) should * be provided in inverse scene units. For instance, when a world-space * distance of 1 unit corresponds to a meter, the scattering coefficents should * have units of inverse meters. For convenience, the \code{scale} * parameter can be used to correct the units. For instance, when the scene is * in meters and the coefficients are in inverse millimeters, set * \code{scale} to \code{1000}. * * \renderings{ * \rendering{A squishy ball rendered with subsurface scattering and * a dielectric BSDF (courtesy of Chanxi Zheng)}{medium_homogeneous_squishy.jpg} * } * * \begin{xml}[caption=Declaration of a forward scattering medium with high albedo] * * * * * * * * * \end{xml} * * \textbf{Note}: Rendering media that have a spectrally * varying extinction coefficient can be tricky, since all * commonly used medium sampling methods suffer from high * variance in that case. Here, it may often make more sense to render * several monochromatic images separately (using only the coefficients for * a single channel) and then merge them back into a RGB image. There * is a \code{mtsutil} (\secref{mtsutil}) plugin named \code{joinrgb} * that will perform this RGB merging process. * * \begin{table}[h!] * \centering * \vspace{3mm} * {\footnotesize * \begin{tabular}{>{\ttfamily}p{3.8cm}p{.4cm}>{\ttfamily}p{3.8cm}p{.4cm}>{\ttfamily}p{3.8cm}} * \toprule * \rmfamily \small\textbf{Name} && * \rmfamily \small\textbf{Name} && * \rmfamily \small\textbf{Name} \\ * \cmidrule{1-1} \cmidrule{3-3} \cmidrule{5-5} * Apple && Chicken1 && Chicken2 \\ * Cream && Ketchup && Potato \\ * Skimmilk && Skin1 && Skin2 \\ * Spectralon && Wholemilk && \\ * \cmidrule{1-1} \cmidrule{3-3} \cmidrule{5-5} * Lowfat Milk && Gatorade && White Grapefruit Juice \\ * Reduced Milk && Chardonnay && Shampoo \\ * Regular Milk && White Zinfandel && Strawberry Shampoo \\ * Espresso && Merlot && \mbox{Head \& Shoulders Shampoo} \\ * Mint Mocha Coffee && Budweiser Beer && Lemon Tea Powder \\ * Lowfat Soy Milk && Coors Light Beer && Orange Juice Powder \\ * Regular Soy Milk && Clorox && Pink Lemonade Powder \\ * Lowfat Chocolate Milk && Apple Juice && Cappuccino Powder \\ * Regular Chocolate Milk && Cranberry Juice && Salt Powder \\ * Coke && Grape Juice && Sugar Powder \\ * Pepsi && Ruby Grapefruit Juice && Suisse Mocha \\ * Sprite && && \\ * \bottomrule * \end{tabular}} * \caption{\label{tbl:medium-coefficients}This * table lists all supported medium material presets. The * top entries are from Jensen et al. \cite{Jensen2001Practical}, and the * bottom ones are from Narasimhan et al. \cite{Narasimhan2006Acquiring}. * They all use units of $\frac{1}{mm}$, so remember to set * \code{scale} appropriately when your scene is not * in units of millimeters. * These material presets can be used with the plugins * \pluginref{homogeneous},\ * \pluginref{dipole}, and \ * \pluginref{hk} * } * \end{table} */ class HomogeneousMedium : public Medium { public: /** * This class supports the following sampling strategies for choosing * a suitable scattering location when sampling the RTE */ enum ESamplingStrategy { EBalance, /// Exponential distrib.; pick a random channel each time ESingle, /// Exponential distrib.; pick a specified channel EManual, /// Exponential distrib.; manually specify the falloff EMaximum /// Maximum-of-exponential distribution }; HomogeneousMedium(const Properties &props) : Medium(props), m_samplingDensity(0.0f), m_maxExpDist(NULL) { std::string strategy = props.getString("strategy", "balance"); /** * The goal of the medium sampling weight is to be able to * sample medium intarctions according to * sigma_s(t) * tau(0 <-> t) * as opposed to * sigma_t(t) * tau(0 <-> t) * See the separate writeup for more details. */ m_mediumSamplingWeight = props.getFloat("mediumSamplingWeight", -1); if (m_mediumSamplingWeight == -1) { for (int i=0; i m_mediumSamplingWeight && m_sigmaT[i] != 0) m_mediumSamplingWeight = albedo; } if (m_mediumSamplingWeight > 0) { /* The medium scatters some light -> place at least half of the samples in it, otherwise we will render lots of spatially varying noise where one pixel has a medium interaction and the neighbors don't */ m_mediumSamplingWeight = std::max(m_mediumSamplingWeight, (Float) 0.5f); } } if (strategy == "balance") { m_strategy = EBalance; } else if (strategy == "single") { m_strategy = ESingle; /* By default, choose the lowest-variance channel (the one with the smallest sigma_t, that is) */ int channel = 0; Float smallest = std::numeric_limits::infinity(); for (int i=0; i= 0 && channel < SPECTRUM_SAMPLES); m_samplingDensity = m_sigmaT[channel]; if (props.getBoolean("monochromatic", false)) { /* Optionally turn this into a monochromatic medium based on the chosen color channel. This is useful when the whole scene is rendered once per channel and then recombined to create a color image */ m_sigmaA = Spectrum(m_sigmaA[channel]); m_sigmaS = Spectrum(m_sigmaS[channel]); m_sigmaT = m_sigmaA + m_sigmaS; } } else if (strategy == "maximum") { m_strategy = EMaximum; std::vector coeffs(SPECTRUM_SAMPLES); for (int i=0; ireadInt(); m_samplingDensity = stream->readFloat(); m_mediumSamplingWeight = stream->readFloat(); if (m_strategy == EMaximum) { std::vector coeffs(SPECTRUM_SAMPLES); for (int i=0; iwriteInt(m_strategy); stream->writeFloat(m_samplingDensity); stream->writeFloat(m_mediumSamplingWeight); } Spectrum evalTransmittance(const Ray &ray, Sampler *) const { Float negLength = ray.mint - ray.maxt; Spectrum transmittance; for (int i=0; inext1D(), sampledDistance; Float samplingDensity = m_samplingDensity; if (rand < m_mediumSamplingWeight) { rand /= m_mediumSamplingWeight; if (m_strategy != EMaximum) { /* Choose the sampling density to be used */ if (m_strategy == EBalance) { int channel = std::min((int) (sampler->next1D() * SPECTRUM_SAMPLES), SPECTRUM_SAMPLES-1); samplingDensity = m_sigmaT[channel]; } sampledDistance = -math::fastlog(1-rand) / samplingDensity; } else { sampledDistance = m_maxExpDist->sample(1-rand, mRec.pdfSuccess); } } else { /* Don't generate a medium interaction */ sampledDistance = std::numeric_limits::infinity(); } Float distSurf = ray.maxt - ray.mint; bool success = true; if (sampledDistance < distSurf) { mRec.t = sampledDistance + ray.mint; mRec.p = ray(mRec.t); mRec.sigmaA = m_sigmaA; mRec.sigmaS = m_sigmaS; mRec.time = ray.time; mRec.medium = this; /* Fail if there is no forward progress (e.g. due to roundoff errors) */ if (mRec.p == ray.o) success = false; } else { sampledDistance = distSurf; success = false; } switch (m_strategy) { case EMaximum: mRec.pdfFailure = 1-m_maxExpDist->cdf(sampledDistance); break; case EBalance: mRec.pdfFailure = 0; mRec.pdfSuccess = 0; for (int i=0; ipdf(distance); mRec.pdfFailure = 1-m_maxExpDist->cdf(distance); break; default: Log(EError, "Unknown sampling strategy!"); } mRec.transmittance = (m_sigmaT * (-distance)).exp(); mRec.pdfSuccess = mRec.pdfSuccessRev = mRec.pdfSuccess * m_mediumSamplingWeight; mRec.pdfFailure = mRec.pdfFailure * m_mediumSamplingWeight + (1-m_mediumSamplingWeight); mRec.sigmaA = m_sigmaA; mRec.sigmaS = m_sigmaS; mRec.time = ray.time; mRec.medium = this; if (mRec.transmittance.max() < 1e-20) mRec.transmittance = Spectrum(0.0f); } bool isHomogeneous() const { return true; } std::string toString() const { std::ostringstream oss; oss << "HomogeneousMedium[" << endl << " sigmaA = " << m_sigmaA.toString() << "," << endl << " sigmaS = " << m_sigmaS.toString() << "," << endl << " sigmaT = " << m_sigmaT.toString() << "," << endl << " mediumSamplingWeight = " << m_mediumSamplingWeight << "," << endl << " samplingDensity = " << m_samplingDensity << "," << endl << " strategy = "; switch (m_strategy) { case ESingle: oss << "single," << endl; break; case EManual: oss << "manual," << endl; break; case EBalance: oss << "balance," << endl; break; case EMaximum: oss << "maximum," << endl; break; } oss << " phase = " << indent(m_phaseFunction.toString()) << endl << "]"; return oss.str(); } MTS_DECLARE_CLASS() private: Float m_samplingDensity, m_mediumSamplingWeight; ESamplingStrategy m_strategy; MaxExpDist *m_maxExpDist; Float m_albedo; }; MTS_IMPLEMENT_CLASS_S(HomogeneousMedium, false, Medium) MTS_EXPORT_PLUGIN(HomogeneousMedium, "Homogeneous medium"); MTS_NAMESPACE_END