OSG图元重启动绘制

时间:2024-05-22 15:27:40

1.OpenGL图元重启动

图元重启功能,是OpenGL3.1版本以后支持的一项,快速绘制重复图元的技术。

当需要处理较大的顶点数据集的时候,我们可能会*执行大量的OpenGL绘制操作,并且每次绘制的内容总是与前一次图元的类型相同(例如GL_TRIANGLE_STRIP)。当然,我们可以使用glMultiDraw*()形式的函数,但是这样需要额外去管理图元的起始索引位置和长度的数组。

OpenGL支持在同一个渲染命令中进行图元重启动的功能,此时需要指定一个特殊的值,叫做图元重启动索引(primitive restart index),OpenGL内部会对它做特殊的处理。如果绘制调用过程中遇到了这个重启动索引,那么就会从这个索引之后的顶点开始,重新开始进行相同图元类型的渲染。图元重启动索引的定义是通过glPrimitiveRestartIndex()函数来完成的(引用《OpenGL编程指南8》)

使用到的主要函数:
void glPrimitiveRestartIndex(GLuint index);
同时,开启状图:
glDisable(GL_PRIMITIVE_RESTART);

如,需要绘制一个立方体的八个面,不使用图元重启动只需调用两次绘制命令,使用图元重启动,则一次就可完成:

// 设置使用glDrawElements绘制
glBindVertexArray(vao[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER<ebo[0]);

#if USE_PRIMITIVE_RESTART
// 如果启用了图元重启动,那么就需要调用一次绘制命令
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(0xFFFF);
glDrawElements(GL_TRIANGLE_STRIP,17,GL_UNSIGNED_SHORT,NULL);

#else 
// 如果没有开启图元重启动,需要调用两次绘制命令
glDrawElements(GL_TRIANGLE_STRIP,8,GL_UNSIGNED_SHORT,NULL);
glDrawElements(GL_TRIANGLE_STRIP,8,GL_UNSIGNED_SHORT,
			   (const GLvoid*)(9*sizeof(GLushort)));
#endif 

以上代码的关键,就是要在定义索引时,将0xFFFF插入到正常的索引值中,作为特殊标记来使用。

static const GLushort cube_indices[] =
{
0, 1, 2, 3, 6, 7, 4, 5, // 第一组条带
0xFFFF, // 图元重启标识符
2, 6, 0, 4, 1, 5, 3, 7 // 第二组条带
};

OSG图元重启动绘制

2. OSG 图元重启动绘制

对于OSG程序,osg库中有对glPrimitiveRestartIndex的封装,即:PrimitiveRestartIndex

但osg中确不能向OpenGL中那像使用,原因是osg要使用索引来访问节点,计算包围盒。如果使用0xFFFF作为标记,则程序出现访问越界。

为此,使用一个绘制地形网格线为例子,说明怎么使用图元重启:

  • 程序使用W:切换线框模式和填充模式,
  • left:增加x方向网格密度
  • right:减少x方向网格密度
  • up:增加y方向网格密度
  • down:减少y方向网格密度

效果如图:
OSG图元重启动绘制

	#include <osgViewer/viewer>
	
	#include <osg/PrimitiveSet>
	#include <osg/StateSet>
	#include <osg/LineWidth>
	#include <osg/PolygonMode>
	#include <osg/StateAttribute>
	#include <osg/PrimitiveRestartIndex>
	#include <osg/Geometry>
	#include <osg/Geode>
	#include <osg/array>
	#include <osg/Hint>
	#include <osg/Texture2D>
	#include <osgDB/ReadFile>
	#include <osgDB/FileUtils>
	#include <osgGA/TrackballManipulator>
	#include <osgGA/GUIEventHandler>

	#include <iostream>
	
	// 使用图元重启绘制网格
	// dimessionX: X方向上细分多少个点
	// dimessionY: Y方向上细分多少个点
	// width:  X方总向长度
	// height: Y方向总长度
	osg::Node* CreateGround(int dimensionX = 5, int dimensionY= 5, float width = 80, float height = 80)
	{
	    // 网格绘制,按照列序进行绘制,每一列为一组Quad_Strip,然后插入重启绘制标识符。
		float dx = width / (dimensionX - 1); // x方向,每段线的长度
		float dy = height / (dimensionY - 1); // y方向,每段线的长度
		float startX = - width / 2;
		float startY = - height / 2;
	    float endX =  - startX;
	    float endY = - startY;
	
		osg::ref_ptr<osg::Geode> node = new osg::Geode;
		osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
		osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
		osg::ref_ptr<osg::Vec2Array> coords = new osg::Vec2Array;
	  
	    // 索引数组个数
	    // 2*dimessionX * dimessionY 标识所有顶点都重复使用
	    // - dimensionY * 2  减掉 网格开始和结束的两条边界
	    // (dimessionX -2) 重启索引个数
	    int indicesSize = 2 * dimensionX * dimensionY - dimensionY * 2 + (dimensionX - 2);
		osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(osg::PrimitiveSet::QUAD_STRIP, indicesSize);
		unsigned int restartFlag = 0xFFFF;
		// 使用图元重启绘制网格
		/*	
		  X1 Y1  ----  X2,Y1
				|	|
				|	|
		 X1,Y2	----- X2,Y2
		*/
	
	    // calculate vertices 
	    for(int i = 0; i < dimensionX; ++i)
	    {
	        for(int j = 0; j < dimensionY; ++j)
	        {
	            float x1 = startX + dx * i;
	            float y1 = startY + dy * j;
	            osg::Vec3 pos1(x1, y1, 0);
	            vertices->push_back(pos1);
	            
	            osg::Vec2 coord(float(i)/(dimensionX - 1),float(j)/(dimensionY - 1));
	            coords->push_back(coord);
	        }
	    }
	    vertices->push_back(osg::Vec3(endX,endY,0));// 多存储一个顶点,用作因使用图元重启,而导致计算包围盒访问越界。
	    // calculate indices
	    // fist quad strip 
	    for (int j=0;j<dimensionY;j++)
	    {
	        (*indices)[j*2] = j;
	        (*indices)[j*2+1] = dimensionY+j;
	      
	        std::cout <<  j*2 <<"," <<  j*2+1 << "-->"<< j <<"," << dimensionY +j <<std::endl;
	    }
	    std::cout << std::endl;
	
		unsigned int restartIndex = 0;
		for (int i = 1; i < dimensionX - 1 ; ++i)  
		{
	        restartIndex += 2 * dimensionY;
	     // 重启索引所设置的标识符, 此处不能这么设置,必须设置为一个特殊的点,比如,最后一个顶点,因为osg在计算包围盒时,
	     // 会通过索引去访问顶点,若为0xFFFF,则出现访问越界,所以使用最后一个顶点
	    // (*indices)[restartIndex] = restartFlag;
	       (*indices)[restartIndex] = dimensionX * dimensionY;
	        restartIndex++; // 存储下一个有效的索引
			for (int j = 0; j < dimensionY; ++j) // 每次绘制一列的quad_strip
			{
			 
	            (*indices)[restartIndex + j*2] = dimensionY*i + j; // 计算当前顶点索引
	            (*indices)[restartIndex + j*2+1] = dimensionY*(i+1) + j;
	          
				std::cout << restartIndex + j*2 <<"," << restartIndex + j*2+1 << "-->"<< dimensionY*i + j <<"," << dimensionY*(i+1) + j<<std::endl;
			}
	        std::cout << std::endl;
		}
	 
		osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
		geometry->addPrimitiveSet(indices);
	    //geometry->addPrimitiveSet(new osg::DrawArrays(GL_QUAD_STRIP,0,dimensionX * dimensionY));
		geometry->setVertexArray(vertices);
		geometry->setTexCoordArray(0, coords, osg::Array::BIND_PER_PRIMITIVE_SET);
	
		normals->push_back(osg::Vec3d(0, 0, 1));
		geometry->setNormalArray(normals, osg::Array::BIND_PER_PRIMITIVE_SET);
	
		osg::StateSet* stateset = new osg::StateSet();
		osg::PolygonMode* pm = new osg::PolygonMode(osg::PolygonMode::Face::FRONT_AND_BACK, osg::PolygonMode::Mode::LINE);
		stateset->setAttributeAndModes(pm, osg::StateAttribute::ON);
		stateset->setAttributeAndModes(new osg::LineWidth(1.5f), osg::StateAttribute::ON);
	
		stateset->setMode(GL_LINE_SMOOTH, osg::StateAttribute::ON);
		stateset->setMode(GL_POLYGON_SMOOTH, osg::StateAttribute::ON);
	
		stateset->setAttributeAndModes(new osg::Hint(GL_LINE_SMOOTH_HINT, GL_NICEST), osg::StateAttribute::ON);
		stateset->setAttributeAndModes(new osg::Hint(GL_POLYGON_SMOOTH_HINT, GL_NICEST), osg::StateAttribute::ON);
	
	    stateset->setAttributeAndModes(new osg::PrimitiveRestartIndex(dimensionX * dimensionY), osg::StateAttribute::ON);
	    stateset->setMode(GL_PRIMITIVE_RESTART, osg::StateAttribute::ON);
		std::string fileName = "Images/primitives.gif";
		osg::Image* image = osgDB::readImageFile(fileName);
		osg::Texture2D* tex = new osg::Texture2D;
		tex->setImage(image);
		stateset->setTextureAttributeAndModes(0, tex,osg::StateAttribute::ON);
		geometry->setStateSet(stateset);
		geometry->setUseVertexBufferObjects(true);
	 
		node->addDrawable(geometry);
	
		return node.release();
	}

	class keyBoardHandler :public osgGA::GUIEventHandler
	{
	public:
	    keyBoardHandler(osg::Group* groundRoot):m_ground(groundRoot),m_dimessionX(2),m_dimessionY(2)
	    {
	    }
	
	    virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*) 
	    {
	
	        if(ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
	        {
	            if(ea.getKey() == osgGA::GUIEventAdapter::KEY_W)
	            {
	                osg::Geode* geode = m_ground->getChild(0)->asGeode();
	                if(geode)
	                {
	
	                    osg::Geometry* geom = geode->getDrawable(0)->asGeometry();
	                    if(geom)
	                    {
	                        osg::StateAttribute* attrib = geom->getOrCreateStateSet()->getAttribute(osg::StateAttribute::POLYGONMODE);
	                        osg::PolygonMode* pm = dynamic_cast<osg::PolygonMode*>(attrib);
	                        if(pm)
	                        {
	                            osg::PolygonMode::Mode mode =  pm->getMode(osg::PolygonMode::Face::FRONT_AND_BACK);
	                            if(mode == osg::PolygonMode::Mode::LINE)
	                            {
	                                pm->setMode(osg::PolygonMode::Face::FRONT_AND_BACK, osg::PolygonMode::Mode::FILL);
	                            }
	                            else
	                            {
	                                pm->setMode(osg::PolygonMode::Face::FRONT_AND_BACK, osg::PolygonMode::Mode::LINE);
	                            }
	                        }
	                    }
	                }
	                return true;
	            }
	
	            else if(ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
	            {
	                m_dimessionY++;
	                ChangeLines(m_dimessionX,m_dimessionY);
	                return true;
	            }
	            else if(ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)
	            {
	                m_dimessionY--;
	                ChangeLines(m_dimessionX,m_dimessionY);
	                return true;
	
	            }
	            else if(ea.getKey() == osgGA::GUIEventAdapter::KEY_Left)
	            {
	                m_dimessionX++;
	                ChangeLines(m_dimessionX,m_dimessionY);
	                return true;
	            }
	            else if(ea.getKey() == osgGA::GUIEventAdapter::KEY_Right)
	            {
	                m_dimessionX--;
	                ChangeLines(m_dimessionX,m_dimessionY);
	            }
	        }
	        return false;
	    }
	
	    void ChangeLines(int x, int y)
	    {
	        if(x <2) x = 2;
	        if(y < 2) y  = 2;
	        m_ground->removeChild(0,1);
	        m_ground->addChild(CreateGround(x,y));
	    }
	private:
	    osg::ref_ptr<osg::Group> m_ground;
	    int m_dimessionX;
	    int m_dimessionY;
	};

	void main()
	{
	    osgViewer::Viewer viewer;
	    osg::Group* root = new osg::Group;
	    osg::Node* pNode = osgDB::readNodeFile("cow.osg");
	   
	    root->addChild(pNode);
	    osg::Group* ground = new osg::Group;
	    osg::Node* groundMesh = CreateGround(13,3);
	    ground->addChild(groundMesh);
	    root->addChild(ground);
	
	    viewer.setSceneData(root);
	    viewer.setUpViewInWindow(10,10,800,600);
	    //viewer.addEventHandler(new osgGA::TrackballManipulator);
	    viewer.addEventHandler(new keyBoardHandler(ground));
	    viewer.run();
	}

referenced:
https://blog.****.net/yulinxx/article/details/77896541
https://blog.****.net/qq_36665171/article/details/81459338