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

Revision 2943, 15.6 KB checked in by mattausch, 16 years ago (diff)

shadows working nicely

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(Light *light, int size, const AxisAlignedBox3 &sceneBox, Camera *cam):
140mSceneBox(sceneBox), mSize(size), mCamera(cam), mLight(light)
141{
142        mFbo = new FrameBufferObject(size, size, FrameBufferObject::DEPTH_32, true);
143        // the diffuse color buffer
144        mFbo->AddColorBuffer(ColorBufferObject::BUFFER_UBYTE, ColorBufferObject::WRAP_CLAMP_TO_EDGE, ColorBufferObject::FILTER_LINEAR, false);
145        //mFbo->AddColorBuffer(ColorBufferObject::BUFFER_FLOAT_32, ColorBufferObject::WRAP_CLAMP_TO_EDGE, ColorBufferObject::FILTER_LINEAR, false);
146
147        mShadowCam = new Camera(mSize, mSize);//mSceneBox.Size().x * 0.5f, mSceneBox.Size().y * 0.5f);
148        mShadowCam->SetOrtho(true);
149}
150
151
152ShadowMap::~ShadowMap()
153{
154        DEL_PTR(mFbo);
155        DEL_PTR(mShadowCam);
156}
157
158
159static void DrawPoly(Polyhedron *poly, const Vector3 &color)
160{
161        if (!poly) return;
162
163        for (size_t i = 0; i < poly->NumPolygons(); ++ i)
164        {
165                glColor3f(color.x, color.y, color.z);
166
167                glBegin(GL_LINE_LOOP);
168
169                Polygon3 *p = poly->GetPolygons()[i];
170
171                for (size_t j = 0; j < p->mVertices.size(); ++ j)
172                {
173                        Vector3 v = p->mVertices[j];
174                        glVertex3d(v.x, v.y, v.z);
175                }
176
177                glEnd();
178        }
179}
180
181
182void ShadowMap::DrawPolys()
183{
184        DrawPoly(lightPoly, Vector3(1, 0, 1));
185        DrawPoly(polyhedron, Vector3(0, 1, 0));
186}
187
188
189float ShadowMap::ComputeN(const AxisAlignedBox3 &extremalPoints) const
190{
191        const float n = mCamera->GetNear();
192       
193        const float d = fabs(extremalPoints.Max()[2] - extremalPoints.Min()[2]);
194       
195        const float dotProd = DotProd(mCamera->GetDirection(), mShadowCam->GetDirection());
196        const float sinGamma = sin(fabs(acos(dotProd)));
197
198        return (n + sqrt(n * (n + d * sinGamma))) /  sinGamma;
199}
200
201
202Matrix4x4 ShadowMap::CalcLispSMTransform(const Matrix4x4 &lightSpace,
203                                                                                 const AxisAlignedBox3 &extremalPoints,
204                                                                                 const VertexArray &body
205                                                                                 )
206{
207        //return IdentityMatrix();
208
209        AxisAlignedBox3 bounds_ls = GetExtremalPoints(lightSpace, body);
210
211        ///////////////
212        //-- We apply the lispsm algorithm in order to calculate an optimal light projection matrix
213        //-- first find the free parameter values n, and P (the projection center), and the projection depth
214
215        const float n = ComputeN(bounds_ls);
216
217        //cout << "n: " << n << endl;
218
219        const Vector3 nearPt = GetNearCameraPointE(body);
220       
221        //get the coordinates of the near camera point in light space
222        const Vector3 lsNear = lightSpace * nearPt;
223
224        // the start point has the x and y coordinate of e, the z coord of the near plane of the light volume
225        const Vector3 startPt = Vector3(lsNear.x, lsNear.y, bounds_ls.Max().z);
226       
227        // the new projection center
228        const Vector3 projCenter = startPt + Vector3::UNIT_Z() * n;
229
230        //construct a translation that moves to the projection center
231        const Matrix4x4 projectionCenter = TranslationMatrix(-projCenter);
232
233        // light space y size
234        const float d = fabs(bounds_ls.Max()[2] - bounds_ls.Min()[2]);
235
236        const float dy = fabs(bounds_ls.Max()[1] - bounds_ls.Min()[1]);
237        const float dx = fabs(bounds_ls.Max()[0] - bounds_ls.Min()[0]);
238
239       
240
241        //////////
242        //-- now apply these values to construct the perspective lispsm matrix
243
244        Matrix4x4 matLispSM;
245       
246        matLispSM = GetFrustum(-1.0, 1.0, -1.0, 1.0, n, n + d);
247
248        // translate to the projection center
249        matLispSM = projectionCenter * matLispSM;
250
251        // transform into OpenGL right handed system
252        Matrix4x4 refl = ScaleMatrix(1.0f, 1.0f, -1.0f);
253        matLispSM *= refl;
254       
255        return matLispSM;
256}
257
258#if 0
259Vector3 ShadowMap::GetNearCameraPointE(const VertexArray &pts) const
260{
261        float maxDist = -1e25f;
262        Vector3 nearest = Vector3::ZERO();
263
264        Matrix4x4 eyeView;
265        mCamera->GetModelViewMatrix(eyeView);
266
267        VertexArray newPts;
268        polyhedron->CollectVertices(newPts);
269       
270        //the LVS volume is always in front of the camera
271        VertexArray::const_iterator it, it_end = pts.end();     
272
273        for (it = pts.begin(); it != it_end; ++ it)
274        {
275                Vector3 pt = *it;
276                Vector3 ptE = eyeView * pt;
277               
278                if (ptE.z > 0) cerr <<"should not happen " << ptE.z << endl;
279                else
280                if (ptE.z > maxDist)
281                {
282                        cout << " d " << ptE.z;
283       
284                        maxDist = ptE.z;
285                        nearest = pt;
286                }
287        }
288
289        //      return Invert(eyeView) * nearest;
290        return nearest;
291}
292
293#else
294
295Vector3 ShadowMap::GetNearCameraPointE(const VertexArray &pts) const
296{
297        VertexArray newPts;
298        polyhedron->CollectVertices(newPts);
299
300        Vector3 nearest = Vector3::ZERO();
301        float minDist = 1e25f;
302
303        const Vector3 camPos = mCamera->GetPosition();
304
305        VertexArray::const_iterator it, it_end = newPts.end();
306
307        for (it = newPts.begin(); it != it_end; ++ it)
308        {
309                Vector3 pt = *it;
310
311                const float dist = SqrDistance(pt, camPos);
312
313                if (dist < minDist)
314                {
315                        minDist = dist;
316                        nearest = pt;
317                }
318        }
319
320        return nearest;
321}
322
323#endif
324
325Vector3 ShadowMap::GetProjViewDir(const Matrix4x4 &lightSpace,
326                                                                  const VertexArray &pts) const
327{
328        //get the point in the LVS volume that is nearest to the camera
329        const Vector3 e = GetNearCameraPointE(pts);
330
331        //construct edge to transform into light-space
332        const Vector3 b = e + mCamera->GetDirection();
333        //transform to light-space
334        const Vector3 e_lp = lightSpace * e;
335        const Vector3 b_lp = lightSpace * b;
336
337        Vector3 projDir(b_lp - e_lp);
338
339        //project the view direction into the shadow map plane
340        projDir.y = .0f;
341
342        return Normalize(projDir);
343}
344
345
346bool ShadowMap::CalcLightProjection(Matrix4x4 &lightProj)
347{
348        ///////////////////
349        //-- First step: calc frustum clipped by scene box
350
351        DEL_PTR(polyhedron);
352        polyhedron = CalcClippedFrustum(mSceneBox);
353
354        if (!polyhedron) return false; // something is wrong
355
356        // include the part of the light volume that "sees" the frustum
357        // we only require frustum vertices
358
359        VertexArray frustumPoints;
360        //Vector3 lightDir = Vector3(0, 0, -1);
361        IncludeLightVolume(*polyhedron, frustumPoints, mLight->GetDirection(), mSceneBox);
362
363
364        ///////////////
365        //-- transform points from world view to light view and calculate extremal points
366
367        Matrix4x4 lightView;
368        mShadowCam->GetModelViewMatrix(lightView);
369
370        const AxisAlignedBox3 extremalPoints = GetExtremalPoints(lightView, frustumPoints);
371
372        // we use directional lights, so the projection can be set to identity
373        lightProj = IdentityMatrix();
374
375        // switch coordinate system to that used in the lispsm algorithm for calculations
376        Matrix4x4 transform2LispSM = ZeroMatrix();
377
378        transform2LispSM.x[0][0] =  1.0f;
379        transform2LispSM.x[1][2] =  -1.0f; // y => -z
380        transform2LispSM.x[2][1] =  1.0f; // z => y
381        transform2LispSM.x[3][3] =  1.0f;
382
383
384        //switch to the lightspace used in the article
385        lightProj *= transform2LispSM;
386
387        const Vector3 projViewDir = GetProjViewDir(lightView * lightProj, frustumPoints);
388
389        //do Light Space Perspective shadow mapping
390        //rotate the lightspace so that the projected light view always points upwards
391        //calculate a frame matrix that uses the projViewDir[lightspace] as up vector
392        //look(from position, into the direction of the projected direction, with unchanged up-vector)
393        //const Matrix4x4 frame = MyLookAt2(Vector3::ZERO(), projViewDir, Vector3::UNIT_Y());
394        const Matrix4x4 frame = MyLookAt(Vector3::ZERO(), projViewDir, Vector3::UNIT_Y());
395
396        lightProj *= frame;
397
398        const Matrix4x4 matLispSM =
399                CalcLispSMTransform(lightView * lightProj, extremalPoints, frustumPoints);
400
401        lightProj *= matLispSM;
402
403        // change back to GL coordinate system
404        Matrix4x4 transformToGL = ZeroMatrix();
405       
406        transformToGL.x[0][0] =   1.0f;
407        transformToGL.x[1][2] =   1.0f; // z => y
408        transformToGL.x[2][1] =  -1.0f; // y => -z
409        transformToGL.x[3][3] =   1.0f;
410
411        lightProj *= transformToGL;
412
413        AxisAlignedBox3 lightPts = GetExtremalPoints(lightView * lightProj, frustumPoints);
414
415        // focus projection matrix on the extremal points => scale to unit cube
416        Matrix4x4 scaleTranslate = GetFittingProjectionMatrix(lightPts);
417
418        lightProj = lightProj * scaleTranslate;
419
420        Matrix4x4 mymat = lightView * lightProj;
421
422        AxisAlignedBox3 lightPtsNew = GetExtremalPoints(mymat, frustumPoints);
423
424        // we have to flip the signs in order to tranform to opengl right handed system
425        Matrix4x4 refl = ScaleMatrix(1, 1, -1);
426        lightProj *= refl;
427       
428        return true;
429}
430
431
432Polyhedron *ShadowMap::CalcClippedFrustum(const AxisAlignedBox3 &box) const
433{
434        Polyhedron *p = mCamera->ComputeFrustum();
435       
436        Polyhedron *clippedPolyhedron = box.CalcIntersection(*p);
437
438        DEL_PTR(p);
439       
440        return clippedPolyhedron;
441}
442
443
444//calculates the up vector for the light coordinate frame
445static Vector3 CalcUpVec(const Vector3 viewDir, const Vector3 lightDir)
446{
447        //we do what gluLookAt does...
448        //left is the normalized vector perpendicular to lightDir and viewDir
449        //this means left is the normalvector of the yz-plane from the paper
450        Vector3 left = CrossProd(lightDir, viewDir);
451       
452        //we now can calculate the rotated(in the yz-plane) viewDir vector
453        //and use it as up vector in further transformations
454        Vector3 up = CrossProd(left, lightDir);
455
456        return Normalize(up);
457}
458
459
460void ShadowMap::GetTextureMatrix(Matrix4x4 &m) const
461{
462        m = mTextureMatrix;
463}
464
465 
466unsigned int ShadowMap::GetDepthTexture() const
467{
468        return mFbo->GetDepthTex();
469}
470
471unsigned int ShadowMap::GetShadowColorTexture() const
472{
473        return mFbo->GetColorBuffer(0)->GetTexture();
474       
475}
476
477
478void ShadowMap::IncludeLightVolume(const Polyhedron &polyhedron,
479                                                                   VertexArray &frustumPoints,
480                                                                   const Vector3 lightDir,
481                                                                   const AxisAlignedBox3 &sceneBox
482                                                                   )
483{
484        // we don't need closed form anymore => just store vertices
485        VertexArray vertices;
486        polyhedron.CollectVertices(vertices);
487
488        // we 'look' at each point and calculate intersections of rays with scene bounding box
489        VertexArray::const_iterator it, it_end = vertices.end();
490
491        for (it = vertices.begin(); it != it_end; ++ it)
492        {
493                Vector3 v  = *it;
494
495                frustumPoints.push_back(v);
496               
497                // hack: start at point which is guaranteed to be outside of box
498                v -= Magnitude(mSceneBox.Diagonal()) * lightDir;
499
500                SimpleRay ray(v, lightDir);
501
502                float tNear, tFar;
503
504                if (sceneBox.Intersects(ray, tNear, tFar))
505                {
506                        Vector3 newpt = ray.Extrap(tNear);
507                        frustumPoints.push_back(newpt);                 
508                }
509        }
510}
511
512
513void ShadowMap::ComputeShadowMap(RenderTraverser *renderer, const Matrix4x4 &projView)
514{
515        mFbo->Bind();
516       
517        glDrawBuffers(1, mrt);
518
519        glPushAttrib(GL_VIEWPORT_BIT);
520        glViewport(0, 0, mSize, mSize);
521
522        glDisable(GL_LIGHTING);
523        //glDisable(GL_TEXTURE_2D);
524        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
525
526        glShadeModel(GL_FLAT);
527
528
529        /////////////
530        //-- render scene into shadow map
531
532        _Render(renderer);
533
534
535        //////////////
536        //-- compute texture matrix
537
538        static Matrix4x4 biasMatrix(0.5f, 0.0f, 0.0f, 0.5f,
539                                                                0.0f, 0.5f, 0.0f, 0.5f,
540                                                                0.0f, 0.0f, 0.5f, 0.5f,
541                                                                0.0f, 0.0f, 0.0f, 1.0f);
542
543        mTextureMatrix = mLightProjView * biasMatrix;
544
545        glPopAttrib();
546       
547        glShadeModel(GL_SMOOTH);
548        glEnable(GL_LIGHTING);
549        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
550
551#if 0
552        float *data = new float[mSize * mSize];
553
554        GrabDepthBuffer(data, mFbo->GetDepthTex());
555        ExportDepthBuffer(data, mSize);
556
557        delete [] data;
558       
559        PrintGLerror("shadow map");
560#endif
561       
562        FrameBufferObject::Release();
563}
564
565
566void ShadowMap::RenderShadowView(RenderTraverser *renderer, const Matrix4x4 &projView)
567{
568        glEnable(GL_LIGHTING);
569       
570        _Render(renderer);
571       
572        /*glDisable(GL_LIGHTING);
573        glDisable(GL_DEPTH_TEST);
574
575        //glLineWidth(2);
576        Polyhedron *hpoly = CreatePolyhedron(projView, mSceneBox);
577        DrawPoly(hpoly, Vector3(1, 1, 1));
578
579        DEL_PTR(hpoly);
580
581        glEnable(GL_LIGHTING);
582        glEnable(GL_DEPTH_TEST);*/
583
584        glDisable(GL_POLYGON_OFFSET_FILL);
585}
586
587
588void ShadowMap::_Render(RenderTraverser *renderer)
589{
590        const Vector3 dir = mLight->GetDirection();
591        //const Vector3 dir(0, 0, -1);
592
593        mShadowCam->SetDirection(dir);
594
595        // set position so that we can see the whole scene
596        Vector3 pos = mSceneBox.Center();
597        pos -= dir * Magnitude(mSceneBox.Diagonal() * 0.5f);
598
599        mShadowCam->SetPosition(mCamera->GetPosition());
600
601        Vector3 upVec = CalcUpVec(mCamera->GetDirection(), dir);
602        Matrix4x4 lightView = MyLookAt(mShadowCam->GetPosition(), dir, upVec);
603
604        mShadowCam->mViewOrientation = lightView;
605
606        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
607
608        glPolygonOffset(1.0f, 2000.0f);
609        glEnable(GL_POLYGON_OFFSET_FILL);
610       
611        Matrix4x4 lightProj;
612        CalcLightProjection(lightProj);
613
614        glMatrixMode(GL_PROJECTION);
615        glPushMatrix();
616        glLoadMatrixf((float *)lightProj.x);
617
618        mLightProjView = lightView * lightProj;
619
620        DEL_PTR(lightPoly);
621        lightPoly = CreatePolyhedron(mLightProjView, mSceneBox);
622
623        glMatrixMode(GL_MODELVIEW);
624        glPushMatrix();
625        glLoadIdentity();
626
627        //glDisable(GL_CULL_FACE);
628        mShadowCam->SetupCameraView();
629
630       
631        /////////////
632        //-- render scene into shadow map
633
634        renderer->RenderScene();
635
636
637        glMatrixMode(GL_MODELVIEW);
638        glPopMatrix();
639
640        glMatrixMode(GL_PROJECTION);
641        glPopMatrix();
642
643       
644        glDisable(GL_POLYGON_OFFSET_FILL);
645}
646} // namespace
Note: See TracBrowser for help on using the repository browser.