/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2011 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 #include #include #include #include #include #include MTS_NAMESPACE_BEGIN #define DISTANCE 50 #define PREVIEW_DISTANCE 2.5 /*!\plugin{envmap}{Environment map luminaire} * \parameters{ * \parameter{intensityScale}{\Float}{ * This parameter can be used to scale the the amount of illumination * emitted by the luminaire. \default{1} * } * } * * This plugin implements a simple environment map luminaire with * importance sampling. It uses the scene's bounding sphere to simulate an * infinitely far-away light source and expects an EXR image in * latitude-longitude (equirectangular) format. */ class EnvMapLuminaire : public Luminaire { public: EnvMapLuminaire(const Properties &props) : Luminaire(props) { m_intensityScale = props.getFloat("intensityScale", 1); ref bitmap; if (props.hasProperty("bitmap")) { bitmap = reinterpret_cast(props.getData("bitmap").ptr); m_path = ""; } else { m_path = Thread::getThread()->getFileResolver()->resolve(props.getString("filename")); Log(EInfo, "Loading environment map \"%s\"", m_path.leaf().c_str()); ref is = new FileStream(m_path, FileStream::EReadOnly); bitmap = new Bitmap(Bitmap::EEXR, is); } m_mipmap = MIPMap::fromBitmap(bitmap, MIPMap::ETrilinear, MIPMap::ERepeat, 0.0f, Spectrum::EIlluminant); m_average = m_mipmap->triangle(m_mipmap->getLevels()-1, 0, 0) * m_intensityScale; m_type = EOnSurface; } EnvMapLuminaire(Stream *stream, InstanceManager *manager) : Luminaire(stream, manager) { m_intensityScale = stream->readFloat(); m_path = stream->readString(); m_bsphere = BSphere(stream); Log(EInfo, "Unserializing environment map \"%s\"", m_path.leaf().c_str()); uint32_t size = stream->readUInt(); ref mStream = new MemoryStream(size); stream->copyTo(mStream, size); mStream->setPos(0); ref bitmap = new Bitmap(Bitmap::EEXR, mStream); m_mipmap = MIPMap::fromBitmap(bitmap, MIPMap::ETrilinear, MIPMap::ERepeat, 0.0f, Spectrum::EIlluminant); m_average = m_mipmap->triangle(m_mipmap->getLevels()-1, 0, 0) * m_intensityScale; m_surfaceArea = 4 * m_bsphere.radius * m_bsphere.radius * M_PI; m_invSurfaceArea = 1/m_surfaceArea; if (Scheduler::getInstance()->hasRemoteWorkers() && !fs::exists(m_path)) { /* This code is running on a machine different from the one that created the stream. Because we might later have to handle a call to serialize(), the whole bitmap must be kept in memory */ m_stream = mStream; } configure(); } void serialize(Stream *stream, InstanceManager *manager) const { Luminaire::serialize(stream, manager); stream->writeFloat(m_intensityScale); stream->writeString(m_path.file_string()); m_bsphere.serialize(stream); if (m_stream.get()) { stream->writeUInt((unsigned int) m_stream->getSize()); stream->write(m_stream->getData(), m_stream->getSize()); } else { ref is = new FileStream(m_path, FileStream::EReadOnly); stream->writeUInt((uint32_t) is->getSize()); is->copyTo(stream); } } void configure() { int mipMapLevel = std::min(3, m_mipmap->getLevels()-1); m_pdfResolution = m_mipmap->getLevelResolution(mipMapLevel); m_pdfInvResolution = Vector2(1.0f / m_pdfResolution.x, 1.0f / m_pdfResolution.y); Log(EDebug, "Creating a %ix%i sampling density", m_pdfResolution.x, m_pdfResolution.y); const Spectrum *coarseImage = m_mipmap->getImageData(mipMapLevel); int index = 0; m_pdf = DiscretePDF(m_pdfResolution.x * m_pdfResolution.y); for (int y=0; ygetBSphere(); m_bsphere.radius *= 1.01f; } if (scene->getCamera()) { BSphere old = m_bsphere; m_bsphere.expandBy(scene->getCamera()->getPosition()); if (old != m_bsphere) m_bsphere.radius *= 1.01f; } m_surfaceArea = 4 * m_bsphere.radius * m_bsphere.radius * M_PI; m_invSurfaceArea = 1/m_surfaceArea; } Spectrum getPower() const { return m_average * m_surfaceArea * M_PI; } /// Sample an emission direction inline Vector sampleDirection(Point2 sample, Float &pdf, Spectrum &value) const { int idx = m_pdf.sampleReuse(sample.x, pdf); int row = idx / m_pdfResolution.x; int col = idx - m_pdfResolution.x * row; Float x = col + sample.x, y = row + sample.y; value = m_mipmap->triangle(0, x * m_pdfInvResolution.x, y * m_pdfInvResolution.y) * m_intensityScale; Float theta = m_pdfPixelSize.y * y, phi = m_pdfPixelSize.x * x; /* Spherical-to-cartesian coordinate mapping with theta=0 => Y=1 */ Float cosTheta = std::cos(theta), sinTheta = std::sqrt(1-cosTheta*cosTheta), cosPhi = std::cos(phi), sinPhi = std::sin(phi); Vector sampledDirection(sinTheta * sinPhi, cosTheta, -sinTheta*cosPhi); pdf = pdf / (m_pdfPixelSize.x * m_pdfPixelSize.y * std::max(Epsilon, sinTheta)); return m_luminaireToWorld(-sampledDirection); } inline Float pdfDirection(const Vector &dir) const { const Vector d = m_worldToLuminaire(-dir); Point2 xy = fromSphere(d); xy.x *= m_pdfResolution.x; xy.y *= m_pdfResolution.y; int xPos = std::min(std::max((int) std::floor(xy.x), 0), m_pdfResolution.x-1); int yPos = std::min(std::max((int) std::floor(xy.y), 0), m_pdfResolution.y-1); Float pdf = m_pdf[xPos + yPos * m_pdfResolution.x]; Float sinTheta = std::sqrt(std::max((Float) Epsilon, 1-d.y*d.y)); return pdf / (m_pdfPixelSize.x * m_pdfPixelSize.y * sinTheta); } Point2 fromSphere(const Vector &d) const { Float u = std::atan2(d.x,-d.z) * (0.5f * INV_PI), v = std::acos(std::max((Float) -1.0f, std::min((Float) 1.0f, d.y))) * INV_PI; if (u < 0) u += 1; return Point2(u, v); } inline Spectrum Le(const Vector &direction) const { Point2 uv = fromSphere(m_worldToLuminaire(direction)); return m_mipmap->triangle(0, uv.x, uv.y) * m_intensityScale; } inline Spectrum Le(const Ray &ray) const { return Le(ray.d); } void sample(const Point &p, LuminaireSamplingRecord &lRec, const Point2 &sample) const { Vector d = sampleDirection(sample, lRec.pdf, lRec.value); Float nearHit, farHit; if (m_bsphere.contains(p) && m_bsphere.rayIntersect(Ray(p, -d, 0.0f), nearHit, farHit)) { lRec.sRec.p = p - d * nearHit; lRec.sRec.n = normalize(m_bsphere.center - lRec.sRec.p); lRec.d = d; } else { lRec.pdf = 0.0f; } } Float pdf(const Point &p, const LuminaireSamplingRecord &lRec, bool delta) const { return pdfDirection(lRec.d); } void sampleEmission(EmissionRecord &eRec, const Point2 &sample1, const Point2 &sample2) const { Assert(eRec.type == EmissionRecord::ENormal); Vector d = sampleDirection(sample1, eRec.pdfArea, eRec.value); const Float renv = m_bsphere.radius * DISTANCE, ratio = 1.0f / DISTANCE, cosThetaMax = std::sqrt(1-ratio*ratio); eRec.pdfArea /= renv*renv; eRec.sRec.p = m_bsphere.center - d * renv; eRec.sRec.n = Normal(d); /* Solid angle corresponding to the spherical cap covered by the scene bounding sphere */ eRec.pdfDir = 1.0f / (2 * M_PI * (1-cosThetaMax)); eRec.d = Frame(eRec.sRec.n).toWorld( squareToCone(cosThetaMax, sample2)); } void sampleEmissionArea(EmissionRecord &eRec, const Point2 &sample) const { Vector d = sampleDirection(sample, eRec.pdfArea, eRec.value); const Float distance = eRec.type == EmissionRecord::EPreview ? PREVIEW_DISTANCE : DISTANCE, renv = m_bsphere.radius * distance, ratio = 1.0f / distance, cosThetaMax = std::sqrt(1-ratio*ratio); eRec.pdfArea /= renv*renv; eRec.sRec.p = m_bsphere.center - d * renv; eRec.sRec.n = Normal(d); eRec.value *= 2 * M_PI * (1-cosThetaMax); } Spectrum sampleEmissionDirection(EmissionRecord &eRec, const Point2 &sample) const { const Float distance = eRec.type == EmissionRecord::EPreview ? PREVIEW_DISTANCE : DISTANCE, ratio = 1.0f / distance, cosThetaMax = std::sqrt(1-ratio*ratio); eRec.pdfDir = 1.0f / (2 * M_PI * (1-cosThetaMax)); eRec.d = Frame(eRec.sRec.n).toWorld( squareToCone(cosThetaMax, sample)); return Spectrum(eRec.pdfDir); } void pdfEmission(EmissionRecord &eRec, bool delta) const { Assert(eRec.type == EmissionRecord::ENormal); if (delta) { eRec.pdfArea = 0.0f; eRec.pdfDir = 0.0f; return; } const Float renv = m_bsphere.radius * DISTANCE, ratio = 1.0f / DISTANCE, cosThetaMax = std::sqrt(1-ratio*ratio); eRec.pdfArea = pdfDirection(eRec.d) / renv*renv; /* Solid angle corresponding to the spherical cap covered by the scene bounding sphere */ eRec.pdfDir = 1.0f / (2 * M_PI * (1-cosThetaMax)); } Spectrum evalDirection(const EmissionRecord &eRec) const { const Float distance = eRec.type == EmissionRecord::EPreview ? PREVIEW_DISTANCE : DISTANCE, ratio = 1.0f / distance, cosThetaMax = std::sqrt(1-ratio*ratio); return Spectrum(1.0f / (2 * M_PI * (1-cosThetaMax))); } Spectrum evalArea(const EmissionRecord &eRec) const { const Float ratio = 1.0f / DISTANCE, cosThetaMax = std::sqrt(1-ratio*ratio); return Le(normalize(eRec.sRec.p - m_bsphere.center)) * 2 * M_PI * (1-cosThetaMax); } bool createEmissionRecord(EmissionRecord &eRec, const Ray &ray) const { Float nearHit, farHit; BSphere sphere(m_bsphere); sphere.radius *= DISTANCE; if (!sphere.contains(ray.o) || !sphere.rayIntersect(ray, nearHit, farHit)) { Log(EWarn, "Could not create an emission record -- the ray " "in question appears to be outside of the scene bounds!"); return false; } eRec.type = EmissionRecord::ENormal; eRec.sRec.p = ray(nearHit); eRec.sRec.n = normalize(m_bsphere.center - eRec.sRec.p); eRec.d = -ray.d; eRec.value = Le(ray.d); eRec.luminaire = this; pdfEmission(eRec, false); return true; } bool isBackgroundLuminaire() const { return true; } std::string toString() const { std::ostringstream oss; oss << "EnvMapLuminaire[" << endl << " name = \"" << m_name << "\"," << endl << " path = \"" << m_path << "\"," << endl << " intensityScale = " << m_intensityScale << "," << endl << " power = " << getPower().toString() << "," << endl << " bsphere = " << m_bsphere.toString() << endl << "]"; return oss.str(); } Shader *createShader(Renderer *renderer) const; MTS_DECLARE_CLASS() private: Spectrum m_average; BSphere m_bsphere; Float m_intensityScale; Float m_surfaceArea; Float m_invSurfaceArea; fs::path m_path; ref m_mipmap; ref m_stream; DiscretePDF m_pdf; Vector2i m_pdfResolution; Vector2 m_pdfInvResolution; Vector2 m_pdfPixelSize; }; // ================ Hardware shader implementation ================ class EnvMapLuminaireShader : public Shader { public: EnvMapLuminaireShader(Renderer *renderer, const fs::path &filename, ref bitmap, Float intensityScale, const Transform &worldToLuminaire) : Shader(renderer, ELuminaireShader) { m_gpuTexture = renderer->createGPUTexture(filename.leaf(), bitmap); m_gpuTexture->setWrapType(GPUTexture::ERepeat); m_gpuTexture->setMaxAnisotropy(8); m_gpuTexture->init(); /* Release the memory on the host side */ m_gpuTexture->setBitmap(0, NULL); m_intensityScale = intensityScale; m_worldToLuminaire = worldToLuminaire; } void cleanup(Renderer *renderer) { m_gpuTexture->cleanup(); } void resolve(const GPUProgram *program, const std::string &evalName, std::vector ¶meterIDs) const { parameterIDs.push_back(program->getParameterID(evalName + "_texture", false)); parameterIDs.push_back(program->getParameterID(evalName + "_intensityScale", false)); parameterIDs.push_back(program->getParameterID(evalName + "_worldToLuminaire", false)); } void generateCode(std::ostringstream &oss, const std::string &evalName, const std::vector &depNames) const { const Float ratio = 1.0f / PREVIEW_DISTANCE, cosThetaMax = std::sqrt(1-ratio*ratio); oss << "uniform sampler2D " << evalName << "_texture;" << endl << "uniform float " << evalName << "_intensityScale;" << endl << "uniform mat4 " << evalName << "_worldToLuminaire;" << endl << endl << "vec3 " << evalName << "_dir(vec3 wo) {" << endl << " return vec3(" << 1.0f / (2*M_PI * (1-cosThetaMax)) << ");" << endl << "}" << endl << endl << "vec3 " << evalName << "_background(vec3 wo) {" << endl << " vec3 d = normalize((" << evalName << "_worldToLuminaire * vec4(wo, 0.0)).xyz);" << endl << " float u = atan(d.x, -d.z) * 0.15915;" << endl << " if (u < 0.0)" << endl << " u += 1.0;" << endl << " float v = acos(max(-1.0, min(1.0, d.y))) * 0.318309;" << endl // The following is not very elegant, but necessary to trick GLSL // into doing correct texture filtering across the u=0 to u=1 seam. << " if (u < 0.1)" << endl << " return texture2D(" << evalName << "_texture, vec2(u+1.0, v)).rgb * " << evalName << "_intensityScale;" << endl << " else" << endl << " return texture2D(" << evalName << "_texture, vec2(u, v)).rgb * " << evalName << "_intensityScale;" << endl << "}" << endl; } void bind(GPUProgram *program, const std::vector ¶meterIDs, int &textureUnitOffset) const { m_gpuTexture->bind(textureUnitOffset++); program->setParameter(parameterIDs[0], m_gpuTexture.get()); program->setParameter(parameterIDs[1], m_intensityScale); program->setParameter(parameterIDs[2], m_worldToLuminaire); } void unbind() const { m_gpuTexture->unbind(); } MTS_DECLARE_CLASS() private: ref m_gpuTexture; Float m_intensityScale; Transform m_worldToLuminaire; }; Shader *EnvMapLuminaire::createShader(Renderer *renderer) const { return new EnvMapLuminaireShader(renderer, m_path, m_mipmap->getBitmap(), m_intensityScale, m_worldToLuminaire); } MTS_IMPLEMENT_CLASS_S(EnvMapLuminaire, false, Luminaire) MTS_IMPLEMENT_CLASS(EnvMapLuminaireShader, false, Shader) MTS_EXPORT_PLUGIN(EnvMapLuminaire, "Environment map luminaire"); MTS_NAMESPACE_END