您当前的位置: 首页 >  ar

Jave.Lin

暂无认证

  • 4浏览

    0关注

    704博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

LearnGL - 18 - Instancing/Instanced Rendering - 多实例渲染1 - glDrawElementsInstanced

Jave.Lin 发布时间:2020-09-02 21:05:51 ,浏览量:4

文章目录
  • Instancing - 多实例渲染
  • Draw Instancing API - 绘制的接口
  • 实践
    • 应用层
    • Vertex Shader
    • 绘制效果
    • draw call
  • 问题
    • Uniform block size 的大小限制
    • 获取 单个UBO大小 的最大限制
  • References

LearnGL - 学习笔记目录

Instancing - 多实例渲染

引用 OpenGL 红宝书 - 第9版中的部分描述:

实例化(instancing)或者多实例渲染(instanced rendering)是一种连续执行多条相同的渲染命令的方法,并且每个渲染命令所产生的结果都会有轻微的差异。这是一种非常有效的,使用少量 API 调用来渲染大量几何体的方法。OpenGL 中已经提供了一些常用绘制函数的多变量形式来优化命令的多次执行。此外。OpenGL 中也提供了多种机制,允许着色器使用绘制的不同实例作为输入。并且对每个实例(而不是每个顶点)都赋予不同的顶点属性值。

这种渲染方法通常是对很多网格相同的对象要渲染时,对局部不同的属性,如:transform变换(mat4 modelMatrix[n])、颜色(vec4 color[n])、纹理(sampler2DArray),等,其他的属性都可以使用数组形式来存放(也不一定是数组,后面可以是多实例的顶点属性的方式,不过这需要依赖硬件的支持,不过现在的硬件,基本都会支持了,除非比较低端的嵌入式,如:低端手机),让后使用着色器中的 gl_InstanceID 来作为索引值获取对应数组中的属性值。

gl_InstanceID 每绘制一个实例后的实例数量都会+1

Draw Instancing API - 绘制的接口

接口有好几种,而不同的接口,实现上也稍有不同:

  • glDrawArrayInstanced
  • glDrawElementsInstanced
  • glDrawElementsInstancedBaseVertex
  • glVertexAttribDivisor
  • glDrawArrayInstancedBaseInstance
  • glDrawElementInstancedBaseInstance
  • glDrawElementsInstanceBaseVertexBaseInstance

这里 的测试方法使用的是:glDrawElementsInstanced 接口,它对我们之前的文章中使用的 glDrawElements 非常类似,只不过 glDrawElementsInstanced 多一个实例化数量的参数,所以使用起来也是非常简单的

void glDrawElementsInstanced(
	GLenum mode, 
	GLsizei count,
	GLenum type, 
	const void* indices, 
	GLsizei primCount);

通过 mode、count 和 indices 所构成的几何体图元集(相当于 glDrawElements() 函数所需的独立参数),绘制它的 primCount 个实例。与 glDrawArrayInstanced() 类似,对于每个实例,内置变量 gl_InstanceID 都会一次递增,新的数值会被传递到顶点着色器,以区分不同实例的顶点属性。

再次注意到,glDrawElementsInstanced() 的参数与 glDrawElements() 的是等价的,只是新增了 primCount 参数。每次调用多实例函数时,在本质上 OpenGL 都会根据 primCount 参数来设置多次运行整个命令。这看起来并不是很有用的功能。不过,OpenGL 提供了两种机制来设置对应不同实例的顶点属性,并且在顶点着色器中可以获取当前实例所对应的索引号。

它的底层绘制伪代码就像是:

void glDrawElementsInstanced(
	GLenum mode, 
	GLsizei count,
	GLenum type, 
	const void* indices, 
	GLsizei primCount) {
	for (int i = 0; i instancing = true;
		mat->instanc_count = INSTANCING_COUNT;

		// mesh renderer - component
		MeshRenderer* mr = new MeshRenderer();
		mr->setQueue(RenderQueueType::Transprent);
		getOwner()->addComp(mr);

		UBO_DATA* ubo_data = new UBO_DATA("Instancing_UBO");
		ubo_data->addNameMap({ "instancingMat",			(size_t)instancingMat });

		ShaderProgram::registerUBO(ubo_data);

		GameObject* go = getOwner();
		vec3 loc_scl = go->getTrans()->local_scale;

		for (size_t i = 0; i getTrans()->local_rotation.y += (double)Timer::inst()->deltaTime * sp_rotate;
	}

这里使用了:UBO (uniform block object) 的方式,但这种方式不是最高效的,如果使用的是 unfirom 的话,同样有大小,与数量的限制

		UBO_DATA* ubo_data = new UBO_DATA("Instancing_UBO");
		ubo_data->addNameMap({ "instancingMat",			(size_t)instancingMat });

		ShaderProgram::registerUBO(ubo_data);

开启 instancing 的方式,这种方式比较简单,对材质设置好属性即可,在上面的代码中就是:

		mat->instancing = true;
		mat->instanc_count = INSTANCING_COUNT;

在绘制接口的时候,我们会根据是否开启了 instancing 来做分支处理:

		if (mat->instancing) {
			// 实例化绘制图元
			glDrawElementsInstanced(
				DrawState_Pritmive::vec_value[(int)mesh->primiveType],
				mesh->indices_byte_size(), GL_UNSIGNED_INT, (GLvoid*)(0), mat->instanc_count);
			checkGLError();
		}
		else {
			// 绘制图元,可以按网格给定的图元类型来绘制
			glDrawElements(
				DrawState_Pritmive::vec_value[(int)mesh->primiveType],
				mesh->indices_byte_size(), GL_UNSIGNED_INT, (GLvoid*)(0));
			checkGLError();
		}
Vertex Shader

主要是 vertex shader 有变化,其他的 shader 没有任何变化

// jave.lin - testing_instancing.vert
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"

// vertex data
in vec3 vPos;		// 顶点坐标
in vec2 vUV0;		// 顶点纹理坐标
in vec3 vNormal;		// 顶点法线

// vertex data - interpolation
out vec2 fUV0;			// 给 fragment shader 传入的插值
out vec3 fNormal;		// 世界坐标顶点法线
out vec3 fWorldPos;		// 世界坐标

uniform Instancing_UBO {
	mat4 instancingMat[1000];		// instancing 矩阵
};

void main() {

	mat4 instancing_mMat = instancingMat[gl_InstanceID];	// 从 ubo 中获取 model matrix
	mat4 new_mMat = mMat * instancing_mMat;					// 将原来的 mMat 累计变换到新的 model matrix
	mat4 it_mMat = transpose(inverse(new_mMat));			// 求得新的逆矩阵的转置矩阵,用于变换法线用

	// vec4 worldPos = mMat * vec4(vPos, 1.0);	// 原来直接 model matrix 变换即可
	vec4 worldPos = new_mMat * vec4(vPos, 1.0);	// 现在使用新的 model matrix 变换
	fUV0 = vUV0;							// UV0
	fNormal = normalize(mat3(it_mMat) * vNormal);	// 用新的 it_mMat 矩阵来将模型空间的法线,变换到,世界坐标法线
	fWorldPos = worldPos.xyz;				// 世界坐标
	gl_Position = vpMat * worldPos;			// Clip pos
}
绘制效果

在这里插入图片描述

在这里插入图片描述

draw call

使用两个 DC (draw call),却绘制了 1001 个对象,1个行星,1000个陨石 在这里插入图片描述

问题

前面有说,这种方法使用的是:glDrawElementsInstanced + UBO内的数组方法

这种方法的 UBO 对象大小限制比较大,后面会只用其他接口来实现,会更好

Uniform block size 的大小限制

如,上面的示例中,我们绘制了1000个陨石,如果我们将数量提升到 2000 个,会怎么样?

在编译 shader 的时候就会报错: 在这里插入图片描述

那要怎么样才能知道 UBO 最大的大小限制呢?

获取 单个UBO大小 的最大限制

使用 glGetIntegerv 接口即可,传入:GL_MAX_UNIFORM_BLOCK_SIZE

	// GL_MAX_UNIFORM_BLOCK_SIZE
	GLint maxUniformBlockSize;
	glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
	std::cout
		            
关注
打赏
1664331872
查看更多评论
0.0822s