root/OpenSceneGraph/trunk/src/osgShadow/ShadowMap.cpp @ 7692

Revision 7692, 20.7 kB (checked in by robert, 6 years ago)

Added setting of ambient bias uniform

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
2 *
3 * This library is open source and may be redistributed and/or modified under 
4 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
5 * (at your option) any later version.  The full license is in LICENSE file
6 * included with this distribution, and on the openscenegraph.org website.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * OpenSceneGraph Public License for more details.
12*/
13
14#include <osgShadow/ShadowMap>
15#include <osgShadow/ShadowedScene>
16#include <osg/Notify>
17#include <osg/ComputeBoundsVisitor>
18#include <osg/PolygonOffset>
19#include <osg/CullFace>
20#include <osg/io_utils>
21
22using namespace osgShadow;
23
24#include <iostream>
25//for debug
26#include <osg/LightSource>
27#include <osg/PolygonMode>
28#include <osg/Geometry>
29#include <osgDB/ReadFile>
30#include <osgText/Text>
31
32//////////////////////////////////////////////////////////////////
33// fragment shader
34//
35static const char fragmentShaderSource_noBaseTexture[] =
36    "uniform sampler2DShadow osgShadow_shadowTexture; \n"
37    "uniform vec2 osgShadow_ambientBias; \n"
38    "\n"
39    "void main(void) \n"
40    "{ \n"
41    "    gl_FragColor = gl_Color * (osgShadow_ambientBias.x + shadow2DProj( osgShadow_shadowTexture, gl_TexCoord[0] ) * osgShadow_ambientBias.y); \n"
42    "}\n";
43
44//////////////////////////////////////////////////////////////////
45// fragment shader
46//
47static const char fragmentShaderSource_withBaseTexture[] =
48    "uniform sampler2D osgShadow_baseTexture; \n"
49    "uniform sampler2DShadow osgShadow_shadowTexture; \n"
50    "uniform vec2 osgShadow_ambientBias; \n"
51    "\n"
52    "void main(void) \n"
53    "{ \n"
54    "    vec4 color = gl_Color * texture2D( osgShadow_baseTexture, gl_TexCoord[0].xy ); \n"
55    "    gl_FragColor = color * (osgShadow_ambientBias.x + shadow2DProj( osgShadow_shadowTexture, gl_TexCoord[1] ) * osgShadow_ambientBias.y); \n"
56    "}\n";
57
58//////////////////////////////////////////////////////////////////
59// fragment shader
60//
61static const char fragmentShaderSource_debugHUD_texcoord[] =
62    "uniform sampler2D osgShadow_shadowTexture; \n"
63    " \n"
64    "void main(void) \n"
65    "{ \n"
66    "   vec4 texCoord = gl_TexCoord[1].xyzw; \n"
67    "   float value = texCoord.z / texCoord.w; \n"
68    "   gl_FragColor = vec4( value, value, value, 1.0 ); \n"
69    "} \n";
70
71static const char fragmentShaderSource_debugHUD[] =
72    "uniform sampler2D osgShadow_shadowTexture; \n"
73    " \n"
74    "void main(void) \n"
75    "{ \n"
76    "   vec4 texResult = texture2D(osgShadow_shadowTexture, gl_TexCoord[0].st ); \n"
77    "   float value = texResult.r + 0.5; \n"
78    "   gl_FragColor = vec4( value, value, value, 0.8 ); \n"
79    "} \n";
80
81ShadowMap::ShadowMap():
82_baseTextureUnit(0),
83    _shadowTextureUnit(1),
84    _ambientBias(0.5f,0.5f),
85    _textureSize(1024,1024)
86{
87}
88
89ShadowMap::ShadowMap(const ShadowMap& copy, const osg::CopyOp& copyop):
90ShadowTechnique(copy,copyop),
91    _baseTextureUnit(copy._baseTextureUnit),
92    _shadowTextureUnit(copy._shadowTextureUnit),
93    _ambientBias(copy._ambientBias),
94    _textureSize(copy._textureSize)
95{
96}
97
98void ShadowMap::setTextureUnit(unsigned int unit)
99{
100    _shadowTextureUnit = unit;
101}
102
103void ShadowMap::setAmbientBias(const osg::Vec2& ambientBias)
104{
105    _ambientBias = ambientBias;
106    if (_ambientBiasUniform.valid()) _ambientBiasUniform->set(_ambientBias);
107}
108
109void ShadowMap::setTextureSize(const osg::Vec2s& textureSize)
110{
111    _textureSize = textureSize;
112    dirty();
113}
114
115void ShadowMap::setLight(osg::Light* light)
116{
117    _light = light;
118}
119
120
121void ShadowMap::setLight(osg::LightSource* ls)
122{
123    _ls = ls;
124    _light = _ls->getLight();
125}
126
127void ShadowMap::createUniforms()
128{
129    _uniformList.clear();
130
131    osg::Uniform* baseTextureSampler = new osg::Uniform("osgShadow_baseTexture",(int)_baseTextureUnit);
132    _uniformList.push_back(baseTextureSampler);
133
134    osg::Uniform* shadowTextureSampler = new osg::Uniform("osgShadow_shadowTexture",(int)_shadowTextureUnit);
135    _uniformList.push_back(shadowTextureSampler);
136
137    _ambientBiasUniform = new osg::Uniform("osgShadow_ambientBias",_ambientBias);
138    _uniformList.push_back(_ambientBiasUniform.get());
139
140}
141
142void ShadowMap::createShaders()
143{
144    // if we are not given shaders, use the default
145    if( _shaderList.empty() )
146    {
147        if (_shadowTextureUnit==0)
148        {
149            osg::Shader* fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_noBaseTexture);
150            _shaderList.push_back(fragment_shader);
151        }
152        else
153        {
154            osg::Shader* fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture);
155            _shaderList.push_back(fragment_shader);
156
157        }
158    }
159}
160
161void ShadowMap::init()
162{
163    if (!_shadowedScene) return;
164
165    _texture = new osg::Texture2D;
166    _texture->setTextureSize(_textureSize.x(), _textureSize.y());
167    _texture->setInternalFormat(GL_DEPTH_COMPONENT);
168    _texture->setShadowComparison(true);
169    _texture->setShadowTextureMode(osg::Texture2D::LUMINANCE);
170    _texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR);
171    _texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR);
172
173    // the shadow comparison should fail if object is outside the texture
174    _texture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::CLAMP_TO_BORDER);
175    _texture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::CLAMP_TO_BORDER);
176    _texture->setBorderColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f));
177
178    // set up the render to texture camera.
179    {
180        // create the camera
181        _camera = new osg::Camera;
182
183        _camera->setCullCallback(new CameraCullCallback(this));
184
185        _camera->setClearMask(GL_DEPTH_BUFFER_BIT);
186        //_camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
187        _camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f));
188        _camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
189
190        // set viewport
191        _camera->setViewport(0,0,_textureSize.x(),_textureSize.y());
192
193        // set the camera to render before the main camera.
194        _camera->setRenderOrder(osg::Camera::PRE_RENDER);
195
196        // tell the camera to use OpenGL frame buffer object where supported.
197        _camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
198        //_camera->setRenderTargetImplementation(osg::Camera::SEPERATE_WINDOW);
199
200        // attach the texture and use it as the color buffer.
201        _camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get());
202
203        osg::StateSet* stateset = _camera->getOrCreateStateSet();
204
205
206#if 1
207        // cull front faces so that only backfaces contribute to depth map
208
209        osg::ref_ptr<osg::CullFace> cull_face = new osg::CullFace;
210        cull_face->setMode(osg::CullFace::FRONT);
211        stateset->setAttribute(cull_face.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
212        stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
213
214        // negative polygonoffset - move the backface nearer to the eye point so that backfaces
215        // shadow themselves
216        float factor = -1.0f;
217        float units = -1.0f;
218
219        osg::ref_ptr<osg::PolygonOffset> polygon_offset = new osg::PolygonOffset;
220        polygon_offset->setFactor(factor);
221        polygon_offset->setUnits(units);
222        stateset->setAttribute(polygon_offset.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
223        stateset->setMode(GL_POLYGON_OFFSET_FILL, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
224#else
225        // disabling cull faces so that only front and backfaces contribute to depth map
226        stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
227
228        // negative polygonoffset - move the backface nearer to the eye point
229        // so that front faces do not shadow themselves.
230        float factor = 1.0f;
231        float units = 1.0f;
232
233        osg::ref_ptr<osg::PolygonOffset> polygon_offset = new osg::PolygonOffset;
234        polygon_offset->setFactor(factor);
235        polygon_offset->setUnits(units);
236        stateset->setAttribute(polygon_offset.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
237        stateset->setMode(GL_POLYGON_OFFSET_FILL, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
238#endif
239    }
240
241    {
242        _stateset = new osg::StateSet;       
243        _stateset->setTextureAttributeAndModes(_shadowTextureUnit,_texture.get(),osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
244        _stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON);
245        _stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON);
246        _stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON);
247        _stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON);
248
249        _texgen = new osg::TexGen;
250
251        // add Program, when empty of Shaders then we are using fixed functionality
252        _program = new osg::Program;
253        _stateset->setAttribute(_program.get());
254
255        // create default shaders if needed
256        createShaders();
257
258        // add the shader list to the program
259        for(ShaderList::const_iterator itr=_shaderList.begin();
260            itr!=_shaderList.end();
261            ++itr)
262        {
263            _program->addShader(itr->get());
264        }
265
266        // create own uniforms
267        createUniforms();
268
269        // add the uniform list to the stateset
270        for(UniformList::const_iterator itr=_uniformList.begin();
271            itr!=_uniformList.end();
272            ++itr)
273        {
274            _stateset->addUniform(itr->get());
275        }
276
277        {
278            // fake texture for baseTexture, add a fake texture
279            // we support by default at least one texture layer
280            // without this fake texture we can not support
281            // textured and not textured scene
282
283            // TODO: at the moment the PSSM supports just one texture layer in the GLSL shader, multitexture are
284            //       not yet supported !
285
286            osg::Image* image = new osg::Image;
287            // allocate the image data, noPixels x 1 x 1 with 4 rgba floats - equivilant to a Vec4!
288            int noPixels = 1;
289            image->allocateImage(noPixels,1,1,GL_RGBA,GL_FLOAT);
290            image->setInternalTextureFormat(GL_RGBA);
291            // fill in the image data.
292            osg::Vec4* dataPtr = (osg::Vec4*)image->data();
293            osg::Vec4 color(1,1,1,1);
294            *dataPtr = color;
295            // make fake texture
296            osg::Texture2D* fakeTex = new osg::Texture2D;
297            fakeTex->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::CLAMP_TO_BORDER);
298            fakeTex->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::CLAMP_TO_BORDER);
299            fakeTex->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR);
300            fakeTex->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR);
301            fakeTex->setImage(image);
302            // add fake texture
303            _stateset->setTextureAttribute(_baseTextureUnit,fakeTex,osg::StateAttribute::ON);
304            _stateset->setTextureMode(_baseTextureUnit,GL_TEXTURE_1D,osg::StateAttribute::OFF);
305            _stateset->setTextureMode(_baseTextureUnit,GL_TEXTURE_2D,osg::StateAttribute::ON);
306            _stateset->setTextureMode(_baseTextureUnit,GL_TEXTURE_3D,osg::StateAttribute::OFF);
307        }
308    }
309
310    _dirty = false;
311}
312
313
314void ShadowMap::update(osg::NodeVisitor& nv)
315{
316    _shadowedScene->osg::Group::traverse(nv);
317}
318
319void ShadowMap::cull(osgUtil::CullVisitor& cv)
320{
321    // record the traversal mask on entry so we can reapply it later.
322    unsigned int traversalMask = cv.getTraversalMask();
323
324    osgUtil::RenderStage* orig_rs = cv.getRenderStage();
325
326    // do traversal of shadow receiving scene which does need to be decorated by the shadow map
327    {
328        cv.pushStateSet(_stateset.get());
329
330        _shadowedScene->osg::Group::traverse(cv);
331
332        cv.popStateSet();
333
334    }
335
336    // need to compute view frustum for RTT camera.
337    // 1) get the light position
338    // 2) get the center and extents of the view frustum
339
340    const osg::Light* selectLight = 0;
341    osg::Vec4 lightpos;
342    osg::Vec3 lightDir;
343
344    //MR testing giving a specific light
345    osgUtil::PositionalStateContainer::AttrMatrixList& aml = orig_rs->getPositionalStateContainer()->getAttrMatrixList();
346    for(osgUtil::PositionalStateContainer::AttrMatrixList::iterator itr = aml.begin();
347        itr != aml.end();
348        ++itr)
349    {
350        const osg::Light* light = dynamic_cast<const osg::Light*>(itr->first.get());
351        if (light)
352        {
353            if( _light.valid()) {
354                if( _light.get() == light )
355                    selectLight = light;
356                else
357                    continue;
358            }
359            else
360                selectLight = light;
361
362            osg::RefMatrix* matrix = itr->second.get();
363            if (matrix)
364            {
365                lightpos = light->getPosition() * (*matrix);
366                lightDir = light->getDirection() * (*matrix);
367            }
368            else 
369            {
370                lightpos = light->getPosition();
371                lightDir = light->getDirection();
372            }
373
374        }
375    }
376
377    osg::Matrix eyeToWorld;
378    eyeToWorld.invert(*cv.getModelViewMatrix());
379
380    lightpos = lightpos * eyeToWorld;
381    //MR do we do this for the direction also ? preliminary Yes, but still no good result
382    lightDir = lightDir * eyeToWorld;
383    lightDir.normalize();
384
385    if (selectLight)
386    {
387
388        // set to ambient on light to black so that the ambient bias uniform can take it's affect
389        const_cast<osg::Light*>(selectLight)->setAmbient(osg::Vec4(0.0f,0.0f,0.0f,1.0f));
390
391        //std::cout<<"----- VxOSG::ShadowMap selectLight spot cutoff "<<selectLight->getSpotCutoff()<<std::endl;
392
393        if(selectLight->getSpotCutoff() < 180.0f)   // spotlight, then we don't need the bounding box
394        {
395            osg::Vec3 position(lightpos.x(), lightpos.y(), lightpos.z());
396            float spotAngle = selectLight->getSpotCutoff();
397            _camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
398            _camera->setProjectionMatrixAsPerspective(spotAngle, 1.0, 0.1, 1000.0);
399            _camera->setViewMatrixAsLookAt(position,position+lightDir,osg::Vec3(0.0f,1.0f,0.0f));
400        }
401        else
402        {
403            // get the bounds of the model.   
404            osg::ComputeBoundsVisitor cbbv(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN);
405            cbbv.setTraversalMask(getShadowedScene()->getCastsShadowTraversalMask());
406
407            _shadowedScene->osg::Group::traverse(cbbv);
408
409            osg::BoundingBox bb = cbbv.getBoundingBox();
410
411            if (lightpos[3]!=0.0)   // point light
412            {
413                osg::Vec3 position(lightpos.x(), lightpos.y(), lightpos.z());
414
415                float centerDistance = (position-bb.center()).length();
416
417                float znear = centerDistance-bb.radius();
418                float zfar  = centerDistance+bb.radius();
419                float zNearRatio = 0.001f;
420                if (znear<zfar*zNearRatio) znear = zfar*zNearRatio;
421
422                float top   = (bb.radius()/centerDistance)*znear;
423                float right = top;
424
425                _camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
426                _camera->setProjectionMatrixAsFrustum(-right,right,-top,top,znear,zfar);
427                _camera->setViewMatrixAsLookAt(position,bb.center(),osg::Vec3(0.0f,1.0f,0.0f));
428            }
429            else    // directional light
430            {
431                // make an orthographic projection
432                osg::Vec3 lightDir(lightpos.x(), lightpos.y(), lightpos.z());
433                lightDir.normalize();
434
435                // set the position far away along the light direction
436                osg::Vec3 position = lightDir * bb.radius()  * 20;
437
438                float centerDistance = (position-bb.center()).length();
439
440                float znear = centerDistance-bb.radius();
441                float zfar  = centerDistance+bb.radius();
442                float zNearRatio = 0.001f;
443                if (znear<zfar*zNearRatio) znear = zfar*zNearRatio;
444
445                float top   = bb.radius();
446                float right = top;
447
448                _camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
449                _camera->setProjectionMatrixAsOrtho(-right, right, -top, top, znear, zfar);
450                _camera->setViewMatrixAsLookAt(position,bb.center(),osg::Vec3(0.0f,1.0f,0.0f));
451            }
452
453
454        }
455            // compute the matrix which takes a vertex from local coords into tex coords
456            // will use this later to specify osg::TexGen..
457            osg::Matrix MVPT = _camera->getViewMatrix() *
458                _camera->getProjectionMatrix() *
459                osg::Matrix::translate(1.0,1.0,1.0) *
460                osg::Matrix::scale(0.5f,0.5f,0.5f);
461
462            _texgen->setMode(osg::TexGen::EYE_LINEAR);
463            _texgen->setPlanesFromMatrix(MVPT);
464
465        cv.setTraversalMask( traversalMask &
466            getShadowedScene()->getCastsShadowTraversalMask() );
467
468        // do RTT camera traversal
469        _camera->accept(cv);
470
471        orig_rs->getPositionalStateContainer()->addPositionedTextureAttribute(_shadowTextureUnit, cv.getModelViewMatrix(), _texgen.get());
472    } // if(selectLight)
473
474
475    // reapply the original traversal mask
476    cv.setTraversalMask( traversalMask );
477}
478
479void ShadowMap::cleanSceneGraph()
480{
481}
482
483///////////////////// Debug Methods
484
485osg::ref_ptr<osg::Camera> ShadowMap::makeDebugHUD()
486{
487    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
488
489    osg::Vec2 size(1280, 1024);
490    // set the projection matrix
491    camera->setProjectionMatrix(osg::Matrix::ortho2D(0,size.x(),0,size.y()));
492
493    // set the view matrix   
494    camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
495    camera->setViewMatrix(osg::Matrix::identity());
496
497    // only clear the depth buffer
498    camera->setClearMask(GL_DEPTH_BUFFER_BIT);
499    camera->setClearColor(osg::Vec4(0.2f, 0.3f, 0.5f, 0.2f));
500    //camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
501
502    // draw subgraph after main camera view.
503    camera->setRenderOrder(osg::Camera::POST_RENDER);
504
505    // we don't want the camera to grab event focus from the viewers main camera(s).
506    camera->setAllowEventFocus(false);
507
508    osg::Geode* geode = new osg::Geode;
509
510    osg::Vec3 position(10.0f,size.y()-100.0f,0.0f);
511    osg::Vec3 delta(0.0f,-120.0f,0.0f);
512    float lenght = 300.0f;
513
514    // turn the text off to avoid linking with osgText
515#if 0
516    std::string timesFont("fonts/arial.ttf");
517
518    {
519        osgText::Text* text = new  osgText::Text;
520        geode->addDrawable( text );
521
522        text->setFont(timesFont);
523        text->setPosition(position);
524        text->setText("Shadow Map HUD");
525
526        position += delta;
527    }
528#endif
529
530    osg::Vec3 widthVec(lenght, 0.0f, 0.0f);
531    osg::Vec3 depthVec(0.0f,lenght, 0.0f);
532    osg::Vec3 centerBase( 10.0f + lenght/2, size.y()-lenght/2, 0.0f);
533    centerBase += delta;
534
535    geode->addDrawable( osg::createTexturedQuadGeometry( centerBase-widthVec*0.5f-depthVec*0.5f,
536                                                         widthVec, depthVec) );
537
538    osg::StateSet* stateset = geode->getOrCreateStateSet();
539
540    stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
541    stateset->setMode(GL_BLEND,osg::StateAttribute::ON);
542    //stateset->setAttribute(new osg::PolygonOffset(1.0f,1.0f),osg::StateAttribute::ON);
543    stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
544
545    // test with regular texture
546    //stateset->setTextureAttributeAndModes(_baseTextureUnit, new osg::Texture2D(osgDB::readImageFile("Images/lz.rgb")));
547
548    stateset->setTextureAttributeAndModes(_shadowTextureUnit,_texture.get(),osg::StateAttribute::ON);
549
550    //test to check the texture coordinates generated during shadow pass
551#if 0
552    stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON);
553    stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON);
554    stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON);
555    stateset->setTextureMode(_shadowTextureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON);
556
557    // create TexGen node
558    osg::ref_ptr<osg::TexGenNode> texGenNode = new osg::TexGenNode;
559    texGenNode->setTextureUnit(_shadowTextureUnit);
560    texGenNode->setTexGen(_texgen.get());
561    camera->addChild(texGenNode.get());
562#endif
563    //shader for correct display
564
565    osg::ref_ptr<osg::Program> program = new osg::Program;
566    stateset->setAttribute(program.get());
567
568    osg::Shader* fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_debugHUD);
569    program->addShader(fragment_shader);
570
571    camera->addChild(geode);
572
573    return camera;
574}
575
576//////////////////////// End Debug Section
Note: See TracBrowser for help on using the browser.