OpenGL ES 学习教程(十七) Unity GPU Instance 原理及 GLES 实现

Posted _Captain

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenGL ES 学习教程(十七) Unity GPU Instance 原理及 GLES 实现相关的知识,希望对你有一定的参考价值。

1.前言

上一篇 OpenGL ES 学习教程(十七) Unity GPU Instance 原理及 GLES 实现(一) 介绍了使用Uniform数组实现GPU Instance的方法,但是由于Uniform变量数量有上限,所以这种方式受到很大限制。
本篇介绍使用顶点缓冲区VBO的形式来实现GPU Instance,只要系统允许,可以绘制无限个实例。

2.了解顶点缓冲区VBO

VBO在之前的教程中介绍过了,请跳转 OpenGL ES 学习教程(五) 极速绘制,使用 VBO (顶点缓冲区对象)!

3.使用VBO实现GPU Instance

在上一篇中,我们使用Uniform数组,从C++代码里,一次性将50个MVP矩阵传递给Shader的Uniform数组里,然后借助 gl_InstanceID 这个渲染实例序号,作为下标读取当前实例的MVP。

//mvp矩阵数组,存储每个立方体的mvp。然后以gl_InstanceID为下标来取。
uniform mat4 m_mvps[50];
 
void main()

	vec4 pos=vec4(m_position,1);
        mat4 mvp = m_mvps[gl_InstanceID];//gl_InstanceID是递增的
	......

使用VBO来实现GPU Instance,就无需再使用 gl_InstanceID,OpenGL在逻辑层提供了接口 glVertexAttribDivisor 来标记指定的VBO对象,里面存储的是每一个实例一个元素。

下面来看具体的步骤。

3.1 修改顶点Shader

顶点Shader中的MVP,原来是Uniform标记的,现在修改为 Attribute。

const char* vertexShader =

	"precision lowp float;"

	"attribute vec3 m_position;"
	"attribute vec2 m_uv;"

	//"uniform mat4 m_mvp;"

	"attribute mat4 m_mvp;"

	
	"varying vec2 m_outUV;"

	"void main()"
	""
	"	vec4 pos=vec4(m_position,1);"
	"	gl_Position=m_mvp*pos;"
	"	m_outUV=m_uv;"
	""
;

为什么要修改为Attribute?
顶点Shader,是对每个顶点,执行一次。
Uniform表示,在对当前实例所有顶点,都用这一份相同的数据。
Attribute表示,对每个顶点,都使用不同的数据,如上面的m_position表示当前处理的坐标。

那将MVP修改为 Attribute,那岂不是表示每个顶点使用不同的MVP矩阵吗?
这里就要提到OpenGL新增的API glVertexAttribDivisor,借助它,我们可以将Attribute属性标记为每个实例使用不同的数据,而不再是每个顶点。
例如我们要绘制10000个实例,那么就往VBO中存储10000个MVP矩阵,然后使用glVertexAttribDivisor进行标记,这样在顶点Shader中,使用MVP,就会获得当前实例所属的MVP。
这相当于OpenGL在逻辑层封装了 gl_InstanceID。

3.2 获取m_mvp

获取m_mvp的代码进行修改

//GLProgram_Cube.h Line79
//m_mvp = glGetUniformLocation(m_programId, "m_mvp");
m_mvp = glGetAttribLocation(m_programId, "m_mvp");

设置m_mvp可用

//GLProgram_Cube.h Line85
virtual void begin()

	glUseProgram(m_programId);

	// Enable depth test
	glEnable(GL_DEPTH_TEST);
	// Accept fragment if it closer to the camera than the former one
	glDepthFunc(GL_LESS);

	//设置m_mvp可用
	glEnableVertexAttribArray(m_mvp);
	glEnableVertexAttribArray(m_mvp+1);
	glEnableVertexAttribArray(m_mvp+2);
	glEnableVertexAttribArray(m_mvp+3);

	glEnableVertexAttribArray(m_position);
	glEnableVertexAttribArray(m_uv);


virtual void end()

	glDisableVertexAttribArray(m_mvp);
	glDisableVertexAttribArray(m_mvp + 1);
	glDisableVertexAttribArray(m_mvp + 2);
	glDisableVertexAttribArray(m_mvp + 3);

	glDisableVertexAttribArray(m_position);
	glDisableVertexAttribArray(m_uv);
	glUseProgram(0);

可能你会很奇怪,为什么设置m_mvp 可用,有4行代码。

//设置m_mvp可用
glEnableVertexAttribArray(m_mvp);
glEnableVertexAttribArray(m_mvp+1);
glEnableVertexAttribArray(m_mvp+2);
glEnableVertexAttribArray(m_mvp+3);

这是因为Attribute,最大是4分量的,就是说Attribute只可以标记float,vec2,vec3,vec4。
那我们这里标记的是mat4x4,OpenGL就会帮我们拆分为 4 个 vec4。
就相当于有4个Attribute。

在调试的时候也发现,
m_mvp id=0
m_position id=4
m_uv id=5

在shader中 m_mvp 和 m_position 是相邻的,按理说m_position 应该 id =1。
但是因为 m_mvp被占了为 4 个Attribute,所以 m_position 就后移了。

3.3 创建VBO

新建成员变量存储VBO ID。

//MyApp.h Line21
GLuint instanceVBO;

初始化为 -1。

//MyApp.h Line28
MyApp()

	m_diffusetexture2D = new Texture2D();

	instanceVBO = -1;

创建VBO对象

//MyApp.h Line81
glGenBuffers(1, &instanceVBO);

3.4 使用VBO对象

创建好VBO对象之后,只需要计算出MVP矩阵数组,然后将数组数据上传至VBO对象缓冲中。

//MyApp.h Line55
if (instanceVBO == -1)

	glm::mat4 scale = glm::scale(glm::vec3(1.5f, 1.5f, 1.5f));
	glm::mat4 rotation = glm::eulerAngleYXZ(glm::radians(30.0f), glm::radians(0.0f), glm::radians(0.0f));

	// generate a list of 100 quad locations/translation-vectors
	// ---------------------------------------------------------
	glm::mat4x4 mvps[INSTANCE_NUM];

	srand((int)time(0));//用系统时间作为随机种子
	int min = -20;
	int max = 20;
	for (size_t i = 0; i < INSTANCE_NUM; i++)
	
		int x = rand() % (max - min + 1) + min;//生成的随机数
		int y = rand() % (max - min + 1) + min;//生成的随机数

		//model;
		glm::mat4 trans = glm::translate(glm::vec3(x / 10.0f, y / 10.0f, 0));

		glm::mat4 model = trans*scale*rotation;

		mvps[i] = proj*view*model;
	


	glGenBuffers(1, &instanceVBO);
	glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(glm::mat4) * INSTANCE_NUM, &mvps[0], GL_STATIC_DRAW);

	// also set instance data
	glVertexAttribPointer(m_programCube.m_mvp, 4, GL_FLOAT, false, 4 * sizeof(glm::vec4), (void*)0);
	glVertexAttribPointer(m_programCube.m_mvp + 1, 4, GL_FLOAT, false, 4 * sizeof(glm::vec4), (void*)(sizeof(glm::vec4)));
	glVertexAttribPointer(m_programCube.m_mvp + 2, 4, GL_FLOAT, false, 4 * sizeof(glm::vec4), (void*)(2 * sizeof(glm::vec4)));
	glVertexAttribPointer(m_programCube.m_mvp + 3, 4, GL_FLOAT, false, 4 * sizeof(glm::vec4), (void*)(3 * sizeof(glm::vec4)));

	glVertexAttribDivisor(m_programCube.m_mvp, 1); // tell OpenGL this is an instanced vertex attribute.
	glVertexAttribDivisor(m_programCube.m_mvp + 1, 1);
	glVertexAttribDivisor(m_programCube.m_mvp + 2, 1);
	glVertexAttribDivisor(m_programCube.m_mvp + 3, 1);

	glBindBuffer(GL_ARRAY_BUFFER, 0);

4.效果

绘制10000个实例的效果。
CPU及GPU占用。

可以看到CPU占用是很低的,因为只有1个DrawCall。
但是GPU已经满负载了,因为减DrawCall并不能降低GPU负担。

5.实例代码下载

https://download.csdn.net/download/cp790621656/14801116

6.参考资料

OpenGL ES 学习教程(五) 极速绘制,使用 VBO (顶点缓冲区对象)!

GPU Instance

以上是关于OpenGL ES 学习教程(十七) Unity GPU Instance 原理及 GLES 实现的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL ES 学习教程(十七) Unity GPU Instance 原理及 GLES 实现

OpenGL ES 学习教程(十七) Unity GPU Instance 原理及 GLES 实现

iOS OpenGL ES2.0 开发实例

OpenGL ES 学习教程(十三) Stencil_TEST(模板缓冲测试)

OpenGL ES 学习教程(十三) Stencil_TEST(模板缓冲测试)

Unity3D 导出apk,OpenGL es2手机不支持问题的解决