/*
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 .
*/
#include
#include
#include "data/shaders.h"
MTS_NAMESPACE_BEGIN
ShadowMapGenerator::ShadowMapGenerator(Renderer *renderer) {
/* Standard directional (i.e. orthographic) shadow map generator */
GPUProgram *prog = renderer->createGPUProgram("Directional shadow map generator");
prog->setSource(GPUProgram::EVertexProgram, sh_directional_vert);
prog->setSource(GPUProgram::EFragmentProgram, sh_directional_frag);
m_program[EDirectional] = prog;
/* Cube (i.e. omnidirectional) shadow map generator */
prog = renderer->createGPUProgram("Cube/Hemicube shadow map generator (5/6 pass version)");
prog->setSource(GPUProgram::EVertexProgram, sh_cube_6pass_vert);
prog->setSource(GPUProgram::EFragmentProgram, sh_cube_6pass_frag);
m_program[ECube] = prog;
if (renderer->getCapabilities()->isSupported(RendererCapabilities::EGeometryShaders)) {
/* Paraboloid shadow map generator based on "Fast Non-Linear Projections
using Graphics Hardware" by Jean-Dominique Gascuel, Nicolas Holzschuch,
Gabrier Fournier, and Bernhard Peroche (I3D, 2008) */
prog = renderer->createGPUProgram("Paraboloid shadow map generator");
prog->setSource(GPUProgram::EVertexProgram, sh_paraboloid_vert);
prog->setSource(GPUProgram::EGeometryProgram, sh_paraboloid_geom);
prog->setSource(GPUProgram::EFragmentProgram, sh_paraboloid_frag);
prog->setInputGeometryType(GPUProgram::ETriangles);
prog->setOutputGeometryType(GPUProgram::ETriangleStrips);
prog->setMaxVertices(4);
m_program[EParaboloid] = prog;
/* Cube shadow map generator (single pass via a geometry shader) */
prog = renderer->createGPUProgram("Cube shadow map generator (1 pass version)");
prog->setSource(GPUProgram::EVertexProgram, sh_cube_1pass_vert);
prog->setSource(GPUProgram::EGeometryProgram, sh_cube_1pass_geom);
prog->setSource(GPUProgram::EFragmentProgram, sh_cube_1pass_frag);
prog->setInputGeometryType(GPUProgram::ETriangles);
prog->setOutputGeometryType(GPUProgram::ETriangleStrips);
prog->setMaxVertices(6*3); /* One triangle per cube map face */
m_program[ECubeSinglePass] = prog;
/* Hemicube shadow map generator (single pass via a geometry shader) */
prog = renderer->createGPUProgram("Hemicube shadow map generator (1 pass version)");
prog->setSource(GPUProgram::EVertexProgram, sh_hemicube_1pass_vert);
prog->setSource(GPUProgram::EGeometryProgram, sh_hemicube_1pass_geom);
prog->setSource(GPUProgram::EFragmentProgram, sh_hemicube_1pass_frag);
prog->setInputGeometryType(GPUProgram::ETriangles);
prog->setOutputGeometryType(GPUProgram::ETriangleStrips);
prog->setMaxVertices(5*3); /* One triangle per hemicube map face */
m_program[EHemicubeSinglePass] = prog;
}
m_program[EHemicube] = m_program[ECube];
/* By default, assume that cube depth maps are supported by the driver */
m_cubeDepthMapsSupported = true;
}
size_t ShadowMapGenerator::getShaderCount() const {
size_t result = 0;
for (int i=0; iinit();
switch (i) {
case ECube:
m_cubeTransform = m_program[i]->getParameterID("transform");
m_cubeProjDir = m_program[i]->getParameterID("projDir", false);
break;
case ECubeSinglePass:
for (int j=0; j<6; ++j) {
m_cubeSinglePassTransform[j] = m_program[i]->getParameterID(formatString("transform[%i]", j));
m_cubeSinglePassProjDir[j] = m_program[i]->getParameterID(formatString("projDir[%i]", j));
}
break;
case EHemicubeSinglePass:
for (int j=0; j<5; ++j) {
m_hemicubeSinglePassTransform[j] = m_program[i]->getParameterID(formatString("transform[%i]", j));
m_hemicubeSinglePassProjDir[j] = m_program[i]->getParameterID(formatString("projDir[%i]", j));
}
break;
case EParaboloid:
m_paraboloidMinDepth = m_program[i]->getParameterID("minDepth");
m_paraboloidInvDepthRange = m_program[i]->getParameterID("invDepthRange");
break;
default:
break;
}
}
}
void ShadowMapGenerator::cleanup() {
for (int i=0; icleanup();
}
}
ref ShadowMapGenerator::allocate(Renderer *renderer,
EShadowMapType type, int res) {
ref result = renderer->createGPUTexture("Shadow map");
result->setSize(Point3i(res, res, 1));
result->setComponentFormat(GPUTexture::EFloat32);
result->setPixelFormat(GPUTexture::EDepth);
result->setFrameBufferType(GPUTexture::EDepthBuffer);
result->setDepthMode(GPUTexture::ENormal);
result->setMipMapped(false);
retry:
if (type == ECube || type == ECubeSinglePass ||
type == EHemicube || type == EHemicubeSinglePass) {
result->setWrapType(GPUTexture::EClampToEdge);
result->setType(GPUTexture::ETextureCubeMap);
if (!m_cubeDepthMapsSupported) {
/* A few graphics cards can't deal with depth cube maps although
they claim to provide all of the necessary extensions. Use
a rather wasteful workaround in this case: create a RGB cube map
(luminance is not widely supported ..) with a single depth buffer */
result->setFrameBufferType(GPUTexture::EColorBuffer);
result->setPixelFormat(GPUTexture::ERGB);
}
} else {
result->setType(GPUTexture::ETexture2D);
result->setBorderColor(Color3(0.0f));
result->setWrapType(GPUTexture::EClampToBorder);
}
try {
result->init();
} catch (const std::exception &ex) {
if (result->getType() == GPUTexture::ETextureCubeMap && m_cubeDepthMapsSupported) {
m_cubeDepthMapsSupported = false;
Log(EWarn, "Graphics driver refused to create a cube depth map! [%s] "
"Attempting to use a workaround ...", ex.what());
result->cleanup();
/* Generate new cube map shader with workaround */
ref prog = renderer->createGPUProgram("Alternate Cube/Hemicube "
"shadow map generator (5/6 pass version)");
prog->define("DEPTH_CUBEMAPS_UNSUPPORTED");
prog->setSource(GPUProgram::EVertexProgram, sh_cube_6pass_vert);
prog->setSource(GPUProgram::EFragmentProgram, sh_cube_6pass_frag);
prog->init();
m_program[EHemicube] = m_program[ECube] = prog;
goto retry;
} else {
throw;
}
}
return result;
}
void ShadowMapGenerator::render(Renderer *renderer, GPUTexture *shadowMap,
EShadowMapType type, const Transform &trafo, Float minDepth, Float maxDepth,
const std::vector &geo) {
GPUProgram *prog = m_program[type];
if (!prog)
Log(EError, "Cannot render shadow map (the "
"graphics card has insufficient capabilities)");
float invDepthRange = (float) (1.0f / (maxDepth - minDepth));
shadowMap->activateTarget();
renderer->setDepthTest(true);
prog->bind();
switch (type) {
case EDirectional: {
Matrix4x4 identity;
identity.setIdentity();
shadowMap->clear();
renderer->setCamera(identity, trafo.getMatrix());
renderer->drawAll(geo);
}
break;
case EParaboloid: {
Matrix4x4 identity;
identity.setIdentity();
shadowMap->clear();
renderer->setCamera(identity, trafo.getMatrix());
prog->setParameter(m_paraboloidMinDepth, minDepth);
prog->setParameter(m_paraboloidInvDepthRange, invDepthRange);
renderer->drawAll(geo);
}
break;
case EHemicube:
case ECube : {
renderer->clearTransforms();
Transform projTrafo = Transform::glPerspective(90.0f, minDepth, maxDepth);
renderer->setClearColor(Color3(0));
/* Render each cube face separately */
for (int i=0; i<6; ++i) {
Transform viewTrafo;
Point o(0.0f);
switch (i) {
/* These come from the OpenGL spec, see section 3.8.10 ("Texture application") */
case 0: viewTrafo = Transform::lookAt(o, Point( 1, 0, 0), Vector(0, -1, 0)); break;
case 1: viewTrafo = Transform::lookAt(o, Point(-1, 0, 0), Vector(0, -1, 0)); break;
case 2: viewTrafo = Transform::lookAt(o, Point( 0, 1, 0), Vector(0, 0, 1)); break;
case 3: viewTrafo = Transform::lookAt(o, Point( 0, -1, 0), Vector(0, 0, -1)); break;
case 4: viewTrafo = Transform::lookAt(o, Point( 0, 0, 1), Vector(0, -1, 0)); break;
case 5: viewTrafo = Transform::lookAt(o, Point( 0, 0, -1), Vector(0, -1, 0)); break;
}
/* Need a left-handed view matrix to render the faces (see the ARB_texture_cube_map spec) */
viewTrafo = Transform::scale(Vector(1, -1, 1)) * viewTrafo.inverse() * trafo;
prog->setParameter(m_cubeTransform, projTrafo * viewTrafo);
prog->setParameter(m_cubeProjDir,
(-viewTrafo.getMatrix().row(2) - Vector4(0, 0, 0, minDepth)) * invDepthRange);
shadowMap->activateSide(i);
if (type == ECube || i != 4) {
shadowMap->clear();
renderer->drawAll(geo);
} else {
renderer->setClearDepth(0);
shadowMap->clear();
renderer->setClearDepth(1);
}
}
}
break;
case ECubeSinglePass: {
renderer->clearTransforms();
shadowMap->activateSide(-1);
shadowMap->clear();
Transform projTrafo = Transform::glPerspective(90.0f, minDepth, maxDepth);
/* Set the parameters for each cube map face */
for (int i=0; i<6; ++i) {
Transform viewTrafo;
Point o(0.0f);
switch (i) {
/* These come from the OpenGL spec, see section 3.8.10 ("Texture application") */
case 0: viewTrafo = Transform::lookAt(o, Point( 1, 0, 0), Vector(0, -1, 0)); break;
case 1: viewTrafo = Transform::lookAt(o, Point(-1, 0, 0), Vector(0, -1, 0)); break;
case 2: viewTrafo = Transform::lookAt(o, Point( 0, 1, 0), Vector(0, 0, 1)); break;
case 3: viewTrafo = Transform::lookAt(o, Point( 0, -1, 0), Vector(0, 0, -1)); break;
case 4: viewTrafo = Transform::lookAt(o, Point( 0, 0, 1), Vector(0, -1, 0)); break;
case 5: viewTrafo = Transform::lookAt(o, Point( 0, 0, -1), Vector(0, -1, 0)); break;
}
/* Need a left-handed view matrix to render the faces (see the ARB_texture_cube_map spec) */
viewTrafo = Transform::scale(Vector(1, -1, 1)) * viewTrafo.inverse() * trafo;
prog->setParameter(m_cubeSinglePassTransform[i], projTrafo * viewTrafo);
prog->setParameter(m_cubeSinglePassProjDir[i],
(-viewTrafo.getMatrix().row(2) - Vector4(0, 0, 0, minDepth)) * invDepthRange);
}
/* Send the geometry to the GPU once */
renderer->drawAll(geo);
}
break;
case EHemicubeSinglePass: {
renderer->clearTransforms();
for (int i=0; i<6; ++i) {
shadowMap->activateSide(i);
if (i != 4) {
shadowMap->clear();
} else {
renderer->setClearDepth(0);
shadowMap->clear();
renderer->setClearDepth(1);
}
}
shadowMap->activateSide(-1);
Transform projTrafo = Transform::glPerspective(90.0f, minDepth, maxDepth);
/* Set the parameters for each cube map face */
for (int i=0; i<5; ++i) {
Transform viewTrafo;
Point o(0.0f);
switch (i) {
/* These come from the OpenGL spec, see section 3.8.10 ("Texture application") */
case 0: viewTrafo = Transform::lookAt(o, Point( 1, 0, 0), Vector(0, -1, 0)); break;
case 1: viewTrafo = Transform::lookAt(o, Point(-1, 0, 0), Vector(0, -1, 0)); break;
case 2: viewTrafo = Transform::lookAt(o, Point( 0, 1, 0), Vector(0, 0, 1)); break;
case 3: viewTrafo = Transform::lookAt(o, Point( 0, -1, 0), Vector(0, 0, -1)); break;
case 4: viewTrafo = Transform::lookAt(o, Point( 0, 0, -1), Vector(0, -1, 0)); break;
}
/* Need a left-handed view matrix to render the faces (see the ARB_texture_cube_map spec) */
viewTrafo = Transform::scale(Vector(1, -1, 1)) * viewTrafo.inverse() * trafo;
prog->setParameter(m_hemicubeSinglePassTransform[i], projTrafo * viewTrafo);
prog->setParameter(m_hemicubeSinglePassProjDir[i],
(-viewTrafo.getMatrix().row(2) - Vector4(0, 0, 0, minDepth)) * invDepthRange);
}
/* Send the geometry to the GPU once */
renderer->drawAll(geo);
}
break;
default:
Log(EError, "Invalid shadow map type!");
}
prog->unbind();
shadowMap->releaseTarget();
}
Transform ShadowMapGenerator::directionalFindGoodFrame(const AABB &aabb, const Vector &d) const {
/* Start with an arbitrary frame */
Frame frame(d);
Vector samples[8], center(0.0f);
for (int i=0; i<8; ++i) {
Vector v = frame.toLocal(Vector(aabb.getCorner(i)));
samples[i] = v;
center += v;
}
center *= 1.0f / 8.0f;
for (int i=0; i<8; ++i)
samples[i] -= center;
/* Heuristic: do a 3x3 principal components analysis to try
to align the projection with the coordinate axis. This is to
make best use of the shadow map resolution */
Matrix2x2 M;
M.setZero();
for (int i=0; i<2; ++i) {
for (int j=0; j<=i; ++j) {
Float sum = 0.0f;
for (int k=0; k<8; ++k)
sum += samples[k][i] * samples[k][j];
M(i, j) = sum;
}
}
M(0, 1) = M(1, 0);
Float eig[2];
Matrix2x2 Q;
M.symEig(Q, eig);
Frame newFrame(
frame.s*Q(1, 0) + frame.t*Q(1, 1),
frame.s*Q(0, 0) + frame.t*Q(0, 1), d);
Transform trafo = Transform::fromFrame(newFrame).inverse() *
Transform::translate(-frame.s*center.x-frame.t*center.y);
AABB aabb2;
for (int i=0; i<8; ++i)
aabb2.expandBy(trafo(aabb.getCorner(i)));
Vector extents = aabb2.getExtents();
aabb2.min -= extents * 1e-3f;
aabb2.max += extents * 1e-3f;
return Transform::glOrthographic(
aabb2.min.x, aabb2.max.x,
aabb2.min.y, aabb2.max.y,
-aabb2.max.z, -aabb2.min.z) * trafo;
}
MTS_IMPLEMENT_CLASS(ShadowMapGenerator, false, Object)
MTS_NAMESPACE_END