/** * ========================================================================= * File : ShadowMap.cpp * Project : Pyrogenesis * Description : Shadow mapping related texture and matrix management * * @author Nicolai Hähnle * ========================================================================= */ #include "precompiled.h" #include "ogl.h" #include "graphics/LightEnv.h" #include "maths/Bound.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "renderer/Renderer.h" #include "renderer/ShadowMap.h" /////////////////////////////////////////////////////////////////////////////////////////////////// // ShadowMap implementation /** * Struct ShadowMapInternals: Internal data for the ShadowMap implementation */ struct ShadowMapInternals { // handle of shadow map GLuint Texture; // width, height of shadow map u32 Width, Height; // object space bound of shadow casting objects CBound m_ShadowBound; // project light space into projected light space CMatrix3D LightProjection; // transform world space into light space CMatrix3D LightTransform; // transform world space into texture space CMatrix3D TextureMatrix; // transform world space into light space CMatrix3D NewLightTransform; // transform light space into world space CMatrix3D InvLightTransform; // bounding box of shadowed objects in light space CBound NewShadowBound; // Helper functions void BuildTransformation( const CVector3D& pos,const CVector3D& right,const CVector3D& up, const CVector3D& dir,CMatrix3D& result); void ConstructLightTransform(const CVector3D& pos,const CVector3D& dir,CMatrix3D& result); void CalcShadowMatrices(const CBound& bound); }; /////////////////////////////////////////////////////////////////////////////////////////////////// // Construction/Destruction ShadowMap::ShadowMap() { m = new ShadowMapInternals; m->Texture = 0; } ShadowMap::~ShadowMap() { if (m->Texture) glDeleteTextures(1, &m->Texture); delete m; } ////////////////////////////////////////////////////////////////////////////// // SetCameraAndLight: camera and light direction for this frame void ShadowMap::SetCameraAndLight(const CCamera& camera, const CVector3D& lightdir) { CVector3D z = lightdir; CVector3D y; CVector3D x = camera.m_Orientation.GetIn(); CVector3D eyepos = camera.m_Orientation.GetTranslation(); z.Normalize(); x -= z * z.Dot(x); if (x.GetLength() < 0.001) { // this is invoked if the camera and light directions almost coincide // assumption: light direction has a significant Z component x = CVector3D(1.0, 0.0, 0.0); x -= z * z.Dot(x); } x.Normalize(); y = z.Cross(x); // X axis perpendicular to light direction, flowing along with view direction m->NewLightTransform._11 = x.X; m->NewLightTransform._12 = x.Y; m->NewLightTransform._13 = x.Z; // Y axis perpendicular to light and view direction m->NewLightTransform._21 = y.X; m->NewLightTransform._22 = y.Y; m->NewLightTransform._23 = y.Z; // Z axis is in direction of light m->NewLightTransform._31 = z.X; m->NewLightTransform._32 = z.Y; m->NewLightTransform._33 = z.Z; // eye is at the origin of the coordinate system m->NewLightTransform._14 = -x.Dot(eyepos); m->NewLightTransform._24 = -y.Dot(eyepos); m->NewLightTransform._34 = -z.Dot(eyepos); m->NewLightTransform._41 = 0.0; m->NewLightTransform._42 = 0.0; m->NewLightTransform._43 = 0.0; m->NewLightTransform._44 = 1.0; m->NewLightTransform.GetInverse(m->InvLightTransform); m->NewShadowBound.SetEmpty(); } ////////////////////////////////////////////////////////////////////////////// // AddShadowedBound: add a world-space bounding box to the bounds of shadowed // objects void ShadowMap::AddShadowedBound(const CBound& bounds) { CBound lightspacebounds; bounds.Transform(m->NewLightTransform, lightspacebounds); m->NewShadowBound += lightspacebounds; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // BuildTransformation: build transformation matrix from a position and standard basis vectors // TODO: Shouldn't this be part of CMatrix3D? void ShadowMapInternals::BuildTransformation( const CVector3D& pos,const CVector3D& right,const CVector3D& up, const CVector3D& dir,CMatrix3D& result) { // build basis result._11=right.X; result._12=right.Y; result._13=right.Z; result._14=0; result._21=up.X; result._22=up.Y; result._23=up.Z; result._24=0; result._31=dir.X; result._32=dir.Y; result._33=dir.Z; result._34=0; result._41=0; result._42=0; result._43=0; result._44=1; CMatrix3D trans; trans.SetTranslation(-pos.X,-pos.Y,-pos.Z); result=result*trans; } /////////////////////////////////////////////////////////////////////////////////////////////////// // ConstructLightTransform: build transformation matrix for light at given position casting in // given direction void ShadowMapInternals::ConstructLightTransform(const CVector3D& pos,const CVector3D& dir,CMatrix3D& result) { CVector3D right,up; CVector3D viewdir = g_Renderer.GetCullCamera().m_Orientation.GetIn(); if (fabs(dir.Y)>0.01f) { up=CVector3D(viewdir.X,(-dir.Z*viewdir.Z-dir.X*dir.X)/dir.Y,viewdir.Z); } else { up=CVector3D(0,0,1); } up.Normalize(); right=dir.Cross(up); right.Normalize(); BuildTransformation(pos,right,up,dir,result); } /////////////////////////////////////////////////////////////////////////////////////////////////// // CalcShadowMatrices: calculate required matrices for shadow map generation - the light's // projection and transformation matrices void ShadowMapInternals::CalcShadowMatrices(const CBound& bounds) { const CLightEnv& lightenv = g_Renderer.GetLightEnv(); const CCamera& camera = g_Renderer.GetCullCamera(); int i; // get centre of bounds CVector3D centre; bounds.GetCentre(centre); // get sunlight direction // ??? RC more optimal light placement? CVector3D lightpos=centre-(lightenv.m_SunDir * 1000); // make light transformation matrix ConstructLightTransform(lightpos, lightenv.m_SunDir, LightTransform); // transform shadow bounds to light space, calculate near and far bounds CVector3D vp[8]; LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[0].Z),vp[0]); LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[0].Z),vp[1]); LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[0].Z),vp[2]); LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[0].Z),vp[3]); LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[1].Z),vp[4]); LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[1].Z),vp[5]); LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[1].Z),vp[6]); LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[1].Z),vp[7]); float left=vp[0].X; float right=vp[0].X; float top=vp[0].Y; float bottom=vp[0].Y; float znear=vp[0].Z; float zfar=vp[0].Z; for (i=1;i<8;i++) { if (vp[i].Xright) right=vp[i].X; if (vp[i].Ytop) top=vp[i].Y; if (vp[i].Zzfar) zfar=vp[i].Z; } // shift near and far clip planes slightly to avoid artifacts with points // exactly on the clip planes znear=(znear projected light space (-1 to 1) TextureMatrix = TextureMatrix * LightTransform; // transform world -> light space #if 0 #if 0 // TODO, RC - trim against frustum? // get points of view frustum in world space CVector3D frustumPts[8]; m_Camera.GetFrustumPoints(frustumPts); // transform to light space for (i=0;i<8;i++) { m_LightTransform.Transform(frustumPts[i],vp[i]); } float left1=vp[0].X; float right1=vp[0].X; float top1=vp[0].Y; float bottom1=vp[0].Y; float znear1=vp[0].Z; float zfar1=vp[0].Z; for (int i=1;i<8;i++) { if (vp[i].Xright1) right1=vp[i].X; if (vp[i].Ytop1) top1=vp[i].Y; if (vp[i].Zzfar1) zfar1=vp[i].Z; } left=max(left,left1); right=min(right,right1); top=min(top,top1); bottom=max(bottom,bottom1); znear=max(znear,znear1); zfar=min(zfar,zfar1); #endif // experimental stuff, do not use .. // TODO, RC - desperately need to improve resolution here if we're using shadow maps; investigate // feasibility of PSMs // transform light space bounds to image space - TODO, RC: safe to just use 3d transform here? CVector4D vph[8]; for (i=0;i<8;i++) { CVector4D tmp(vp[i].X,vp[i].Y,vp[i].Z,1.0f); m_LightProjection.Transform(tmp,vph[i]); vph[i][0]/=vph[i][2]; vph[i][1]/=vph[i][2]; } // find the two points furthest apart int p0,p1; float maxdistsqrd=-1; for (i=0;i<8;i++) { for (int j=i+1;j<8;j++) { float dx=vph[i][0]-vph[j][0]; float dy=vph[i][1]-vph[j][1]; float distsqrd=dx*dx+dy*dy; if (distsqrd>maxdistsqrd) { p0=i; p1=j; maxdistsqrd=distsqrd; } } } // now we want to rotate the camera such that the longest axis lies the diagonal at 45 degrees - // get angle between points float angle=atan2(vph[p0][1]-vph[p1][1],vph[p0][0]-vph[p1][0]); float rotation=-angle; // build rotation matrix CQuaternion quat; quat.FromAxisAngle(lightdir,rotation); CMatrix3D m; quat.ToMatrix(m); // rotate up vector by given rotation CVector3D up(m_LightTransform._21,m_LightTransform._22,m_LightTransform._23); up=m.Rotate(up); up.Normalize(); // TODO, RC - required?? // rebuild right vector CVector3D rightvec; rightvec=lightdir.Cross(up); rightvec.Normalize(); BuildTransformation(lightpos,rightvec,up,lightdir,m_LightTransform); // retransform points m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[0].Z),vp[0]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[0].Z),vp[1]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[0].Z),vp[2]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[0].Z),vp[3]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[1].Z),vp[4]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[1].Z),vp[5]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[1].Z),vp[6]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[1].Z),vp[7]); // recalculate projection left=vp[0].X; right=vp[0].X; top=vp[0].Y; bottom=vp[0].Y; znear=vp[0].Z; zfar=vp[0].Z; for (i=1;i<8;i++) { if (vp[i].Xright) right=vp[i].X; if (vp[i].Ytop) top=vp[i].Y; if (vp[i].Zzfar) zfar=vp[i].Z; } // shift near and far clip planes slightly to avoid artifacts with points // exactly on the clip planes znear-=0.01f; zfar+=0.01f; m_LightProjection.SetZero(); m_LightProjection._11=2/(right-left); m_LightProjection._22=2/(top-bottom); m_LightProjection._33=2/(zfar-znear); m_LightProjection._14=-(right+left)/(right-left); m_LightProjection._24=-(top+bottom)/(top-bottom); m_LightProjection._34=-(zfar+znear)/(zfar-znear); m_LightProjection._44=1; #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // Prepare for the next frame: Matrix calculations and texture creation if necessary void ShadowMap::SetupFrame(const CBound& visibleBounds) { m->CalcShadowMatrices(visibleBounds); if (!m->Texture) { // get shadow map size as next power of two up from view width and height m->Width = g_Renderer.GetWidth(); m->Width = RoundUpToPowerOf2(m->Width); m->Height = g_Renderer.GetHeight(); m->Height = RoundUpToPowerOf2(m->Height); // create texture object - initially filled with white, so clamp to edge clamps to correct color glGenTextures(1, &m->Texture); g_Renderer.BindTexture(0, m->Texture); u32 size = m->Width*m->Height; u32* buf=new u32[size]; for (uint i=0;iWidth,m->Height,0,GL_RGBA,GL_UNSIGNED_BYTE,buf); delete[] buf; // set texture parameters glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up to render into shadow map texture void ShadowMap::BeginRender() { // HACK HACK: this depends in non-obvious ways on the behaviour of the caller CRenderer& renderer = g_Renderer; int renderWidth = renderer.GetWidth(); int renderHeight = renderer.GetHeight(); // clear buffers glClearColor(1,1,1,0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // setup viewport glViewport(0, 0, renderWidth, renderHeight); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadMatrixf(&m->LightProjection._11); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixf(&m->LightTransform._11); glEnable(GL_SCISSOR_TEST); glScissor(1,1, renderWidth-2, renderHeight-2); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Finish rendering into shadow map texture void ShadowMap::EndRender() { glDisable(GL_SCISSOR_TEST); // copy result into shadow map texture g_Renderer.BindTexture(0, m->Texture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight()); // restore matrix stack glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Retrieve the texture handle and texture matrix for shadowing GLuint ShadowMap::GetTexture() { return m->Texture; } const CMatrix3D& ShadowMap::GetTextureMatrix() { return m->TextureMatrix; } ////////////////////////////////////////////////////////////////////////////// // RenderDebugDisplay: debug visualizations // - blue: objects in shadow void ShadowMap::RenderDebugDisplay() { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glMultMatrixf(&m->InvLightTransform._11); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(0,0,255,64); m->NewShadowBound.Render(); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(0,0,255); m->NewShadowBound.Render(); glBegin(GL_LINES); glVertex3f(0.0, 0.0, 0.0); glVertex3f(0.0, 0.0, 50.0); glEnd(); glBegin(GL_POLYGON); glVertex3f(0.0, 0.0, 50.0); glVertex3f(50.0, 0.0, 50.0); glVertex3f(0.0, 50.0, 50.0); glEnd(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPopMatrix(); }