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

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