GPU可编程渲染的矢量点要素符号三维可视化

来源:互联网 发布:内存卡检测软件 编辑:程序博客网 时间:2024/04/30 18:44
图形实时渲染是典型的CPU+GPU异构并行任务。传统三维图形管线的渲染由CPU负责输入和定义渲染规则,GPU固定渲染管线负责图形绘制。这种固定的处理模式虽然简化了开发流程,但难以实现复杂效果的三维模拟,束缚了三维图形的表达效果。GPU可编程特性改变了固定图形流水线的模式,实现了面向顶点与像素的编程,使得图形渲染更加灵活和高效。OpenGL 2.0增加了可编程渲染管线,用OpenGL着色语言控制顶点和片段的处理,通过这种更为灵活的图形硬件控制,满足三维图形特效开发的需要。以下为GPU可编程渲染的流程图

纹理映射符号法


(一)逐矢量要素的纹理映射的方法

对场景中的节点(Node)或几何体(Geometry)应用纹理属性时,通常需要预先指定每个顶点的纹理坐标,以便将图像正确地贴至物体上。只有几何体可以设置纹理坐标,因此在设置节点的纹理坐标之前,必须得到该节点子树中所有的几何体对象并设置它们的纹理坐标。为几何体对象geom的纹理单元0设置纹理坐标的具体代码如下:
osg::ref_ptr<osg::Vec2Array> texcoords = newosg::Vec2Array;texcoords->push_back(…);…                                   Geom->setTexCoordArray(0,texcoords.get());

其中,texcoords为纹理坐标,Geom为几何体对象,setTexCoordArray()用于设置几何体对象的纹理坐标数组。
上述方式虽然能实现矢量要素的符号化,但由于此方法每次只能将单个矢量要素进行纹理映射,当加载海量矢量要素时,绘制效率受到影响。


(二)着色器阶段实现矢量要素纹理映射的方法

分析上述方法存在的不足,在此基础上,本文提出了在着色器阶段基于纹理映射实现矢量要素符号化的方法。此方法的主要思想在于利用GPU的高速计算能力和可编程的功能,将纹理映射在着色器阶段实现。采用并行计算能力达到本文预期实现的矢量要素高效符号化的结果。

(1) 顶点着色器主要实现顶点位置的传入与将一些用于后期编程的主要参数的传入(符号尺寸、旋转角度、符号类型等)。具体如下:

gl_TexCoord[0] = vec4(a_size,a_rotation,a_markerType,1.0); gl_Position = gl_Vertex;
   其中,a_size,a_rotation,a_markerType为传入参数(符号尺寸、旋转角度、符号类型等),将其传入纹理坐标数组中;并将所有的顶点坐标数据并行地加入顶点着色器。

(2) 几何着色器主要实现顶点坐标的计算,纹理坐标的设置,并将点要素根据传入参数拓展成一个矩形。
根据传入参数计算拓展成的几何体的具体算法如下:

     float r= sqrt(earthHeart.x*earthHeart.x+earthHeart.y*earthHeart.y+earthHeart.z*earthHeart.z);                           floata = gl_PositionIn[0].x-earthHeart.x;                           floatb = gl_PositionIn[0].y-earthHeart.y;                           floatc = gl_PositionIn[0].z-earthHeart.z;                           floatw = gl_PositionIn[0].w;                           floats = gl_TexCoordIn[0][0].x/30000;                           floatra = radians(gl_TexCoordIn[0][0].y-45);                           floatrb = radians(gl_TexCoordIn[0][0].y+45);                           floatrc = radians(gl_TexCoordIn[0][0].y+135);                           floatrd = radians(gl_TexCoordIn[0][0].y+225);                           floatmarkerType = gl_TexCoordIn[0][0].z;
   其中,earthHeart为地球球心的数组,ra,rb,rc,rd分别为所需矩形的四个顶点坐标的角度,并计算球心至矢量要素所在位置的距离,存入变量r中。

接下来对每个顶点数据进行图元处理,确定整个渲染模型的形状。以下以其中一个顶点数据为例,实现在片元着色器之前创建新的顶点和形状。

     gl_TexCoord[0]= vec4(1, 1, markerType, 1);                                  gl_Position= gl_ModelViewProjectionMatrix * vec4(gl_PositionIn[0].x+s/(2*sqrt((r*r)/(b*b)-1))*(-a*cos(rc)+r*c/b*sin(rc)),gl_PositionIn[0].y+s/(2*sqrt((r*r)/(b*b)-1))*((r*r)/b-b)*cos(rc),gl_PositionIn[0].z+s/(2*sqrt((r*r)/(b*b)-1))*(-c*cos(rc)-r*a/b*sin(rc)),w);                                  gl_FrontColor= gl_FrontColorIn[0];                                   EmitVertex();

其中,gl_TexCoord[0]为纹理坐标,gl_Position为地理坐标,gl_FrontColor为正面颜色,通过顶点坐标传入的顶点坐标数组以及旋转角度参数,经过旋转变换、投影变换等,求得新的顶点的地理坐标;并通过将纹理坐标绑定到相应的地理坐标上,为之后的纹理贴图做好准备;EmitVertex()方法将新产生的顶点加入到几何着色器中。
以上步骤就完成了一个顶点的创建,通过重复上述步骤,新建多个顶点并将其加入到几何着色器中,从而实现点形成面的过程。
(3) 片元着色器是实现矢量要素符号化的核心阶段,也是实现纹理映射的关键。具体步骤如下:
i. 二维纹理的创建,具体实现如下:

osg::ref_ptr< osg::Image> iLogo =osgDB::readImageFile( Images/Buoy.png );             osg::Texture2D*texLogo = new osg::Texture2D( iLogo.get() );             texLogo->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR );             texLogo->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR );osg::ref_ptr< osg::Uniform > texLogoUniform=                    newosg::Uniform( tex, 0 );

以上步骤通过图像(Image)实现创建二维纹理,并且设置二维纹理的参数,将此二维纹理添加入统一变量(Uniform),便于在片元着色器中调用。
ii. 片元着色器中调用二维纹理,具体实现如下:

uniform sampler2D tex;vec4 color = texture2D(tex,gl_TexCoord[0].st);gl_FragColor = color;

其中,texture2D()方法实现将纹理坐标(gl_TexCoord[0])与二维纹理(tex)进行绑定,并将其存入颜色的四维数组(color),赋给OpenGL的内置变量gl_FragColor。从而实现纹理映射。
基于几何的方法将矢量数据与地形网格紧密关联起来,在地形起伏状况比较大的情况下,要求矢量数据附着于地形表面,需要解决的问题在于地物与地形相匹配。如果地物与地形不匹配,则可能会出现地物悬浮在地形上方或陷入地表等与真实世界不相符的状况。国内外相关的研究集中在解决矢量数据与地形表面精确匹配的问题,其效果大都不太理想,究其原因,在于矢量数据转换到地形中渲染时,在同一个层次下矢量数据的顶点不能很好的离散插值到地形表面上。


几何符号法


(一) 逐矢量要素几何构建的方法
逐矢量要素几何构建的方法的实现流程:遍历整个矢量数据集,单个数据构建单独的叶节点(Geode),并逐个加入到场景树中,具体实现过程如下所示:

for( FeatureList::iterator f = features.begin(); f!= features.end(); ++f )             {                    Feature*input = f->get();                    GeometryIteratorparts( input->getGeometry(), true );                    while(parts.hasMore() )                    {                           Geometry*part = parts.next();                           osg::ref_ptr<osg::Geometry>osgGeom = new osg::Geometry();                           osg::Vec3Array*allPoints = new osg::Vec3Array();                           osgGeom->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, allPoints->getNumFeature()) );                           osgGeom->setVertexArray(allPoints );                           geode->addDrawable(osgGeom );                    }             }

其中,features为矢量要素集,osgGeom为几何体对象,geode 为叶节点,setVertexArray()用于设置几何体对象的点要素数组,addDrawable()用于添加几何体对象到叶节点中。
在现有OsgEarth框架下,每当获取一次矢量数据源生成相应的几何体(Geometry)后,将几何体加入到子节点(Geode)中,然后将节点添加入场景树。此方法当数据量较大时,循环次数随之增加,必然会导致内存压力,在渲染速度上迅速下降,而且浏览过程中会导致卡顿现象,效率不能达到本文所预期的效果。


(二) 着色器阶段实现几何符号化的方法
本文在此基础上,结合实际需求,考虑矢量要素的多形式表达,研究设计典型的符号库,从而达到支持非制图工作者便捷使用地图符号的目的。此方法的主要思想在于利用GPU的高速计算能力和可编程的功能,将纹理映射在着色器阶段实现,通过在着色器建立不同符号的计算模型,从而实现矢量要素的多种符号化表达。
根据上一节中GPU加速的基于纹理的矢量要素符号化方法中关于着色器的实现流程,本文已实现在几何着色器中,将符号化形状的参数(markerType)传入到纹理坐标数组中,接下来,将会在片元着色器中通过逐像素着色的原理实现特定符号的形状,并附着特定颜色。
本文实现了常见类型形状的着色,包括正方形、圆形、三角形、五边形等,接下来以简单的图形符号(三角形)为例,介绍其实现符号化的主要原理,具体实现如下:

if(markerType>=1.5&&markerType<2.5){                                  if((0.5-gl_TexCoord[0].x)*2-gl_TexCoord[0].y>=0 ||(gl_TexCoord[0].x-0.5)*2-gl_TexCoord[0].y>=0)                                         discard;                           }

其中markerType为传入的符号化形状的参数,discard用于将像素丢弃,不进行渲染。通过判断像素是否在形状区域内部,剔除外部像素,从而达到符号化的效果。


下图为使用此方法的多几何形状的符号化结果。
这里写图片描述

1 0