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

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

removed leaks. added class for shaders

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        DEL_PTR(p);
518       
519        return clippedPolyhedron;
520}
521
522
523//calculates the up vector for the light coordinate frame
524static Vector3 CalcUpVec(const Vector3 viewDir, const Vector3 lightDir)
525{
526        //we do what gluLookAt does...
527        //left is the normalized vector perpendicular to lightDir and viewDir
528        //this means left is the normalvector of the yz-plane from the paper
529        Vector3 left = CrossProd(lightDir, viewDir);
530       
531        //we now can calculate the rotated(in the yz-plane) viewDir vector
532        //and use it as up vector in further transformations
533        Vector3 up = CrossProd(left, lightDir);
534
535        return Normalize(up);
536}
537
538
539void ShadowMap::GetTextureMatrix(Matrix4x4 &m) const
540{
541        m = mTextureMatrix;
542}
543
544 
545unsigned int ShadowMap::GetDepthTexture() const
546{
547        return mFbo->GetDepthTex();
548}
549
550unsigned int ShadowMap::GetShadowColorTexture() const
551{
552        return mFbo->GetColorBuffer(0)->GetTexture();
553       
554}
555
556
557void ShadowMap::IncludeLightVolume(const Polyhedron &polyhedron,
558                                                                   VertexArray &frustumPoints,
559                                                                   const Vector3 lightDir,
560                                                                   const AxisAlignedBox3 &sceneBox
561                                                                   )
562{
563        // we don't need closed form anymore => just store vertices
564        VertexArray vertices;
565        polyhedron.CollectVertices(vertices);
566
567        // we 'look' at each point and calculate intersections of rays with scene bounding box
568        VertexArray::const_iterator it, it_end = vertices.end();
569
570        for (it = vertices.begin(); it != it_end; ++ it)
571        {
572                Vector3 v  = *it;
573
574                frustumPoints.push_back(v);
575               
576                // hack: start at point which is guaranteed to be outside of box
577                v -= Magnitude(mSceneBox.Diagonal()) * lightDir;
578
579                SimpleRay ray(v, lightDir);
580
581                float tNear, tFar;
582
583                if (sceneBox.Intersects(ray, tNear, tFar))
584                {
585                        Vector3 newpt = ray.Extrap(tNear);
586                        frustumPoints.push_back(newpt);                 
587                }
588        }
589}
590
591
592void ShadowMap::ComputeShadowMap(RenderTraverser *renderer, const Matrix4x4 &projView)
593{
594        mFbo->Bind();
595       
596        glDrawBuffers(1, mrt);
597
598        glPushAttrib(GL_VIEWPORT_BIT);
599        glViewport(0, 0, mSize, mSize);
600
601        glDisable(GL_LIGHTING);
602        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
603
604        glShadeModel(GL_FLAT);
605
606
607        /////////////
608        //-- render scene into shadow map
609
610        _Render(renderer);
611
612
613        //////////////
614        //-- compute texture matrix
615
616        static Matrix4x4 biasMatrix(0.5f, 0.0f, 0.0f, 0.5f,
617                                                                0.0f, 0.5f, 0.0f, 0.5f,
618                                                                0.0f, 0.0f, 0.5f, 0.5f,
619                                                                0.0f, 0.0f, 0.0f, 1.0f);
620
621        mTextureMatrix = mLightProjView * biasMatrix;
622
623        glPopAttrib();
624       
625        glShadeModel(GL_SMOOTH);
626        glEnable(GL_LIGHTING);
627        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
628
629#if 0
630        float *data = new float[mSize * mSize];
631
632        GrabDepthBuffer(data, mFbo->GetDepthTex());
633        ExportDepthBuffer(data, mSize);
634
635        delete [] data;
636       
637        PrintGLerror("shadow map");
638#endif
639       
640        FrameBufferObject::Release();
641}
642
643
644void ShadowMap::RenderShadowView(RenderTraverser *renderer, const Matrix4x4 &projView)
645{
646        glEnable(GL_LIGHTING);
647       
648        _Render(renderer);
649       
650        /*glDisable(GL_LIGHTING);
651        glDisable(GL_DEPTH_TEST);
652
653        Polyhedron *hpoly = CreatePolyhedron(projView, mSceneBox);
654        DrawPoly(hpoly, Vector3(1, 1, 1));
655        DEL_PTR(hpoly);
656
657        glEnable(GL_LIGHTING);
658        glEnable(GL_DEPTH_TEST);*/
659
660        glDisable(GL_POLYGON_OFFSET_FILL);
661}
662
663
664void ShadowMap::_Render(RenderTraverser *renderer)
665{
666        const Vector3 dir = mLight->GetDirection();
667
668        mShadowCam->SetDirection(dir);
669
670        // set position so that we can see the whole scene
671        Vector3 pos = mSceneBox.Center();
672        pos -= dir * Magnitude(mSceneBox.Diagonal() * 0.5f);
673
674        mShadowCam->SetPosition(mCamera->GetPosition());
675
676        Vector3 upVec = CalcUpVec(mCamera->GetDirection(), dir);
677        Matrix4x4 lightView = LookAt(mShadowCam->GetPosition(), dir, upVec);
678
679        mShadowCam->mViewOrientation = lightView;
680
681        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
682
683        glPolygonOffset(5.0f, 100.0f);
684        glEnable(GL_POLYGON_OFFSET_FILL);
685       
686        Matrix4x4 lightProj;
687        CalcLightProjection(lightProj);
688
689        glMatrixMode(GL_PROJECTION);
690        glPushMatrix();
691        glLoadMatrixf((float *)lightProj.x);
692
693        mLightProjView = lightView * lightProj;
694
695        DEL_PTR(lightPoly);
696        lightPoly = CreatePolyhedron(mLightProjView, mSceneBox);
697
698        glMatrixMode(GL_MODELVIEW);
699        glPushMatrix();
700       
701        mShadowCam->SetupCameraView();
702
703       
704        /////////////
705        //-- render scene into shadow map
706
707        renderer->RenderScene();
708
709
710        glMatrixMode(GL_PROJECTION);
711        glPopMatrix();
712
713        glMatrixMode(GL_MODELVIEW);
714        glPopMatrix();
715
716        glDisable(GL_POLYGON_OFFSET_FILL);
717}
718} // namespace
Note: See TracBrowser for help on using the repository browser.