sssbrdf-related bugfixes/improvements
parent
ba8b62dd74
commit
cd69e50a73
|
@ -400,7 +400,7 @@ public:
|
|||
<< " specularSamplingWeight = " << m_specularSamplingWeight << "," << endl
|
||||
<< " sigmaA = " << indent(m_sigmaA->toString()) << "," << endl
|
||||
<< " thickness = " << m_thickness << "," << endl
|
||||
<< " nested = " << indent(m_nested->toString()) << endl
|
||||
<< " nested = " << indent(m_nested.toString()) << endl
|
||||
<< "]";
|
||||
return oss.str();
|
||||
}
|
||||
|
|
|
@ -32,8 +32,12 @@ public:
|
|||
DipoleBRDF(const Properties &props)
|
||||
: BSDF(props) {
|
||||
|
||||
Spectrum sigmaS, sigmaA;
|
||||
Float eta;
|
||||
lookupMaterial(props, sigmaS, sigmaA, &eta, false);
|
||||
|
||||
/* Specifies the internal index of refraction at the interface */
|
||||
m_intIOR = lookupIOR(props, "intIOR", "bk7");
|
||||
m_intIOR = lookupIOR(props, "intIOR", eta);
|
||||
|
||||
/* Specifies the external index of refraction at the interface */
|
||||
m_extIOR = lookupIOR(props, "extIOR", "air");
|
||||
|
@ -41,9 +45,6 @@ public:
|
|||
/* Mean cosine angle of the phase function */
|
||||
m_g = props.getFloat("g", 0.0f);
|
||||
|
||||
Spectrum sigmaS, sigmaA;
|
||||
lookupMaterial(props, sigmaS, sigmaA);
|
||||
|
||||
/* Scattering coefficient of the layer */
|
||||
m_sigmaS = new ConstantSpectrumTexture(
|
||||
props.getSpectrum("sigmaS", sigmaS));
|
||||
|
@ -89,6 +90,9 @@ public:
|
|||
| (m_sigmaS->isConstant() && m_sigmaA->isConstant() ? 0 : ESpatiallyVarying));
|
||||
m_usesRayDifferentials = m_sigmaS->usesRayDifferentials() || m_sigmaA->usesRayDifferentials();
|
||||
|
||||
if ((m_sigmaS->getMaximum()+m_sigmaA->getMaximum()).isZero())
|
||||
Log(EError, "Please specify nonzero sigmaS/sigmaA-values!");
|
||||
|
||||
/* relative index of refraction */
|
||||
const Float eta = m_intIOR / m_extIOR;
|
||||
|
||||
|
@ -103,28 +107,25 @@ public:
|
|||
Spectrum sigmaA = m_sigmaA->getValue(its),
|
||||
sigmaS = m_sigmaS->getValue(its),
|
||||
sigmaT = sigmaA + sigmaS,
|
||||
reducedAlbedo(0.0f),
|
||||
Rd(0.0f);
|
||||
reducedAlbedo;
|
||||
|
||||
/* ==================================================================== */
|
||||
/* Diffuse Reflectance due to Multiple Scattering */
|
||||
/* Diffuse Reflectance due to Multiple Scattering */
|
||||
/* ==================================================================== */
|
||||
|
||||
|
||||
/* Reduced Scattering Albedo */
|
||||
const Spectrum reducedSigmaS = sigmaS * (1.0f - m_g);
|
||||
const Spectrum reducedSigmaT = reducedSigmaS + sigmaA;
|
||||
/* Reduced scattering albedo */
|
||||
const Spectrum reducedSigmaS = sigmaS * (1.0f - m_g),
|
||||
reducedSigmaT = reducedSigmaS + sigmaA;
|
||||
|
||||
/* Avoid divisions by 0 */
|
||||
for (int i = 0; i < SPECTRUM_SAMPLES; i++)
|
||||
reducedAlbedo[i] = reducedSigmaT[i] > 0.0f ? (reducedSigmaS[i]/reducedSigmaT[i]) : 0.0f;
|
||||
reducedAlbedo[i] = reducedSigmaT[i] > 0.0f ?
|
||||
(reducedSigmaS[i]/reducedSigmaT[i]) : 0.0f;
|
||||
|
||||
/* Diffuse Reflectance */
|
||||
const Spectrum rootExp = ((Spectrum(1.0f) - reducedAlbedo) * 3.0f).sqrt();
|
||||
Rd = (reducedAlbedo * 0.5f) * (Spectrum(1.0f) + (-rootExp*(4.0f/3.0f*m_A)).exp())
|
||||
* (-rootExp).exp();
|
||||
|
||||
return Rd;
|
||||
return (reducedAlbedo * 0.5f) * (Spectrum(1.0f) +
|
||||
(-rootExp*(4.0f/3.0f*m_A)).exp()) * (-rootExp).exp();
|
||||
}
|
||||
|
||||
Spectrum eval(const BSDFQueryRecord &bRec, EMeasure measure) const {
|
||||
|
@ -146,16 +147,19 @@ public:
|
|||
}
|
||||
|
||||
Spectrum sample(BSDFQueryRecord &bRec, const Point2 &sample) const {
|
||||
Float pdf;
|
||||
return DipoleBRDF::sample(bRec, pdf, sample);
|
||||
if (!(bRec.typeMask & EDiffuseReflection) || Frame::cosTheta(bRec.wi) <= 0)
|
||||
return Spectrum(0.0f);
|
||||
|
||||
bRec.wo = squareToHemispherePSA(sample);
|
||||
bRec.sampledComponent = 0;
|
||||
bRec.sampledType = EDiffuseReflection;
|
||||
|
||||
return getDiffuseReflectance(bRec.its);
|
||||
}
|
||||
|
||||
Spectrum sample(BSDFQueryRecord &bRec, Float &pdf, const Point2 &sample) const {
|
||||
if (!(bRec.typeMask & EDiffuseReflection) || Frame::cosTheta(bRec.wi) <= 0) {
|
||||
pdf = 0.0f;
|
||||
if (!(bRec.typeMask & EDiffuseReflection) || Frame::cosTheta(bRec.wi) <= 0)
|
||||
return Spectrum(0.0f);
|
||||
}
|
||||
|
||||
bRec.wo = squareToHemispherePSA(sample);
|
||||
bRec.sampledComponent = 0;
|
||||
|
@ -258,22 +262,23 @@ public:
|
|||
<< "uniform float " << evalName << "_g;" << endl
|
||||
<< endl
|
||||
<< "vec3 " << evalName << "(vec2 uv, vec3 wi, vec3 wo) {" << endl
|
||||
<< " float cosThetaI = cosTheta(wi);" << endl
|
||||
<< " float cosThetaO = cosTheta(wo);" << endl
|
||||
<< " if (cosThetaI < 0.0 || cosThetaO < 0.0)" << endl
|
||||
<< " return vec3(0.0);" << endl
|
||||
<< " vec3 sigmaS = " << depNames[0] << "(uv);" << endl
|
||||
<< " vec3 sigmaA = " << depNames[1] << "(uv);" << endl
|
||||
<< " vec3 reducedSigmaS = (1-" << evalName << "_g)*sigmaS/(sigmaS + sigmaA);" << endl
|
||||
<< " vec3 reducedSigmaT = reducedSigmaS + sigmaA;" << endl
|
||||
<< " vec3 reducedAlbedo = reducedSigmaS/reducedSigmaT;" << endl
|
||||
<< " vec3 rootExp = sqrt((1.0 - reducedAlbedo) * 3.0);" << endl
|
||||
<< " return (reducedAlbedo * 0.5) * (1+exp(-rootExp*(4.0/3.0 * " << endl
|
||||
<< " " << evalName << "_A" << "))) * exp(-rootExp) * 0.31831 * abs(cosThetaO);" << endl
|
||||
<< " float cosThetaI = cosTheta(wi);" << endl
|
||||
<< " float cosThetaO = cosTheta(wo);" << endl
|
||||
<< " if (cosThetaI < 0.0 || cosThetaO < 0.0)" << endl
|
||||
<< " return vec3(0.0);" << endl
|
||||
<< " vec3 sigmaS = " << depNames[0] << "(uv);" << endl
|
||||
<< " vec3 sigmaA = " << depNames[1] << "(uv);" << endl
|
||||
<< " vec3 reducedSigmaS = (1.0-" << evalName << "_g)*sigmaS;" << endl
|
||||
<< " vec3 reducedSigmaT = reducedSigmaS + sigmaA, reducedAlbedo;" << endl
|
||||
<< " for (int i=0; i<3; ++i)" << endl
|
||||
<< " reducedAlbedo[i] = reducedSigmaT[i] > 0.0 ? reducedSigmaS[i]/reducedSigmaT[i] : 0.0;" << endl
|
||||
<< " vec3 rootExp = sqrt((1.0 - reducedAlbedo) * 3.0);" << endl
|
||||
<< " return (reducedAlbedo * 0.5) * (1+exp(-rootExp*(4.0/3.0 * " << endl
|
||||
<< " " << evalName << "_A" << "))) * exp(-rootExp) * 0.31831 * cosThetaO;" << endl
|
||||
<< "}" << endl
|
||||
<< endl
|
||||
<< "vec3 " << evalName << "_diffuse(vec2 uv, vec3 wi, vec3 wo) {" << endl
|
||||
<< " return " << evalName << "(uv, wi, wo);" << endl
|
||||
<< " return " << evalName << "(uv, wi, wo);" << endl
|
||||
<< "}" << endl;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ MTS_NAMESPACE_BEGIN
|
|||
* Note that this model does not account for light that undergoes multiple
|
||||
* scattering events within the layer. This leads to energy loss,
|
||||
* particularly at grazing angles, which can be seen in the left-hand image of
|
||||
* \figref{hk-example}. A solution is to use the \pluginref{chkms} plugin,
|
||||
* \figref{hk-example}. A solution is to use the \pluginref{sssbrdf} plugin,
|
||||
* which adds an approximate multiple scattering component.
|
||||
*
|
||||
* \begin{xml}[caption=A thin dielectric layer with measured ketchup scattering parameters, label=lst:hk-coated]
|
||||
|
@ -111,7 +111,7 @@ class HanrahanKrueger : public BSDF {
|
|||
public:
|
||||
HanrahanKrueger(const Properties &props) : BSDF(props) {
|
||||
Spectrum sigmaS, sigmaA;
|
||||
lookupMaterial(props, sigmaS, sigmaA);
|
||||
lookupMaterial(props, sigmaS, sigmaA, NULL, false);
|
||||
|
||||
/* Scattering coefficient of the layer */
|
||||
m_sigmaS = new ConstantSpectrumTexture(
|
||||
|
@ -140,6 +140,9 @@ public:
|
|||
m_phase = static_cast<PhaseFunction *> (PluginManager::getInstance()->
|
||||
createObject(MTS_CLASS(PhaseFunction), Properties("isotropic")));
|
||||
|
||||
if ((m_sigmaS->getMaximum()+m_sigmaA->getMaximum()).isZero())
|
||||
Log(EError, "Please specify nonzero sigmaS/sigmaA-values!");
|
||||
|
||||
m_components.clear();
|
||||
m_components.push_back(EGlossyReflection | EFrontSide | EBackSide | ECanUseSampler);
|
||||
m_components.push_back(EGlossyTransmission | EFrontSide | EBackSide | ECanUseSampler);
|
||||
|
@ -361,9 +364,15 @@ public:
|
|||
if (cClass->derivesFrom(MTS_CLASS(PhaseFunction))) {
|
||||
Assert(m_phase == NULL);
|
||||
m_phase = static_cast<PhaseFunction *>(child);
|
||||
} else if (cClass->derivesFrom(MTS_CLASS(Texture))) {
|
||||
if (name == "sigmaS")
|
||||
m_sigmaS = static_cast<Texture *>(child);
|
||||
else if (name == "sigmaA")
|
||||
m_sigmaA = static_cast<Texture *>(child);
|
||||
else
|
||||
BSDF::addChild(name, child);
|
||||
} else {
|
||||
Log(EError, "Invalid child node! (\"%s\")",
|
||||
cClass->getName().c_str());
|
||||
BSDF::addChild(name, child);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,13 +92,24 @@ static Float lookupIOR(const std::string &name) {
|
|||
return 0.0f;
|
||||
}
|
||||
|
||||
static Float lookupIOR(const Properties &props, const std::string ¶mName, const std::string &defaultValue) {
|
||||
inline Float lookupIOR(const Properties &props, const std::string ¶mName, const std::string &defaultValue) {
|
||||
if (props.hasProperty(paramName) && props.getType(paramName) == Properties::EFloat)
|
||||
return props.getFloat(paramName);
|
||||
else
|
||||
return lookupIOR(props.getString(paramName, defaultValue));
|
||||
}
|
||||
|
||||
inline Float lookupIOR(const Properties &props, const std::string ¶mName, Float defaultValue) {
|
||||
if (props.hasProperty(paramName)) {
|
||||
if (props.getType(paramName) == Properties::EFloat)
|
||||
return props.getFloat(paramName);
|
||||
else
|
||||
return lookupIOR(props.getString(paramName));
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
MTS_NAMESPACE_END
|
||||
|
||||
#endif /* __IOR_DATA_H */
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include <mitsuba/render/bsdf.h>
|
||||
#include <mitsuba/render/sampler.h>
|
||||
#include <mitsuba/render/phase.h>
|
||||
#include <mitsuba/render/medium.h>
|
||||
#include <mitsuba/core/plugin.h>
|
||||
#include <mitsuba/hw/basicshader.h>
|
||||
#include "../medium/materials.h"
|
||||
|
@ -32,43 +31,78 @@ MTS_NAMESPACE_BEGIN
|
|||
* \parameter{material}{\String}{Name of a material preset, see
|
||||
* \tblref{medium-coefficients}. \default{\texttt{skin1}}}
|
||||
* \parameter{sigmaS}{\Spectrum\Or\Texture}{Specifies the scattering coefficient
|
||||
* of the scattering layer. \default{based on \code{material}}}
|
||||
* of the layer. \default{based on \code{material}}}
|
||||
* \parameter{sigmaA}{\Spectrum\Or\Texture}{Specifies the absorption coefficient
|
||||
* of the scattering layer. \default{based on \code{material}}}
|
||||
* of the layer. \default{based on \code{material}}}
|
||||
* \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified
|
||||
* numerically or using a known material name. \default{\texttt{bk7} / 1.5046}}
|
||||
* numerically or using a known material name. \default{based on \code{material}}}
|
||||
* \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified
|
||||
* numerically or using a known material name. \default{\texttt{air} / 1.000277}}
|
||||
* \parameter{g}{\Float\Or\String}{Specifies the phase function anisotropy
|
||||
* --- see the \pluginref{hg} plugin for details\default{0}}
|
||||
* --- see the \pluginref{hg} plugin for details\default{0, i.e. isotropic}}
|
||||
* }
|
||||
*
|
||||
* This plugin implements a BRDF scattering model that emulates interactions
|
||||
* with a participating medium embedded within a dielectric layer. By
|
||||
* with a participating medium embedded inside a smooth dielectric layer. By
|
||||
* approximating these events using a BRDF, any scattered illumination
|
||||
* is assumed to exit the material directly at the original point of incidence.
|
||||
* is assumed to exit the material \emph{directly} at the original point of incidence.
|
||||
* To account for internal light transport with \emph{different} incident
|
||||
* and exitant positions, please refer to Sections~\ref{sec:media}
|
||||
* and \ref{sec:subsurface}.
|
||||
*
|
||||
* Internally, the model is implemented by instantiating a
|
||||
* Hanrahan-Krueger BSDF for single scattering together with an approximate multiple
|
||||
* scattering component based on Jensen's \cite{Jensen2001Practical} integrated
|
||||
* dipole BRDF. These are then embedded into a dielectric layer using
|
||||
* the \pluginref{coating} plugin.
|
||||
* Internally, the model is implemented by instantiating a Hanrahan-Krueger
|
||||
* BSDF for single scattering in an infinitely thick layer together with
|
||||
* an approximate multiple scattering component based on Jensen's
|
||||
* \cite{Jensen2001Practical} integrated dipole BRDF. These are then
|
||||
* embedded into a dielectric layer using the \pluginref{coating} plugin.
|
||||
* This yields a very convenient parameterization of a scattering model
|
||||
* that roughly behaves like a coated diffuse material, but expressed
|
||||
* in terms of the scattering and absorption coefficients \code{sigmaS}
|
||||
* and \code{sigmaA}.
|
||||
*/
|
||||
class SSSBRDF : public BSDF {
|
||||
public:
|
||||
SSSBRDF(const Properties &props)
|
||||
: BSDF(props), m_configured(false) {
|
||||
|
||||
Spectrum sigmaS, sigmaA; // ignored here
|
||||
Float eta;
|
||||
lookupMaterial(props, sigmaS, sigmaA, &eta, false);
|
||||
|
||||
Float g = props.getFloat("g", 0.0f);
|
||||
Properties hgProps("hg");
|
||||
hgProps.setFloat("g", g);
|
||||
|
||||
ref<PhaseFunction> hg = static_cast<PhaseFunction *> (
|
||||
PluginManager::getInstance()->createObject(
|
||||
MTS_CLASS(PhaseFunction), hgProps));
|
||||
|
||||
Properties hkProps(props);
|
||||
hkProps.setPluginName("hk");
|
||||
hkProps.setFloat("thickness", std::numeric_limits<Float>::infinity());
|
||||
m_hk = static_cast<BSDF *> (PluginManager::getInstance()->
|
||||
createObject(MTS_CLASS(BSDF), hkProps));
|
||||
m_hk->addChild("", hg);
|
||||
|
||||
Properties coatingProps(props);
|
||||
coatingProps.setPluginName("coating");
|
||||
if (!props.hasProperty("intIOR"))
|
||||
coatingProps.setFloat("intIOR", eta);
|
||||
|
||||
m_coating = static_cast<BSDF *> (PluginManager::getInstance()->
|
||||
createObject(MTS_CLASS(BSDF), coatingProps));
|
||||
|
||||
Properties dipoleProps(props);
|
||||
dipoleProps.setPluginName("dipolebrdf");
|
||||
m_dipole = static_cast<BSDF *> (PluginManager::getInstance()->
|
||||
createObject(MTS_CLASS(BSDF), dipoleProps));
|
||||
|
||||
Properties mixtureProps("mixture");
|
||||
mixtureProps.setString("weights", "1.0, 1.0");
|
||||
mixtureProps.setBoolean("ensureEnergyConservation", false);
|
||||
m_mixture = static_cast<BSDF *> (PluginManager::getInstance()->
|
||||
createObject(MTS_CLASS(BSDF), mixtureProps));
|
||||
|
||||
props.markQueried("material");
|
||||
props.markQueried("sigmaS");
|
||||
props.markQueried("sigmaA");
|
||||
|
@ -78,19 +112,24 @@ public:
|
|||
|
||||
SSSBRDF(Stream *stream, InstanceManager *manager)
|
||||
: BSDF(stream, manager), m_configured(true) {
|
||||
m_hk = static_cast<BSDF *>(manager->getInstance(stream));
|
||||
m_coating = static_cast<BSDF *>(manager->getInstance(stream));
|
||||
m_hk = static_cast<BSDF *>(manager->getInstance(stream));
|
||||
m_dipole = static_cast<BSDF *>(manager->getInstance(stream));
|
||||
}
|
||||
|
||||
void configure() {
|
||||
if (!m_configured) {
|
||||
m_configured = true;
|
||||
m_hk->configure();
|
||||
m_coating->addChild("", m_hk);
|
||||
m_dipole->configure();
|
||||
m_mixture->addChild("", m_hk);
|
||||
m_mixture->addChild("", m_dipole);
|
||||
m_mixture->configure();
|
||||
m_coating->addChild("", m_mixture);
|
||||
m_coating->configure();
|
||||
|
||||
m_components.clear();
|
||||
for (size_t i=0; i<m_coating->getComponentCount(); ++i)
|
||||
for (int i=0; i<m_coating->getComponentCount(); ++i)
|
||||
m_components.push_back(m_coating->getType(i));
|
||||
BSDF::configure();
|
||||
}
|
||||
|
@ -118,33 +157,39 @@ public:
|
|||
|
||||
void serialize(Stream *stream, InstanceManager *manager) const {
|
||||
BSDF::serialize(stream, manager);
|
||||
|
||||
manager->serialize(stream, m_hk.get());
|
||||
manager->serialize(stream, m_coating.get());
|
||||
manager->serialize(stream, m_hk.get());
|
||||
manager->serialize(stream, m_dipole.get());
|
||||
}
|
||||
|
||||
void addChild(const std::string &name, ConfigurableObject *child) {
|
||||
const Class *cClass = child->getClass();
|
||||
|
||||
if (cClass->derivesFrom(MTS_CLASS(PhaseFunction))) {
|
||||
if (child->getClass()->derivesFrom(MTS_CLASS(Texture))) {
|
||||
m_hk->addChild(name, child);
|
||||
m_dipole->addChild(name, child);
|
||||
} else {
|
||||
Log(EError, "Invalid child node! (\"%s\")",
|
||||
cClass->getName().c_str());
|
||||
BSDF::addChild(name, child);
|
||||
}
|
||||
}
|
||||
|
||||
Shader *createShader(Renderer *renderer) const {
|
||||
return m_coating->createShader(renderer);
|
||||
}
|
||||
|
||||
std::string toString() const {
|
||||
std::ostringstream oss;
|
||||
oss << "SSSBRDF[" << endl
|
||||
<< " coating = " << indent(m_coating->toString()) << endl
|
||||
<< " name = \"" << m_name << "\"" << endl
|
||||
<< " nested = " << indent(m_coating->toString()) << endl
|
||||
<< "]";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
MTS_DECLARE_CLASS()
|
||||
private:
|
||||
ref<BSDF> m_coating, m_hk;
|
||||
ref<BSDF> m_coating;
|
||||
ref<BSDF> m_dipole;
|
||||
ref<BSDF> m_hk;
|
||||
ref<BSDF> m_mixture;
|
||||
bool m_configured;
|
||||
};
|
||||
|
||||
|
|
|
@ -396,7 +396,7 @@ std::string Properties::toString() const {
|
|||
void Properties::markQueried(const std::string &name) const {
|
||||
std::map<std::string, Element>::const_iterator it = m_elements.find(name);
|
||||
if (it == m_elements.end())
|
||||
SLog(EError, "Could not find parameter \"%s\"!", name.c_str());
|
||||
return;
|
||||
it->second.queried = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ static MaterialEntry materialData[] = {
|
|||
{ NULL, { 0.00, 0.00, 0.00 }, { 0.00, 0.00, 0.00 }, 0.0 }
|
||||
};
|
||||
|
||||
static void lookupMaterial(const Properties &props, Spectrum &sigmaS, Spectrum &sigmaA, Float *eta = NULL) {
|
||||
static void lookupMaterial(const Properties &props, Spectrum &sigmaS, Spectrum &sigmaA, Float *eta = NULL, bool requireValues = true) {
|
||||
bool manual = (props.hasProperty("sigmaS") || props.hasProperty("sigmaA"));
|
||||
bool preset = props.hasProperty("material");
|
||||
|
||||
|
@ -62,8 +62,13 @@ static void lookupMaterial(const Properties &props, Spectrum &sigmaS, Spectrum &
|
|||
Float densityMultiplier = props.getFloat("densityMultiplier", 1.0f);
|
||||
|
||||
if (manual) {
|
||||
sigmaS = props.getSpectrum("sigmaS") * densityMultiplier;
|
||||
sigmaA = props.getSpectrum("sigmaA") * densityMultiplier;
|
||||
if (requireValues) {
|
||||
sigmaS = props.getSpectrum("sigmaS") * densityMultiplier;
|
||||
sigmaA = props.getSpectrum("sigmaA") * densityMultiplier;
|
||||
} else {
|
||||
sigmaS = props.getSpectrum("sigmaS", Spectrum(0.0f)) * densityMultiplier;
|
||||
sigmaA = props.getSpectrum("sigmaA", Spectrum(0.0f)) * densityMultiplier;
|
||||
}
|
||||
if (eta)
|
||||
*eta = props.getFloat("eta", 1.3f);
|
||||
} else {
|
||||
|
|
|
@ -76,7 +76,12 @@ public:
|
|||
}
|
||||
|
||||
std::string toString() const {
|
||||
return "Checkerboard[]";
|
||||
std::ostringstream oss;
|
||||
oss << "Checkerboard[" << endl
|
||||
<< " darkColor = " << m_darkColor.toString() << "," << endl
|
||||
<< " brightColor = " << m_brightColor.toString() << endl
|
||||
<< "]";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
Shader *createShader(Renderer *renderer) const;
|
||||
|
|
Loading…
Reference in New Issue