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

Revision 2942, 17.2 KB checked in by mattausch, 16 years ago (diff)

lispsm finally working!!

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