OSG 渲染剖析 之 Geometry 的 VBO生成

22 篇文章 0 订阅
16 篇文章 15 订阅

对于一个渲染引擎的渲染过程,我们一般关心的是渲染引擎如何组织、绘制场景对象。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 0
            OSG_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;
#endif
            newTotalSize = 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也没有存在的必要。 

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值