Pārlūkot izejas kodu

feat: 添加 instance 的解释说明和测试模块

NiceTry12138 9 mēneši atpakaļ
vecāks
revīzija
c4d50566f0

+ 119 - 1
图形学/OpenGL学习/OpenGLDemo.md

@@ -1050,4 +1050,122 @@ void main()
     GenerateLine(1); // second vertex normal
     GenerateLine(2); // third vertex normal
 }
-```
+```
+
+### 实例化
+
+这个实例化并不是创建出对象,而是更像单例的含义,即模型数据只存储一份,通过一些其他的数据差异来渲染出多个相同的物体
+
+比如,有 100 个石头,如果真的加载 100 个模型和对应贴图,消耗会很大,并且每个石头会调用一次 draw-call。如果使用 gl_instanceID 那么只会加载模型和贴图一次,通过 gl_instanceID 来区分石头并动态修改其属性
+
+使用 `glDrawArraysInstanced` 和 `glDrawElementsInstanced` 替代之前的 `glDrawArrays` 和 `glDrawElements`。在着色器中可以使用 `gl_InstanceID` 这个内置变量,该变量是一个从 0 开始的递增的变量,用于区分相同实例的不同物体。如果有 100 个石头,那么 `gl_InstanceID` 就是从 0 ~ 99
+
+以下面的代码为例,在 顶点着色器 中,定义了一个 `offsets` 数组,通过 `gl_InstanceID` 数据来区分不同的物体,再通过序号动态修改每个物体的坐标
+
+```glsl
+#version 330 core
+layout (location = 0) in vec2 aPos;
+layout (location = 1) in vec3 aColor;
+
+out vec3 fColor;
+
+uniform vec2 offsets[100];
+
+void main()
+{
+    vec2 offset = offsets[gl_InstanceID];
+    gl_Position = vec4(aPos + offset, 0.0, 1.0);
+    fColor = aColor;
+}
+```
+
+在 CPU 阶段,可以使用下面代码设置 `offsets`
+
+```cpp
+shader.use();
+for(unsigned int i = 0; i < 100; i++)
+{
+    shader.setVec2(("offsets[" + std::to_string(i) + "]"), translations[i]);
+}
+```
+
+不过由于数组长度的限制,物体数量超过一定值之后**无法使用数组**,毕竟 `uniform` 定义的数组长度是有上限的
+
+这个时候使用**实例化数组**,将数据定义成顶点属性,这样能存储更多内容,并且仅在**顶点着色器**渲染一个新的实例时才会更新
+
+1. 新增一个顶点属性
+
+```glsl
+#version 330 core
+layout (location = 0) in vec2 aPos;
+layout (location = 1) in vec3 aColor;
+layout (location = 2) in vec2 aOffset;
+```
+
+2. 创建顶点顶点缓冲并绑定数据
+
+```cpp
+unsigned int instanceVBO;
+glGenBuffers(1, &instanceVBO);
+glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
+glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
+glBindBuffer(GL_ARRAY_BUFFER, 0);
+```
+
+3. 设置顶点属性
+
+```cpp
+glEnableVertexAttribArray(2);
+glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
+glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
+glBindBuffer(GL_ARRAY_BUFFER, 0);   
+glVertexAttribDivisor(2, 1);
+```
+
+这里出现了一个新的函数 `glVertexAttribDivisor`,该函数时 OpenGL 用于控制顶点属性实例化渲染中如何更新的函数
+
+```cpp
+void glVertexAttribDivisor(
+    GLuint index,   // 顶点属性位置(如 layout(location=2) 中的 2)
+    GLuint divisor  // 除数(Divisor),表示每渲染多少个实例推进一次属性数据
+);
+```
+
+| divisor 值 | 作用 |
+| --- | --- |
+| 0 | 每个顶点更新 |
+| 1 | 每个实例更新 |
+| N | 每渲染 N 个实例更新一次 |
+
+> 比如 100 个石头,每 10 个石头共享同一个颜色,那么 `divisor` 的值就是 10
+
+也可以这么理解,对于每个顶点属性都会调用 `glVertexAttribDivisor(index, 0)` 让其每个顶点都更新,对于一些数据我们希望每个实例更新,所以通过 `glVertexAttribDivisor(index, 1)` 设置
+
+[实例化](https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/10%20Instancing/)教程中,使用了一个 `glm::mat4` 来一次性包含 4 个 `glm::vec4`
+
+```cpp
+glEnableVertexAttribArray(3);
+glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
+glEnableVertexAttribArray(4);
+glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
+glEnableVertexAttribArray(5);
+glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
+glEnableVertexAttribArray(6);
+glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));
+```
+
+> 定义顶点属性
+
+为什么将一个 `glm::mat4` 拆分成 4 个 `glm::vec4` 来定义顶点属性?
+
+因为单个顶点属性最多只能包含 4 个浮点数。`glm::mat4` 包含 16 个浮点,超过顶点定义的大小范围
+
+```glsl
+layout (location = 3) in mat4 aInstanceMatrix;
+```
+
+为什么顶点属性通过 glEnableVertexAttribArray 定义了 3、4、5、6 但是在顶点着色器中直接使用 `location=3` ?
+
+这是因为 OpenGL 允许 **属性聚合**,当顶点属性是 `mat4` 时,OpenGL 会自动将其映射到连续的 4 个 `vec4` 属性
+
+

+ 2 - 0
图形学/OpenGL学习/src/OpenGLDemo/OpenGLDemo/OpenGLDemo.vcxproj

@@ -137,6 +137,7 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="src\testModule\TestGeoShader.cpp" />
+    <ClCompile Include="src\testModule\TestGLInstance.cpp" />
     <ClCompile Include="src\Util\SkyBox.cpp" />
     <ClCompile Include="src\testModule\TestFrameBuffer.cpp" />
     <ClCompile Include="src\testModule\TestSkyBox.cpp" />
@@ -171,6 +172,7 @@
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\testModule\TestGeoShader.h" />
+    <ClInclude Include="src\testModule\TestGLInstance.h" />
     <ClInclude Include="src\Util\SkyBox.h" />
     <ClInclude Include="src\testModule\TestFrameBuffer.h" />
     <ClInclude Include="src\testModule\TestSkyBox.h" />

+ 6 - 0
图形学/OpenGL学习/src/OpenGLDemo/OpenGLDemo/OpenGLDemo.vcxproj.filters

@@ -111,6 +111,9 @@
     <ClCompile Include="src\testModule\TestGeoShader.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
+    <ClCompile Include="src\testModule\TestGLInstance.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\third\imgui\imstb_truetype.h">
@@ -1058,6 +1061,9 @@
     <ClInclude Include="src\testModule\TestGeoShader.h">
       <Filter>头文件</Filter>
     </ClInclude>
+    <ClInclude Include="src\testModule\TestGLInstance.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\third\glm\detail\func_common.inl">

+ 131 - 0
图形学/OpenGL学习/src/OpenGLDemo/OpenGLDemo/src/testModule/TestGLInstance.cpp

@@ -0,0 +1,131 @@
+#include "TestGLInstance.h"
+#include "../Util/RenderSettings.h"
+#include "TestModuleManager.h"
+
+TestGLInstance TestGLInstance::_self;
+
+static GLuint UBOSLOT = 2;
+
+void TestGLInstance::OnEnter(GLFWwindow* window)
+{	// 启动深度测试
+	glEnable(GL_DEPTH_TEST);
+
+	// 初始化 shader
+	m_ModelShader.Init("res/shader/SkyBox/model.vert", "res/shader/SkyBox/model.frag");
+	m_packageModel.Init("res/model/Miku/miku_prefab.fbx");
+
+	m_Camera.SetLocation(glm::vec3(0.0f, 0.0f, 3.0f));
+	BindMouse(window);
+
+	InitSkyBox();
+	InitUBO();
+}
+
+void TestGLInstance::OnExit(GLFWwindow* window)
+{
+	glfwSetWindowUserPointer(window, nullptr);
+	UnBindMouse(window);
+}
+
+void TestGLInstance::UpdateLogic(float delayTime)
+{
+	m_view = m_Camera.GetView();
+	m_Camera.SetMoveSpeed(0.2f);
+	m_Camera.SetRotateSpeed(0.2f);
+
+	// 可能会更新窗口视口大小 每帧更新一下
+	m_proj = glm::perspective(glm::radians(45.0f), (float)RSI->ViewportHeight / (float)RSI->ViewportWidth, 0.1f, 100.0f);
+
+	UpdateUBO();
+}
+
+void TestGLInstance::ClearRender(GLFWwindow* window)
+{
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void TestGLInstance::Render(GLFWwindow* window)
+{
+	glBindBufferBase(GL_UNIFORM_BUFFER, UBOSLOT, m_UBO);
+
+	m_ModelShader.Bind();
+
+	auto CameraLocation = m_Camera.GetCameraLocation();
+
+	auto model = glm::mat4(1.0f);
+	model = glm::rotate(model, glm::radians(-90.0f), glm::vec3(1, 0, 0));
+
+	m_ModelShader.SetUniformMat4f("model", model);
+	m_ModelShader.BindUBO("Matrices", UBOSLOT);
+	
+	m_packageModel.Draw(m_ModelShader);
+
+	m_sky.Draw(m_view, m_proj);
+}
+
+void TestGLInstance::UpdateImGUI(GLFWwindow* window)
+{
+	const auto& io = ImGui::GetIO();
+
+	ImGui::Begin("SkyBox");
+
+	ImGui::End();
+}
+
+void TestGLInstance::InputProcess(GLFWwindow* window)
+{
+	TestWithMouseBase::InputProcess(window);
+
+	m_Camera.InputProcess(window);
+
+	if (glfwGetKey(window, GLFW_KEY_LEFT_ALT) == GLFW_PRESS)
+	{
+		m_bLeftAltPress = true;
+		UnBindMouse(window);
+	}
+	else if (m_bLeftAltPress) {
+		// 因为当前状况 如果不按下 左 alt,每帧都会导致该函数触发,加个判断防止重复触发
+		m_bLeftAltPress = false;
+		BindMouse(window);
+	}
+}
+
+void TestGLInstance::MouseCallback(GLFWwindow* window, double xpos, double ypos)
+{
+	m_Camera.MouseCallback(window, xpos, ypos);
+}
+
+void TestGLInstance::BindMouse(GLFWwindow* window)
+{
+	m_Camera.SetFirstMouse(true);
+	TestWithMouseBase::BindMouse(window);
+}
+
+void TestGLInstance::UnBindMouse(GLFWwindow* window)
+{
+	m_Camera.SetFirstMouse(false);
+	TestWithMouseBase::UnBindMouse(window);
+}
+
+void TestGLInstance::InitSkyBox()
+{
+	m_sky.Init();
+}
+
+void TestGLInstance::InitUBO()
+{
+	GL_CALL(glGenBuffers(1, &m_UBO));
+	GL_CALL(glBindBuffer(GL_UNIFORM_BUFFER, m_UBO));
+	GL_CALL(glBufferData(GL_UNIFORM_BUFFER, sizeof(glm::mat4) * 2, NULL, GL_DYNAMIC_DRAW));
+}
+
+void TestGLInstance::UpdateUBO()
+{
+	GL_CALL(glBindBuffer(GL_UNIFORM_BUFFER, m_UBO));
+	GLvoid* ptr = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY);
+
+	memcpy(ptr, &m_view, sizeof(m_view));
+	memcpy((char*)ptr + sizeof(m_view), &m_proj, sizeof(m_proj));
+
+	glUnmapBuffer(GL_UNIFORM_BUFFER);
+}

+ 58 - 0
图形学/OpenGL学习/src/OpenGLDemo/OpenGLDemo/src/testModule/TestGLInstance.h

@@ -0,0 +1,58 @@
+#pragma once
+#include "TestWithMouseBase.h"
+#include "../Util/CommonData.h"
+
+#include "../Util/Shader.h"
+#include "../Util/Texture.h"
+#include "../Util/Camera.h"
+#include "../Util/Model.h"
+
+#include "../Util/BaseLight.h"
+
+#include "../Util/SkyBox.h"
+
+class TestGLInstance : public TestWithMouseBase
+{
+public:
+	TestGLInstance() : TestWithMouseBase("TestGLInstance") {}
+
+public:
+	virtual void OnEnter(GLFWwindow* window) override;
+	virtual void OnExit(GLFWwindow* window) override;
+
+	virtual void UpdateLogic(float delayTime) override;
+	virtual void ClearRender(GLFWwindow* window) override;
+	virtual void Render(GLFWwindow* window) override;
+	virtual void UpdateImGUI(GLFWwindow* window) override;
+
+	virtual void InputProcess(GLFWwindow* window) override;
+
+	virtual void MouseCallback(GLFWwindow* window, double xpos, double ypos) override;
+
+	virtual void BindMouse(GLFWwindow* window) override;
+	virtual void UnBindMouse(GLFWwindow* window) override;
+
+protected:
+	void InitSkyBox();
+	
+	void InitUBO();
+	void UpdateUBO();
+
+private:
+	glm::mat4 m_model = glm::mat4(1.0f);		// 模型矩阵
+	glm::mat4 m_view = glm::mat4(1.0f);			// 视图矩阵
+	glm::mat4 m_proj = glm::mat4(1.0f);			// 投影矩阵
+
+	Camera m_Camera;
+	bool m_bLeftAltPress = false;
+
+	SkyBox m_sky;
+
+	GLuint m_UBO;								// Uniform Buffer Obejct
+
+	Shader m_ModelShader;
+	Model m_packageModel;
+
+	static TestGLInstance _self;
+};
+