RenderStage::drawInner

news2025/4/21 14:54:59

文章目录

  • RenderStage::drawInner
  • OSG渲染后台关系图
  • OSG的渲染流程
  • RenderBin::draw(renderInfo,previous)
  • RenderBin::drawImplementation
    • RenderLeaf::render
      • osg::State::apply(const StateSet*)
      • Drawable::draw(RenderInfo& renderInfo)
        • Drawable::drawInner(RenderInfo& renderInfo)
          • Drawable::drawImplementation

RenderStage::drawInner

void RenderStage::drawInner(osg::RenderInfo& renderInfo,RenderLeaf*& previous, bool& doCopyTexture)
{
    struct SubFunc
    {
        static void applyReadFBO(bool& apply_read_fbo,
            const FrameBufferObject* read_fbo, osg::State& state)
        {
            if (read_fbo->isMultisample())
            {
                OSG_WARN << "Attempting to read from a"
                    " multisampled framebuffer object. Set a resolve"
                    " framebuffer on the RenderStage to fix this." << std::endl;
            }

            if (apply_read_fbo)
            {
                // Bind the monosampled FBO to read from
                // ogl操作:
				//创建一个新的fbo
				//遍历设置的_attachments,编译纹理
				//ext->glBindFramebuffer(READ_FRAMEBUFFER, fboID);
				//调用glDrawBuffers(GLsizei n, const GLenum *bufs);,指定在一个渲染操作中输出到多个颜色缓冲区的目标
				//遍历设置的_attachments,添加附件
                read_fbo->apply(state, FrameBufferObject::READ_FRAMEBUFFER);
                apply_read_fbo = false;
            }
        }
    };

    osg::State& state = *renderInfo.getState();

    osg::GLExtensions* ext = _fbo.valid() ? state.get<osg::GLExtensions>() : 0;
    // 判断显示卡是否支持FBO
    bool fbo_supported = ext && ext->isFrameBufferObjectSupported;

	// 详见RenderStage::runCameraSetUp
    bool using_multiple_render_targets = fbo_supported && _fbo->hasMultipleRenderingTargets();

    if (!using_multiple_render_targets)
    {
        #if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE)

            if( getDrawBufferApplyMask() )
                state.glDrawBuffer(_drawBuffer);

            if( getReadBufferApplyMask() )
                state.glReadBuffer(_readBuffer);

        #endif
    }

    if (fbo_supported)
    {	
    	// ogl操作:
		//创建一个新的fbo
		//遍历设置的_attachments,编译纹理
		//ext->glBindFramebuffer(READ_FRAMEBUFFER, fboID);
		//调用glDrawBuffers(GLsizei n, const GLenum *bufs);,指定在一个渲染操作中输出到多个颜色缓冲区的目标
		//遍历设置的_attachments,添加附件
        _fbo->apply(state);
    }

    // do the drawing itself.
    RenderBin::draw(renderInfo,previous);


    if(state.getCheckForGLErrors()!=osg::State::NEVER_CHECK_GL_ERRORS)
    {
        if (state.checkGLErrors("after RenderBin::draw(..)"))
        {
            if ( ext )
            {
                GLenum fbstatus = ext->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT);
                if ( fbstatus != GL_FRAMEBUFFER_COMPLETE_EXT )
                {
                    OSG_NOTICE<<"RenderStage::drawInner(,) FBO status = 0x"<<std::hex<<fbstatus<<std::dec<<std::endl;
                }
            }
        }
    }

	// 以下是copy纹理,写image,生成mipmap等操作
    const FrameBufferObject* read_fbo = fbo_supported ? _fbo.get() : 0;
    bool apply_read_fbo = false;

	//glBlitFramebuffer用于在帧缓冲对象之间快速复制像素数据
    if (fbo_supported && _resolveFbo.valid() && ext->glBlitFramebuffer)
    {
        GLbitfield blitMask = 0;
        bool needToBlitColorBuffers = false;

        //find which buffer types should be copied
        for (FrameBufferObject::AttachmentMap::const_iterator
            it = _resolveFbo->getAttachmentMap().begin(),
            end =_resolveFbo->getAttachmentMap().end(); it != end; ++it)
        {
            switch (it->first)
            {
            case Camera::DEPTH_BUFFER:
                blitMask |= GL_DEPTH_BUFFER_BIT;
                break;
            case Camera::STENCIL_BUFFER:
                blitMask |= GL_STENCIL_BUFFER_BIT;
                break;
            case Camera::PACKED_DEPTH_STENCIL_BUFFER:
                blitMask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
                break;
            case Camera::COLOR_BUFFER:
                blitMask |= GL_COLOR_BUFFER_BIT;
                break;
            default:
                needToBlitColorBuffers = true;
                break;
            }
        }

        // Bind the resolve framebuffer to blit into.
        _fbo->apply(state, FrameBufferObject::READ_FRAMEBUFFER);
        _resolveFbo->apply(state, FrameBufferObject::DRAW_FRAMEBUFFER);// 如果不使用MSFBO则为0

        if (blitMask)
        {
            // Blit to the resolve framebuffer.
            // Note that (with nvidia 175.16 windows drivers at least) if the read
            // framebuffer is multisampled then the dimension arguments are ignored
            // and the whole framebuffer is always copied.
            // 将READ_FRAMEBUFFER拷贝到DRAW_FRAMEBUFFER
            ext->glBlitFramebuffer(
                static_cast<GLint>(_viewport->x()), static_cast<GLint>(_viewport->y()),
                static_cast<GLint>(_viewport->x() + _viewport->width()), static_cast<GLint>(_viewport->y() + _viewport->height()),
                static_cast<GLint>(_viewport->x()), static_cast<GLint>(_viewport->y()),
                static_cast<GLint>(_viewport->x() + _viewport->width()), static_cast<GLint>(_viewport->y() + _viewport->height()),
                blitMask, GL_NEAREST);
        }

#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE)
		// 拷贝多传输颜色附件
        if (needToBlitColorBuffers)
        {
            for (FrameBufferObject::AttachmentMap::const_iterator
                it = _resolveFbo->getAttachmentMap().begin(),
                end =_resolveFbo->getAttachmentMap().end(); it != end; ++it)
            {
                osg::Camera::BufferComponent attachment = it->first;
                if (attachment >=osg::Camera::COLOR_BUFFER0)
                {
                    state.glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + (attachment - osg::Camera::COLOR_BUFFER0));
                    state.glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + (attachment - osg::Camera::COLOR_BUFFER0));

                    ext->glBlitFramebuffer(
                        static_cast<GLint>(_viewport->x()), static_cast<GLint>(_viewport->y()),
                        static_cast<GLint>(_viewport->x() + _viewport->width()), static_cast<GLint>(_viewport->y() + _viewport->height()),
                        static_cast<GLint>(_viewport->x()), static_cast<GLint>(_viewport->y()),
                        static_cast<GLint>(_viewport->x() + _viewport->width()), static_cast<GLint>(_viewport->y() + _viewport->height()),
                        GL_COLOR_BUFFER_BIT, GL_NEAREST);
                }
            }
            // reset the read and draw buffers?  will comment out for now with the assumption that
            // the buffers will be set explicitly when needed elsewhere.
            // glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
            // glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
        }
#endif

        apply_read_fbo = true;
        read_fbo = _resolveFbo.get();

        using_multiple_render_targets = read_fbo->hasMultipleRenderingTargets();
    }

    // now copy the rendered image to attached texture.
    if (doCopyTexture)
    {
        if (read_fbo) SubFunc::applyReadFBO(apply_read_fbo, read_fbo, state);
        copyTexture(renderInfo);
    }

	//如果有设置附件的image,则将附件读入image中
    for(std::map< osg::Camera::BufferComponent, Attachment>::const_iterator itr = _bufferAttachmentMap.begin();
        itr != _bufferAttachmentMap.end();
        ++itr)
    {
        if (itr->second._image.valid())
        {
            if (read_fbo) SubFunc::applyReadFBO(apply_read_fbo, read_fbo, state);

            #if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE)

                if (using_multiple_render_targets)
                {
                    int attachment=itr->first;
                    if (attachment==osg::Camera::DEPTH_BUFFER || attachment==osg::Camera::STENCIL_BUFFER) {
                        // assume first buffer rendered to is the one we want
                        glReadBuffer(read_fbo->getMultipleRenderingTargets()[0]);
                    } else {
                        glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + (attachment - osg::Camera::COLOR_BUFFER0));
                    }
                } else {
                    if (_readBuffer != GL_NONE)
                    {
                        glReadBuffer(_readBuffer);
                    }
                }

            #endif

            GLenum pixelFormat = itr->second._image->getPixelFormat();
            if (pixelFormat==0) pixelFormat = _imageReadPixelFormat;
            if (pixelFormat==0) pixelFormat = GL_RGB;

            GLenum dataType = itr->second._image->getDataType();
            if (dataType==0) dataType = _imageReadPixelDataType;
            if (dataType==0) dataType = GL_UNSIGNED_BYTE;

            itr->second._image->readPixels(static_cast<int>(_viewport->x()),
                                           static_cast<int>(_viewport->y()),
                                           static_cast<int>(_viewport->width()),
                                           static_cast<int>(_viewport->height()),
                                           pixelFormat, dataType);
        }
    }

    if (fbo_supported)
    {
    	// 是否需要解除绑定回复默认帧缓冲区
        if (getDisableFboAfterRender())
        {
            // switch off the frame buffer object
            GLuint fboId = state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0;
            ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId);
        }

        doCopyTexture = true;
    }

    if (fbo_supported && _camera.valid())
    {
        // now generate mipmaps if they are required.
        const osg::Camera::BufferAttachmentMap& bufferAttachments = _camera->getBufferAttachmentMap();
        for(osg::Camera::BufferAttachmentMap::const_iterator itr = bufferAttachments.begin();
            itr != bufferAttachments.end();
            ++itr)
        {
            if (itr->second._texture.valid() && itr->second._mipMapGeneration)
            {
                state.setActiveTextureUnit(0);//设置当前纹理单元
                state.applyTextureAttribute(0, itr->second._texture.get());
                //用于生成指定纹理的多级渐进纹理(Mipmap):void glGenerateMipmap(GLenum target);
                //target:GL_TEXTURE_2D,GL_TEXTURE_CUBE_MAP,GL_TEXTURE_3D
                ext->glGenerateMipmap(itr->second._texture->getTextureTarget());
            }
        }
    }
}

OSG渲染后台关系图

在这里插入图片描述
在这里插入图片描述

  • 如果我们需要自己创建新的派生自Drawable的对象(就像osgText中所实现的),drawable几何体对象的具体实现在于drawImplementation函数。
  • 如果想自己创建一种新的渲染属性(派生自StateAttribute),渲染属性的具现函数为StateAttribute::apply(State&),所有的渲染属性都重写了这一函数,以实现自己的功能。
  • OSG渲染后台与用户层的接口是摄像机类(Camera)。场景中至少有一个主摄像机,它关联了一个图形设备(GraphicsContext,通常是窗口),以及一个渲染器(Renderer);我们可以在场景树中(或者别的视图View中,对于复合视景器而言)添加更多的摄像机,它们可以关联相同的或者其它的图形设备,但都会配有单独的渲染器,用以保存该摄像机的筛选设置、显示器设置等信息。
  • 场景筛选和绘制的工作由渲染器来完成,而图形设备GraphicsContext则负责根据不同时机的选择,调用渲染器的相关函数。例如在单线程模式中,ViewerBase::renderingTraversals函数依次执行Renderer::cull和Renderer::draw函数(后者通过GraphicsContext::runOperations调用),而在多线程模型中调用者的关系将更加错综复杂。
  • OSG渲染后台的调度中心是场景视图(SceneView),它负责保存和执行筛选访问器(CullVisitor)。CullVisitor负责遍历并裁减场景,同时在遍历过程中构建对于场景绘制至关重要的渲染树和状态树;生成的状态树以StateGraph为根节点和各级子节点(其中保存场景树的渲染状态StateSet数据),以RenderLeaf为末端叶节点的内容(其中保存场景树中的几何体Drawable对象);渲染树则以RenderStage为根节点,RenderBin为各级子节点,根据渲染顺序和方法的设定,状态树中的节点和渲染叶(RenderLeaf)被记录到RenderStage和各级RenderBin中;SceneView负责保存和维护状态树和渲染树。
  • 绘制场景时,渲染树中的各级节点将取出保存的渲染叶数据,传递给OSG状态机(State),其中OSG状态机(State)是OpenGL状态机制的封装和实现,也是场景绘制的核心元件。状态机取得渲染叶中的几何数据之后,再向根部遍历状态树,取得该几何体绘制相关的所有渲染状态设置,并亲自或者交由StateAttribute派生类完成渲染状态的实际设定,以及场景元素的实际绘制工作。此时用到的就已经是我们耳熟能详的各种OpenGL函数了。

OSG的渲染流程

1、渲染树的作用是遍历各个渲染元(RenderBin),并按照指定的顺序执行其中各个渲染叶的渲染函数(RenderLeaf::render)。

2、状态树保存了从根节点到当前渲染叶的路径,遍历这条路径并收集所有的渲染属性数据,通过StateGraph::moveStateGraph关闭父节点所有状态,并打开当前节点所有状态,即可获得当前渲染叶渲染所需的所有OpenGL状态数据。

3、渲染叶的渲染函数(RenderLeaf::render)负责向状态机(osg::State)传递渲染状态数据,进而由渲染属性类本身完成参数在OpenGL中的注册和加载工作(StateAttribute::apply);渲染叶还负责调用几何体(Drawable)的绘制函数(drawImplementation),传递顶点和索引数据并完成场景的绘制工作。

RenderBin::draw(renderInfo,previous)

void RenderBin::draw(osg::RenderInfo& renderInfo,RenderLeaf*& previous)
{
    renderInfo.pushRenderBin(this);

    if (_drawCallback.valid())
    {
        _drawCallback->drawImplementation(this,renderInfo,previous);
    }
    else drawImplementation(renderInfo,previous);

    renderInfo.popRenderBin();
}

RenderBin::drawImplementation

void RenderBin::drawImplementation(osg::RenderInfo& renderInfo,RenderLeaf*& previous)
{
    osg::State& state = *renderInfo.getState();

    // OSG_NOTICE<<"begin RenderBin::drawImplementation "<<className()<<" sortMode "<<getSortMode()<<std::endl;


    unsigned int numToPop = (previous ? StateGraph::numToPop(previous->_parent) : 0);
    if (numToPop>1) --numToPop;
    unsigned int insertStateSetPosition = state.getStateSetStackSize() - numToPop;

    if (_stateset.valid())
    {
        state.insertStateSet(insertStateSetPosition, _stateset.get());
    }

	// 遍历所有的子渲染元(RenderBin::_bins),其中渲染顺序号小于0的渲染元将在这里执行它们的RenderBin::draw函数,
	// 由于draw函数内部调用了drawImplementation,因此这构成了一个递归调用,直至渲染树遍历至末端节点。
	// 在用户程序中,渲染顺序号的设置使用StateSet::setRenderBinDetails函数(RenderBinList的key)。
    // draw first set of draw bins.
    RenderBinList::iterator rbitr;
    for(rbitr = _bins.begin();
        rbitr!=_bins.end() && rbitr->first<0;
        ++rbitr)
    {
        rbitr->second->draw(renderInfo,previous);
    }

	// 遍历当前RenderBin所保存的所有渲染叶(RenderBin::_renderLeafList),执行RenderLeaf::render函数,实现场景的绘制。
	// 通常只有被设置为“DepthSortedBin”的渲染元会选择保存渲染叶而非状态节点(StateGraph),因为这样便于按照深度值排序对象。
    // draw fine grained ordering.
    for(RenderLeafList::iterator rlitr= _renderLeafList.begin();
        rlitr!= _renderLeafList.end();
        ++rlitr)
    {
        RenderLeaf* rl = *rlitr;
        rl->render(renderInfo,previous);
        previous = rl;
    }


    bool draw_forward = true; //(_sortMode!=SORT_BY_STATE) || (state.getFrameStamp()->getFrameNumber() % 2)==0;

	// 遍历当前RenderBin所保存的所有状态节点(RenderBin::_stateGraphList),
 	// 获取其中保存的RenderLeaf对象(保存为StateGraph::_leaves),并执行其render函数。
    // draw coarse grained ordering.
    if (draw_forward)
    {
        for(StateGraphList::iterator oitr=_stateGraphList.begin();
            oitr!=_stateGraphList.end();
            ++oitr)
        {

            for(StateGraph::LeafList::iterator dw_itr = (*oitr)->_leaves.begin();
                dw_itr != (*oitr)->_leaves.end();
                ++dw_itr)
            {
                RenderLeaf* rl = dw_itr->get();
                rl->render(renderInfo,previous);
                previous = rl;

            }
        }
    }
    else
    {
        for(StateGraphList::reverse_iterator oitr=_stateGraphList.rbegin();
            oitr!=_stateGraphList.rend();
            ++oitr)
        {

            for(StateGraph::LeafList::iterator dw_itr = (*oitr)->_leaves.begin();
                dw_itr != (*oitr)->_leaves.end();
                ++dw_itr)
            {
                RenderLeaf* rl = dw_itr->get();
                rl->render(renderInfo,previous);
                previous = rl;

            }
        }
    }

	//遍历所有的子渲染元(RenderBin::_bins),其中渲染顺序号大于0的渲染元此时才执行它们的RenderBin::draw函数。
    // draw post bins.
    for(;
        rbitr!=_bins.end();
        ++rbitr)
    {
        rbitr->second->draw(renderInfo,previous);
    }

    if (_stateset.valid())
    {
        state.removeStateSet(insertStateSetPosition);
        // state.apply();
    }


    // OSG_NOTICE<<"end RenderBin::drawImplementation "<<className()<<std::endl;
}

  • 由此可知,渲染树中最先被绘制的将是那些顺序号小于0的末端RenderBin节点,其次则依次是顺序号等于0的末端节点,大于0的末端节点,小于0的倒数第二级节点……而作为渲染树根节点的RenderStage中保存的数据将最后被渲染。
  • 渲染树同一层中不可能存在渲染顺序号相同的渲染元,因为使用setRenderBinDetails设置了相同数字参量的StateSet对象被构建成状态节点(StateGraph)之后,将插入到同一个RenderBin中,而不同name则是不同层级的renderbin。

RenderLeaf::render

  • 渲染叶RenderLeaf是OSG渲染后台中几何体(Drawable)对象的唯一管理者。
  • 而这里的render函数主要负责获取之前保存的Drawable指针,并将它们传递给负责渲染状态处理的State类,以及执行Drawable::draw函数。
void RenderLeaf::render(osg::RenderInfo& renderInfo,RenderLeaf* previous)
{
    osg::State& state = *renderInfo.getState();

    // don't draw this leaf if the abort中止 rendering flag has been set.
    if (state.getAbortRendering())
    {
        //cout << "early abort"<<endl;
        return;
    }

    if (previous)
    {
        // apply matrices if required.
        state.applyProjectionMatrix(_projection.get());
        state.applyModelViewMatrix(_modelview.get());

		// 如果当前渲染叶与上一次处理的渲染叶父节点不同,则需要遍历状态树中相应的路径,
		// 并更新State状态机中保存的渲染状态数据(采用std::map类型,分别名为_modeMap和_attributeMap)。
        // apply state if required.
        StateGraph* prev_rg = previous->_parent;
        StateGraph* prev_rg_parent = prev_rg->_parent;
        StateGraph* rg = _parent;
        if (prev_rg_parent!=rg->_parent)
        {
        // 它负责清除上一次使用的各种渲染状态,再沿着状态树中的路径,依次添加当前渲染叶所需的数据。
        // 最后执行函数State::apply(const StateSet*),由OSG状态机处理并执行相应的OpenGL指令。
            StateGraph::moveStateGraph(state,prev_rg_parent,rg->_parent);

            // send state changes and matrix changes to OpenGL.
            state.apply(rg->getStateSet());

        }
        else if (rg!=prev_rg)
        {

            // send state changes and matrix changes to OpenGL.
            state.apply(rg->getStateSet());

        }

		// 设置着色器osg内置变量
        // if we are using osg::Program which requires OSG's generated uniforms to track
        // modelview and projection matrices then apply them now.
        if (state.getUseModelViewAndProjectionUniforms()) state.applyModelViewAndProjectionUniformsIfRequired();

		// 执行此渲染叶所保存的Drawable对象的draw函数,完成几何体的绘制。
		// Geometry对象将在这一函数中(实际上是Drawable::drawImplementation)向状态机传递顶点和索引数据,
		// 并交由状态机对象来完成几何数据的绘制。
        // draw the drawable
        _drawable->draw(renderInfo);
    }
    else
    {
        // apply matrices if required.
        state.applyProjectionMatrix(_projection.get());
        state.applyModelViewMatrix(_modelview.get());

        // apply state if required.
        StateGraph::moveStateGraph(state,NULL,_parent->_parent);

        state.apply(_parent->getStateSet());

        // if we are using osg::Program which requires OSG's generated uniforms to track
        // modelview and projection matrices then apply them now.
        if (state.getUseModelViewAndProjectionUniforms()) state.applyModelViewAndProjectionUniformsIfRequired();

        // draw the drawable
        _drawable->draw(renderInfo);
    }

    if (_dynamic)
    {
        state.decrementDynamicObjectCount();
    }

    // OSG_NOTICE<<"RenderLeaf "<<_drawable->getName()<<" "<<_depth<<std::endl;
}

osg::State::apply(const StateSet*)

Drawable::draw(RenderInfo& renderInfo)

inline void Drawable::draw(RenderInfo& renderInfo) const
{
    State& state = *renderInfo.getState();
    bool useVertexArrayObject = state.useVertexArrayObject(_useVertexArrayObject);
    if (useVertexArrayObject)
    {
        unsigned int contextID = renderInfo.getContextID();

        VertexArrayState* vas = _vertexArrayStateList[contextID].get();
        if (!vas)
        {
            _vertexArrayStateList[contextID] = vas = createVertexArrayState(renderInfo);
        }
        else
        {
            // vas->setRequiresSetArrays(getDataVariance()==osg::Object::DYNAMIC);
        }

        State::SetCurrentVertexArrayStateProxy setVASProxy(state, vas);

		// 但凡是使用ogl核心模式,都要使用vao
        state.bindVertexArrayObject(vas);

        drawInner(renderInfo);

        vas->setRequiresSetArrays(getDataVariance()==osg::Object::DYNAMIC);

        return;
    }

    // TODO, add check against whether VAO is active and supported
    if (state.getCurrentVertexArrayState())
    {
        //OSG_NOTICE<<"state.getCurrentVertexArrayState()->getVertexArrayObject()="<< state.getCurrentVertexArrayState()->getVertexArrayObject()<<std::endl;
        state.bindVertexArrayObject(state.getCurrentVertexArrayState());
    }

	// 使用显示列表
#ifdef OSG_GL_DISPLAYLISTS_AVAILABLE
    if (!state.useVertexBufferObject(_supportsVertexBufferObjects && _useVertexBufferObjects) && _useDisplayList)
    {
        // get the contextID (user defined ID of 0 upwards) for the
        // current OpenGL context.
        unsigned int contextID = renderInfo.getContextID();

        // get the globj for the current contextID.
        GLuint& globj = _globjList[contextID];

        if( globj == 0 )
        {
            // compile the display list
            globj = generateDisplayList(contextID, getGLObjectSizeHint());
            glNewList( globj, GL_COMPILE );

            drawInner(renderInfo);

            glEndList();
        }

        // call the display list
        glCallList( globj);
    }
    else
#endif
    {
        // if state.previousVertexArrayState() is different than currentVertexArrayState bind current

        // OSG_NOTICE<<"Fallback drawInner()........................"<<std::endl;

        drawInner(renderInfo);
    }
}
#endif
Drawable::drawInner(RenderInfo& renderInfo)
inline void drawInner(RenderInfo& renderInfo) const
        {
            if (_drawCallback.valid())
                _drawCallback->drawImplementation(renderInfo,this);
            else
                drawImplementation(renderInfo);
        }
Drawable::drawImplementation
 /** drawImplementation(RenderInfo&) is a pure virtual method for the actual implementation of OpenGL drawing calls, such as vertex arrays and primitives, that
          * must be implemented in concrete subclasses of the Drawable base class, examples include osg::Geometry and osg::ShapeDrawable.
          * drawImplementation(RenderInfo&) is called from the draw(RenderInfo&) method, with the draw method handling management of OpenGL display lists,
          * and drawImplementation(RenderInfo&) handling the actual drawing itself.
          * renderInfo : The osg::RenderInfo object that encapsulates the current rendering information including the osg::State OpenGL state for the current graphics context. */

 virtual void drawImplementation(RenderInfo& /*renderInfo*/) const {}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2339496.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++初阶-类和对象(中)

目录 1.类的默认成员函数 2.构造函数&#xff08;难度较高&#xff09; ​编辑 ​编辑 ​编辑 3.析构函数 4.拷贝构造函数 5.赋值运算符重载 5.1运算符重载 5.2赋值运算符重载 6.取地址运算符重载 6.1const成员函数 6.2取地址运算符重载 7.总结 1.类的默认成员函数…

智谱开源新一代GLM模型,全面布局AI智能体生态

2024年4月15日&#xff0c;智谱在中关村论坛上正式发布了全球首个集深度研究与实际操作能力于一体的AI智能体——AutoGLM沉思。这一革命性技术的发布标志着智谱在AGI&#xff08;通用人工智能&#xff09;领域的又一次重要突破。智谱的最新模型不仅推动了AI智能体技术的升级&am…

分治-快排-75.颜色分类-力扣(LeetCode)

一、题目解析 给定一个数组将其元素按照0&#xff0c;1,&#xff0c;2三段式排序&#xff0c;并且在原地进行排序。 二、算法原理 解法&#xff1a;三指针 用cur遍历数组&#xff0c;left记录0的最左侧&#xff0c;right记录2的最右侧。 left初始值为-1&#xff0c;right的初…

铅酸电池充电器方案EG1253+EG4321

参考&#xff1a; 基于EG1253EG4321铅酸电池(48V20AH)三段式充电器 屹晶微高性价比的电瓶车充电器方案——EG1253 电瓶电压 48V电瓶锂电池&#xff0c;其充满约为55V~56V&#xff0c;因此充电器输出电压为55V~56V&#xff1b; 若是48V铅酸电池&#xff0c;标称电压为48V&…

vue 中formatter

:formatter 是前端表格组件&#xff08;如 Element UI、Vxe-Table 等&#xff09;中用于 ​​自定义单元格内容显示格式​​ 的属性。它的核心作用是&#xff1a;将后端返回的原始数据&#xff08;如编码、状态值等&#xff09;转换为更友好、更易读的文本。 这段代码 :forma…

协程?协程与线程的区别?Java是否支持协程?

一、前言 协程&#xff08;Coroutine&#xff09; 是一种轻量级的并发编程模型&#xff0c;允许在单线程内通过协作式多任务调度实现并发。由用户代码显式控制&#xff08;用户态调度而非操作系统内核调度&#xff09;&#xff0c;避免了线程上下文切换的开销&#xff0c;适合…

Muduo网络库实现 [十六] - HttpServer模块

目录 设计思路 类的设计 模块的实现 公有接口 私有接口 疑问点 设计思路 本模块就是设计一个HttpServer模块&#xff0c;提供便携的搭建http协议的服务器的方法。那么这个模块需要如何设计呢&#xff1f; 这还需要从Http请求说起。 首先从http请求的请求行开始分析&…

关于进程状态

目录 进程的各种状态 运行状态 阻塞状态 挂起状态 linux中的进程状态、 进程状态查看 S状态&#xff08;浅睡眠&#xff09; t 状态&#xff08;追踪状态&#xff09; T状态&#xff08;暂停状态&#xff09; ​编辑 kill命令手册 D状态&#xff08;深度睡眠&#…

SQL注入 01

0x01 用户、脚本、数据库之间的关系 首先客户端发出了ID36的请求&#xff0c;脚本引擎收到后将ID36的请求先代入脚本的sql查询语句Select * from A where id 36 &#xff0c; 然后将此代入到数据库中进行查询&#xff0c;查到后将返回查询到的所有记录给脚本引擎&#xff0c;接…

学习笔记:黑马程序员JavaWeb开发教程(2025.3.24)

11.2 案例-文件上传-简介 火狐浏览器可以看到文件上传传递的底层数据&#xff0c;而chrome对这一块数据进行了包装 在输出日志代码处打了一个断点&#xff0c;看服务端接收到的数据&#xff0c;在上传文件的保存地址中&#xff0c;可以看到&#xff0c;有三个临时文件&…

计算机视觉cv2入门之视频处理

在我们进行计算机视觉任务时&#xff0c;经常会对视频中的图像进行操作&#xff0c;这里我来给大家分享一下&#xff0c;cv2对视频文件的操作方法。这里我们主要介绍cv2.VideoCapture函数的基本使用方法。 cv2.VideoCapture函数 当我们在使用cv2.VideoCapture函数时&#xff…

【Linux】Rhcsa复习5

一、Linux文件系统权限 1、文件的一般权限 文件权限针对三类对象进行定义&#xff1a; owner 属主&#xff0c;缩写u group 属组&#xff0c; 缩写g other 其他&#xff0c;缩写o 每个文件针对每类访问者定义了三种主要权限&#xff1a; r&#xff1a;read 读 w&…

FFmpeg:M3U8的AES加密

1、加密用的key&#xff0c;命令&#xff1a; openssl rand 16>enc.key 2、目的是生成一个enc.key文件 生成iv openssl rand -hex 16 生成后记录下来这个字符串 3、新建一个enc.keyinfo文件&#xff0c;内容有如下三行&#xff1a; key URIenc.key的路径&#xff0c;…

VMware虚拟机走主机代理上网

&#x1f310; VMware虚拟机走主机代理上网&#x1f511; 你是否也遇到过这样的困境&#xff1f;&#x1f4a1; 在虚拟机中测试某个项目&#xff0c;却因为网络限制而寸步难行。今天&#xff0c;就让我们一起探索如何让VMware虚拟机轻松调用本机的代理上网工具&#xff0c;开启…

Cursor入门教程-JetBrains过度向

Cursor使用笔记 **前置&#xff1a;**之前博主使用的是JetBrains的IDE&#xff0c;VSCode使用比较少&#xff0c;所以会尽量朝着JetBrains的使用习惯及样式去调整。 一、设置语言为中文 如果刚上手Cursor&#xff0c;那么肯定对Cursor中的众多选项配置项不熟悉&#xff0c;这…

Mac OS系统下kernel_task占用大量CPU资源导致系统卡顿

CPU负载突然飙升&#xff0c;如截图&#xff1a; 根本原因&#xff0c;大家从各种博主上已知晓&#xff0c;现在提供自己的解决办法&#xff0c;亲测有效 一、设置开机自动禁用温度管理守护进程 1.创建脚本文件 mkdir -p ~/Scripts touch ~/Scripts/disable_thermald.sh …

宝塔面板部署 Dify-latest 最新版本

一、本地部署Windows 版本宝塔面板 宝塔面板是一款简单容易上手使用的服务器管理软件&#xff0c;它可以帮助用户方便地管理服务器以及部署网站等。 &#xff08;1&#xff09;在宝塔面板官网的下载界面&#xff0c;选择 windows 版本下载。点此进入下载 &#xff08;2&#x…

《TCP/IP网络编程》学习笔记 | Chapter 24:制作 HTTP 服务器端

《TCP/IP网络编程》学习笔记 | Chapter 24&#xff1a;制作 HTTP 服务器端 《TCP/IP网络编程》学习笔记 | Chapter 24&#xff1a;制作 HTTP 服务器端HTTP 概要理解 Web 服务器端无状态的 Stateless 协议请求消息&#xff08;Request Message&#xff09;的结构响应消息&#x…

Origin将双Y轴柱状图升级为双向分组柱状图

当变量同时存在两个数值时的可视化时&#xff0c;往往会想到用双Y轴柱状图来表达我们的数据。 双Y轴柱状图是一种在同一图表中使用左右两个Y轴的可视化形式&#xff0c;常用于展示两组量纲不同或数值范围差异较大的数据。 双向分组柱状图是一种结合了双向柱状图和分组柱状图的…

测试基础笔记第六天

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、操作系统1.什么是操作系统2.操作系统的常见分类 二、linux系统1.linux发行版本2.文件和路径3.远程连接操作常见的远程连接工具远程连接基本步骤 三、linux命令操…