396 lines
13 KiB
C++
396 lines
13 KiB
C++
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of NVIDIA CORPORATION nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Copyright (c) 2007-2018 NVIDIA Corporation. All rights reserved.
|
|
//----------------------------------------------------------------------------------
|
|
// File: ShadowMapping.cpp
|
|
// Original Author: Rouslan Dimitrov
|
|
// Modified by: Nuttapong Chentanez and Matthias Mueller-Fischer
|
|
//----------------------------------------------------------------------------------
|
|
|
|
#include "ShadowMap.h"
|
|
#include <GL/glew.h>
|
|
|
|
//----------------------------------------------------------------------------------
|
|
struct ShadowMap::Vec4 {
|
|
Vec4() {}
|
|
Vec4(const PxVec3 &v, float vw = 1.0f) { x = v.x; y = v.y; z = v.z; w = vw; }
|
|
float x,y,z,w;
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------
|
|
struct ShadowMap::Matrix44 {
|
|
Vec4 operator*(const Vec4& v) const {
|
|
Vec4 res;
|
|
res.x = elems[0] * v.x + elems[4] * v.y + elems[8] * v.z + elems[12] * v.w;
|
|
res.y = elems[1] * v.x + elems[5] * v.y + elems[9] * v.z + elems[13] * v.w;
|
|
res.z = elems[2] * v.x + elems[6] * v.y + elems[10] * v.z + elems[14] * v.w;
|
|
res.w = elems[3] * v.x + elems[7] * v.y + elems[11] * v.z + elems[15] * v.w;
|
|
return res;
|
|
}
|
|
float &element(int i, int j) { return elems[i + 4*j]; }
|
|
void zero() { for (int i = 0; i < 16; i++) elems[i] = 0.0f; }
|
|
void id() { zero(); elems[0] = 1.0f; elems[5] = 1.0f; elems[10] = 1.0f; elems[15] = 1.0f; }
|
|
|
|
float elems[16];
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------
|
|
ShadowMap::ShadowMap( int w, int h, float fovi, int matOffseti, int resolution)
|
|
{
|
|
shadowOff = 1.0f;
|
|
shadowOff2 = 2048.0f;
|
|
fov = fovi;
|
|
cur_num_splits = 1;
|
|
//cur_num_splits = 3;
|
|
|
|
width = w;
|
|
height = h;
|
|
depth_size = resolution;
|
|
split_weight = 0.75;
|
|
matOffset = matOffseti;
|
|
|
|
minZAdd = 0;
|
|
maxZAdd = 30.0f;
|
|
|
|
init();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
void ShadowMap::updateFrustumPoints(Frustum &f, const PxVec3 ¢er, const PxVec3 &view_dir)
|
|
{
|
|
PxVec3 up(0.0, 1.0, 0.0);
|
|
PxVec3 right = view_dir.cross(up);
|
|
|
|
PxVec3 fc = center + view_dir*f.fard;
|
|
PxVec3 nc = center + view_dir*f.neard;
|
|
|
|
right.normalize();
|
|
up = right.cross(view_dir);
|
|
up.normalize();
|
|
|
|
// these heights and widths are half the heights and widths of
|
|
// the near and far plane rectangles
|
|
float near_height = tanf(f.fov/2.0f) * f.neard;
|
|
float near_width = near_height * f.ratio;
|
|
float far_height = tanf(f.fov/2.0f) * f.fard;
|
|
float far_width = far_height * f.ratio;
|
|
|
|
f.point[0] = nc - up*near_height - right*near_width;
|
|
f.point[1] = nc + up*near_height - right*near_width;
|
|
f.point[2] = nc + up*near_height + right*near_width;
|
|
f.point[3] = nc - up*near_height + right*near_width;
|
|
|
|
f.point[4] = fc - up*far_height - right*far_width;
|
|
f.point[5] = fc + up*far_height - right*far_width;
|
|
f.point[6] = fc + up*far_height + right*far_width;
|
|
f.point[7] = fc - up*far_height + right*far_width;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// updateSplitDist computes the near and far distances for every frustum slice
|
|
// in camera eye space - that is, at what distance does a slice start and end
|
|
void ShadowMap::updateSplitDist(Frustum f[MAX_SPLITS], float nd, float fd)
|
|
{
|
|
float lambda = split_weight;
|
|
float ratio = fd/nd;
|
|
f[0].neard = nd;
|
|
|
|
for(int i=1; i<cur_num_splits; i++)
|
|
{
|
|
float si = i / (float)cur_num_splits;
|
|
|
|
f[i].neard = lambda*(nd*powf(ratio, si)) + (1-lambda)*(nd + (fd - nd)*si);
|
|
f[i-1].fard = f[i].neard * 1.005f;
|
|
}
|
|
f[cur_num_splits-1].fard = fd;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// this function builds a projection matrix for rendering from the shadow's POV.
|
|
// First, it computes the appropriate z-range and sets an orthogonal projection.
|
|
// Then, it translates and scales it, so that it exactly captures the bounding box
|
|
// of the current frustum slice
|
|
float ShadowMap::applyCropMatrix(Frustum &f)
|
|
{
|
|
float shad_proj[16];
|
|
float maxX = -1000.0;
|
|
float maxY = -1000.0;
|
|
float maxZ;
|
|
float minX = 1000.0;
|
|
float minY = 1000.0;
|
|
float minZ;
|
|
|
|
Matrix44 nv_mvp;
|
|
Vec4 transf;
|
|
|
|
// find the z-range of the current frustum as seen from the light
|
|
// in order to increase precision
|
|
glGetFloatv(GL_MODELVIEW_MATRIX, nv_mvp.elems);
|
|
|
|
// note that only the z-component is need and thus
|
|
// the multiplication can be simplified
|
|
// transf.z = shad_modelview[2] * f.point[0].x + shad_modelview[6] * f.point[0].y + shad_modelview[10] * f.point[0].z + shad_modelview[14];
|
|
transf = nv_mvp * Vec4(f.point[0]);
|
|
minZ = transf.z;
|
|
maxZ = transf.z;
|
|
for(int i=1; i<8; i++)
|
|
{
|
|
transf = nv_mvp * Vec4(f.point[i]);
|
|
if(transf.z > maxZ) maxZ = transf.z;
|
|
if(transf.z < minZ) minZ = transf.z;
|
|
}
|
|
|
|
minZ += minZAdd;
|
|
maxZ += maxZAdd;
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
// set the projection matrix with the new z-bounds
|
|
// note the inversion because the light looks at the neg. z axis
|
|
// gluPerspective(LIGHT_FOV, 1.0, maxZ, minZ); // for point lights
|
|
glOrtho(-1.0, 1.0, -1.0, 1.0, -maxZ, -minZ);
|
|
glGetFloatv(GL_PROJECTION_MATRIX, shad_proj);
|
|
glPushMatrix();
|
|
glMultMatrixf(nv_mvp.elems);
|
|
glGetFloatv(GL_PROJECTION_MATRIX, nv_mvp.elems);
|
|
glPopMatrix();
|
|
|
|
// find the extends of the frustum slice as projected in light's homogeneous coordinates
|
|
for(int i=0; i<8; i++)
|
|
{
|
|
transf = nv_mvp * Vec4(f.point[i]);
|
|
|
|
transf.x /= transf.w;
|
|
transf.y /= transf.w;
|
|
|
|
if(transf.x > maxX) maxX = transf.x;
|
|
if(transf.x < minX) minX = transf.x;
|
|
if(transf.y > maxY) maxY = transf.y;
|
|
if(transf.y < minY) minY = transf.y;
|
|
}
|
|
|
|
float scaleX = 2.0f/(maxX - minX);
|
|
float scaleY = 2.0f/(maxY - minY);
|
|
float offsetX = -0.5f*(maxX + minX)*scaleX;
|
|
float offsetY = -0.5f*(maxY + minY)*scaleY;
|
|
|
|
// apply a crop matrix to modify the projection matrix we got from glOrtho.
|
|
nv_mvp.id();
|
|
nv_mvp.element(0,0) = scaleX;
|
|
nv_mvp.element(1,1) = scaleY;
|
|
nv_mvp.element(0,3) = offsetX;
|
|
nv_mvp.element(1,3) = offsetY;
|
|
glLoadMatrixf(nv_mvp.elems);
|
|
glMultMatrixf(shad_proj);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
return minZ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// here all shadow map textures and their corresponding matrices are created
|
|
void ShadowMap::makeShadowMap(const PxVec3 &cameraPos, const PxVec3 &cameraDir, const PxVec3 &lightDir, float znear, float zfar,
|
|
void (*renderShadowCasters)())
|
|
{
|
|
float shad_modelview[16];
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
gluLookAt(
|
|
cameraPos.x, cameraPos.y, cameraPos.z,
|
|
cameraPos.x-lightDir.x, cameraPos.y-lightDir.y, cameraPos.z-lightDir.z,
|
|
-1.0, 0.0, 0.0);
|
|
|
|
glGetFloatv(GL_MODELVIEW_MATRIX, shad_modelview);
|
|
|
|
// redirect rendering to the depth texture
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fb);
|
|
// store the screen viewport
|
|
glPushAttrib(GL_VIEWPORT_BIT);
|
|
// and render only to the shadowmap
|
|
glViewport(0, 0, depth_size, depth_size);
|
|
// offset the geometry slightly to prevent z-fighting
|
|
// note that this introduces some light-leakage artifacts
|
|
glPolygonOffset(shadowOff, shadowOff2);
|
|
// cout<<"shadowOff = "<<shadowOff<<endl;
|
|
// cout<<"shadowOff2 = "<<shadowOff2<<endl;
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
|
|
// compute the z-distances for each split as seen in camera space
|
|
updateSplitDist(f, znear, zfar);
|
|
|
|
// for all shadow maps:
|
|
for(int i=0; i<cur_num_splits; i++)
|
|
{
|
|
// compute the camera frustum slice boundary points in world space
|
|
updateFrustumPoints(f[i], cameraPos, cameraDir);
|
|
// adjust the view frustum of the light, so that it encloses the camera frustum slice fully.
|
|
// note that this function sets the projection matrix as it sees best fit
|
|
// minZ is just for optimization to cull trees that do not affect the shadows
|
|
float minZ = applyCropMatrix(f[i]);
|
|
// make the current depth map a rendering target
|
|
glFramebufferTextureLayerEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, depth_tex_ar, 0, i);
|
|
|
|
// clear the depth texture from last time
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
|
|
// draw the scene
|
|
(*renderShadowCasters)();
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
// store the product of all shadow matries for later
|
|
glMultMatrixf(shad_modelview);
|
|
glGetFloatv(GL_PROJECTION_MATRIX, shad_cpm[i]);
|
|
}
|
|
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
glPopAttrib();
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPopMatrix();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
void ShadowMap::cameraInverse(float dst[16], float src[16])
|
|
{
|
|
dst[0] = src[0];
|
|
dst[1] = src[4];
|
|
dst[2] = src[8];
|
|
dst[3] = 0.0;
|
|
dst[4] = src[1];
|
|
dst[5] = src[5];
|
|
dst[6] = src[9];
|
|
dst[7] = 0.0;
|
|
dst[8] = src[2];
|
|
dst[9] = src[6];
|
|
dst[10] = src[10];
|
|
dst[11] = 0.0;
|
|
dst[12] = -(src[12] * src[0]) - (src[13] * src[1]) - (src[14] * src[2]);
|
|
dst[13] = -(src[12] * src[4]) - (src[13] * src[5]) - (src[14] * src[6]);
|
|
dst[14] = -(src[12] * src[8]) - (src[13] * src[9]) - (src[14] * src[10]);
|
|
dst[15] = 1.0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
void ShadowMap::doneRender()
|
|
{
|
|
// Unbind texture
|
|
for (int i = 0; i < 8; i++) {
|
|
glActiveTexture(GL_TEXTURE0 + i);
|
|
glMatrixMode(GL_TEXTURE);
|
|
glLoadIdentity();
|
|
}
|
|
glActiveTexture(GL_TEXTURE0);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
void ShadowMap::prepareForRender(float* cam_modelview, float* cam_proj)
|
|
{
|
|
float cam_inverse_modelview[16];
|
|
const float bias[16] = { 0.5, 0.0, 0.0, 0.0,
|
|
0.0, 0.5, 0.0, 0.0,
|
|
0.0, 0.0, 0.5, 0.0,
|
|
0.5, 0.5f, 0.5, 1.0 };
|
|
|
|
// update the camera, so that the user can have a free look
|
|
cameraInverse(cam_inverse_modelview, cam_modelview);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
for(int i=cur_num_splits; i<MAX_SPLITS; i++)
|
|
far_bound[i] = 0;
|
|
|
|
// for every active split
|
|
for(int i=0; i<cur_num_splits; i++)
|
|
{
|
|
// f[i].fard is originally in eye space - tell's us how far we can see.
|
|
// Here we compute it in camera homogeneous coordinates. Basically, we calculate
|
|
// cam_proj * (0, 0, f[i].fard, 1)^t and then normalize to [0; 1]
|
|
far_bound[i] = 0.5f*(-f[i].fard*cam_proj[10]+cam_proj[14])/f[i].fard + 0.5f;
|
|
|
|
// compute a matrix that transforms from camera eye space to light clip space
|
|
// and pass it to the shader through the OpenGL texture matrices, since we
|
|
// don't use them now
|
|
glActiveTexture(GL_TEXTURE0 + (GLenum)i + matOffset);
|
|
glMatrixMode(GL_TEXTURE);
|
|
glLoadMatrixf(bias);
|
|
glMultMatrixf(shad_cpm[i]);
|
|
// multiply the light's (bias*crop*proj*modelview) by the inverse camera modelview
|
|
// so that we can transform a pixel as seen from the camera
|
|
glMultMatrixf(cam_inverse_modelview);
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
void ShadowMap::init()
|
|
{
|
|
//glClearColor(0.8f, 0.8f , 0.9f, 1.0);
|
|
//glEnable(GL_CULL_FACE);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_LIGHTING);
|
|
glEnable(GL_LIGHT0);
|
|
|
|
|
|
glGenFramebuffersEXT(1, &depth_fb);
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fb);
|
|
glDrawBuffer(GL_NONE);
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
|
|
|
glGenTextures(1, &depth_tex_ar);
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, depth_tex_ar);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_DEPTH_COMPONENT32, depth_size, depth_size, MAX_SPLITS, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
|
|
|
for(int i=0; i<MAX_SPLITS; i++)
|
|
{
|
|
// note that fov is in radians here and in OpenGL it is in degrees.
|
|
// the 0.2f factor is important because we might get artifacts at
|
|
// the screen borders.
|
|
f[i].fov = fov/57.2957795f + 0.2f;
|
|
f[i].ratio = (float)width/(float)height;
|
|
}
|
|
|
|
}
|
|
|