OpenGL ES 3.0 技术总结(一):多实例渲染

举报
大数据小粉 发表于 2017/04/10 15:38:55 2017/04/10
【摘要】 我们的视讯终端类产品不可避免的要用到图形界面,而图形界面的底层GUI引擎必然越来越少不了图形硬件加速功能,而谈到图形硬件加速功能,必然就是OpenGL ES了。

我们的视讯终端类产品不可避免的要用到图形界面,而图形界面的底层GUI引擎必然越来越少不了图形硬件加速功能,而谈到图形硬件加速功能,必然就是OpenGL ES了。 我们TWS界面引擎平台当前应用的是OpenGL ES 2.0,而OpenGL ES最新的版本是3.0,目前Android和iOS已经支持OpenGL ES3.0了,这也是今后的发展方向,大势所趋,想躲都躲不开。
OpenGL ES 3.0新增了大量的新特性,其中的多实例渲染是很重要的一个。大型3D图形软件经常需要渲染的一个场景是:场景中同时存在有大量的物体需要渲染,这些物体的位置、大小、角度、颜色、纹理各不相同,但是这些物体本身具有相同的网格(mesh);举个例子,看看下面这张图。图中有上千个矮人角色,他们用同一个网格构造,却有着不同位置、大小、姿态、颜色及纹理。

图1 多实例动画(选自GPU Gems 3)

想一想在OpenGL ES 2.0时代,我们要渲染这样的一个场景,标准步骤是这样的:


for (/*每一个物体*/)
{
/*设置投影模型视图变换距阵(Project-View-Modal Matrix)*/
int i32Location = glGetUniformLocation(m_uiProgramObject, "myPMVMatrix");
glUniformMatrix4fv( i32Location, 1, GL_FALSE, aPMVMatrix);

/*设置模型视图变换距阵(View-Modal Matrix)*/
i32Location = glGetUniformLocation(m_uiProgramObject, "myModelViewIT");
glUniformMatrix3fv( i32Location, 1, GL_FALSE, aModelViewIT);

/*设置光线向量*/
i32Location = glGetUniformLocation(m_uiProgramObject, "myLightDirection");
glUniform3f( i32Location, 0, 0, 1);

/*设置纹理对象*/
glBindTexture(GL_TEXTURE_2D, g_Covers[coverIndex].ui32TexID);

/*绑定顶点缓冲区对象VBO*/
glBindBuffer(GL_ARRAY_BUFFER, m_ui32Vbo);

/*设置顶点属性变量,位置,纹理坐标,法线向量*/
glEnableVertexAttribArray(VERTEX_ARRAY);
glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, m_ui32VertexStride, 0);
glEnableVertexAttribArray(TEXCOORD_ARRAY);
glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, m_ui32VertexStride, (void*) (3 * sizeof(GLfloat)) /* Uvs start after the vertex position */);
glEnableVertexAttribArray(NORMAL_ARRAY);
glVertexAttribPointer(NORMAL_ARRAY, 3, GL_FLOAT, GL_FALSE, m_ui32VertexStride, (void*) (5 * sizeof(GLfloat)) /* Normals start after the position and uvs */);

/*绘制Mesh*/
glDrawArrays(GL_TRIANGLES, 0, ulVertexNum);

/*取消绑定*/
glBindBuffer(GL_ARRAY_BUFFER, 0);
}

当物体的个数越来越多时,由于gl函数调用的次数成倍增多,性能下降的就会越来越厉害。当然可以通过CPU端的遮挡查询和LOD层次模型进行优化,但是总的来说,可优化的空间不大。
OpenGL ES 3.0的多实例渲染特性引入了两个函数:
void glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
void glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei primcount);
分别对应OpenGL ES 2.0的glDrawArrays()和glDrawElements(),唯一的差别就是最后的参数primcount。primcount代表的实例个数,而glDrawArraysInstanced()和glDrawElementsInstanced()的含义就是针对每一个实例绘制一遍网格,共绘制primcount遍。
这有什么用?在同一位置绘制了N遍,那和绘制一遍的效果是一模一样的。
于是,OpenGL ES3.0在它的顶点着色器中增加了一个内置输入变量:
gl_InstanceID,表示实例索引。于是,我们可以在顶点着色器中使用该变量区分是哪个实例,从而得到相应的变换距阵、光线矢量(可优化成一致变量)、颜色、纹理等等数据,从而可以在一次绘制中完成整个场景渲染。过程如下:
/*第一步:把变换距阵,光线矢量等等所有的一致变量打包设置到着色器中*/
……;
/*第二步:绘制多实例(n个)*/
glDrawArraysInstanced(GL_TRIANGLES, 0, ulVertexNum, n);
着色器中也需要相应修改。如此,极大减少了gl调用次数,性能会有极大提升。如果你够细心,可能会发现一个问题,那就是第一步不好实现,因为一般的GPU对uniform变量的个数是有限制的,当实例一多起来,打的包太大,根本放不下。对此有两种解法:
(1) 使用实例属性变量(vertex attribute divisor);
(2) 使用一致缓冲区对象(uniform buffer objects);
这两个都是OpenGL ES 3.0新增特性,具体待续。

作者 | 李常民

转载请注明出处:华为云博客 https://portal.hwclouds.com/blogs

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。