/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield * Copyright (C) 2014 Pawel Ksiezopolski * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ShapeToGeometry.h" #include "AggregateGeometryVisitor.h" #include "DrawIndirectPrimitiveSet.h" #include "GpuCullShaders.h" #ifndef GL_RASTERIZER_DISCARD #define GL_RASTERIZER_DISCARD 0x8C89 #endif // each instance type may have max 8 LODs ( if you change // this value, don't forget to change it in vertex shaders accordingly ) const unsigned int OSGGPUCULL_MAXIMUM_LOD_NUMBER = 8; // during culling each instance may be sent to max 4 indirect targets const unsigned int OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER = 4; // Struct defining LOD data for particular instance type struct InstanceLOD { InstanceLOD() : bbMin(FLT_MAX,FLT_MAX,FLT_MAX,1.0f), bbMax(-FLT_MAX,-FLT_MAX,-FLT_MAX,1.0f) { } InstanceLOD( const InstanceLOD& iLod ) : bbMin(iLod.bbMin), bbMax(iLod.bbMax), indirectTargetParams(iLod.indirectTargetParams), distances(iLod.distances) { } InstanceLOD& operator=( const InstanceLOD& iLod ) { if( &iLod != this ) { bbMin = iLod.bbMin; bbMax = iLod.bbMax; indirectTargetParams = iLod.indirectTargetParams; distances = iLod.distances; } return *this; } inline void setBoundingBox( const osg::BoundingBox& bbox ) { bbMin = osg::Vec4f( bbox.xMin(), bbox.yMin(), bbox.zMin(), 1.0f ); bbMax = osg::Vec4f( bbox.xMax(), bbox.yMax(), bbox.zMax(), 1.0f ); } inline osg::BoundingBox getBoundingBox() { return osg::BoundingBox( bbMin.x(), bbMin.y(), bbMin.z(), bbMax.x(), bbMax.y(), bbMax.z() ); } osg::Vec4f bbMin; // LOD bounding box osg::Vec4f bbMax; osg::Vec4i indirectTargetParams; // x=lodIndirectCommand, y=lodIndirectCommandIndex, z=offsetsInTarget, w=lodMaxQuantity osg::Vec4f distances; // x=minDistance, y=minFadeDistance, z=maxFadeDistance, w=maxDistance }; // Struct defining information about specific instance type : bounding box, lod ranges, indirect target indices etc struct InstanceType { InstanceType() : bbMin(FLT_MAX,FLT_MAX,FLT_MAX,1.0f), bbMax(-FLT_MAX,-FLT_MAX,-FLT_MAX,1.0f) { params.x() = 0; // this variable defines the number of LODs for(unsigned int i=0;i= OSGGPUCULL_MAXIMUM_LOD_NUMBER) return; params.x() = osg::maximum( params.x(), i+1); lods[i].indirectTargetParams = osg::Vec4i( targetID, indexInTarget, offsetInTarget, maxQuantity ); lods[i].distances = distance; lods[i].setBoundingBox( lodBBox ); expandBy(lodBBox); } inline void expandBy( const osg::BoundingBox& bbox ) { osg::BoundingBox myBBox = getBoundingBox(); myBBox.expandBy(bbox); setBoundingBox(myBBox); } inline void setBoundingBox( const osg::BoundingBox& bbox ) { bbMin = osg::Vec4f( bbox.xMin(), bbox.yMin(), bbox.zMin(), 1.0f ); bbMax = osg::Vec4f( bbox.xMax(), bbox.yMax(), bbox.zMax(), 1.0f ); } inline osg::BoundingBox getBoundingBox() { return osg::BoundingBox( bbMin.x(), bbMin.y(), bbMin.z(), bbMax.x(), bbMax.y(), bbMax.z() ); } osg::Vec4f bbMin; // bounding box that includes all LODs osg::Vec4f bbMax; osg::Vec4i params; // x=number of active LODs InstanceLOD lods[OSGGPUCULL_MAXIMUM_LOD_NUMBER]; // information about LODs }; // CPU side representation of a struct defined in ARB_draw_indirect extension struct DrawArraysIndirectCommand { DrawArraysIndirectCommand() : count(0), primCount(0), first(0), baseInstance(0) { } DrawArraysIndirectCommand( unsigned int aFirst, unsigned int aCount ) : count(aCount), primCount(0), first(aFirst), baseInstance(0) { } unsigned int count; unsigned int primCount; unsigned int first; unsigned int baseInstance; }; // During the first phase of instance rendering cull shader places information about // instance LODs in texture buffers called "indirect targets" // All data associated with the indirect target is placed in struct defined below // ( like for example - draw shader associated with specific indirect target. // Draw shader performs second phase of instance rendering - the actual rendering of objects // to screen or to frame buffer object ). struct IndirectTarget { IndirectTarget() : maxTargetQuantity(0) { indirectCommands = new osg::BufferTemplate< std::vector >; } IndirectTarget( AggregateGeometryVisitor* agv, osg::Program* program ) : geometryAggregator(agv), drawProgram(program), maxTargetQuantity(0) { indirectCommands = new osg::BufferTemplate< std::vector >; } void endRegister(unsigned int index, unsigned int rowsPerInstance, GLenum pixelFormat, GLenum type, GLint internalFormat, bool useMultiDrawArraysIndirect ) { osg::Image* indirectCommandImage = new osg::Image; indirectCommandImage->setImage( indirectCommands->getTotalDataSize()/sizeof(unsigned int), 1, 1, GL_R32I, GL_RED, GL_UNSIGNED_INT, (unsigned char*)indirectCommands->getDataPointer(), osg::Image::NO_DELETE ); osg::VertexBufferObject * indirectCommandImagebuffer=new osg::VertexBufferObject(); indirectCommandImagebuffer->setUsage(GL_DYNAMIC_DRAW); indirectCommandImage->setBufferObject(indirectCommandImagebuffer); indirectCommandTextureBuffer = new osg::TextureBuffer(indirectCommandImage); indirectCommandTextureBuffer->setInternalFormat( GL_R32I ); indirectCommandTextureBuffer->bindToImageUnit(index, osg::Texture::READ_WRITE); indirectCommandTextureBuffer->setUnRefImageDataAfterApply(false); // add proper primitivesets to geometryAggregators if( !useMultiDrawArraysIndirect ) // use glDrawArraysIndirect() { std::vector newPrimitiveSets; for(unsigned int j=0;jgetData().size(); ++j) newPrimitiveSets.push_back( new DrawArraysIndirect( GL_TRIANGLES, j*sizeof( DrawArraysIndirectCommand ) ) ); geometryAggregator->getAggregatedGeometry()->removePrimitiveSet(0,geometryAggregator->getAggregatedGeometry()->getNumPrimitiveSets() ); for(unsigned int j=0;jgetData().size(); ++j) geometryAggregator->getAggregatedGeometry()->addPrimitiveSet( newPrimitiveSets[j] ); } else // use glMultiDrawArraysIndirect() { geometryAggregator->getAggregatedGeometry()->removePrimitiveSet(0,geometryAggregator->getAggregatedGeometry()->getNumPrimitiveSets() ); geometryAggregator->getAggregatedGeometry()->addPrimitiveSet( new MultiDrawArraysIndirect( GL_TRIANGLES, 0, indirectCommands->getData().size(), 0 ) ); } ///attach a DrawIndirect buffer binding to the stateset osg::ref_ptr bb=new osg::DrawIndirectBufferBinding(); bb->setBufferObject(indirectCommandImage->getBufferObject()); geometryAggregator->getAggregatedGeometry()->getOrCreateStateSet()->setAttribute(bb ); geometryAggregator->getAggregatedGeometry()->setUseDisplayList(false); geometryAggregator->getAggregatedGeometry()->setUseVertexBufferObjects(true); osg::Image* instanceTargetImage = new osg::Image; instanceTargetImage->allocateImage( maxTargetQuantity*rowsPerInstance, 1, 1, pixelFormat, type ); osg::VertexBufferObject * instanceTargetImageBuffer=new osg::VertexBufferObject(); instanceTargetImageBuffer->setUsage(GL_DYNAMIC_DRAW); instanceTargetImage->setBufferObject(instanceTargetImageBuffer); instanceTarget = new osg::TextureBuffer(instanceTargetImage); instanceTarget->setInternalFormat( internalFormat ); instanceTarget->bindToImageUnit(OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+index, osg::Texture::READ_WRITE); } void addIndirectCommandData( const std::string& uniformNamePrefix, int index, osg::StateSet* stateset ) { std::string uniformName = uniformNamePrefix + char( '0' + index ); osg::Uniform* uniform = new osg::Uniform(uniformName.c_str(), (int)index ); stateset->addUniform( uniform ); stateset->setTextureAttribute( index, indirectCommandTextureBuffer.get() ); } void addIndirectTargetData( bool cullPhase, const std::string& uniformNamePrefix, int index, osg::StateSet* stateset ) { std::string uniformName; if( cullPhase ) uniformName = uniformNamePrefix + char( '0' + index ); else uniformName = uniformNamePrefix; osg::Uniform* uniform = new osg::Uniform(uniformName.c_str(), (int)(OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+index) ); stateset->addUniform( uniform ); stateset->setTextureAttribute( OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+index, instanceTarget.get() ); } void addDrawProgram( const std::string& uniformBlockName, osg::StateSet* stateset ) { drawProgram->addBindUniformBlock(uniformBlockName, 1); stateset->setAttributeAndModes( drawProgram.get(), osg::StateAttribute::ON ); } osg::ref_ptr< osg::BufferTemplate< std::vector > > indirectCommands; osg::ref_ptr indirectCommandTextureBuffer; osg::ref_ptr< AggregateGeometryVisitor > geometryAggregator; osg::ref_ptr drawProgram; osg::ref_ptr< osg::TextureBuffer > instanceTarget; unsigned int maxTargetQuantity; }; // This is the main structure holding all information about particular 2-phase instance rendering // ( instance types, indirect targets, etc ). struct GPUCullData { GPUCullData() { useMultiDrawArraysIndirect = false; instanceTypes = new osg::BufferTemplate< std::vector >; // build Uniform BufferObject with instanceTypes data instanceTypesUBO = new osg::UniformBufferObject; // instanceTypesUBO->setUsage( GL_STREAM_DRAW ); instanceTypes->setBufferObject( instanceTypesUBO.get() ); instanceTypesUBB = new osg::UniformBufferBinding(1, instanceTypesUBO.get(), 0, 0); } void setUseMultiDrawArraysIndirect( bool value ) { useMultiDrawArraysIndirect = value; } void registerIndirectTarget( unsigned int index, AggregateGeometryVisitor* agv, osg::Program* targetDrawProgram ) { if(index>=OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER || agv==NULL || targetDrawProgram==NULL ) return; targets[index] = IndirectTarget( agv, targetDrawProgram ); } bool registerType(unsigned int typeID, unsigned int targetID, osg::Node* node, const osg::Vec4& lodDistances, float maxDensityPerSquareKilometer ) { if( typeID >= instanceTypes->getData().size() ) instanceTypes->getData().resize(typeID+1); InstanceType& itd = instanceTypes->getData().at(typeID); unsigned int lodNumber = (unsigned int)itd.params.x(); if( lodNumber >= OSGGPUCULL_MAXIMUM_LOD_NUMBER ) return false; std::map::iterator target = targets.find(targetID); if( target==targets.end() ) return false; // AggregateGeometryVisitor creates single osg::Geometry from all objects used by specific indirect target AggregateGeometryVisitor::AddObjectResult aoResult = target->second.geometryAggregator->addObject( node , typeID, lodNumber ); // Information about first vertex and a number of vertices is stored for later primitiveset creation target->second.indirectCommands->getData().push_back( DrawArraysIndirectCommand( aoResult.first, aoResult.count ) ); osg::ComputeBoundsVisitor cbv; node->accept(cbv); // Indirect target texture buffers have finite size, therefore each instance LOD has maximum number that may be rendered in one frame. // This maximum number of rendered instances is estimated from the area that LOD covers and maximum density of instances per square kilometer. float lodArea = osg::PI * ( lodDistances.w() * lodDistances.w() - lodDistances.x() * lodDistances.x() ) / 1000000.0f; // calculate max quantity of objects in lodArea using maximum density per square kilometer unsigned int maxQuantity = (unsigned int) ceil( lodArea * maxDensityPerSquareKilometer ); itd.setLodDefinition( lodNumber, targetID, aoResult.index, lodDistances, target->second.maxTargetQuantity, maxQuantity, cbv.getBoundingBox() ) ; target->second.maxTargetQuantity += maxQuantity; return true; } // endRegister() method is called after all indirect targets and instance types are registered. // It creates indirect targets with pixel format and data type provided by user ( indirect targets may hold // different information about single instance depending on user's needs ( in our example : static rendering // sends all vertex data to indirect target during GPU cull phase, while dynamic rendering sends only a "pointer" // to texture buffer containing instance data ( look at endRegister() invocations in createStaticRendering() and // createDynamicRendering() ) void endRegister( unsigned int rowsPerInstance, GLenum pixelFormat, GLenum type, GLint internalFormat ) { OSG_INFO<<"instance types"<getData().size(); ++i) { InstanceType& iType = instanceTypes->getData().at(i); int sum = 0; OSG_INFO<<"Type "<" << iType.lods[j].indirectTargetParams.w() << " "; sum += iType.lods[j].indirectTargetParams.w(); } OSG_INFO<< ") => " << sum << " elements"<::iterator it,eit; for(it=targets.begin(), eit=targets.end(); it!=eit; ++it) { for(unsigned j=0; jsecond.indirectCommands->getData().size(); ++j) { DrawArraysIndirectCommand& iComm = it->second.indirectCommands->getData().at(j); OSG_INFO<<"("<second.maxTargetQuantity * sizeof(osg::Vec4); OSG_INFO<<" => Maximum elements in target : "<< it->second.maxTargetQuantity <<" ( "<< sizeInBytes <<" bytes, " << sizeInBytes/1024<< " kB )" << std::endl; } instanceTypesUBB->setSize( instanceTypes->getTotalDataSize() ); for(it=targets.begin(), eit=targets.end(); it!=eit; ++it) it->second.endRegister(it->first,rowsPerInstance,pixelFormat,type,internalFormat,useMultiDrawArraysIndirect); } bool useMultiDrawArraysIndirect; osg::ref_ptr< osg::BufferTemplate< std::vector > > instanceTypes; osg::ref_ptr instanceTypesUBO; osg::ref_ptr instanceTypesUBB; std::map targets; }; // StaticInstance struct represents data of a single static instance : // its position, type, identification and additional params // ( params are user dependent. In our example params are used // to describe color, waving amplitude, waving frequency and waving offset ) struct StaticInstance { StaticInstance( unsigned int typeID, unsigned int id, const osg::Matrixf& m, const osg::Vec4& params ) : position(m), extraParams(params), idParams(typeID,id,0,0) { } osg::Vec3d getPosition() const { return position.getTrans(); } osg::Matrixf position; osg::Vec4f extraParams; osg::Vec4i idParams; }; // DynamicInstance ( compared to StaticInstance ) holds additional data, like "bones" used to // animate wheels and propellers in the example const unsigned int OSGGPUCULL_MAXIMUM_BONES_NUMBER = 8; struct DynamicInstance { DynamicInstance( unsigned int typeID, unsigned int id, const osg::Matrixf& m, const osg::Vec4& params ) : position(m), extraParams(params), idParams(typeID,id,0,0) { for(unsigned int i=0; i class InstanceCell : public osg::Referenced { public: typedef std::vector< osg::ref_ptr > > InstanceCellList; InstanceCell(): _parent(0) {} InstanceCell(osg::BoundingBox& bb):_parent(0), _bb(bb) {} void addCell(InstanceCell* cell) { cell->_parent=this; _cells.push_back(cell); } void computeBound(); bool contains(const osg::Vec3& position) const { return _bb.contains(position); } bool divide(unsigned int maxNumInstancesPerCell=100); bool divide(bool xAxis, bool yAxis, bool zAxis); void bin(); InstanceCell* _parent; osg::BoundingBox _bb; InstanceCellList _cells; std::vector _instances; }; template void InstanceCell::computeBound() { _bb.init(); for(typename InstanceCellList::iterator citr=_cells.begin(); citr!=_cells.end(); ++citr) { (*citr)->computeBound(); _bb.expandBy((*citr)->_bb); } for(typename std::vector::iterator titr=_instances.begin(); titr!=_instances.end(); ++titr) { _bb.expandBy( titr->getPosition() ); } } template bool InstanceCell::divide(unsigned int maxNumInstancesPerCell) { if (_instances.size()<=maxNumInstancesPerCell) return false; computeBound(); float radius = _bb.radius(); float divide_distance = radius*0.7f; if (divide((_bb.xMax()-_bb.xMin())>divide_distance,(_bb.yMax()-_bb.yMin())>divide_distance,(_bb.zMax()-_bb.zMin())>divide_distance)) { // recusively divide the new cells till maxNumInstancesPerCell is met. for(typename InstanceCellList::iterator citr=_cells.begin(); citr!=_cells.end(); ++citr) { (*citr)->divide(maxNumInstancesPerCell); } return true; } else { return false; } } template bool InstanceCell::divide(bool xAxis, bool yAxis, bool zAxis) { if (!(xAxis || yAxis || zAxis)) return false; if (_cells.empty()) _cells.push_back(new InstanceCell(_bb)); if (xAxis) { unsigned int numCellsToDivide=_cells.size(); for(unsigned int i=0;i_bb); float xCenter = (orig_cell->_bb.xMin()+orig_cell->_bb.xMax())*0.5f; orig_cell->_bb.xMax() = xCenter; new_cell->_bb.xMin() = xCenter; _cells.push_back(new_cell); } } if (yAxis) { unsigned int numCellsToDivide=_cells.size(); for(unsigned int i=0;i_bb); float yCenter = (orig_cell->_bb.yMin()+orig_cell->_bb.yMax())*0.5f; orig_cell->_bb.yMax() = yCenter; new_cell->_bb.yMin() = yCenter; _cells.push_back(new_cell); } } if (zAxis) { unsigned int numCellsToDivide=_cells.size(); for(unsigned int i=0;i_bb); float zCenter = (orig_cell->_bb.zMin()+orig_cell->_bb.zMax())*0.5f; orig_cell->_bb.zMax() = zCenter; new_cell->_bb.zMin() = zCenter; _cells.push_back(new_cell); } } bin(); return true; } template void InstanceCell::bin() { // put treeste cells. std::vector instancesNotAssigned; for(typename std::vector::iterator titr=_instances.begin(); titr!=_instances.end(); ++titr) { osg::Vec3 iPosition = titr->getPosition(); bool assigned = false; for(typename InstanceCellList::iterator citr=_cells.begin(); citr!=_cells.end() && !assigned; ++citr) { if ((*citr)->contains(iPosition)) { (*citr)->_instances.push_back(*titr); assigned = true; } } if (!assigned) instancesNotAssigned.push_back(*titr); } // put the unassigned trees back into the original local tree list. _instances.swap(instancesNotAssigned); // prune empty cells. InstanceCellList cellsNotEmpty; for(typename InstanceCellList::iterator citr=_cells.begin(); citr!=_cells.end(); ++citr) { if (!((*citr)->_instances.empty())) { cellsNotEmpty.push_back(*citr); } } _cells.swap(cellsNotEmpty); } // Every geometry in the static instance tree stores matrix and additional parameters on the vertex attributtes number 10-15. osg::Geometry* buildGPUCullGeometry( const std::vector& instances ) { osg::Vec3Array* vertexArray = new osg::Vec3Array; osg::Vec4Array* attrib10 = new osg::Vec4Array; osg::Vec4Array* attrib11 = new osg::Vec4Array; osg::Vec4Array* attrib12 = new osg::Vec4Array; osg::Vec4Array* attrib13 = new osg::Vec4Array; osg::Vec4Array* attrib14 = new osg::Vec4Array; osg::Vec4Array* attrib15 = new osg::Vec4Array; osg::BoundingBox bbox; std::vector::const_iterator it,eit; for(it=instances.begin(), eit=instances.end(); it!=eit; ++it) { vertexArray->push_back( it->getPosition() ); attrib10->push_back( osg::Vec4( it->position(0,0), it->position(0,1), it->position(0,2), it->position(0,3) ) ); attrib11->push_back( osg::Vec4( it->position(1,0), it->position(1,1), it->position(1,2), it->position(1,3) ) ); attrib12->push_back( osg::Vec4( it->position(2,0), it->position(2,1), it->position(2,2), it->position(2,3) ) ); attrib13->push_back( osg::Vec4( it->position(3,0), it->position(3,1), it->position(3,2), it->position(3,3) ) ); attrib14->push_back( it->extraParams ); attrib15->push_back( osg::Vec4( it->idParams.x(), it->idParams.y(), 0.0, 0.0 ) ); bbox.expandBy( it->getPosition() ); } osg::ref_ptr geom = new osg::Geometry; geom->setVertexArray(vertexArray); geom->setVertexAttribArray(10, attrib10, osg::Array::BIND_PER_VERTEX); geom->setVertexAttribArray(11, attrib11, osg::Array::BIND_PER_VERTEX); geom->setVertexAttribArray(12, attrib12, osg::Array::BIND_PER_VERTEX); geom->setVertexAttribArray(13, attrib13, osg::Array::BIND_PER_VERTEX); geom->setVertexAttribArray(14, attrib14, osg::Array::BIND_PER_VERTEX); geom->setVertexAttribArray(15, attrib15, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, instances.size() ) ); geom->setInitialBound( bbox ); geom->setUseDisplayList(false); geom->setUseVertexBufferObjects(true); return geom.release(); } template osg::Node* createInstanceGraph(InstanceCell* cell, const osg::BoundingBox& objectsBBox ) { bool needGroup = !(cell->_cells.empty()); bool needInstances = !(cell->_instances.empty()); osg::Geode* geode = 0; osg::Group* group = 0; if (needInstances) { osg::Geometry* geometry = buildGPUCullGeometry(cell->_instances); // buildGPUCullGeometry() function creates initial bound using points // it must be enlarged by bounding boxes of all instance types osg::BoundingBox bbox = geometry->getInitialBound(); bbox.xMin() += objectsBBox.xMin(); bbox.xMax() += objectsBBox.xMax(); bbox.yMin() += objectsBBox.yMin(); bbox.yMax() += objectsBBox.yMax(); bbox.zMin() += objectsBBox.zMin(); bbox.zMax() += objectsBBox.zMax(); geometry->setInitialBound(bbox); geode = new osg::Geode; geometry->setUseDisplayList(false); geometry->setUseVertexBufferObjects(true); geode->addDrawable( geometry ); } if (needGroup) { group = new osg::Group; for(typename InstanceCell::InstanceCellList::iterator itr=cell->_cells.begin(); itr!=cell->_cells.end(); ++itr) { group->addChild(createInstanceGraph(itr->get(),objectsBBox)); } if (geode) group->addChild(geode); } if (group) return group; else return geode; } template osg::Node* createInstanceTree(const std::vector& instances, const osg::BoundingBox& objectsBBox, unsigned int maxNumInstancesPerCell ) { osg::ref_ptr > rootCell = new InstanceCell(); rootCell->_instances = instances; rootCell->divide( maxNumInstancesPerCell ); osg::ref_ptr resultNode = createInstanceGraph( rootCell.get(), objectsBBox ); return resultNode.release(); } // Texture buffers holding information about the number of instances to render ( named "indirect command // texture buffers", or simply - indirect commands ) must reset instance number to 0 in the beginning of each frame. // It is done by simple texture reload from osg::Image. // Moreover - texture buffers that use texture images ( i mean "images" as defined in ARB_shader_image_load_store extension ) // should call glBindImageTexture() before every shader that uses imageLoad(), imageStore() and imageAtomic*() GLSL functions. // It looks like glBindImageTexture() should be used the same way the glBindTexture() is used. struct ResetTexturesCallback : public osg::StateSet::Callback { ResetTexturesCallback() { } void addTextureDirty( unsigned int texUnit ) { texUnitsDirty.push_back(texUnit); } void addTextureDirtyParams( unsigned int texUnit ) { texUnitsDirtyParams.push_back(texUnit); } virtual void operator() (osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { std::vector::iterator it,eit; for(it=texUnitsDirty.begin(), eit=texUnitsDirty.end(); it!=eit; ++it) { osg::Texture* tex = dynamic_cast( stateset->getTextureAttribute(*it,osg::StateAttribute::TEXTURE) ); if(tex==NULL) continue; osg::Image* img = tex->getImage(0); if(img!=NULL) img->dirty(); } for(it=texUnitsDirtyParams.begin(), eit=texUnitsDirtyParams.end(); it!=eit; ++it) { osg::Texture* tex = dynamic_cast( stateset->getTextureAttribute(*it,osg::StateAttribute::TEXTURE) ); if(tex!=NULL) tex->dirtyTextureParameters(); } } std::vector texUnitsDirty; std::vector texUnitsDirtyParams; }; // We must ensure that cull shader finished filling indirect commands and indirect targets, before draw shader // starts using them. We use glMemoryBarrier() barrier to achieve that. // It is also possible that we should use glMemoryBarrier() after resetting textures, but i implemented that only for // dynamic rendering. struct InvokeMemoryBarrier : public osg::Drawable::DrawCallback { InvokeMemoryBarrier( GLbitfield barriers ) : _barriers(barriers) { } virtual void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const { //DrawIndirectGLExtensions *ext = DrawIndirectGLExtensions::getExtensions( renderInfo.getContextID(), true ); renderInfo.getState()->get()->glMemoryBarrier( _barriers ); drawable->drawImplementation(renderInfo); } GLbitfield _barriers; }; osg::Program* createProgram( const std::string& name, const std::string& vertexSource, const std::string& fragmentSource ) { osg::ref_ptr program = new osg::Program; program->setName( name ); osg::ref_ptr vertexShader = new osg::Shader(osg::Shader::VERTEX, vertexSource); vertexShader->setName( name + "_vertex" ); program->addShader(vertexShader.get()); osg::ref_ptr fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, fragmentSource); fragmentShader->setName( name + "_fragment" ); program->addShader(fragmentShader.get()); return program.release(); } float random(float min,float max) { return min + (max-min)*(float)rand()/(float)RAND_MAX; } osg::Group* createConiferTree( float detailRatio, const osg::Vec4& leafColor, const osg::Vec4& trunkColor ) { osg::ref_ptr tessHints = new osg::TessellationHints; tessHints->setCreateTextureCoords(true); tessHints->setDetailRatio(detailRatio); osg::ref_ptr root = new osg::Group; osg::ref_ptr trunk = new osg::Cylinder( osg::Vec3( 0.0, 0.0, 1.0 ), 0.25, 2.0 ); osg::ref_ptr trunkGeode = convertShapeToGeode( *trunk.get(), tessHints.get(), trunkColor ); root->addChild( trunkGeode.get() ); osg::ref_ptr shapeCone = new osg::Cone( osg::Vec3( 0.0, 0.0, 4.0 ), 2.0, 8.0 ); osg::ref_ptr shapeGeode = convertShapeToGeode( *shapeCone.get(), tessHints.get(), leafColor ); root->addChild( shapeGeode.get() ); return root.release(); } // Few functions that create geometries of objects used in example. // Vertex size in all objects is controlled using single float parameter ( detailRatio ) // Thanks to this ( and "--triangle-modifier" application parameter ) we may experiment with // triangle quantity of the scene and how it affects the time statistics osg::Group* createDecidousTree( float detailRatio, const osg::Vec4& leafColor, const osg::Vec4& trunkColor ) { osg::ref_ptr tessHints = new osg::TessellationHints; tessHints->setCreateTextureCoords(true); tessHints->setDetailRatio(detailRatio); osg::ref_ptr root = new osg::Group; osg::ref_ptr trunk = new osg::Cylinder( osg::Vec3( 0.0, 0.0, 1.0 ), 0.4, 2.0 ); osg::ref_ptr trunkGeode = convertShapeToGeode( *trunk.get(), tessHints.get(), trunkColor ); root->addChild( trunkGeode.get() ); osg::ref_ptr shapeCapsule = new osg::Capsule( osg::Vec3( 0.0, 0.0, 7.4 ), 3.0, 5.0 ); osg::ref_ptr shapeGeode = convertShapeToGeode( *shapeCapsule.get(), tessHints.get(), leafColor ); root->addChild( shapeGeode.get() ); return root.release(); } osg::Group* createSimpleHouse( float detailRatio, const osg::Vec4& buildingColor, const osg::Vec4& chimneyColor ) { osg::ref_ptr tessHints = new osg::TessellationHints; tessHints->setCreateTextureCoords(true); tessHints->setDetailRatio(detailRatio); osg::ref_ptr root = new osg::Group; osg::ref_ptr building = new osg::Box( osg::Vec3( 0.0, 0.0, 8.0 ), 15.0, 9.0, 16.0 ); osg::ref_ptr buildingGeode = convertShapeToGeode( *building.get(), tessHints.get(), buildingColor ); root->addChild( buildingGeode.get() ); // osg::Box always creates geometry with 12 triangles, so to differentiate building LODs we will add three "chimneys" { osg::ref_ptr chimney = new osg::Cylinder( osg::Vec3( -6.0, 3.0, 16.75 ), 0.1, 1.5 ); osg::ref_ptr chimneyGeode = convertShapeToGeode( *chimney.get(), tessHints.get(), chimneyColor ); root->addChild( chimneyGeode.get() ); } { osg::ref_ptr chimney = new osg::Cylinder( osg::Vec3( -5.5, 3.0, 16.5 ), 0.1, 1.0 ); osg::ref_ptr chimneyGeode = convertShapeToGeode( *chimney.get(), tessHints.get(), chimneyColor ); root->addChild( chimneyGeode.get() ); } { osg::ref_ptr chimney = new osg::Cylinder( osg::Vec3( -5.0, 3.0, 16.25 ), 0.1, 0.5 ); osg::ref_ptr chimneyGeode = convertShapeToGeode( *chimney.get(), tessHints.get(), chimneyColor ); root->addChild( chimneyGeode.get() ); } return root.release(); } osg::MatrixTransform* createPropeller( float detailRatio, int propNum, float propRadius, const osg::Vec4& color ) { osg::ref_ptr tessHints = new osg::TessellationHints; tessHints->setCreateTextureCoords(true); tessHints->setDetailRatio(detailRatio); osg::ref_ptr root = new osg::MatrixTransform; osg::ref_ptr center = new osg::Cone( osg::Vec3( 0.0, 0.0, 0.05 ), 0.1*propRadius, 0.25*propRadius ); osg::ref_ptr centerGeode = convertShapeToGeode( *center.get(), tessHints.get(), color ); osg::ref_ptr prop = new osg::Cone( osg::Vec3( 0.0, 0.0, -0.75*propRadius ), 0.1*propRadius, 1.0*propRadius ); osg::ref_ptr propGeode = convertShapeToGeode( *prop.get(), tessHints.get(), color ); root->addChild( centerGeode.get() ); for( int i=0; i propMt = new osg::MatrixTransform( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0) ) * osg::Matrix::scale(1.0,1.0,0.3) * osg::Matrix::rotate( angle, osg::Vec3(0.0,0.0,1.0) ) ); propMt->addChild( propGeode.get() ); root->addChild( propMt.get() ); } return root.release(); } osg::Group* createBlimp( float detailRatio, const osg::Vec4& hullColor, const osg::Vec4& propColor ) { osg::ref_ptr tessHints = new osg::TessellationHints; tessHints->setCreateTextureCoords(true); tessHints->setDetailRatio(detailRatio); osg::ref_ptr root = new osg::Group; osg::ref_ptr hull = new osg::Capsule( osg::Vec3( 0.0, 0.0, 0.0 ), 5.0, 10.0 ); osg::ref_ptr hullGeode = convertShapeToGeode( *hull.get(), tessHints.get(), hullColor ); osg::ref_ptr gondola = new osg::Capsule( osg::Vec3( 5.5, 0.0, 0.0 ), 1.0, 6.0 ); osg::ref_ptr gondolaGeode = convertShapeToGeode( *gondola.get(), tessHints.get(), hullColor ); osg::ref_ptr rudderV = new osg::Box( osg::Vec3( 0.0, 0.0, -10.0 ), 8.0, 0.3, 4.0 ); osg::ref_ptr rudderVGeode = convertShapeToGeode( *rudderV.get(), tessHints.get(), hullColor ); osg::ref_ptr rudderH = new osg::Box( osg::Vec3( 0.0, 0.0, -10.0 ), 0.3, 8.0, 4.0 ); osg::ref_ptr rudderHGeode = convertShapeToGeode( *rudderH.get(), tessHints.get(), hullColor ); osg::Matrix m; m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)); osg::ref_ptr hullMt = new osg::MatrixTransform(m); hullMt->addChild( hullGeode.get() ); hullMt->addChild( gondolaGeode.get() ); hullMt->addChild( rudderVGeode.get() ); hullMt->addChild( rudderHGeode.get() ); root->addChild( hullMt.get() ); osg::ref_ptr propellerLeft = createPropeller( detailRatio, 4, 1.0, propColor ); propellerLeft->setMatrix( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,2.0,-6.0) ); propellerLeft->setName("prop0"); root->addChild( propellerLeft.get() ); osg::ref_ptr propellerRight = createPropeller( detailRatio, 4, 1.0, propColor ); propellerRight->setMatrix( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,-2.0,-6.0) ); propellerRight->setName("prop1"); root->addChild( propellerRight.get() ); return root.release(); } osg::Group* createCar( float detailRatio, const osg::Vec4& hullColor, const osg::Vec4& wheelColor ) { osg::ref_ptr tessHints = new osg::TessellationHints; tessHints->setCreateTextureCoords(true); tessHints->setDetailRatio(detailRatio); osg::ref_ptr root = new osg::Group; osg::ref_ptr wheel = new osg::Cylinder( osg::Vec3( 0.0, 0.0, 0.0 ), 1.0, 0.6 ); osg::ref_ptr wheelGeom = osg::convertShapeToGeometry( *wheel.get(), tessHints.get(), wheelColor, osg::Array::BIND_PER_VERTEX ); // one random triangle on every wheel will use black color to show that wheel is rotating osg::Vec4Array* colorArray = dynamic_cast( wheelGeom->getColorArray() ); if(colorArray!=NULL) { unsigned int triCount = colorArray->size() / 3; unsigned int randomTriangle = random(0,triCount); for(unsigned int i=0;i<3;++i) (*colorArray)[3*randomTriangle+i] = osg::Vec4(0.0,0.0,0.0,1.0); } osg::ref_ptr wheelGeode = new osg::Geode; wheelGeode->addDrawable( wheelGeom.get() ); osg::ref_ptr hull = new osg::Box( osg::Vec3( 0.0, 0.0, 1.3 ), 5.0, 3.0, 1.4 ); osg::ref_ptr hullGeode = convertShapeToGeode( *hull.get(), tessHints.get(), hullColor ); root->addChild( hullGeode.get() ); osg::Matrix m; m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,1.8,1.0); osg::ref_ptr wheel0Mt = new osg::MatrixTransform(m); wheel0Mt->setName("wheel0"); wheel0Mt->addChild( wheelGeode.get() ); root->addChild( wheel0Mt.get() ); m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,1.8,1.0); osg::ref_ptr wheel1Mt = new osg::MatrixTransform(m); wheel1Mt->setName("wheel1"); wheel1Mt->addChild( wheelGeode.get() ); root->addChild( wheel1Mt.get() ); m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,-1.8,1.0); osg::ref_ptr wheel2Mt = new osg::MatrixTransform(m); wheel2Mt->setName("wheel2"); wheel2Mt->addChild( wheelGeode.get() ); root->addChild( wheel2Mt.get() ); m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,-1.8,1.0); osg::ref_ptr wheel3Mt = new osg::MatrixTransform(m); wheel3Mt->setName("wheel3"); wheel3Mt->addChild( wheelGeode.get() ); root->addChild( wheel3Mt.get() ); return root.release(); } osg::Group* createAirplane( float detailRatio, const osg::Vec4& hullColor, const osg::Vec4& propColor ) { osg::ref_ptr tessHints = new osg::TessellationHints; tessHints->setCreateTextureCoords(true); tessHints->setDetailRatio(detailRatio); osg::ref_ptr root = new osg::Group; osg::ref_ptr hull = new osg::Capsule( osg::Vec3( 0.0, 0.0, 0.0 ), 0.8, 6.0 ); osg::ref_ptr hullGeode = convertShapeToGeode( *hull.get(), tessHints.get(), hullColor ); osg::ref_ptr wing0 = new osg::Box( osg::Vec3( 0.4, 0.0, 1.3 ), 0.1, 7.0, 1.6 ); osg::ref_ptr wing0Geode = convertShapeToGeode( *wing0.get(), tessHints.get(), hullColor ); osg::ref_ptr wing1 = new osg::Box( osg::Vec3( -1.4, 0.0, 1.5 ), 0.1, 10.0, 1.8 ); osg::ref_ptr wing1Geode = convertShapeToGeode( *wing1.get(), tessHints.get(), hullColor ); osg::ref_ptr rudderV = new osg::Box( osg::Vec3( -0.8, 0.0, -3.9 ), 1.5, 0.05, 1.0 ); osg::ref_ptr rudderVGeode = convertShapeToGeode( *rudderV.get(), tessHints.get(), hullColor ); osg::ref_ptr rudderH = new osg::Box( osg::Vec3( -0.2, 0.0, -3.9 ), 0.05, 4.0, 1.0 ); osg::ref_ptr rudderHGeode = convertShapeToGeode( *rudderH.get(), tessHints.get(), hullColor ); osg::Matrix m; m = osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)); osg::ref_ptr hullMt = new osg::MatrixTransform(m); hullMt->addChild( hullGeode.get() ); hullMt->addChild( wing0Geode.get() ); hullMt->addChild( wing1Geode.get() ); hullMt->addChild( rudderVGeode.get() ); hullMt->addChild( rudderHGeode.get() ); root->addChild( hullMt.get() ); osg::ref_ptr propeller = createPropeller( detailRatio, 3, 1.6, propColor ); propeller->setMatrix( osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(3.8,0.0,0.0) ); propeller->setName("prop0"); root->addChild( propeller.get() ); return root.release(); } // createStaticRendering() shows how to use any OSG graph ( wheter it is single osg::Geode, or sophisticated osg::PagedLOD tree covering whole earth ) // as a source of instance data. This way, the OSG graph of arbitrary size is at first culled using typical OSG mechanisms, then remaining osg::Geometries // are sent to cull shader ( cullProgram ). Cull shader does not draw anything to screen ( thanks to GL_RASTERIZER_DISCARD mode ), but calculates if particular // instances - sourced from above mentioned osg::Geometries - are visible and what LODs for these instances should be rendered. // Information about instances is stored into indirect commands ( the number of instances sent to indirect target ) and in indirect targets // ( static rendering sends all instance data from vertex attributes to indirect target : position, id, extra params, etc ). // Next the draw shader ( associated with indirect target ) plays its role. The main trick at draw shader invocation is a right use of indirect command. // Indirect command is bound as a GL_DRAW_INDIRECT_BUFFER and glDrawArraysIndirect() function is called. Thanks to this - proper number // of instances is rendered without time-consuming GPU->CPU roundtrip. Draw shader renders an aggregate geometry - an osg::Geometry object // that contains all objects to render ( for specific indirect target ). void createStaticRendering( osg::Group* root, GPUCullData& gpuData, const osg::Vec2& minArea, const osg::Vec2& maxArea, unsigned int maxNumInstancesPerCell, float lodModifier, float densityModifier, float triangleModifier, bool exportInstanceObjects ) { // Creating objects using ShapeToGeometry - its vertex count my vary according to triangleModifier variable. // Every object may be stored to file if you want to inspect it ( to show how many vertices it has for example ). // To do it - use "--export-objects" parameter in commandline. // Every object LOD has different color to show the place where LODs switch. osg::ref_ptr coniferTree0 = createConiferTree( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*coniferTree0.get(),"coniferTree0.osgt"); osg::ref_ptr coniferTree1 = createConiferTree( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*coniferTree1.get(),"coniferTree1.osgt"); osg::ref_ptr coniferTree2 = createConiferTree( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*coniferTree2.get(),"coniferTree2.osgt"); osg::ref_ptr decidousTree0 = createDecidousTree( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*decidousTree0.get(),"decidousTree0.osgt"); osg::ref_ptr decidousTree1 = createDecidousTree( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*decidousTree1.get(),"decidousTree1.osgt"); osg::ref_ptr decidousTree2 = createDecidousTree( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*decidousTree2.get(),"decidousTree2.osgt"); osg::ref_ptr simpleHouse0 = createSimpleHouse( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*simpleHouse0.get(),"simpleHouse0.osgt"); osg::ref_ptr simpleHouse1 = createSimpleHouse( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*simpleHouse1.get(),"simpleHouse1.osgt"); osg::ref_ptr simpleHouse2 = createSimpleHouse( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*simpleHouse2.get(),"simpleHouse2.osgt"); // Two indirect targets are registered : the first one renders static objects. The second one is used to render trees waving in the wind. gpuData.registerIndirectTarget( 0, new AggregateGeometryVisitor( new ConvertTrianglesOperatorClassic() ), createProgram( "static_draw_0", SHADER_STATIC_DRAW_0_VERTEX, SHADER_STATIC_DRAW_0_FRAGMENT ) ); gpuData.registerIndirectTarget( 1, new AggregateGeometryVisitor( new ConvertTrianglesOperatorClassic() ), createProgram( "static_draw_1", SHADER_STATIC_DRAW_1_VERTEX, SHADER_STATIC_DRAW_1_FRAGMENT ) ); float objectDensity[3]; objectDensity[0] = 10000.0f * densityModifier; objectDensity[1] = 1000.0f * densityModifier; objectDensity[2] = 100.0f * densityModifier; // Three types of instances are registered - each one with three LODs, but first two LODs for trees will be sent to second indirect target gpuData.registerType( 0, 1, coniferTree0.get(), osg::Vec4(0.0f,0.0f,100.0f,110.0f ) * lodModifier, objectDensity[0] ); gpuData.registerType( 0, 1, coniferTree1.get(), osg::Vec4(100.0f,110.0f,500.0f,510.0f) * lodModifier, objectDensity[0] ); gpuData.registerType( 0, 0, coniferTree2.get(), osg::Vec4(500.0f,510.0f,1200.0f,1210.0f) * lodModifier, objectDensity[0] ); gpuData.registerType( 1, 1, decidousTree0.get(), osg::Vec4(0.0f,0.0f,120.0f,130.0f ) * lodModifier, objectDensity[1] ); gpuData.registerType( 1, 1, decidousTree1.get(), osg::Vec4(120.0f,130.0f,600.0f,610.0f) * lodModifier, objectDensity[1] ); gpuData.registerType( 1, 0, decidousTree2.get(), osg::Vec4(600.0f,610.0f,1400.0f,1410.0f) * lodModifier, objectDensity[1] ); gpuData.registerType( 2, 0, simpleHouse0.get(), osg::Vec4(0.0f,0.0f,140.0f,150.0f ) * lodModifier, objectDensity[2] ); gpuData.registerType( 2, 0, simpleHouse1.get(), osg::Vec4(140.0f,150.0f,700.0f,710.0f) * lodModifier, objectDensity[2] ); gpuData.registerType( 2, 0, simpleHouse2.get(), osg::Vec4(700.0f,710.0f,2000.0f,2010.0f) * lodModifier, objectDensity[2] ); // every target will store 6 rows of data in GL_RGBA32F_ARB format ( the whole StaticInstance structure ) gpuData.endRegister(6,GL_RGBA,GL_FLOAT,GL_RGBA32F_ARB); // Now we are going to build the tree of instances. Each instance type will be recognized by its TypeID // All instances will be placed in area (minArea x maxArea) with densities registered in GPUCullData std::vector instances; osg::BoundingBox bbox( minArea.x(), minArea.y(), 0.0, maxArea.x(), maxArea.y(), 0.0 ); float fullArea = ( bbox.xMax() - bbox.xMin() ) * ( bbox.yMax() - bbox.yMin() ); unsigned int currentID = 0; // First - all instances are stored in std::vector for( unsigned int i=0; i<3; ++i) { // using LOD area and maximum density - the maximum instance quantity is calculated int objectQuantity = (int) floor ( objectDensity[i] * fullArea / 1000000.0f ); for(int j=0; j::iterator iit,ieit; for(iit=gpuData.instanceTypes->getData().begin(), ieit=gpuData.instanceTypes->getData().end(); iit!=ieit; ++iit) allObjectsBbox.expandBy(iit->getBoundingBox()); osg::ref_ptr instancesTree = createInstanceTree(instances, allObjectsBbox, maxNumInstancesPerCell); if( exportInstanceObjects ) osgDB::writeNodeFile(*instancesTree.get(),"staticInstancesTree.osgt"); root->addChild( instancesTree.get() ); // instance OSG tree is connected to cull shader with all necessary data ( all indirect commands, all // indirect targets, necessary OpenGl modes etc. ) { osg::ref_ptr resetTexturesCallback = new ResetTexturesCallback; osg::ref_ptr cullProgram = createProgram( "static_cull", SHADER_STATIC_CULL_VERTEX, SHADER_STATIC_CULL_FRAGMENT ); cullProgram->addBindUniformBlock("instanceTypesData", 1); instancesTree->getOrCreateStateSet()->setAttributeAndModes( cullProgram.get(), osg::StateAttribute::ON ); instancesTree->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); instancesTree->getOrCreateStateSet()->setMode( GL_RASTERIZER_DISCARD, osg::StateAttribute::ON ); std::map::iterator it,eit; for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) { it->second.addIndirectCommandData( "indirectCommand", it->first, instancesTree->getOrCreateStateSet() ); resetTexturesCallback->addTextureDirty( it->first ); resetTexturesCallback->addTextureDirtyParams( it->first ); it->second.addIndirectTargetData( true, "indirectTarget", it->first, instancesTree->getOrCreateStateSet() ); resetTexturesCallback->addTextureDirtyParams( OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+it->first ); } osg::Uniform* indirectCommandSize = new osg::Uniform( osg::Uniform::INT, "indirectCommandSize" ); indirectCommandSize->set( (int)(sizeof(DrawArraysIndirectCommand) / sizeof(unsigned int)) ); instancesTree->getOrCreateStateSet()->addUniform( indirectCommandSize ); instancesTree->getOrCreateStateSet()->setUpdateCallback( resetTexturesCallback.get() ); } // in the end - we create OSG objects that draw instances using indirect targets and commands. std::map::iterator it,eit; for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) { osg::ref_ptr drawGeode = new osg::Geode; it->second.geometryAggregator->getAggregatedGeometry()->setDrawCallback( new InvokeMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT) ); drawGeode->addDrawable( it->second.geometryAggregator->getAggregatedGeometry() ); drawGeode->setCullingActive(false); it->second.addIndirectTargetData( false, "indirectTarget", it->first, drawGeode->getOrCreateStateSet() ); drawGeode->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); it->second.addDrawProgram( "instanceTypesData", drawGeode->getOrCreateStateSet() ); drawGeode->getOrCreateStateSet()->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ) ); root->addChild(drawGeode.get()); } } // Dynamic instances are stored in a single osg::Geometry. This geometry holds only a "pointer" // to texture buffer that contains all info about particular instance. When we animate instances // we only change information in this texture buffer osg::Geometry* buildGPUCullGeometry( const std::vector& instances ) { osg::Vec3Array* vertexArray = new osg::Vec3Array; osg::Vec4iArray* attrib10 = new osg::Vec4iArray; osg::BoundingBox bbox; std::vector::const_iterator it,eit; for(it=instances.begin(), eit=instances.end(); it!=eit; ++it) { vertexArray->push_back( it->getPosition() ); attrib10->push_back( it->idParams ); bbox.expandBy( it->getPosition() ); } osg::ref_ptr geom = new osg::Geometry; geom->setVertexArray(vertexArray); geom->setVertexAttribArray(10, attrib10, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, instances.size() ) ); geom->setInitialBound( bbox ); return geom.release(); } // To animate dynamic objects we use an update callback. It performs some simple // instance wandering ( object goes to random destination point and when it reaches // destination, it picks another random point and so on ). // Object parts are animated ( wheels and propellers ) struct AnimateObjectsCallback : public osg::DrawableUpdateCallback { AnimateObjectsCallback( osg::BufferTemplate< std::vector >* instances, osg::Image* instancesImage, const osg::BoundingBox& bbox, unsigned int quantityPerType ) : _instances(instances), _instancesImage(instancesImage), _bbox(bbox), _quantityPerType(quantityPerType), _lastTime(0.0) { _destination = new osg::Vec2Array; _destination->reserve(3*_quantityPerType); unsigned int i; for(i=0; i<3*_quantityPerType; ++i) _destination->push_back( osg::Vec2(random( _bbox.xMin(), _bbox.xMax()), random( _bbox.yMin(), _bbox.yMax()) ) ); i=0; for(; i<_quantityPerType; ++i) // speed of blimps _speed.push_back( random( 5.0, 10.0) ); for(; i<2*_quantityPerType; ++i) // speed of cars _speed.push_back( random( 1.0, 5.0) ); for(; i<3*_quantityPerType; ++i) // speed of airplanes _speed.push_back( random( 10.0, 16.0 ) ); } virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable) { if( nv->getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR ) return; const osg::FrameStamp* frameStamp = nv->getFrameStamp(); if(frameStamp==NULL) return; double currentTime = frameStamp->getSimulationTime(); double deltaTime = 0.016; if(_lastTime!=0.0) deltaTime = currentTime - _lastTime; _lastTime = currentTime; osg::Geometry* geom = dynamic_cast(drawable); if(geom==NULL) return; osg::Vec3Array* vertexArray = dynamic_cast( geom->getVertexArray() ); if( vertexArray == NULL ) return; osg::BoundingBox nbbox; unsigned int i=0; for(;i<_quantityPerType;++i) // update blimps { nbbox.expandBy( updateObjectPosition( vertexArray, i, deltaTime ) ); // now we update propeler positions setRotationUsingRotSpeed( i, 5, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,2.0,-6.0), currentTime, 0.5 ); setRotationUsingRotSpeed( i, 6, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(0.0,-2.0,-6.0), currentTime, -0.5 ); } for(;i<2*_quantityPerType;++i) //update cars { nbbox.expandBy( updateObjectPosition( vertexArray, i, deltaTime ) ); // calculate car wheel rotation speed measured in rot/sec double wheelRotSpeed = -1.0 * _speed[i] / ( 2.0*osg::PI ); setRotationUsingRotSpeed( i, 1, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,1.8,1.0), currentTime, wheelRotSpeed ); setRotationUsingRotSpeed( i, 2, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,1.8,1.0), currentTime, wheelRotSpeed ); setRotationUsingRotSpeed( i, 3, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(2.0,-1.8,1.0), currentTime, wheelRotSpeed ); setRotationUsingRotSpeed( i, 4, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(1.0,0.0,0.0)) * osg::Matrix::translate(-2.0,-1.8,1.0), currentTime, wheelRotSpeed ); } for(;i<3*_quantityPerType;++i) // update airplanes { nbbox.expandBy( updateObjectPosition( vertexArray, i, deltaTime ) ); setRotationUsingRotSpeed( i, 5, osg::Matrix::rotate( osg::DegreesToRadians(90.0), osg::Vec3(0.0,1.0,0.0)) * osg::Matrix::translate(3.8,0.0,0.0), currentTime, 1.5 ); } geom->setInitialBound( nbbox ); _instancesImage->dirty(); } osg::Vec3 updateObjectPosition( osg::Vec3Array* vertexArray, unsigned int index, float deltaTime ) { osg::Vec3 oldPosition = _instances->getData().at(index).getPosition(); osg::Vec2 op2(oldPosition.x(), oldPosition.y() ); while( ( (*_destination)[index]- op2).length() < 10.0 ) (*_destination)[index] = osg::Vec2(random( _bbox.xMin(), _bbox.xMax()), random( _bbox.yMin(), _bbox.yMax()) ); osg::Vec2 direction = (*_destination)[index] - op2; direction.normalize(); osg::Vec2 np2 = direction * deltaTime * _speed[index] ; osg::Vec3 newPosition = oldPosition + osg::Vec3( np2.x(), np2.y(), 0.0 ); osg::Quat heading; heading.makeRotate( osg::Vec3(1.0,0.0,0.0), osg::Vec3(direction.x(), direction.y(), 0.0) ); _instances->getData().at(index).position.setTrans( newPosition ); _instances->getData().at(index).position.setRotate( heading ); (*vertexArray)[index] = newPosition; return newPosition; } void setRotationUsingRotSpeed( unsigned int index, unsigned int boneIndex, const osg::Matrix& zeroMatrix, double currentTime, double rotSpeed ) { // setRotationUsingRotSpeed() is a very unoptimally written ( because it uses osg::Matrix::inverse() ), // and that is done on purpose : in real life scenario functions making updates may take long time // to calculate new object positions due to sophisticated physics models, geometry intersections etc. osg::Matrix mRot = osg::Matrix::rotate( fmod( 2.0 * osg::PI * rotSpeed * currentTime,2.0*osg::PI) , osg::Vec3(0.0,0.0,1.0) ); _instances->getData().at(index).bones[boneIndex] = osg::Matrix::inverse(zeroMatrix) * mRot * zeroMatrix; } osg::ref_ptr< osg::BufferTemplate< std::vector > > _instances; osg::ref_ptr _instancesImage; osg::BoundingBox _bbox; unsigned int _quantityPerType; osg::ref_ptr _destination; std::vector _speed; double _lastTime; }; // createDynamicRendering() is similar to earlier createStaticRendering() method. The differences are as follows : // - instance input data is not stored in osg::Geometry vertex attributes but in dedicated texture buffer ( named "dynamicInstancesData" ). Vertex attributes // in osg::Geometry store only a "pointer" to that buffer. This pointer is later sent to indirect target by the cull shader. And then - used by draw // shader to get instance data from "dynamicInstancesData" buffer during rendering. // - draw shader shows how to animate objects using "bones". Each vertex in rendered geometry is associated with single "bone". Such association is // sufficient to animate mechanical objects. It is also possible to animate organic objects ( large crowds of people ) but it is far beyond // the scope of this example. void createDynamicRendering( osg::Group* root, GPUCullData& gpuData, osg::BufferTemplate< std::vector >* instances, const osg::Vec2& minArea, const osg::Vec2& maxArea, float lodModifier, float densityModifier, float triangleModifier, bool exportInstanceObjects ) { // Creating objects using ShapeToGeometry - its vertex count my vary according to triangleModifier variable. // Every object may be stored to file if you want to inspect it ( to show how many vertices it has for example ). // To do it - use "--export-objects" parameter in commandline. // Every object LOD has different color to show the place where LODs switch. osg::ref_ptr blimpLod0 = createBlimp( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*blimpLod0.get(),"blimpLod0.osgt"); osg::ref_ptr blimpLod1 = createBlimp( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*blimpLod1.get(),"blimpLod1.osgt"); osg::ref_ptr blimpLod2 = createBlimp( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*blimpLod2.get(),"blimpLod2.osgt"); osg::ref_ptr carLod0 = createCar( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.3,0.3,0.3,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*carLod0.get(),"carLod0.osgt"); osg::ref_ptr carLod1 = createCar( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*carLod1.get(),"carLod1.osgt"); osg::ref_ptr carLod2 = createCar( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*carLod2.get(),"carLod2.osgt"); osg::ref_ptr airplaneLod0 = createAirplane( 0.75f * triangleModifier, osg::Vec4(1.0,1.0,1.0,1.0), osg::Vec4(0.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*airplaneLod0.get(),"airplaneLod0.osgt"); osg::ref_ptr airplaneLod1 = createAirplane( 0.45f * triangleModifier, osg::Vec4(0.0,0.0,1.0,1.0), osg::Vec4(1.0,1.0,0.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*airplaneLod1.get(),"airplaneLod1.osgt"); osg::ref_ptr airplaneLod2 = createAirplane( 0.15f * triangleModifier, osg::Vec4(1.0,0.0,0.0,1.0), osg::Vec4(0.0,0.0,1.0,1.0) ); if( exportInstanceObjects ) osgDB::writeNodeFile(*airplaneLod2.get(),"airplaneLod2.osgt"); // ConvertTrianglesOperatorClassic struct is informed which node names correspond to which bone indices ConvertTrianglesOperatorClassic* target0Converter = new ConvertTrianglesOperatorClassic(); target0Converter->registerBoneByName("wheel0",1); target0Converter->registerBoneByName("wheel1",2); target0Converter->registerBoneByName("wheel2",3); target0Converter->registerBoneByName("wheel3",4); target0Converter->registerBoneByName("prop0",5); target0Converter->registerBoneByName("prop1",6); // dynamic rendering uses only one indirect target in this example gpuData.registerIndirectTarget( 0, new AggregateGeometryVisitor( target0Converter ), createProgram( "dynamic_draw_0", SHADER_DYNAMIC_DRAW_0_VERTEX, SHADER_DYNAMIC_DRAW_0_FRAGMENT ) ); float objectDensity = 100.0f * densityModifier; // all instance types are registered and will render using the same indirect target gpuData.registerType( 0, 0, blimpLod0.get(), osg::Vec4(0.0f,0.0f,150.0f,160.0f) * lodModifier, objectDensity ); gpuData.registerType( 0, 0, blimpLod1.get(), osg::Vec4(150.0f,160.0f,800.0f,810.0f) * lodModifier, objectDensity ); gpuData.registerType( 0, 0, blimpLod2.get(), osg::Vec4(800.0f,810.0f,6500.0f,6510.0f) * lodModifier, objectDensity ); gpuData.registerType( 1, 0, carLod0.get(), osg::Vec4(0.0f,0.0f,50.0f,60.0f) * lodModifier, objectDensity ); gpuData.registerType( 1, 0, carLod1.get(), osg::Vec4(50.0f,60.0f,300.0f,310.0f) * lodModifier, objectDensity ); gpuData.registerType( 1, 0, carLod2.get(), osg::Vec4(300.0f,310.0f,1000.0f,1010.0f) * lodModifier, objectDensity ); gpuData.registerType( 2, 0, airplaneLod0.get(), osg::Vec4(0.0f,0.0f,80.0f,90.0f) * lodModifier, objectDensity ); gpuData.registerType( 2, 0, airplaneLod1.get(), osg::Vec4(80.0f,90.0f,400.0f,410.0f) * lodModifier, objectDensity ); gpuData.registerType( 2, 0, airplaneLod2.get(), osg::Vec4(400.0f,410.0f,1200.0f,1210.0f) * lodModifier, objectDensity ); // dynamic targets store only a "pointer" to instanceTarget buffer gpuData.endRegister(1,GL_RGBA,GL_INT, GL_RGBA32I_EXT); // add dynamic instances to instances container float objectZ[3]; objectZ[0] = 50.0f; objectZ[1] = 0.0f; objectZ[2] = 20.0f; osg::BoundingBox bbox( minArea.x(), minArea.y(), 0.0, maxArea.x(), maxArea.y(), 0.0 ); float fullArea = ( bbox.xMax() - bbox.xMin() ) * ( bbox.yMax() - bbox.yMin() ); unsigned int objectQuantity = (unsigned int) floor ( objectDensity * fullArea / 1000000.0f ); unsigned int currentID = 0; for( unsigned int i=0; i<3; ++i) { for(unsigned int j=0; jgetData().push_back(DynamicInstance(i,currentID, osg::Matrixf::rotate(rotation, osg::Vec3(0.0,0.0,1.0) ) * osg::Matrixf::translate(position) , osg::Vec4( brightness, 0.0,0.0,0.0 ) ) ); currentID++; } } // all data about instances is stored in texture buffer ( compare it with static rendering ) osg::Image* instancesImage = new osg::Image; instancesImage->setImage( instances->getTotalDataSize() / sizeof(osg::Vec4f), 1, 1, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT, (unsigned char*)instances->getDataPointer(), osg::Image::NO_DELETE ); osg::VertexBufferObject *instancesBuffer =new osg::VertexBufferObject; instancesBuffer->setUsage(GL_STATIC_DRAW); instancesImage->setBufferObject(instancesBuffer); osg::TextureBuffer* instancesTextureBuffer = new osg::TextureBuffer(instancesImage); instancesTextureBuffer->setUnRefImageDataAfterApply(false); osg::Uniform* dynamicInstancesDataUniform = new osg::Uniform( "dynamicInstancesData", 8 ); osg::Uniform* dynamicInstancesDataSize = new osg::Uniform( osg::Uniform::INT, "dynamicInstancesDataSize" ); dynamicInstancesDataSize->set( (int)(sizeof(DynamicInstance) / sizeof(osg::Vec4f)) ); // all instance "pointers" are stored in a single geometry rendered with cull shader osg::ref_ptr instanceGeometry = buildGPUCullGeometry( instances->getData() ); osg::ref_ptr instanceGeode = new osg::Geode; instanceGeode->addDrawable(instanceGeometry.get()); if( exportInstanceObjects ) osgDB::writeNodeFile(*instanceGeode.get(),"dynamicInstanceGeode.osgt"); // update callback that animates dynamic objects instanceGeometry->setUpdateCallback( new AnimateObjectsCallback( instances, instancesImage, bbox, objectQuantity ) ); instanceGeometry->setDrawCallback( new InvokeMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT) ); root->addChild( instanceGeode.get() ); // instance geode is connected to cull shader with all necessary data ( all indirect commands, all // indirect targets, necessary OpenGL modes etc. ) { instanceGeode->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); osg::ref_ptr resetTexturesCallback = new ResetTexturesCallback; osg::ref_ptr cullProgram = createProgram( "dynamic_cull", SHADER_DYNAMIC_CULL_VERTEX, SHADER_DYNAMIC_CULL_FRAGMENT ); cullProgram->addBindUniformBlock("instanceTypesData", 1); instanceGeode->getOrCreateStateSet()->setAttributeAndModes( cullProgram.get(), osg::StateAttribute::ON ); instanceGeode->getOrCreateStateSet()->setMode( GL_RASTERIZER_DISCARD, osg::StateAttribute::ON ); instanceGeode->getOrCreateStateSet()->setTextureAttribute( 8, instancesTextureBuffer ); instanceGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataUniform ); instanceGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataSize ); std::map::iterator it,eit; for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) { it->second.addIndirectCommandData( "indirectCommand", it->first, instanceGeode->getOrCreateStateSet() ); resetTexturesCallback->addTextureDirty( it->first ); resetTexturesCallback->addTextureDirtyParams( it->first ); it->second.addIndirectTargetData( true, "indirectTarget", it->first, instanceGeode->getOrCreateStateSet() ); resetTexturesCallback->addTextureDirtyParams( OSGGPUCULL_MAXIMUM_INDIRECT_TARGET_NUMBER+it->first ); } osg::Uniform* indirectCommandSize = new osg::Uniform( osg::Uniform::INT, "indirectCommandSize" ); indirectCommandSize->set( (int)(sizeof(DrawArraysIndirectCommand) / sizeof(unsigned int)) ); instanceGeode->getOrCreateStateSet()->addUniform( indirectCommandSize ); instanceGeode->getOrCreateStateSet()->setUpdateCallback( resetTexturesCallback.get() ); } // in the end - we create OSG objects that draw instances using indirect targets and commands. std::map::iterator it,eit; for(it=gpuData.targets.begin(), eit=gpuData.targets.end(); it!=eit; ++it) { osg::ref_ptr drawGeode = new osg::Geode; it->second.geometryAggregator->getAggregatedGeometry()->setDrawCallback( new InvokeMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT) ); drawGeode->addDrawable( it->second.geometryAggregator->getAggregatedGeometry() ); drawGeode->setCullingActive(false); drawGeode->getOrCreateStateSet()->setTextureAttribute( 8, instancesTextureBuffer ); drawGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataUniform ); drawGeode->getOrCreateStateSet()->addUniform( dynamicInstancesDataSize ); it->second.addIndirectTargetData( false, "indirectTarget", it->first, drawGeode->getOrCreateStateSet() ); drawGeode->getOrCreateStateSet()->setAttributeAndModes( gpuData.instanceTypesUBB.get() ); it->second.addDrawProgram( "instanceTypesData", drawGeode->getOrCreateStateSet() ); drawGeode->getOrCreateStateSet()->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ) ); root->addChild(drawGeode.get()); } } int main( int argc, char **argv ) { // use an ArgumentParser object to manage the program arguments. osg::ArgumentParser arguments(&argc,argv); arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the example which demonstrates two-phase geometry instancing using GPU to cull and lod objects"); arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] "); arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display this information"); arguments.getApplicationUsage()->addCommandLineOption("--skip-static","Skip rendering of static objects"); arguments.getApplicationUsage()->addCommandLineOption("--skip-dynamic","Skip rendering of dynamic objects"); arguments.getApplicationUsage()->addCommandLineOption("--export-objects","Export instance objects to files"); arguments.getApplicationUsage()->addCommandLineOption("--use-multi-draw","Use glMultiDrawArraysIndirect in draw shader. Requires OpenGL version 4.3."); arguments.getApplicationUsage()->addCommandLineOption("--instances-per-cell","How many static instances per cell = 4096"); arguments.getApplicationUsage()->addCommandLineOption("--static-area-size","Size of the area for static rendering = 2000"); arguments.getApplicationUsage()->addCommandLineOption("--dynamic-area-size","Size of the area for dynamic rendering = 1000"); arguments.getApplicationUsage()->addCommandLineOption("--lod-modifier","LOD range [%] = 100"); arguments.getApplicationUsage()->addCommandLineOption("--density-modifier","Instance density [%] = 100"); arguments.getApplicationUsage()->addCommandLineOption("--triangle-modifier","Instance triangle quantity [%] = 100"); if (arguments.read("-h") || arguments.read("--help")) { arguments.getApplicationUsage()->write(std::cout); return 1; } // application configuration - default values bool showStaticRendering = true; bool showDynamicRendering = true; bool exportInstanceObjects = false; bool useMultiDrawArraysIndirect = false; unsigned int instancesPerCell = 4096; float staticAreaSize = 2000.0f; float dynamicAreaSize = 1000.0f; float lodModifier = 100.0f; float densityModifier = 100.0f; float triangleModifier = 100.0f; if ( arguments.read("--skip-static") ) showStaticRendering = false; if ( arguments.read("--skip-dynamic") ) showDynamicRendering = false; if ( arguments.read("--export-objects") ) exportInstanceObjects = true; if ( arguments.read("--use-multi-draw") ) useMultiDrawArraysIndirect = true; arguments.read("--instances-per-cell",instancesPerCell); arguments.read("--static-area-size",staticAreaSize); arguments.read("--dynamic-area-size",dynamicAreaSize); arguments.read("--lod-modifier",lodModifier); arguments.read("--density-modifier",densityModifier); arguments.read("--triangle-modifier",triangleModifier); lodModifier /= 100.0f; densityModifier /= 100.0f; triangleModifier /= 100.0f; // construct the viewer. osgViewer::Viewer viewer(arguments); // To enable proper LOD fading we use multisampling osg::DisplaySettings::instance()->setNumMultiSamples( 4 ); // Add basic event handlers and manipulators viewer.addEventHandler(new osgViewer::StatsHandler); viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet())); viewer.addEventHandler(new osgViewer::ThreadingHandler); osg::ref_ptr keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator; keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() ); keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() ); keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() ); keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() ); viewer.setCameraManipulator( keyswitchManipulator.get() ); // create root node osg::ref_ptr root = new osg::Group; // add lightsource to root node osg::ref_ptr lSource = new osg::LightSource; lSource->getLight()->setPosition(osg::Vec4(1.0,-1.0,1.0,0.0)); lSource->getLight()->setDiffuse(osg::Vec4(0.9,0.9,0.9,1.0)); lSource->getLight()->setAmbient(osg::Vec4(0.1,0.1,0.1,1.0)); root->addChild( lSource.get() ); // Add ground osg::ref_ptr groundGeometry = createTexturedQuadGeometry( osg::Vec3(-0.5*staticAreaSize,-0.5*staticAreaSize,0.0), osg::Vec3(0.0,staticAreaSize,0.0), osg::Vec3(staticAreaSize,0.0,0.0) ); osg::ref_ptr groundGeode = new osg::Geode; groundGeode->addDrawable( groundGeometry.get() ); root->addChild( groundGeode.get() ); // Uniform for trees waving in the wind osg::Vec2 windDirection( random(-1.0,1.0), random(-1.0,1.0) ); windDirection.normalize(); osg::ref_ptr windDirectionUniform = new osg::Uniform("windDirection", windDirection); root->getOrCreateStateSet()->addUniform( windDirectionUniform.get() ); // create static objects and setup its rendering GPUCullData staticGpuData; staticGpuData.setUseMultiDrawArraysIndirect( useMultiDrawArraysIndirect ); if(showStaticRendering) { createStaticRendering( root.get(), staticGpuData, osg::Vec2(-0.5*staticAreaSize,-0.5*staticAreaSize), osg::Vec2(0.5*staticAreaSize,0.5*staticAreaSize), instancesPerCell, lodModifier, densityModifier, triangleModifier, exportInstanceObjects ); } // create dynamic objects and setup its rendering GPUCullData dynamicGpuData; dynamicGpuData.setUseMultiDrawArraysIndirect( useMultiDrawArraysIndirect ); osg::ref_ptr< osg::BufferTemplate< std::vector< DynamicInstance> > > dynamicInstances; if(showDynamicRendering) { dynamicInstances = new osg::BufferTemplate< std::vector >(); createDynamicRendering( root.get(), dynamicGpuData, dynamicInstances.get(), osg::Vec2(-0.5*dynamicAreaSize,-0.5*dynamicAreaSize), osg::Vec2(0.5*dynamicAreaSize,0.5*dynamicAreaSize), lodModifier, densityModifier, triangleModifier, exportInstanceObjects ); } viewer.setSceneData( root.get() ); viewer.realize(); // shaders use osg_ variables so we must do the following osgViewer::Viewer::Windows windows; viewer.getWindows(windows); for(osgViewer::Viewer::Windows::iterator itr = windows.begin(); itr != windows.end(); ++itr) (*itr)->getState()->setUseModelViewAndProjectionUniforms(true); return viewer.run(); }