最近美术想了一个需求,想要实现全屏同时播放上千个粒子系统。这个需求确实挺疯狂的,因为对于CPU粒子来说,改动很大;而对于GPU粒子来说基本不可能(?)。老大对于这个需求非常质疑,我本人倒是很感兴趣,在寻找渲染实例化对象的实例化的(InstancedBaseInstance)方法的时候,发现高版本openGL的很多有意思的地方。在本文,总结一下高版本openGL draw call的类型。

Direct rendering

       Direct rendering和其他drawcall类型最显著的区别,就是Direct rendering的所有数据都是从CPU传来的,而其他的渲染方式都有数据是从openGl object(GPU)传来。

Basic Drawing

void glDrawArrays( GLenum mode​, GLint first​, GLsizei count​ );
void glDrawElements( GLenum mode​, GLsizei count​, GLenum type​, void * indices​ );

Multi-Draw

       创建或者修改VAO都是一个很费的操作,因此有了这个功能,在一个VAO中存储多个mesh,然后批量绘制(batch)

void glMultiDrawArrays( GLenum mode​, GLint *first​, GLsizei *count​, GLsizei primcount​);
void glMultiDrawElements( GLenum mode​, GLsizei *count​, GLenum type​, void **indices​, GLsizei primcount​ );

       实际上相当于

void glMultiDrawArrays( GLenum mode, GLint *first, GLsizei *count, GLsizei primcount )
{
	for (int i = 0; i < primcount; i++)
	{
		if (count[i] > 0)
			glDrawArrays(mode, first[i], count[i]);
	}
}

void glMultiDrawElements( GLenum mode, GLsizei *count, GLenum type, void **indices, GLsizei primcount )
{
	for (int i = 0; i < primcount; i++)
	{
		if (count[i]) > 0)
			glDrawElements(mode, count[i], type, indices[i]);
	}
}

Multi-Draw所有物体使用相同材质

Base Index

       将多个模型存在同一个VBO中,如下所示

[A00 A01 A02 A03 A04... Ann B00 B01 B02... Bmm]

       glDrawArrays的话指定start index和count就能画出指定的模型了,glDrawElements却存在一些小问题,就是B的索引并非m,而是m + nn。调整一下其实没多麻烦,但openGL还是提供了接口,可以选择一个basevertex作为索引基础(nn),其他的索引自动加上这个basevertex的索引

void glDrawElementsBaseVertex( GLenum mode​, GLsizei count​,
    GLenum type​, void *indices​, GLint basevertex​);

Instancing

       这个应该都很熟悉了,不多介绍了

void glDrawArraysInstanced( GLenum mode​, GLint first​,
    GLsizei count​, GLsizei instancecount​ );
void glDrawElementsInstanced( GLenum mode​, GLsizei count​, 
    GLenum type​, const void *indices​, GLsizei instancecount​ );

gl_InstanceID还有Instanced_Array是实例之间的唯一差距。

       在 openGL 4.2 版本或者支持扩展 ARB_base_instance,实例化的实例化已经成为可能!

void glDrawArraysInstancedBaseInstance( GLenum mode​, GLint first​,
    GLsizei count​, GLsizei instancecount​, GLuint baseinstance​​ );
void glDrawElementsInstancedBaseInstance( GLenum mode​, GLsizei count​, 
    GLenum type​, const void *indices​, GLsizei instancecount​, GLuint baseinstance​​ );

gl_InstanceID不随baseinstance​​变化,因此每实例差异只能靠Instanced_Array来实现。在 openGL 4.6 或者 ARB_shader_draw_parameters扩展的情况下,可以通过gl_BaseInstance访问函数中的baseinstance参数

Range

       “Range”版本的glDrawElements也用来处理多个模型存储在同一个VBO的情况,但是不会有索引数组越界的错误

void glDrawRangeElements( GLenum mode​, GLuint start​, 
    GLuint end​, GLsizei count​, GLenum type​, void *indices​ );

Combinations

       Combinations和openGL的primitive restart特性紧密相关。

       Base vertex可以和MultiDraw, Range, 或 Instancing结合

void glMultiDrawElementsBaseVertex( GLenum mode​, 
    GLsizei *count​, GLenum type​, void **indices​, 
    GLsizei primcount​, GLint *basevertex​ );
 
void glDrawRangeElementsBaseVertex( GLenum mode​, 
    GLuint start​, GLuint end​, GLsizei count​, GLenum type​, 
    void *indices​, GLint basevertex​ );

void glDrawElementsInstancedBaseVertex( GLenum mode​, 
    GLsizei count​, GLenum type​, const void *indices​, 
    GLsizei instancecount​, GLint basevertex​ );

       在openGL 4.2版本或者ARB_base_instance,BaseVertex 也能和 BaseInstance 结合

void glDrawElementsInstancedBaseVertexBaseInstance( GLenum mode​, 
    GLsizei count​, GLenum type​, const void *indices​, 
    GLsizei instancecount​, GLint basevertex​, GLuint baseinstance​);

Transform feedback rendering

       可以使用Transform Feedback来生成顶点信息用于渲染。通常的数据流程是GPU->CPU->GPU,但有一些API允许在CPU读取确切数据之前直接绘制。

void glDrawTransformFeedback(GLenum mode​, GLuint id​);
void glDrawTransformFeedbackStream(GLenum mode​, GLuint id​, GLuint stream​);

       在 openGL 4.2版本或者支持ARB_transform_feedback_instanced 的设备上,上述两个函数的实例化版本也可以使用

void glDrawTransformFeedbackInstanced(GLenum mode​, GLuint id​, GLsizei instancecount​);
void glDrawTransformFeedbackStreamInstanced(GLenum mode​, GLuint id​, GLuint stream​, GLsizei instancecount​);

Indirect rendering

       该特性的目的是允许直接渲染GPU中的数据,省去GPU->CPU->GPU的拷贝过程。运行时数据来源可能是Compute Shader,也可能是Geometry Shader + Transform Feedback,甚至可能是openCL/CUDA。

       所有Indirect rendering都适用于下列情况:

void glDrawArraysIndirect(GLenum mode​, const void *indirect​);

       此函数功能上等价于

typedef  struct {
   GLuint  count;
   GLuint  instanceCount;
   GLuint  first;
   GLuint  baseInstance;
} DrawArraysIndirectCommand;

glDrawArraysInstancedBaseInstance(mode, cmd->first, cmd->count, cmd->instanceCount, cmd->baseInstance);

       在openGL 4.3或者ARB_multi_draw_indirect扩展下,也可以用于multi draw

void glMultiDrawArraysIndirect(GLenum mode​, const void *indirect​, GLsizei drawcount​, GLsizei stride​);

       对于索引渲染

void glDrawElementsIndirect(GLenum mode​, GLenum type​, const void *indirect​);

       在功能上等价于

typedef  struct {
    GLuint  count;
    GLuint  instanceCount;
    GLuint  firstIndex;
    GLuint  baseVertex;
    GLuint  baseInstance;
} DrawElementsIndirectCommand;

glDrawElementsInstancedBaseVertexBaseInstance(mode, cmd->count, type,
  cmd->firstIndex * size-of-type, cmd->instanceCount, cmd->baseVertex, cmd->baseInstance);

       在openGL 4.3或者ARB_multi_draw_indirect扩展下,也可以用于multi draw

void glMultiDrawElementsIndirect(GLenum mode​, GLenum type​, const void *indirect​, GLsizei drawcount​, GLsizei stride​);

Conditional rendering

       条件渲染就是只在某些条件下渲染,在某些条件下就不渲染。最经典的运用就是Occlusion Query。

glBeginConditionalRender(GLuint id​, GLenum mode​);
glEndConditionalRender();

       支持的命令如下

参数中mode可以是如下选项: