对于一个渲染引擎的渲染过程,我们一般关心的是渲染引擎如何组织、绘制场景对象。OSG 通过状态树与渲染树对渲染节点进行分组、排序与渲染。我们先抛开OSG 事件更新与处理等宏观渲染过程,这里我们从更细的角度研究OSG 是如何将场景对象数据与状态转换成OpenGL 具体实现。

如果让你设计一个渲染引擎渲染场景对象,你通常会怎么做?我们一般都会这样做:首先设计场景对象的组织与数据封装类,将数据从文件或程序处理过程中获取顶点、法向、颜色等数据初始化场景对象;其次,设计OpenGL数据实现层,并与场景对象组织封装对象关联起来,这样在渲染一开始,就可以从数据封装对象获取数据并生成底层OpenGL各种状态数据,例如VBO, VAO,TBO PBO 等;再次调用一个绘制处理过程,将数据与绘制调用传入到OpenGL 执行绘制。

上述的绘制过程OSG是如何设计或组织的呢?

那就是Geometry 类。它的实例对象缓存顶点,法向,颜色,纹理坐标等几何数据并通Geometry.compileGLObjects()生成VBO,VAO数据,通过Geometry.drawImplementation()执行具体的绘制。

Geometry 以及OSG 的OpenGL 相关类的设计比较巧妙,实现效率也较高,多处使用链表等缓存数据结构提高VBO(具体实现类 GLBufferObject)生成效率。

OSG数据接口类 osg::BufferData

class OSG_EXPORT BufferData : public Object
{virtual osg::Array* asArray() { return 0; }virtual const osg::Array* asArray() const { return 0; }virtual osg::PrimitiveSet* asPrimitiveSet() { return 0; }virtual const osg::PrimitiveSet* asPrimitiveSet() const { return 0; }virtual osg::Image* asImage() { return 0; }virtual const osg::Image* asImage() const { return 0; }void setBufferObject(BufferObject* bufferObject);BufferObject* getBufferObject() { return _bufferObject.get(); }    const BufferObject* getBufferObject() const { return _bufferObject.get(); }void setBufferIndex(unsigned int index) { _bufferIndex = index; }    unsigned int getBufferIndex() const { return _bufferIndex; }GLBufferObject* getGLBufferObject(unsigned int contextID) const { return _bufferObject.valid() ? _bufferObject->getGLBufferObject(contextID) : 0; }GLBufferObject* getOrCreateGLBufferObject(unsigned int contextID) const { return _bufferObject.valid() ? _bufferObject->getOrCreateGLBufferObject(contextID) : 0; }}

Osg::Image、osg::Array、 osg::PrimitiveSet 类派生于 BufferData;

osg::Array 具体派生类由模版类class TemplateArray : public Array, public MixinVector<T>给出;

顶点,法向,纹理坐标,颜色 数组均派生于 osg::Array;

索引数据类为向量类:

class DrawElements : public PrimitiveSet

class DrawElementsUByte : public DrawElements, public VectorGLubyte

BufferData 实例会关联BufferObject 实例,BufferObject 是什么? VBO? 我们往下看。

我们以顶点数组的生成为例,看看OSG 如何将BufferData数据转换成OpenGL 对应的数据,请看这段代码:

void Geometry::setVertexArray(Array* array)
{if (array && array->getBinding()==osg::Array::BIND_UNDEFINED) array->setBinding(osg::Array::BIND_PER_VERTEX);_vertexArray = array;dirtyGLObjects();dirtyBound();if (/*_useVertexBufferObjects && */array){_vertexArrayStateList.assignVertexArrayDispatcher();addVertexBufferObjectIfRequired(array);}
}void Geometry::addVertexBufferObjectIfRequired(osg::Array* array)
{if (/*_useVertexBufferObjects &&*/ array->getBinding()==Array::BIND_PER_VERTEX || array->getBinding()==Array::BIND_UNDEFINED){if (!array->getVertexBufferObject()){array->setVertexBufferObject(getOrCreateVertexBufferObject());}}
}osg::VertexBufferObject* Geometry::getOrCreateVertexBufferObject()
{ArrayList arrayList;getArrayList(arrayList);ArrayList::iterator vitr;for(vitr = arrayList.begin();vitr != arrayList.end();++vitr){osg::Array* array = vitr->get();if (array->getVertexBufferObject()) return array->getVertexBufferObject();}return new osg::VertexBufferObject;
}

没错,您看到了 VBO对象的生成与数组对象的关联是在Geometry设置顶点数组数据的时候。array->setVertexBufferObject(VBO),那您应该也看到Geometry::getOrCreateVertexBufferObject()里有个数组循环收集的过程,为什么如果一个数组对象有了VBO 立即返回了?而不是一个数组关联一个VBO对象? 也许您猜对了,我们不能颜色数据,法向坐标数组,雾坐标数组,纹理坐标数组每个都生成一个VBO, 那将是VBO 灾难,VBO 数量剧增会导致渲染性能严重下降!OSG 的每个Geometry只生成一个缓存对象VBO, 其它数组数据也就是BufferData 在VBO中只记录偏移值和数据大小

下面我们看看VBO 的相关的中介类BufferObject:

class OSG_EXPORT BufferObject : public Object
{public:void setTarget(GLenum target) { _profile._target = target; }GLenum getTarget() const { return _profile._target; }/** Set what type of usage the buffer object will have. Options are:*          GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,*          GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,*          GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, or GL_DYNAMIC_COPY.*/void setUsage(GLenum usage) { _profile._usage = usage; }/** Get the type of usage the buffer object has been set up for.*/GLenum getUsage() const { return _profile._usage; }BufferObjectProfile& getProfile() { return _profile; }const BufferObjectProfile& getProfile() const { return _profile; }void dirty();/** Resize any per context GLObject buffers to specified size. */virtual void resizeGLObjectBuffers(unsigned int maxSize);/** If State is non-zero, this function releases OpenGL objects for* the specified graphics context. Otherwise, releases OpenGL objects* for all graphics contexts. */void releaseGLObjects(State* state=0) const;unsigned int addBufferData(BufferData* bd);void removeBufferData(unsigned int index);void removeBufferData(BufferData* bd);void setBufferData(unsigned int index, BufferData* bd);BufferData* getBufferData(unsigned int index) { return _bufferDataList[index]; }const BufferData* getBufferData(unsigned int index) const { return _bufferDataList[index]; }unsigned int getNumBufferData() const { return static_cast<unsigned int>(_bufferDataList.size()); }void setGLBufferObject(unsigned int contextID, GLBufferObject* glbo) { _glBufferObjects[contextID] = glbo; }GLBufferObject* getGLBufferObject(unsigned int contextID) const { return _glBufferObjects[contextID].get(); }GLBufferObject* getOrCreateGLBufferObject(unsigned int contextID) const;unsigned int computeRequiredBufferSize() const;protected:~BufferObject();typedef std::vector< BufferData* > BufferDataList;typedef osg::buffered_object< osg::ref_ptr<GLBufferObject> > GLBufferObjects;BufferObjectProfile     _profile;bool                    _copyDataAndReleaseGLBufferObject;BufferDataList          _bufferDataList;mutable GLBufferObjects _glBufferObjects;
};

看到 Profile , Usage, Target ,这不是我们熟悉的OpenGL 缓存对象 glBufferData 相关参数的内容? VeryGood, 总算找到了OpenGL 具体实现。饿。。。还差那么一点,真正的OpenGL VBO具体实现类它在这呢 GLBufferObject* getOrCreateGLBufferObject(unsigned int contextID) const。 BufferObject 对象只是负责收集 BufferData, 添加到 BufferDataList   _bufferDataList 中。_glBufferObjects 是 上下文相关的VBO 数据, 我们知道 OpenGL 实例的生成是依托于窗口的,每个窗口(WINDOW)都有一个ID  contextID. 您要是在一个窗口上调用其它窗口的openGL环境,不是崩溃就是渲染错误,调试器哇哇的报警,而且您要注意OpenGL 资源是与线程相关的。

这里我们看到了OpenGL VBO 具体实现类GLBufferObject : public GraphicsObject,我们在看看它是咋初始化的:

class OSG_EXPORT GLBufferObject : public GraphicsObject
{public:GLBufferObject(unsigned int contextID, BufferObject* bufferObject, unsigned int glObjectID=0);void setProfile(const BufferObjectProfile& profile) { _profile = profile; }const BufferObjectProfile& getProfile() const { return _profile; }void setBufferObject(BufferObject* bufferObject);BufferObject* getBufferObject() { return _bufferObject; }struct BufferEntry{BufferEntry(): numRead(0), modifiedCount(0),dataSize(0),offset(0),dataSource(0) {}BufferEntry(const BufferEntry& rhs):numRead(rhs.numRead),modifiedCount(rhs.modifiedCount),dataSize(rhs.dataSize),offset(rhs.offset),dataSource(rhs.dataSource) {}BufferEntry& operator = (const BufferEntry& rhs){if (&rhs==this) return *this;numRead = rhs.numRead;modifiedCount = rhs.modifiedCount;dataSize = rhs.dataSize;offset = rhs.offset;dataSource = rhs.dataSource;return *this;}unsigned int getNumClients() const;unsigned int        numRead;unsigned int        modifiedCount;unsigned int        dataSize;unsigned int        offset;BufferData*         dataSource;};inline unsigned int getContextID() const { return _contextID; }inline GLuint& getGLObjectID() { return _glObjectID; }inline GLuint getGLObjectID() const { return _glObjectID; }inline GLsizeiptr getOffset(unsigned int i) const { return _bufferEntries[i].offset; }inline void bindBuffer();inline void unbindBuffer(){_extensions->glBindBuffer(_profile._target,0);}/** release GLBufferObject to the orphan list to be reused or deleted.*/void release();inline bool isDirty() const { return _dirty; }void dirty() { _dirty = true; }void clear();void compileBuffer();void deleteGLObject();void assign(BufferObject* bufferObject);bool isPBOSupported() const { return _extensions->isPBOSupported; }bool hasAllBufferDataBeenRead() const;void setBufferDataHasBeenRead(const osg::BufferData* bd);protected:virtual ~GLBufferObject();unsigned int computeBufferAlignment(unsigned int pos, unsigned int bufferAlignment) const{return osg::computeBufferAlignment(pos, bufferAlignment);}unsigned int            _contextID;GLuint                  _glObjectID;BufferObjectProfile     _profile;unsigned int            _allocatedSize;bool                    _dirty;typedef std::vector<BufferEntry> BufferEntries;BufferEntries           _bufferEntries;BufferObject*           _bufferObject;public:GLBufferObjectSet*      _set;GLBufferObject*         _previous;GLBufferObject*         _next;unsigned int            _frameLastUsed;public:GLExtensions*          _extensions;};看到 GLBufferObject.compileBuffer()? 这就是真正的OpenGL VBO  生成并初始化的地方。
void GLBufferObject::compileBuffer()
{_dirty = false;_bufferEntries.reserve(_bufferObject->getNumBufferData());bool compileAll = false;bool offsetChanged = false;unsigned int bufferAlignment = 4;unsigned int newTotalSize = 0;unsigned int i=0;for(; i<_bufferObject->getNumBufferData(); ++i){BufferData* bd = _bufferObject->getBufferData(i);if (i<_bufferEntries.size()){BufferEntry& entry = _bufferEntries[i];if (offsetChanged ||entry.dataSource != bd ||entry.dataSize != bd->getTotalDataSize()){unsigned int previousEndOfBufferDataMarker = osg::computeBufferAlignment(entry.offset + entry.dataSize, bufferAlignment);// OSG_NOTICE<<"GLBufferObject::compileBuffer(..) updating BufferEntry"<<std::endl;entry.numRead = 0;entry.modifiedCount = 0xffffff;entry.offset = newTotalSize;entry.dataSize = bd->getTotalDataSize();entry.dataSource = bd;newTotalSize += entry.dataSize;if (previousEndOfBufferDataMarker!=newTotalSize){offsetChanged = true;}}else{newTotalSize = osg::computeBufferAlignment(newTotalSize + entry.dataSize, bufferAlignment);}}else{BufferEntry entry;entry.offset = newTotalSize;entry.modifiedCount = 0xffffff;entry.dataSize = bd ? bd->getTotalDataSize() : 0;entry.dataSource = bd;
#if 0OSG_NOTICE<<"entry"<<std::endl;OSG_NOTICE<<"   offset "<<entry.offset<<std::endl;OSG_NOTICE<<"   dataSize "<<entry.dataSize<<std::endl;OSG_NOTICE<<"   dataSource "<<entry.dataSource<<std::endl;OSG_NOTICE<<"   modifiedCount "<<entry.modifiedCount<<std::endl;
#endifnewTotalSize = computeBufferAlignment(newTotalSize + entry.dataSize, bufferAlignment);_bufferEntries.push_back(entry);}}if (i<_bufferEntries.size()){// triming end of bufferEntries as the source data is has less entries than the originally held._bufferEntries.erase(_bufferEntries.begin()+i, _bufferEntries.end());}_extensions->glBindBuffer(_profile._target, _glObjectID);_extensions->debugObjectLabel(GL_BUFFER, _glObjectID, _bufferObject->getName());if (newTotalSize > _profile._size){OSG_INFO<<"newTotalSize="<<newTotalSize<<", _profile._size="<<_profile._size<<std::endl;unsigned int sizeDifference = newTotalSize - _profile._size;_profile._size = newTotalSize;if (_set){_set->moveToSet(this, _set->getParent()->getGLBufferObjectSet(_profile));_set->getParent()->getCurrGLBufferObjectPoolSize() += sizeDifference;}}if (_allocatedSize != _profile._size){_allocatedSize = _profile._size;OSG_INFO<<"    Allocating new glBufferData(), _allocatedSize="<<_allocatedSize<<std::endl;_extensions->glBufferData(_profile._target, _profile._size, NULL, _profile._usage);compileAll = true;}for(BufferEntries::iterator itr = _bufferEntries.begin();itr != _bufferEntries.end();++itr){BufferEntry& entry = *itr;if (entry.dataSource && (compileAll || entry.modifiedCount != entry.dataSource->getModifiedCount())){// OSG_NOTICE<<"GLBufferObject::compileBuffer(..) downloading BufferEntry "<<&entry<<std::endl;entry.numRead = 0;entry.modifiedCount = entry.dataSource->getModifiedCount();const osg::Image* image = entry.dataSource->asImage();if (image && !(image->isDataContiguous())){unsigned int offset = entry.offset;for(osg::Image::DataIterator img_itr(image); img_itr.valid(); ++img_itr){_extensions->glBufferSubData(_profile._target, (GLintptr)offset, (GLsizeiptr)img_itr.size(), img_itr.data());offset += img_itr.size();}}else{_extensions->glBufferSubData(_profile._target, (GLintptr)entry.offset, (GLsizeiptr)entry.dataSize, entry.dataSource->getDataPointer());}}}
}

代码过程长,但比较简单明了,我就不叨叨了。

它们之间不是单向关联关系,array 关联 BufferObject, GLBufferObject 初始化时父为 BufferObject, BufferObject 创建GLBufferObject 时通过glObjectList 记录。

那啥时机生成VBO 并进行初始化?

有两个地方:一是在整帧开始时, draw() 函数会判断当前是不是第一帧开始,如果是就执行图形编译:

void Renderer::compile()
{DEBUG_MESSAGE<<"Renderer::compile()"<<std::endl;_compileOnNextDraw = false;osgUtil::SceneView* sceneView = _sceneView[0].get();if (!sceneView || _done) return;sceneView->getState()->checkGLErrors("Before Renderer::compile");if (sceneView->getSceneData()){osgUtil::GLObjectsVisitor glov;glov.setState(sceneView->getState());glov.compile(*(sceneView->getSceneData()));}sceneView->getState()->checkGLErrors("After Renderer::compile");
}

osgUtil::GLObjectsVisitor 负责调用 Geometry 的 compileGLObject() 函数生成 VBO 。

二是 ,当个别Geomery 数据dirty() 时 , VAS (VertextArrayState) 绑定VBO或设置VAO时

class OSG_EXPORT VertexArrayState : public osg::Referenced
{inline void bindVertexBufferObject(osg::GLBufferObject* vbo){if (vbo->isDirty()){vbo->compileBuffer();_currentVBO = vbo;}else if (vbo != _currentVBO){vbo->bindBuffer();_currentVBO = vbo;}}

Geometry 类中为了兼容性的需要存在很多DisplayList显示列表相关的代码,显示列表是OpenGL1.0~OpenGL 1.5中的产物,历史有点悠久,包括顶点数组,法线数组这些不符合现代图形硬件的东西也应该统统的去掉,OpenGL 3.0 都是10年前的规范,固定管线那套API也没有存在的必要。

OSG 渲染剖析 之 Geometry 的 VBO生成相关推荐

  1. [转][osg]osg渲染引擎框架图,流程图(根据《最长一帧》整理)

    转自:http://m.blog.csdn.net/article/details?id=49679731 本文参考<<osg最长一帧>>, <<OpenScene ...

  2. osg渲染到纹理技术(二)

    关于什么是osg渲染到纹理技术,请参考<osg渲染到纹理技术(一)> #include <osg/Camera> #include <osg/Group> #inc ...

  3. OSG 渲染引擎特性与架构

    OSG 作为老牌的开源渲染引擎之一,有一定的用户群体,不少个人.企业.科研机构都在使用OSG进行开发.随着不少商业渲染引擎的开源与准门槛的降低(比如Unity3D 授权费用比较低,中小企业甚至个人都能 ...

  4. 实时体积云渲染(地平线):一.云的生成

    实时体积云渲染(地平线):一.云的生成 体积云一直是想尝试的一个东西,最近终于有点自己的时间,花了些功夫实现了"地平线"里用到的体积云算法.实现效果图如下(未加大气散射有空一定补上 ...

  5. osg渲染到纹理技术(一)

    render-to-textures(RTT)允许 开发者根据场景的一部分图像创建成一张纹理图,烘焙到场景中的某一物体上,这种技术用于创建更好看的特殊的表现形式,或者被保存用于以后的延迟着色,和多通道 ...

  6. Cesium Render渲染(3):VBO(Buffer缓冲区),VAO(VBO的封装)和FBO(帧缓冲区)

    shader 负责处理顶点和纹理,但是shader本身是空的. 一:VBO VBO的中文名字是顶点缓存,缓冲区这一块的逻辑主要封装在Cesium 的Frame 类中,在Frame 的构造函数中,可以看 ...

  7. OSG 高效渲染 之 多线程模型与渲染

    这里以OSG多线程渲染为例,谈谈OSG渲染引擎多线程渲染模式,再说说哪里有点"过时"了需要改进. 先谈点题外话,曾经看到知乎上一篇关于游戏引擎的设计讨论的文章,有位"大大 ...

  8. 剖析Unreal Engine超真实人类的渲染技术Part 3 - 毛发渲染及其它

    目录 四.毛发渲染 4.1 毛发的构造及渲染技术 4.1.1 毛发的构造 4.1.2 Marschner毛发渲染模型 4.1.3 毛发的间接光照 4.2 毛发的底层实现 4.3 毛发的材质解析 4.3 ...

  9. OSG三维渲染引擎编程学习(全系列开展OSG学习)

    目录 第一章:OpenSceneGraph介绍 第二章:OSG数学基础 第三章:OSG场景组织 第四章:OSG几何体绘制 第五章:OSG场景渲染 第六章:OSG场景工作机制 第七章:OSG场景图形交互 ...

最新文章

  1. DGL实现同构/异构图卷积模型
  2. 八戒科技服务技术负责人鸿鹄真人:做好技术负责人的4个关键特质
  3. 聊聊Socket、TCP/IP、HTTP、FTP及网络编程
  4. 专科python应届生工资多少-请问学过一点python,应届生怎么找工作?
  5. 转载 JavaScript的24条实用建议
  6. OLTP在线事务处理
  7. SDL(01-10)
  8. 2019国内高端智能云呼叫中心系统,让简单的工作变得更有值得
  9. 软件工程计算机类电子信息类,热门的工科专业还要属计算机类、电子信息类、机械类...
  10. Android开机动画的动态替换
  11. 企业文化是数字化转型最大障碍-解读《2022年首席数据官调查报告》
  12. 简单易懂的贝叶斯公式
  13. 湖北自考 计算机网络,2021年湖北自考计算机网络基础课程考试大纲
  14. 如何查看微信小程序服务器域名并且修改
  15. Programming-寻找发贴水王(C)
  16. 全向轮三轮小车的搭建(一)
  17. 如何制作朋友圈搞笑证件图片(附源码实例)
  18. Python A*算法的简单实现
  19. 南阳理工ACM 题目73 比大小
  20. 【科普】Kubectl基本操作命令

热门文章

  1. mybatis 查询报错:SQLException: 无效的列类型: 1111
  2. JHipster学习记录 - 2 JHipster UAA
  3. C++【坑人神器:绝地求生小游戏2.0】关机代码
  4. 2019-1-24【训练日记】
  5. 表的联接查询之连接查询
  6. 树和树林的实现,不懂数据结构的人也能看懂
  7. c语言移位运算的作用,C语言的移位操作符使用方法
  8. 五款资深高效的Web性能测试工具
  9. iOS开发之百度地图的简单集成——标注POI检索
  10. win7桌面的计算机在哪里,win7更改桌面路径,win7桌面文件在哪里