OGLES图元和绘制函数详细总结

    xiaoxiao2021-03-25  104

    图元的类型点,直线,三角形。

    绘图模式

    //点精灵,顶点着色器输出gl_PointSize指定半径,片段着色器中用gl_PointCoord来指定坐标,坐标是左上为原点和OGL屏幕坐标系(左下为原点)相反所以用gl_PointCoord指定纹理时候要小心看是否要转换,uv坐标都是基于屏幕坐标系的,但是一般顶点信息处理会根据选择DX还是OGL处理好顶点uv传入都是正确的。 #define GL_POINTS 0x0000 #define GL_LINES 0x0001 // 单独 #define GL_LINE_LOOP 0x0002 // 首尾相连 #define GL_LINE_STRIP 0x0003 // v0v1v2v3连续相连,首尾不相连 #define GL_TRIANGLES 0x0004 // 单独三角形 #define GL_TRIANGLE_STRIP 0x0005 // 连续相连,以最末的顶点为起点,使用最多 #define GL_TRIANGLE_FAN 0x0006 // 连续相连,以开始的顶点为起点 这些绘图模式的使用,具体的绕序是和正面方向一致的,也就是右手定则的绕序。 drawCall绘制图元函数有: glDrawArrays glDrawElements glDrawRangeElements glDrawArraysInstanced glDrawElementsInstanced 这些绘制函数,将从顶点数组glVertexAttribPointer从CPU传递到GPU中的,或存在VAO索引和VBO中的(全局指针GL_ARRAY_BUFFER和GL_ELEMENT_ARRAY_BUFFER指向的数据)数据,作为顶点输入调用一次drawcall驱动图形管线进行一次渲染。

    1.glDrawArrays

    有一系列顺序元素索引描述的图元,且几何形状的顶点不共享,则glDrawArrays很好用,较少使用。 glDrawArrays (GLenum mode, GLint first, GLsizei count); 例如: glDrawArrays(GL_TRIANGLE_STRIP, 0, 5);将根据GL_TRIANGLE_STRIP顶点的链接方式,绘制顶点下标从first到first+count-1的顶点下标也就是0到4,绘制三角形为(012)(213)(234)。

    2.glDrawElements

    游戏或3D应用程序一般使用三角形网格,glDrawElements可以处理典型对象由多个三角形网格组成,其中的元素索引可能不一定按照顺序,顶点通常在网格三角形之间共享,因为共享所以glDrawElements更加高效。 给出顶点缓冲和索引缓存,glDrawElements会根据索引缓冲,右手定则的绕序来绘制三角形网格。 glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); 绘图模式mode,count是索引元素个数,type是GL_UNSIGNED_SHORT, indices索引缓冲指针如果使用了VBO索引缓冲,那么可以传递NULL。 绘制一个立方体为: GLfloat vertexPos[3 * VERTEX_POS_SIZE] = { 0.0f, 0.5f, 0.0f, // v0 -0.5f, -0.5f, 0.0f, // v1 0.5f, -0.5f, 0.0f // v2 }; GLushort indices[3] = { 0, 1, 2 }; glDrawElements ( GL_TRIANGLES, sizeof(indices)/sizeof(GLushort), GL_UNSIGNED_SHORT, indices ); 用VBO索引缓冲,则渲染调用为: glDrawElements ( GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, NULL); 绘制图元函数有: glDrawArrays glDrawElements glDrawRangeElements glDrawArraysInstanced glDrawElementsInstanced sizeof(indices)/sizeof(GLubyte)

    3.glDrawRangeElements函数

    glDrawRangeElements和glDrawElements一样,就是多了start和end参数限制,start/end是最小/最大索引值,使用end - start + 1数量的顶点。限定了范围OGL可以提前获得数据范围,可以提高性能。 glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); glDrawRangeElements实例: Lfloat vertices[] = {...}; // 8 of vertex coords GLubyte indices[] = {0,1,2, 2,3,0, // first half (18 indices) 0,3,4, 4,5,0, 0,5,6, 6,1,0, 1,6,7, 7,2,1, // second half (18 indices) 7,4,3, 3,2,7, 4,7,6, 6,5,4}; ... // activate and specify pointer to vertex array glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, vertices); // draw first half, range is 6 - 0 + 1 = 7 vertices used glDrawRangeElements(GL_TRIANGLES, 0, 6, 18, GL_UNSIGNED_BYTE, indices); // draw second half, range is 7 - 1 + 1 = 7 vertices used glDrawRangeElements(GL_TRIANGLES, 1, 7, 18, GL_UNSIGNED_BYTE, indices+18); // deactivate vertex arrays after drawing glDisableClientState(GL_VERTEX_ARRAY);

    4.glDrawArraysInstanced和glDrawElementsInstanced几何形状实例化

    几何形状实例化很高效, 可以用一次drawCall API调用实现多次渲染(同一个顶点执行多少次vertex shader,每次就是一次实例化)具有不同属性(矩阵,颜色,大小)的对象,很有效的减少了drawcall且CPU只用设置改变部分非改变部分不用重复传递数据和调用开销,但是纹理材质顶点信息类似才能使用。 函数原型: glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); first指定启用顶点数组中的起始顶点索引。 count指定绘制的索引数量。 type指定保存在indices中的元素索引类型。 indices指定元素索引存储的位置的一个指针。 indicescount指定绘制的图元实例数量。其实就是glDrawArrays和glDrawElements函数原型后面添加了indicescount。 同一个类似对象要绘制多个实例,需要指定类似的方面,例如实例之间颜色不同或者位置不同,指定方式有两种。 第一种方法是:glVertexAttribDivisor glVertexAttribDivisor (GLuint index, GLuint divisor);index是顶点属性索引下标例如颜色下标,divisor是多少个实例之间更新一次读取该顶点属性上数据,0则是每个顶点读取一次且属性下标内数据位置都相同(默认是0),1是每个实例读取一次属性下标内数据位置会增1,2是每两个实例读取一次性下标内数据位置会增1。在这里顶点属性被glVertexAttribDivisor 第二种方法是使用内建变量gl_InstanceID作为顶点着色器中的缓冲区索引,以访问每个实例的数据。 使用glDrawArraysInstanced和glDrawElementsInstanced 时gl_InstanceID将保存当前图元实例的索引,否则gl_InstanceID是0。 例如: // 顶点属性被升格为每个实例读取一次,且内部维护一个指针,一个drawcall内下次实例读取从该顶点属性下标的内部数组的下个位置开始读取,作为顶点颜色输入。 glVertexAttribDivisor ( COLOR_LOC, 1 ); // One color per instance // One MVP per instance,內部维护一个指针,一个drawcall内多次vertexShader中的下次实例读取将从该顶点属性索引的下个ESMatrix开始的0,1,2,3向量作为实例输入。 glVertexAttribDivisor ( MVP_LOC + 0, 1 ); glVertexAttribDivisor ( MVP_LOC + 1, 1 ); glVertexAttribDivisor ( MVP_LOC + 2, 1 ); glVertexAttribDivisor ( MVP_LOC + 3, 1 ); OGL要获取变换矩阵还要自己封装(没有DX直接)例如: esTranslate ( ESMatrix *result, GLfloat tx, GLfloat ty, GLfloat tz ) { result->m[3][0] += ( result->m[0][0] * tx + result->m[1][0] * ty + result->m[2][0] * tz ); result->m[3][1] += ( result->m[0][1] * tx + result->m[1][1] * ty + result->m[2][1] * tz ); result->m[3][2] += ( result->m[0][2] * tx + result->m[1][2] * ty + result->m[2][2] * tz ); result->m[3][3] += ( result->m[0][3] * tx + result->m[1][3] * ty + result->m[2][3] * tz ); } 大于一个vec4的数据用顶点属性传递到Shader中要拆分为vec4,例如矩阵: shader和VBO中为: "layout(location = 2) in mat4 a_mvpMatrix; \n" glGenBuffers ( 1, &userData->mvpVBO ); glBindBuffer ( GL_ARRAY_BUFFER, userData->mvpVBO ); glBufferData ( GL_ARRAY_BUFFER, NUM_INSTANCES * sizeof ( ESMatrix ), NULL, GL_DYNAMIC_DRAW ); 顶点解析指定为: // Load each matrix row of the MVP. Each row gets an increasing attribute location. glVertexAttribPointer ( MVP_LOC + 0, 4, GL_FLOAT, GL_FALSE, sizeof ( ESMatrix ), ( const void * ) NULL ); glVertexAttribPointer ( MVP_LOC + 1, 4, GL_FLOAT, GL_FALSE, sizeof ( ESMatrix ), ( const void * ) ( sizeof ( GLfloat ) * 4 ) ); glVertexAttribPointer ( MVP_LOC + 2, 4, GL_FLOAT, GL_FALSE, sizeof ( ESMatrix ), ( const void * ) ( sizeof ( GLfloat ) * 8 ) ); glVertexAttribPointer ( MVP_LOC + 3, 4, GL_FLOAT, GL_FALSE, sizeof ( ESMatrix ), ( const void * ) ( sizeof ( GLfloat ) * 12 ) ); glEnableVertexAttribArray ( MVP_LOC + 0 ); glEnableVertexAttribArray ( MVP_LOC + 1 ); glEnableVertexAttribArray ( MVP_LOC + 2 ); glEnableVertexAttribArray ( MVP_LOC + 3 ); // One MVP per instance glVertexAttribDivisor ( MVP_LOC + 0, 1 ); glVertexAttribDivisor ( MVP_LOC + 1, 1 ); glVertexAttribDivisor ( MVP_LOC + 2, 1 ); glVertexAttribDivisor ( MVP_LOC + 3, 1 );

    5.合并三角网格,合并drawcall,提升性能

    图元重启合并drawcall 用glDrawElements*可以使用图元重启。 在一个drawcall中可以渲染多个不相连的图元,通过索引数据IBO合并,再通过VBO缓冲顶点信息合并,纹理图片合并为大图,uv重新生成,可以合并drawcall, 提高渲染效率。 图元重启实例: 两个顶点索引缓冲为(0,1,2,3)和(8,9,10,11),使用图元重启为: (0,1,2,3,255,8,9,10,11),255是GL_UNSIGNED_BYTE类型最大数据,如果是GL_UNSIGNED_SHORT则是65535,这样通过glDrawElements*就可以绘制两个三角网格信息。 使用退化三角形 drawcall应该都可以使用退化三角形合并三角带提高渲染效率,对于图元重启无法实现的,两个不同分离的三角带之间需要插入退化三角形实现网格合并。退化三角形可以很容易被GPU拒绝,所以使用退化三角形可很高效。退化三角形的网格合并,关键是重新构造IBO(三角带很可能没有索引缓冲则需要重新排列顶点缓冲的顺序),构造方法是: 1)三角网可以用多个三角带表示,三角带是顺时针逆时针切换的,且当前三角形与上一个三角形共享一边。 2)使用现有的顶点,构造的三角形是退化的 3)三角形绕序跳转要前后确保一致,且能衔接上 例如: 索引下标顺时针(012)逆时针(213)顺时针(233)和顺时针(8910)逆时针(10911)两个三角带的网格链接,引入退化三角形为: 顺时针(012)逆时针(213) 顺时针(233)逆时针(338)顺时针(388)逆时针(889)顺时针(8910)逆时针(10911)。 (0,1,2,3,4)和(8,9,10,11)两个三角带网格链接,引入退化三角形为: 顺时针(012)逆时针(213)顺时针(234) 逆时针(434)顺时针(444)逆时针(448)顺时针(488)逆时针(889)顺时针(8910)逆时针(10911)。 构造规则是在两个三角带之间插入各自链接的顶点,如果链接之间原来是相反的顺序,则插入4个退化三角形即可,如果链接之间原来是相同的顺序,则插入5个退化三角形,中间插入的顶点自身作为一个三角形的顶点。 大部分GPU使用变换后顶点缓存,在顶点着色器执行顶点(由元素索引给出)之前,进行一次检查,以确定顶点是否已经存在于变换后缓存。 如果顶点存在于变换后缓存,则顶点着色器不执行顶点,如果顶点不在缓存中,则顶点着色器需要执行顶点。使用变换后缓存的大小来确定元素索引的创建方式有助于提升总体性能,因为这将减少顶点着色器执行重用顶点的次数。

    6.图元装配裁剪剔除和光栅化处理

    图元装配 图元顶点在vertexshader输出后会根据drawcall指定的图元类型装配为图元,然后对图元进行裁剪。 图元裁剪 裁剪刚好在4D裁剪空间,裁剪方程组为: -w <=x<=w -w <=y<=w -w <=z<=w 因为变换到设备坐标系中刚好是[-1,1]所以透视除法是除以w属于[-1,1]内的顶点才接纳, 所以这里的-w,w刚好是z值,也就是说[-zn,-zf]内的z值都满足要求,x和y值需要看是否在当前顶点的[-z,z], 也就是裁剪方程组的w是当前顶点的z值。GPU可以快速的实现对基础图元(三角形,直线,点)的裁剪操作。 背面剔除 背面剔除阶段在当今图形硬件中是在屏幕坐标系中光栅化之前根据三角形绕序进行的(自己设置的渲染顺序是需要的,非需要的就剔除),否则需要表面法向量和摄像机到表面的向量,点积大于0则是背面。 指定三角形的正面用: glFrontFace(GL_CCW);//GL_CW 默认值是GL_CCW也就是逆时针方向,DX是顺时针方向 指定裁剪面为: glCullFace(GL_BACK);// GL_FRONT默认是GL_BACK 还要设置启用背面剔除: glEnable(GL_CULL_FACE) glDisable(GL_CULL_FACE);//默认是禁用的,应该启用,因为启用可以改善性能 多边形偏移 在光栅化阶段应用。 是为了解决深度值由于精度冲突穿插导致的图像错乱,引入多边形偏移是在将要渲染的多边形中将其深度值增加一个偏移使得深度更深或更浅的图元被完整绘制出来,通过深度测试后,该深度值会写入深度缓冲而不会加上偏移。 偏移函数为:glPolygonOffset(GLfloat factor, GLfloat units); 偏移量 = m*factor + r*units; m是最大斜率由光栅化期间OES计算是一个正数,r是OES实现定义的常量该常量能保证产生差异的最小值。 遮挡查询 用于镜头炫光特效的可见性检测等任何避免被遮挡的不可见对象上进行图形渲染的优化。 void glGenQueries(GLsizei n, GLuint *ids); void glDeleteQueries(GLsizei n, const GLuint *ids); void glBeginQuery(GLenum target,GLunit id); void glEndQuery(GLenum target); void glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params);指定存储返回参数值的对应类型的数组,将包含了通过深度测试的片段或样本(如果启用了多重采样)的数量,如果数量为0,则表示这个物体完全被遮挡。在完成遮挡查询操作时,可能会有延迟。我们可以通过设置pname为GL_QUERY_RESULT_AVAILABLE来检查是否完成了。如果遮挡查询有效地完成了,则param将为GL_TRUE,否则为GL_FALSE.
    转载请注明原文地址: https://ju.6miu.com/read-35216.html

    最新回复(0)