source: GTP/trunk/App/Demos/Vis/FriendlyCulling/src/ShadowMapping.cpp @ 3019

Revision 3019, 17.7 KB checked in by mattausch, 16 years ago (diff)

detected memory leaks mainly in shadowmapping!!
strange problems with deferred rendering, seems to be uninitialized sometimes (solved?)

Line 
1#include "ShadowMapping.h"
2#include "FrameBufferObject.h"
3#include "RenderState.h"
4#include "RenderTraverser.h"
5#include "Light.h"
6#include "Polygon3.h"
7#include "Polyhedron.h"
8
9#include <IL/il.h>
10#include <assert.h>
11
12
13using namespace std;
14
15
16namespace CHCDemoEngine
17{
18
19static CGprogram sCgShadowProgram;
20static CGparameter sShadowParam;
21
22
23static Polyhedron *polyhedron = NULL;
24static Polyhedron *lightPoly = NULL;
25
26
27static void PrintGLerror(char *msg)
28{
29        GLenum errCode;
30        const GLubyte *errStr;
31       
32        if ((errCode = glGetError()) != GL_NO_ERROR)
33        {
34                errStr = gluErrorString(errCode);
35                fprintf(stderr,"OpenGL ERROR: %s: %s\n", errStr, msg);
36        }
37}
38
39
40static Polyhedron *CreatePolyhedron(const Matrix4x4 &lightMatrix,
41                                                                        const AxisAlignedBox3 &sceneBox)
42{
43        Frustum frustum(lightMatrix);
44
45        vector<Plane3> clipPlanes;
46
47        for (int i = 0; i < 6; ++ i)
48        {
49                ////////////
50                //-- normalize the coefficients
51
52                // the clipping planes look outward the frustum,
53                // so distances > 0 mean that a point is outside
54                const float invLength = -1.0f / Magnitude(frustum.mClipPlanes[i].mNormal);
55
56                frustum.mClipPlanes[i].mD *= invLength;
57                frustum.mClipPlanes[i].mNormal *= invLength;
58        }
59
60        // first create near plane because of precision issues
61        clipPlanes.push_back(frustum.mClipPlanes[4]);
62
63        clipPlanes.push_back(frustum.mClipPlanes[0]);
64        clipPlanes.push_back(frustum.mClipPlanes[1]);
65        clipPlanes.push_back(frustum.mClipPlanes[2]);
66        clipPlanes.push_back(frustum.mClipPlanes[3]);
67        clipPlanes.push_back(frustum.mClipPlanes[5]);
68
69        return Polyhedron::CreatePolyhedron(clipPlanes, sceneBox);
70}
71
72
73static void GrabDepthBuffer(float *data, GLuint depthTexture)
74{
75        glEnable(GL_TEXTURE_2D);
76        glBindTexture(GL_TEXTURE_2D, depthTexture);
77
78        glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, data);
79
80        glBindTexture(GL_TEXTURE_2D, 0);
81        glDisable(GL_TEXTURE_2D);
82}
83
84
85static void ExportDepthBuffer(float *data, int size)
86{
87        ilInit();
88        assert(ilGetError() == IL_NO_ERROR);
89
90        ILstring filename = ILstring("shadow.tga");
91        ilRegisterType(IL_FLOAT);
92
93        const int depth = 1;
94        const int bpp = 1;
95
96        if (!ilTexImage(size, size, depth, bpp, IL_LUMINANCE, IL_FLOAT, data))
97        {
98                cerr << "IL error " << ilGetError() << endl;
99       
100                ilShutDown();
101                assert(ilGetError() == IL_NO_ERROR);
102
103                return;
104        }
105
106        if (!ilSaveImage(filename))
107        {
108                cerr << "TGA write error " << ilGetError() << endl;
109        }
110
111        ilShutDown();
112        assert(ilGetError() == IL_NO_ERROR);
113
114        cout << "exported depth buffer" << endl;
115}
116
117
118
119static AxisAlignedBox3 GetExtremalPoints(const Matrix4x4 &m,
120                                                                                 const VertexArray &pts)
121{
122        AxisAlignedBox3 extremalPoints;
123        extremalPoints.Initialize();
124
125        VertexArray::const_iterator it, it_end = pts.end();
126               
127        for (it = pts.begin(); it != it_end; ++ it)
128        {
129                Vector3 pt = *it;
130                pt = m * pt;
131
132                extremalPoints.Include(pt);
133        }
134
135        return extremalPoints;
136}
137
138
139ShadowMap::ShadowMap(DirectionalLight *light
140                                         , int size,
141                                         const AxisAlignedBox3 &sceneBox,
142                                         Camera *cam):
143mSceneBox(sceneBox), mSize(size), mCamera(cam), mLight(light)
144{
145        mFbo = new FrameBufferObject(size, size, FrameBufferObject::DEPTH_32, true);
146
147        // need a color buffer to keep opengl happy
148        mFbo->AddColorBuffer(ColorBufferObject::RGB_UBYTE,
149                                 ColorBufferObject::WRAP_CLAMP_TO_EDGE,
150                                                 ColorBufferObject::FILTER_NEAREST);
151
152
153        mShadowCam = new Camera(mSize, mSize);
154        mShadowCam->SetOrtho(true);
155}
156
157
158ShadowMap::~ShadowMap()
159{
160        DEL_PTR(mFbo);
161        DEL_PTR(mShadowCam);
162
163        DEL_PTR(lightPoly);
164        DEL_PTR(polyhedron);
165}
166
167
168static void DrawPolyhedron(Polyhedron *poly, const Vector3 &color)
169{
170        if (!poly) return;
171
172        for (size_t i = 0; i < poly->NumPolygons(); ++ i)
173        {
174                glColor3f(color.x, color.y, color.z);
175
176                glBegin(GL_LINE_LOOP);
177
178                Polygon3 *p = poly->GetPolygons()[i];
179
180                for (size_t j = 0; j < p->mVertices.size(); ++ j)
181                {
182                        Vector3 v = p->mVertices[j];
183                        glVertex3d(v.x, v.y, v.z);
184                }
185
186                glEnd();
187        }
188}
189
190
191void ShadowMap::VisualizeFrustra()
192{
193        DrawPolyhedron(lightPoly, Vector3(1, 0, 1));
194        DrawPolyhedron(polyhedron, Vector3(0, 1, 0));
195}
196
197
198// z0 is the point that lies on the parallel plane to the near plane through e (A)
199//and on the near plane of the C frustum (the plane z = bZmax) and on the line x = e.x
200Vector3 ShadowMap::GetLightSpaceZ0(const Matrix4x4 &lightSpace,
201                                                                   const Vector3 &e,
202                                                                   const float maxZ,
203                                                                   const Vector3 &eyeDir) const
204{
205        // to calculate the parallel plane to the near plane through e we
206        // calculate the plane A with the three points
207        Plane3 planeA(e, eyeDir);
208
209        planeA.Transform(lightSpace);
210       
211        // get the parameters of A from the plane equation n dot d = 0
212        const float d = planeA.mD;
213        const Vector3 n = planeA.mNormal;
214       
215        // transform to light space
216        const Vector3 e_ls = lightSpace * e;
217
218        Vector3 z0;
219
220        z0.x = e_ls.x;
221        z0.y = (d - n.z * maxZ - n.x * e_ls.x) / n.y;
222        z0.z = maxZ;
223
224        return z0;
225        //return V3(e_ls.x(),(d-n.z()*b_lsZmax-n.x()*e_ls.x())/n.y(),b_lsZmax);
226}
227
228
229float ShadowMap::ComputeNOpt(const Matrix4x4 &lightSpace,
230                                                         const AxisAlignedBox3 &extremalPoints,
231                                                         const VertexArray &body) const
232{
233        const Vector3 nearPt = GetNearCameraPointE(body);
234        const Vector3 eyeDir = mCamera->GetDirection();
235
236        Matrix4x4 eyeView;
237        mCamera->GetModelViewMatrix(eyeView);
238
239        const Matrix4x4 invLightSpace = Invert(lightSpace);
240
241        const Vector3 z0_ls = GetLightSpaceZ0(lightSpace, nearPt, extremalPoints.Max().z, eyeDir);
242        const Vector3 z1_ls = Vector3(z0_ls.x, z0_ls.y, extremalPoints.Min().z);
243       
244        // transform back to world space
245        const Vector3 z0_ws = invLightSpace * z0_ls;
246        const Vector3 z1_ws = invLightSpace * z1_ls;
247
248        // transform to eye space
249        const Vector3 z0_es = eyeView * z0_ws;
250        const Vector3 z1_es = eyeView * z1_ws;
251
252        const float z0 = z0_es.z;
253        const float z1 = z1_es.z;
254
255        cout << "z0 ls: " << z0_ls << " z1 ls: " << z1_ls << endl;
256        cout << "z0: " << z0_es << " z1: " << z1_es << endl;
257
258        const float d = fabs(extremalPoints.Max()[2] - extremalPoints.Min()[2]);
259
260        const float n = d / (sqrt(z1 / z0) - 1.0f);
261
262        return n;
263}
264
265
266float ShadowMap::ComputeN(const AxisAlignedBox3 &extremalPoints) const
267{
268        const float nearPlane = mCamera->GetNear();
269       
270        const float d = fabs(extremalPoints.Max()[2] - extremalPoints.Min()[2]);
271       
272        const float dotProd = DotProd(mCamera->GetDirection(), mShadowCam->GetDirection());
273        const float sinGamma = sin(fabs(acos(dotProd)));
274
275        // test for values close to zero
276        if (sinGamma < 1e-6f) return 1e6f;
277       
278        const float scale = 2.0f;
279        return scale * (nearPlane + sqrt(nearPlane * (nearPlane + d * sinGamma))) /  sinGamma;
280}
281
282
283Matrix4x4 ShadowMap::CalcLispSMTransform(const Matrix4x4 &lightSpace,
284                                                                                 const AxisAlignedBox3 &extremalPoints,
285                                                                                 const VertexArray &body
286                                                                                 )
287{
288        AxisAlignedBox3 bounds_ls = GetExtremalPoints(lightSpace, body);
289
290        ///////////////
291        //-- We apply the lispsm algorithm in order to calculate an optimal light projection matrix
292        //-- first find the free parameter values n, and P (the projection center), and the projection depth
293
294        const float n = ComputeN(bounds_ls);
295        //const float n = ComputeNOpt(lightSpace, extremalPoints, body); cout << "n: " << n << endl;
296
297        if (n >= 1e6f) // light direction nearly parallel to view => switch to uniform
298                return IdentityMatrix();
299
300        const Vector3 nearPt = GetNearCameraPointE(body);
301       
302        //get the coordinates of the near camera point in light space
303        const Vector3 lsNear = lightSpace * nearPt;
304
305        // the start point has the x and y coordinate of e, the z coord of the near plane of the light volume
306        const Vector3 startPt = Vector3(lsNear.x, lsNear.y, bounds_ls.Max().z);
307       
308        // the new projection center
309        const Vector3 projCenter = startPt + Vector3::UNIT_Z() * n;
310
311        //construct a translation that moves to the projection center
312        const Matrix4x4 projectionCenter = TranslationMatrix(-projCenter);
313
314        // light space y size
315        const float d = fabs(bounds_ls.Max()[2] - bounds_ls.Min()[2]);
316
317        const float dy = fabs(bounds_ls.Max()[1] - bounds_ls.Min()[1]);
318        const float dx = fabs(bounds_ls.Max()[0] - bounds_ls.Min()[0]);
319
320       
321
322        //////////
323        //-- now apply these values to construct the perspective lispsm matrix
324
325        Matrix4x4 matLispSM;
326       
327        matLispSM = GetFrustum(-1.0, 1.0, -1.0, 1.0, n, n + d);
328
329        // translate to the projection center
330        matLispSM = projectionCenter * matLispSM;
331
332        // transform into OpenGL right handed system
333        Matrix4x4 refl = ScaleMatrix(1.0f, 1.0f, -1.0f);
334        matLispSM *= refl;
335       
336        return matLispSM;
337}
338
339#if 0
340Vector3 ShadowMap::GetNearCameraPointE(const VertexArray &pts) const
341{
342        float maxDist = -1e25f;
343        Vector3 nearest = Vector3::ZERO();
344
345        Matrix4x4 eyeView;
346        mCamera->GetModelViewMatrix(eyeView);
347
348        VertexArray newPts;
349        polyhedron->CollectVertices(newPts);
350       
351        //the LVS volume is always in front of the camera
352        VertexArray::const_iterator it, it_end = pts.end();     
353
354        for (it = pts.begin(); it != it_end; ++ it)
355        {
356                Vector3 pt = *it;
357                Vector3 ptE = eyeView * pt;
358               
359                if (ptE.z > 0) cerr <<"should not happen " << ptE.z << endl;
360                else
361                if (ptE.z > maxDist)
362                {
363                        cout << " d " << ptE.z;
364       
365                        maxDist = ptE.z;
366                        nearest = pt;
367                }
368        }
369
370        //      return Invert(eyeView) * nearest;
371        return nearest;
372}
373
374#else
375
376Vector3 ShadowMap::GetNearCameraPointE(const VertexArray &pts) const
377{
378        VertexArray newPts;
379        polyhedron->CollectVertices(newPts);
380
381        Vector3 nearest = Vector3::ZERO();
382        float minDist = 1e25f;
383
384        const Vector3 camPos = mCamera->GetPosition();
385
386        VertexArray::const_iterator it, it_end = newPts.end();
387
388        for (it = newPts.begin(); it != it_end; ++ it)
389        {
390                Vector3 pt = *it;
391
392                const float dist = SqrDistance(pt, camPos);
393
394                if (dist < minDist)
395                {
396                        minDist = dist;
397                        nearest = pt;
398                }
399        }
400
401        return nearest;
402}
403
404#endif
405
406Vector3 ShadowMap::GetProjViewDir(const Matrix4x4 &lightSpace,
407                                                                  const VertexArray &pts) const
408{
409        //get the point in the LVS volume that is nearest to the camera
410        const Vector3 e = GetNearCameraPointE(pts);
411
412        //construct edge to transform into light-space
413        const Vector3 b = e + mCamera->GetDirection();
414        //transform to light-space
415        const Vector3 e_lp = lightSpace * e;
416        const Vector3 b_lp = lightSpace * b;
417
418        Vector3 projDir(b_lp - e_lp);
419
420        //project the view direction into the shadow map plane
421        projDir.y = .0f;
422
423        return Normalize(projDir);
424}
425
426
427bool ShadowMap::CalcLightProjection(Matrix4x4 &lightProj)
428{
429        ///////////////////
430        //-- First step: calc frustum clipped by scene box
431
432        DEL_PTR(polyhedron);
433        polyhedron = CalcClippedFrustum(mSceneBox);
434
435        if (!polyhedron) return false; // something is wrong
436
437        // include the part of the light volume that "sees" the frustum
438        // we only require frustum vertices
439
440        VertexArray frustumPoints;
441        IncludeLightVolume(*polyhedron, frustumPoints, mLight->GetDirection(), mSceneBox);
442
443
444        ///////////////
445        //-- transform points from world view to light view and calculate extremal points
446
447        Matrix4x4 lightView;
448        mShadowCam->GetModelViewMatrix(lightView);
449
450        const AxisAlignedBox3 extremalPoints = GetExtremalPoints(lightView, frustumPoints);
451
452        // we use directional lights, so the projection can be set to identity
453        lightProj = IdentityMatrix();
454
455        // switch coordinate system to that used in the lispsm algorithm for calculations
456        Matrix4x4 transform2LispSM = ZeroMatrix();
457
458        transform2LispSM.x[0][0] =  1.0f;
459        transform2LispSM.x[1][2] =  -1.0f; // y => -z
460        transform2LispSM.x[2][1] =  1.0f; // z => y
461        transform2LispSM.x[3][3] =  1.0f;
462
463
464        //switch to the lightspace used in the article
465        lightProj *= transform2LispSM;
466
467        const Vector3 projViewDir = GetProjViewDir(lightView * lightProj, frustumPoints);
468
469        //do DirectionalLight Space Perspective shadow mapping
470        //rotate the lightspace so that the projected light view always points upwards
471        //calculate a frame matrix that uses the projViewDir[lightspace] as up vector
472        //look(from position, into the direction of the projected direction, with unchanged up-vector)
473        //const Matrix4x4 frame = MyLookAt2(Vector3::ZERO(), projViewDir, Vector3::UNIT_Y());
474        const Matrix4x4 frame = LookAt(Vector3::ZERO(), projViewDir, Vector3::UNIT_Y());
475
476        lightProj *= frame;
477
478        const Matrix4x4 matLispSM =
479                CalcLispSMTransform(lightView * lightProj, extremalPoints, frustumPoints);
480
481        lightProj *= matLispSM;
482
483        // change back to GL coordinate system
484        Matrix4x4 transformToGL = ZeroMatrix();
485       
486        transformToGL.x[0][0] =   1.0f;
487        transformToGL.x[1][2] =   1.0f; // z => y
488        transformToGL.x[2][1] =  -1.0f; // y => -z
489        transformToGL.x[3][3] =   1.0f;
490
491        lightProj *= transformToGL;
492
493        AxisAlignedBox3 lightPts = GetExtremalPoints(lightView * lightProj, frustumPoints);
494
495        // focus projection matrix on the extremal points => scale to unit cube
496        Matrix4x4 scaleTranslate = GetFittingProjectionMatrix(lightPts);
497
498        lightProj = lightProj * scaleTranslate;
499
500        Matrix4x4 mymat = lightView * lightProj;
501
502        AxisAlignedBox3 lightPtsNew = GetExtremalPoints(mymat, frustumPoints);
503
504        // we have to flip the signs in order to tranform to opengl right handed system
505        Matrix4x4 refl = ScaleMatrix(1, 1, -1);
506        lightProj *= refl;
507       
508        return true;
509}
510
511
512Polyhedron *ShadowMap::CalcClippedFrustum(const AxisAlignedBox3 &box) const
513{
514        Polyhedron *p = mCamera->ComputeFrustum();
515       
516        Polyhedron *clippedPolyhedron = box.CalcIntersection(*p);
517
518        DEL_PTR(p);
519       
520        return clippedPolyhedron;
521}
522
523
524//calculates the up vector for the light coordinate frame
525static Vector3 CalcUpVec(const Vector3 viewDir, const Vector3 lightDir)
526{
527        //we do what gluLookAt does...
528        //left is the normalized vector perpendicular to lightDir and viewDir
529        //this means left is the normalvector of the yz-plane from the paper
530        Vector3 left = CrossProd(lightDir, viewDir);
531       
532        //we now can calculate the rotated(in the yz-plane) viewDir vector
533        //and use it as up vector in further transformations
534        Vector3 up = CrossProd(left, lightDir);
535
536        return Normalize(up);
537}
538
539
540void ShadowMap::GetTextureMatrix(Matrix4x4 &m) const
541{
542        m = mTextureMatrix;
543}
544
545 
546unsigned int ShadowMap::GetDepthTexture() const
547{
548        return mFbo->GetDepthTex();
549}
550
551unsigned int ShadowMap::GetShadowColorTexture() const
552{
553        return mFbo->GetColorBuffer(0)->GetTexture();
554       
555}
556
557
558void ShadowMap::IncludeLightVolume(const Polyhedron &polyhedron,
559                                                                   VertexArray &frustumPoints,
560                                                                   const Vector3 lightDir,
561                                                                   const AxisAlignedBox3 &sceneBox
562                                                                   )
563{
564        // we don't need closed form anymore => just store vertices
565        VertexArray vertices;
566        polyhedron.CollectVertices(vertices);
567
568        // we 'look' at each point and calculate intersections of rays with scene bounding box
569        VertexArray::const_iterator it, it_end = vertices.end();
570
571        for (it = vertices.begin(); it != it_end; ++ it)
572        {
573                Vector3 v  = *it;
574
575                frustumPoints.push_back(v);
576               
577                // hack: start at point which is guaranteed to be outside of box
578                v -= Magnitude(mSceneBox.Diagonal()) * lightDir;
579
580                SimpleRay ray(v, lightDir);
581
582                float tNear, tFar;
583
584                if (sceneBox.Intersects(ray, tNear, tFar))
585                {
586                        Vector3 newpt = ray.Extrap(tNear);
587                        frustumPoints.push_back(newpt);                 
588                }
589        }
590}
591
592
593void ShadowMap::ComputeShadowMap(RenderTraverser *renderer, const Matrix4x4 &projView)
594{
595        mFbo->Bind();
596       
597        glDrawBuffers(1, mrt);
598
599        glPushAttrib(GL_VIEWPORT_BIT);
600        glViewport(0, 0, mSize, mSize);
601
602        glDisable(GL_LIGHTING);
603        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
604
605        glShadeModel(GL_FLAT);
606
607
608        /////////////
609        //-- render scene into shadow map
610
611        _Render(renderer);
612
613
614        //////////////
615        //-- compute texture matrix
616
617        static Matrix4x4 biasMatrix(0.5f, 0.0f, 0.0f, 0.5f,
618                                                                0.0f, 0.5f, 0.0f, 0.5f,
619                                                                0.0f, 0.0f, 0.5f, 0.5f,
620                                                                0.0f, 0.0f, 0.0f, 1.0f);
621
622        mTextureMatrix = mLightProjView * biasMatrix;
623
624        glPopAttrib();
625       
626        glShadeModel(GL_SMOOTH);
627        glEnable(GL_LIGHTING);
628        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
629
630#if 0
631        float *data = new float[mSize * mSize];
632
633        GrabDepthBuffer(data, mFbo->GetDepthTex());
634        ExportDepthBuffer(data, mSize);
635
636        delete [] data;
637       
638        PrintGLerror("shadow map");
639#endif
640       
641        FrameBufferObject::Release();
642}
643
644
645void ShadowMap::RenderShadowView(RenderTraverser *renderer, const Matrix4x4 &projView)
646{
647        glEnable(GL_LIGHTING);
648       
649        _Render(renderer);
650       
651        /*glDisable(GL_LIGHTING);
652        glDisable(GL_DEPTH_TEST);
653
654        Polyhedron *hpoly = CreatePolyhedron(projView, mSceneBox);
655        DrawPoly(hpoly, Vector3(1, 1, 1));
656        DEL_PTR(hpoly);
657
658        glEnable(GL_LIGHTING);
659        glEnable(GL_DEPTH_TEST);*/
660
661        glDisable(GL_POLYGON_OFFSET_FILL);
662}
663
664
665void ShadowMap::_Render(RenderTraverser *renderer)
666{
667        const Vector3 dir = mLight->GetDirection();
668
669        mShadowCam->SetDirection(dir);
670
671        // set position so that we can see the whole scene
672        Vector3 pos = mSceneBox.Center();
673        pos -= dir * Magnitude(mSceneBox.Diagonal() * 0.5f);
674
675        mShadowCam->SetPosition(mCamera->GetPosition());
676
677        Vector3 upVec = CalcUpVec(mCamera->GetDirection(), dir);
678        Matrix4x4 lightView = LookAt(mShadowCam->GetPosition(), dir, upVec);
679
680        mShadowCam->mViewOrientation = lightView;
681
682        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
683
684        glPolygonOffset(5.0f, 100.0f);
685        glEnable(GL_POLYGON_OFFSET_FILL);
686       
687        Matrix4x4 lightProj;
688        CalcLightProjection(lightProj);
689
690        glMatrixMode(GL_PROJECTION);
691        glPushMatrix();
692        glLoadMatrixf((float *)lightProj.x);
693
694        mLightProjView = lightView * lightProj;
695
696        DEL_PTR(lightPoly);
697        lightPoly = CreatePolyhedron(mLightProjView, mSceneBox);
698
699        glMatrixMode(GL_MODELVIEW);
700        glPushMatrix();
701       
702        mShadowCam->SetupCameraView();
703
704       
705        /////////////
706        //-- render scene into shadow map
707
708        renderer->RenderScene();
709
710
711        glMatrixMode(GL_PROJECTION);
712        glPopMatrix();
713
714        glMatrixMode(GL_MODELVIEW);
715        glPopMatrix();
716
717       
718        glDisable(GL_POLYGON_OFFSET_FILL);
719}
720} // namespace
Note: See TracBrowser for help on using the repository browser.