【无标题】

【Cherno的OpenGL视频】Batch rendering - An intro

订阅专栏
1、What is batching or batch rendering?

It’s an incredibly useful rendering technique, now batch rendering itself is kind of difficult to define precisely, bc it could refer to a number of different thing and it absolutely ranges in complexity from something really simple that you could just write in a couple of minutes to something that is much greater and part of a larger system. What we’re going to be talking about today is specifically how we can batch geometry together, meaning how we can render more than one piece of geometry in a single draw call, at it’s core that is what batching or batch rendering means to me(Cherno).

Let’s break that down a little bit, what does it mean to be able to render multiple pieces of geometry in a single draw call? Why is that useful? What does it even look like? Well, traditionally speaking the way that we’ve been rendering pretty much anything right now has been:we built up a vertex buffer of vertices, and index buffer with indices, and then we render that together using something like glDrawElements(), and that is our draw, that’s how we get something on the screen, if we want a different object or if we want another square or rectangle to appear on the screen we basically repeat this process, we can of course reuse the same vertex buffer and index buffer if our geometry doesn’t change, for example if we just want a differently shaped rectangle or another rectangle on the screen in a different position, and then we just use uniform matrices in our vertex shader to actually position and potentially transform that particular piece of geometry, that’s great and all but what happens if we want to render a lot of things a huge amount of geometry? Now for this video we’re just gonna throw 3D batching and all of that stuff out the window, we’re just gonna focus on 2D rendering here, how do we render a large amount of 2D quads or rectangles?

Let’s start off with some examples so that we can understand what it is we’re trying to do. Let’s say we’re makeing a 2D game, it’s a 2D kind of top-down RPG, and we move around the world, and the world itself is made up of different tiles, a lot of tiny little tiles something like a Harvest Moon style game.
在这里插入图片描述在这里插入图片描述

These games in a lot of cases do in fact show the view kind of from above and the camera can actually get pretty far and we might have a lot of different tiles on the screen, with our current kind of strategy if we decide to render each tile as a sepatate quad with a texture, that’s gonne be kind of difficult it’s gonna get to the point where it’s just not gonna perform well, because if we have like hundreds or thousands of tiles in our world just been drawn one by one as separate draw calls, our GPU is not gonna be able to keep up with that, now these days on modern hardware we can absolutely on like a desktop computer with a dedicated graphics card get away with thousands of draw call, it’s not as a big of a problem as say on mobile or as it was in the past, so I don’t wanna over emphasize the fact that this really is an issue, but it’s absolutely not ideal, especially for something as simple as a quad, if you’re drawing each quad individually that’s just inefficient, and furthermore of course as we do add more and more quads or maybe we zoom out the camera, and we see more of that map, our game may actually start to drop frames bc it’s just not going to be able to keep up with the thousands of tiles that we’re trying to render.

Another good example is a particle system, if we render a particle system just by using individual quads with transforms, simple as that, but again, what happens if we have thousands of these particles, bc it’s a huge particle system, what if we have particles in our 2D game that also has a huge tar map with many quads that we have to render? We might be looking at ten thousands draw calls now, and to add to that another very popular example of rendering rectangles is UI rendering, what if we’re rendering text on our screen, what if there’s like a quest to kind of journal entry that just has paragraphs and paragraphs of text, the way that we render text usually is by rendering each character as a separate textured quad, so now we’re looking at potentially a thousand characters on the screen, a particle system is going off in the backgroud and all of the this on top of a level with a thousand tiles on the screen at once, that’s gonna be DIFFICULT to render even on modern hardware! But there is an easier way or rather a more efficient way and that is by batching or batch rendering. In a nutshell, batching means we batch together all of this geometry into a single vertex buffer and index buffer, and then simply draw that once. So in other words instead of drawing one square then another square then another square all kind of individually, we put everything in together as if it was one piece of geometry, and then we just simply render that once, and that’s it! The performance improvement with batching together, all of the kind of scenarios that I(Cherno) just described is absolutely huge, in fact I really want to actually do a benchmark of this in the future just to show you guys how much of an impact it actually has.

Now we know how useful batch rendering is.
2、How does Batch rendering actually work?

So up untill now, one quad has always resulted in one draw call, which is an issue bc if we had a thousand of these, we’d get a thousand draw calls, which would be slow! Now realistically 1000 probably isn’t slow on your hardware, but the point is a lot of draw calls is slow. Let’s break down how we would typically render a single quad, in OpenGL we would do something called a vertex array, which you could say contains a vertex buffer and index buffer, and inside that we would have 4 vertices and 6 indices which makes up one quad, and the indices of each triangle being 0 1 2 and 2 3 0, that’s our typical setup for rendering a single quad. And if this is our screen with one quad up here and another quad down there, the way we would achieve this is we would render everything twice, we’d render this vertex array twice with a different transform, so we’d upload a uniform matrix into our vertex shader which is our transform.

Now what if we could somehow take these 2 quads and then put them into a single vertex and index buffer, combine them into just one buffer, let’s try that, let’s take this vertex array and expand the vertex buffer so that it contains 2 quads instead of 1, with the same being done for the index buffer, we bring this down and look at the equivalent what we actually have is 2 of these, the index buffer translates into 0 1 2 2 3 0 for the first quad then 4 5 6 6 7 4 for the second quad, so we end up with 8 vertrices and 12 indices total, what happens now is we render this just once, there are no transforms at all, and we get both of these squares in a single draw call which theoretically faster, this of course does come with a bunch of limitations, because what controls the position of this? how do we set these position of these quads? We obviously can’t set a transform bc that will be a uniform matrix which we can only change between draw calls not during a draw call. So what happens is this position is taken from the vertex buffer, the buffer has the position of each quad, you can still apply an offset to the entire thing in the transform if you like, which is basically what we would do for something like a camera, but the position of each quad is inside the vertex buffer, as another example, if you want to set the color of each quad which is something you might have done in the form of a uniform before, you can also put this data into the vertex buffer, so each vertex would contain a color value which you could then use inside your shader, we’ll explore this more in future videos.

So what does this mean for quads that are constantly moving, for example in a particle system, this vertex buffer has to become dynamic, meaning that we can stream data in every frame, this is extremely important bc if you’re not dealing with dynamic vertex buffer, then everything is essentially static geometry, and we might not want that.

图解析:
在这里插入图片描述

在这里插入图片描述
3、下载代码

github上下载代码。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
Run scripts/Win-Premake.bat and open OpenGL-Sandbox.sln in Visual Studio
在这里插入图片描述

在这里插入图片描述
如图在OpenGL-Sandbox底下创建assets文件夹,再创建frag.glsl和vert.glsl。
在这里插入图片描述
完善项目里面的代码。
frag.glsl代码

#version 450 core
layout (location = 0) out vec4 o_Color;
void main(){
	o_Color = vec4(1.0);
}

vert.glsl代码

#version 450 core
layout (location = 0) in vec3 a_Position;
uniform mat4 u_ViewProj;
uniform mat4 u_Transform;
void main(){
	gl_Position = u_ViewProj * u_Transform * vec4(a_Position, 1.0);
}

SandboxLayer.h代码

#pragma once

#include <GLCore.h>
#include <GLCoreUtils.h>

class SandboxLayer : public GLCore::Layer
{
public:
	SandboxLayer();
	virtual ~SandboxLayer();

	virtual void OnAttach() override;
	virtual void OnDetach() override;
	virtual void OnEvent(GLCore::Event& event) override;
	virtual void OnUpdate(GLCore::Timestep ts) override;
	virtual void OnImGuiRender() override;
private:
	std::unique_ptr<GLCore::Utils::Shader> m_Shader;
	GLCore::Utils::OrthographicCameraController m_CameraController;

	GLuint m_QuadVA, m_QuadVB, m_QuadIB;
};

SandboxLayer.cpp

#include "SandboxLayer.h"

using namespace GLCore;
using namespace GLCore::Utils;

SandboxLayer::SandboxLayer()
	:m_CameraController(16.0f / 9.0f)
{
}

SandboxLayer::~SandboxLayer()
{
}

void SandboxLayer::OnAttach()
{
	EnableGLDebugging();

	// Init here
	m_Shader = std::unique_ptr<Shader>(Shader::FromGLSLTextFiles(
		"assets/vert.glsl",
		"assets/frag.glsl"
	));

	glClearColor(0.1f, 0.1f, 0.1f, 1.0f);

	float vertices[] = {
		-0.5f, -0.5f, 0.0f,
		 0.5f, -0.5f, 0.0f,
		 0.5f,  0.5f, 0.0f,
		-0.5f,  0.5f, 0.0f
	};

	glCreateVertexArrays(1, &m_QuadVA);
	glBindVertexArray(m_QuadVA);

	glCreateBuffers(1, &m_QuadVB);
	glBindBuffer(GL_ARRAY_BUFFER, m_QuadVB);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	glEnableVertexArrayAttrib(m_QuadVB, 0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);

	uint32_t indices[] = {
		0, 1, 2, 2, 3, 0
	};

	glCreateBuffers(1, &m_QuadIB);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadIB);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}

void SandboxLayer::OnDetach()
{
	// Shutdown here
}

void SandboxLayer::OnEvent(Event& event)
{
	// Events here
	m_CameraController.OnEvent(event);
}


static void SetUniformMat4(uint32_t shader, const char* name, const glm::mat4& matrix)
{
	int loc = glGetUniformLocation(shader, name);
	glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(matrix));
}

void SandboxLayer::OnUpdate(Timestep ts)
{
	// Render here
	m_CameraController.OnUpdate(ts);

	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(m_Shader->GetRendererID());

	auto vp = m_CameraController.GetCamera().GetViewProjectionMatrix();
	SetUniformMat4(m_Shader->GetRendererID(), "u_ViewProj", vp);
	SetUniformMat4(m_Shader->GetRendererID(), "u_Transform", glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f)));

	glBindVertexArray(m_QuadVA);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);// 1 draw call.
}

void SandboxLayer::OnImGuiRender()
{
	// ImGui here
}

SandboxApp.cpp代码

#include "GLCore.h"
#include "SandboxLayer.h"

using namespace GLCore;

class Sandbox : public Application
{
public:
	Sandbox()
		:Application("OpenGL Sanbox")
	{
		PushLayer(new SandboxLayer());
	}
};

int main()
{
	std::unique_ptr<Sandbox> app = std::make_unique<Sandbox>();
	app->Run();
}

This is a super simple example of drawing a single quad, let’s see what it looks like.
在这里插入图片描述
So we have this white square here, and we can move the camera around.
在这里插入图片描述
How would we normally render the 2 quads using our what we’re used to, well we would simply issue another draw call and set different transforms for each draw using our shader uniform.
在这里插入图片描述

void SandboxLayer::OnUpdate(Timestep ts)
{
// Render here
m_CameraController.OnUpdate(ts);

glClear(GL_COLOR_BUFFER_BIT);

glUseProgram(m_Shader->GetRendererID());

auto vp = m_CameraController.GetCamera().GetViewProjectionMatrix();
SetUniformMat4(m_Shader->GetRendererID(), "u_ViewProj", vp);

glBindVertexArray(m_QuadVA);
SetUniformMat4(m_Shader->GetRendererID(), "u_Transform", glm::translate(glm::mat4(1.0f), glm::vec3(-1.0f, 0.0f, 0.0f)));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);// 1 draw call.
SetUniformMat4(m_Shader->GetRendererID(), "u_Transform", glm::translate(glm::mat4(1.0f), glm::vec3(1.0f, 0.0f, 0.0f)));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);// issue another draw call.

}

What we’re doing is rendering the same geometry twice but with different transforms, and so we get 2 squares at different positions on our screen.
在这里插入图片描述
How do we batch these together into a single draw call like we talked about?
4、Batching实现

Let’s take a look at the vertex buffer, what we need to do is to duplicate all of these vertices, so that we’ve got 2 squares inside our vertex buffer.
修改SandboxLayer.cpp里OnAttach()里面的vertices为:

float vertices[] = {
	-1.5f, -0.5f, 0.0f,
	-0.5f, -0.5f, 0.0f,
	-0.5f,  0.5f, 0.0f,
	-1.5f,  0.5f, 0.0f,

	 0.5f, -0.5f, 0.0f,
	 1.5f, -0.5f, 0.0f,
	 1.5f,  0.5f, 0.0f,
	 0.5f,  0.5f, 0.0f
};

Now we have enough vertices to make up 2 quads inside this one single vertex buffer.
修改SandboxLayer.cpp里OnAttach()里面的indices为:

uint32_t indices[] = {
	0, 1, 2, 2, 3, 0,
	4, 5, 6, 6, 7, 4
};

修改SandboxLayer.cpp里OnUpdate()里面的rendering code为:

void SandboxLayer::OnUpdate(Timestep ts)
{
// Render here
m_CameraController.OnUpdate(ts);

glClear(GL_COLOR_BUFFER_BIT);

glUseProgram(m_Shader->GetRendererID());

auto vp = m_CameraController.GetCamera().GetViewProjectionMatrix();
SetUniformMat4(m_Shader->GetRendererID(), "u_ViewProj", vp);
SetUniformMat4(m_Shader->GetRendererID(), "u_Transform", glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f)));

glBindVertexArray(m_QuadVA);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, nullptr);// 1 draw call.

}

5、Batching运行效果

Now we’ve got our 2 quads rendering as they did before but this time in a single draw call, they’re batch together, and that’s pretty much it, this is how batch rendering works!
在这里插入图片描述

在这里插入图片描述
————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/AlexiaDong/article/details/126807277

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值