赞
踩
假如你有一个有许多模型的场景,而这些模型的顶点数据都一样,只是进行了不同的世界空间的变换。想象一下,有一个场景中充满了草叶:每根草都是几个三角形组成的。你可能需要绘制很多的草叶,最终一次渲染循环中就肯能有成千上万个草需要绘制了。因为每个草叶只是由几个三角形组成,绘制一个几乎是即刻完成,但是数量巨大以后,执行起来就很慢了。
像这样绘制出你模型的其他实例,多次绘制之后,很快将达到一个瓶颈,这是因为你glDrawArrays或glDrawElements这样的函数(Draw call)过多。这样渲染顶点数据,会明显降低执行效率,这是因为OpenGL在它可以绘制你的顶点数据之前必须做一些准备工作(比如告诉GPU从哪个缓冲读取数据,以及在哪里找到顶点属性,所有这些都会使CPU到GPU的总线变慢)。所以即使渲染顶点超快,而多次给你的GPU下达这样的渲染命令却未必。
for(GLuint i = 0; i < amount_of_models_to_draw; i++)
{
DoSomePreparations(); //在这里绑定VAO、绑定纹理、设置uniform变量等
glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices);
}
如果我们能够将数据一次发送给GPU,就会更方便,然后告诉OpenGL使用一个绘制函数,将这些数据绘制为多个物体。这就是我们将要展开讨论的实例化(Instancing)。
实例化是一种只调用一次渲染函数却能绘制出很多物体的技术,它节省渲染物体时从CPU到GPU的通信时间,而且只需做一次即可。要使用实例化渲染,我们必须将glDrawArrays和glDrawElements各自改为glDrawArraysInstanced和glDrawElementsInstanced。这些用于实例化的函数版本需要设置一个额外的参数,叫做实例数量(Instance Count),它设置我们打算渲染实例的数量。这样我们就只需要把所有需要的数据发送给GPU一次就行了,然后告诉GPU它该如何使用一个函数来绘制所有这些实例。
就其本身而言,这个函数用处不大。渲染同一个物体一千次对我们来说没用,因为每个渲染出的物体不仅相同而且还在同一个位置;我们只能看到一个物体!出于这个原因GLSL在着色器中嵌入了另一个内建变量,叫做gl_InstanceID。
在通过实例化绘制时,gl_InstanceID的初值是0,它在每个实例渲染时都会增加1。如果我们渲染43个实例,那么在顶点着色器gl_InstanceID的值最后就是42。每个实例都拥有唯一的值意味着我们可以索引到一个位置数组,并将每个实例摆放在世界空间的不同的位置上。
我们调用一个实例化渲染函数,在标准化设备坐标中绘制一百个2D四边形来看看实例化绘制的效果是怎样的。通过对一个储存着100个偏移量向量的索引,我们为每个实例四边形添加一个偏移量。最后,窗口被排列精美的四边形网格填满:
效果展示:
顶点着色器:
#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec3 color;
out vec3 fColor;
uniform vec2 offsets[100];
void main()
{
vec2 offset = offsets[gl_InstanceID];
gl_Position = vec4(position + offset, 0.0f, 1.0f);
fColor = color;
}
片段着色器:
#version 330 core
in vec3 fColor;
out vec4 color;
void main()
{
color = vec4(fColor, 1.0f);
}
核心主程序:
Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt" , "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt" ); GLfloat quadVertices[] = { // ---位置--- ------颜色------- -0.05f, 0.05f, 1.0f, 0.0f, 0.0f, 0.05f, -0.05f, 0.0f, 1.0f, 0.0f, -0.05f, -0.05f, 0.0f, 0.0f, 1.0f, -0.05f, 0.05f, 1.0f, 0.0f, 0.0f, 0.05f, -0.05f, 0.0f, 1.0f, 0.0f, 0.05f, 0.05f, 0.0f, 1.0f, 1.0f }; GLuint quadVAO, quadVBO; glGenVertexArrays(1, &quadVAO); glGenBuffers(1, &quadVBO); glBindVertexArray(quadVAO); glBindBuffer(GL_ARRAY_BUFFER,quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat))); glEnableVertexAttribArray(1); glm::vec2 translations[100]; int index = 0; GLfloat offset = 0.1f; for (GLint y = -10; y < 10; y += 2) { for (GLint x = -10; x < 10; x += 2) { glm::vec2 translation; translation.x = (GLfloat)x / 10.0f + offset; translation.y = (GLfloat)y / 10.0f + offset; translations[index++] = translation; } } // Game loop while (!glfwWindowShouldClose(window)) { // Set frame time GLfloat currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; std::cout << deltaTime << std::endl; // Check and call events glfwPollEvents(); Do_Movement(); // Clear buffers glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shader.Use(); for (GLuint i = 0; i < 100; i++) { stringstream ss; string index; ss << i; index = ss.str(); GLint location = glGetUniformLocation(shader.Program, ("offsets[" + index + "]").c_str()); glUniform2f(location, translations[i].x, translations[i].y); } glBindVertexArray(quadVAO); glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100); glBindVertexArray(0); // Swap the buffers glfwSwapBuffers(window); printError(); } glfwTerminate(); return 0;
在这种特定条件下,前面的实现很好,但是当我们有100个实例的时候(这很正常),最终我们将碰到uniform数据数量的上线。为避免这个问题另一个可替代方案是实例化数组(Instanced Array),它使用顶点属性来定义,这样就允许我们使用更多的数据了,当顶点着色器渲染一个新实例时它才会被更新。
使用顶点属性,每次运行顶点着色器都将让GLSL获取到下个顶点属性集合,它们属于当前顶点。当把顶点属性定义为实例数组时,顶点着色器只更新每个实例的顶点属性的内容而不是顶点的内容。这使我们在每个顶点数据上使用标准顶点属性,用实例数组来储存唯一的实例数据。
我们要绘制100个三角形,每个三角形包含:顶点位置2个float值、顶点颜色3个float值、位置偏移2个float值。
1.定义三角形数据:
位置和颜色数据:
GLfloat quadVertices[] = {
// ---位置--- ------颜色-------
-0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
-0.05f, -0.05f, 0.0f, 0.0f, 1.0f,
-0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
0.05f, 0.05f, 0.0f, 1.0f, 1.0f
};
偏移数据:
glm::vec2 translations[100];
int index = 0;
GLfloat offset = 0.1f;
for (GLint y = -10; y < 10; y += 2)
{
for (GLint x = -10; x < 10; x += 2)
{
glm::vec2 translation;
translation.x = (GLfloat)x / 10.0f + offset;
translation.y = (GLfloat)y / 10.0f + offset;
translations[index++] = translation;
}
}
2.设置解析数据方式(解析出数据整体)
根据顶点着色器的顶点属性定义:
layout (location = 0) in vec2 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 offset;
我们可以看出,我们需要解析出position、color、offset,根据其对应数据类型,我们需要分别解析出2个float、3个float、2个float。由于位置和颜色数据定义在一个数组中,而offser数据单独定义在一个数组中,所以使用两个VBO读取。
解析出position和color
GLuint quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER,quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
// 一个个
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
解析出offset:
GLuint 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);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(2, 1);
3.绘制物体:
绘制物体代码:
glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);
glBindVertexArray(0);
上文绘制物体的代码,先绑定了VAO,而VAO中记录了如何从两个顶点数据数组中读取数据的方式,也记录了包含的VBO即从哪里读取数据。
现在调用 glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100) 绘制。这条语句表明要绘制100次,每次传入6个顶点,绘制的图形类型是三角形,从第0个顶点开始传入。
也就是说,OpenGL负责使用6个顶点基于三角形绘制图形,重复100次,而我们负责每次传入6个顶点。
根据顶点着色器中的顶点属性可知每个顶点要有position、color、offset三个顶点属性值。当我们设置VAO时,就已经设置好了数据的对应方式。
于是根据VAO,从第0号顶点开始,读取出一个个position、color、offset。可是我们发现offset是针对一整个图形的,即一个图形6个顶点才对应一个offset,而一个图形每个顶点对应一个position和color,这时就要设置属性除数了。
绘制6个顶点构成一个图形,这个图形叫做绘制的实例。每次绘制实例,会迭代顶点着色器6次,每次传入一个新的顶点的position和color。因此position和color两种顶点属性是在顶点着色器每次迭代时更新的,而offset是在每次绘制不同实例的时候更新的。
使用glVertexAttribDivisor函数可设置顶点属性的更新。
glVertexAttribDivisor(2,1)设置offset顶点属性在每次绘制实例时更新,即每次传入6个顶点绘制新的实例时依据VAO中的解析方法更新offset。
而glVertexAttribDivisor(1,0)设置color顶点属性在每次顶点着色器迭代时更新,即每个顶点更新一次color。当VAO指针将数组读取完后会回到数组起始位置再读取,而一个绘制函数glDrawArrays会存储当前VAO解析到的位置,因此虽然重复绘制100次,但是offset对应数据的数组的读取并不会在每个实例或每个顶点时,回到数组的起始位置,而是基于上一个顶点和实例读取的位置来读取。
效果展示:
顶点着色器:
#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 offset;
out vec3 fColor;
void main()
{
gl_Position = vec4(position + offset, 0.0f, 1.0f);
fColor = color;
}
片段着色器:
#version 330 core
in vec3 fColor;
out vec4 color;
void main()
{
color = vec4(fColor, 1.0f);
}
核心主程序:
Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt" , "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt" ); GLfloat quadVertices[] = { // ---位置--- ------颜色------- -0.05f, 0.05f, 1.0f, 0.0f, 0.0f, 0.05f, -0.05f, 0.0f, 1.0f, 0.0f, -0.05f, -0.05f, 0.0f, 0.0f, 1.0f, -0.05f, 0.05f, 1.0f, 0.0f, 0.0f, 0.05f, -0.05f, 0.0f, 1.0f, 0.0f, 0.05f, 0.05f, 0.0f, 1.0f, 1.0f }; GLuint quadVAO, quadVBO; glGenVertexArrays(1, &quadVAO); glGenBuffers(1, &quadVBO); glBindVertexArray(quadVAO); glBindBuffer(GL_ARRAY_BUFFER,quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat))); glEnableVertexAttribArray(1); glm::vec2 translations[100]; int index = 0; GLfloat offset = 0.1f; for (GLint y = -10; y < 10; y += 2) { for (GLint x = -10; x < 10; x += 2) { glm::vec2 translation; translation.x = (GLfloat)x / 10.0f + offset; translation.y = (GLfloat)y / 10.0f + offset; translations[index++] = translation; } } GLuint 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); glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0); glBindBuffer(GL_ARRAY_BUFFER, 0); glVertexAttribDivisor(2, 1); // Game loop while (!glfwWindowShouldClose(window)) { // Set frame time GLfloat currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; std::cout << deltaTime << std::endl; // Check and call events glfwPollEvents(); Do_Movement(); // Clear buffers glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shader.Use(); glBindVertexArray(quadVAO); glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100); glBindVertexArray(0); // Swap the buffers glfwSwapBuffers(window); printError(); } glfwTerminate(); return 0;
将gl_InstanceID与实例数组结合使用:
void main()
{
vec2 pos = position * (gl_InstanceID / 100.0f);
gl_Position = vec4(pos + offset, 0.0f, 1.0f);
fColor = color;
}
效果展示:
效果展示:
顶点着色器:
// Vertex shader: // ================ #version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal; layout (location = 2) in vec2 texCoords; out vec2 TexCoords; out vec3 fragPosition; out vec3 Normal; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { gl_Position = projection * view * model * vec4(position, 1.0f); fragPosition = vec3(model * vec4(position, 1.0f)); Normal = mat3(transpose(inverse(model))) * normal; TexCoords = texCoords; }
片段着色器:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D texture_diffuse1;
void main()
{
color = vec4(texture(texture_diffuse1, TexCoords));
}
主程序:
// Std. Includes #include <string> #include <algorithm> using namespace std; // GLEW #define GLEW_STATIC #include <GL/glew.h> // GLFW #include <GLFW/glfw3.h> // GL includes #include "Shader.h" #include "Camera.h" #include "Model.h" // GLM Mathemtics #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> // Other Libs #include <SOIL.h> // Properties GLuint screenWidth = 800, screenHeight = 600; // Function prototypes void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void Do_Movement(); void printError(); GLuint loadTexture(const GLchar* path); GLuint loadCubemap(vector<const GLchar*> faces); // Camera Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); bool keys[1024]; GLfloat lastX = 400, lastY = 300; bool firstMouse = true; GLfloat deltaTime = 0.0f; GLfloat lastFrame = 0.0f; // The MAIN function, from here we start our application and run our Game loop int main() { // Init GLFW glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed glfwMakeContextCurrent(window); // Set the required callback functions glfwSetKeyCallback(window, key_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); // Options glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // Initialize GLEW to setup the OpenGL Function pointers glewExperimental = GL_TRUE; glewInit(); glGetError(); // Debug GLEW bug fix // Define the viewport dimensions glViewport(0, 0, screenWidth, screenHeight); // Setup some OpenGL options glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_PROGRAM_POINT_SIZE); // Setup and compile our shaders Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt" , "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt" ); Model rock("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Debug\\rock\\rock.obj"); Model planet("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Debug\\planet\\planet.obj"); unsigned int amount = 3000; glm::mat4* modelMatrices; GLfloat rotaSpeedArray[3000]; modelMatrices = new glm::mat4[amount]; srand(glfwGetTime()); // 初始化随机种子 float radius = 30.0; float offset = 2.5f; for (unsigned int i = 0; i < amount; i++) { glm::mat4 model = glm::mat4(1.0f); // 1. 位移:分布在半径为 'radius' 的圆形上,偏移的范围是 [-offset, offset] float angle = (float)i / (float)amount * 360.0f; float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset; float x = sin(angle) * radius + displacement; displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset; float y = displacement * 0.4f; // 让行星带的高度比x和z的宽度要小 displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset; float z = cos(angle) * radius + displacement; model = glm::translate(model, glm::vec3(x, y, z)); // 2. 缩放:在 0.05 和 0.25f 之间缩放 float scale = (rand() % 20) / 100.0f + 0.05; model = glm::scale(model, glm::vec3(scale)); // 3. 旋转:绕着一个(半)随机选择的旋转轴向量进行随机的旋转 float rotAngle = (rand() % 360); model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f)); float speed = (rand() % 100) / 50.0f + 0.05; rotaSpeedArray[i] = speed; // 4. 添加到矩阵的数组中 modelMatrices[i] = model; } GLfloat rotaSpeed1 = 0.1; glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f)); model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f)); // Game loop while (!glfwWindowShouldClose(window)) { // Set frame time GLfloat currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; std::cout << deltaTime << std::endl; // Check and call events glfwPollEvents(); Do_Movement(); // Clear buffers glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 绘制行星 shader.Use(); glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); model = glm::rotate(model, rotaSpeed1 * deltaTime, glm::vec3(0.4f, 0.6f, 0.8f)); shader.setMat4("view", view); shader.setMat4("model", model); shader.setMat4("projection", projection); planet.Draw(shader); // 绘制小行星 for (unsigned int i = 0; i < amount; i++) { float rotAngle = (rand() % 200)/100 * deltaTime; modelMatrices[i] = glm::rotate(modelMatrices[i], rotaSpeedArray[i] * deltaTime, glm::vec3(0.4f, 0.6f, 0.8f)); shader.setMat4("view", view); shader.setMat4("model", modelMatrices[i]); shader.setMat4("projection", projection); rock.Draw(shader); } // Swap the buffers glfwSwapBuffers(window); printError(); } glfwTerminate(); return 0; } void printError() { GLuint errorCode = glGetError(); if (errorCode) std::cout << errorCode << std::endl; } // Loads a cubemap texture from 6 individual texture faces // Order should be: // +X (right) // -X (left) // +Y (top) // -Y (bottom) // +Z (front) // -Z (back) GLuint loadCubemap(vector<const GLchar*> faces) { GLuint textureID; glGenTextures(1, &textureID); int width, height; unsigned char* image; glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); for (GLuint i = 0; i < faces.size(); i++) { image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); SOIL_free_image_data(image); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); return textureID; } // This function loads a texture from file. Note: texture loading functions like these are usually // managed by a 'Resource Manager' that manages all resources (like textures, models, audio). // For learning purposes we'll just define it as a utility function. GLuint loadTexture(const GLchar* path) { //Generate texture ID and load texture data GLuint textureID; glGenTextures(1, &textureID); int width, height; unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB); // Assign texture to ID glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D); // Parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); SOIL_free_image_data(image); return textureID; } #pragma region "User input" // Moves/alters the camera positions based on user input void Do_Movement() { // Camera controls if (keys[GLFW_KEY_W]) camera.ProcessKeyboard(FORWARD, deltaTime); if (keys[GLFW_KEY_S]) camera.ProcessKeyboard(BACKWARD, deltaTime); if (keys[GLFW_KEY_A]) camera.ProcessKeyboard(LEFT, deltaTime); if (keys[GLFW_KEY_D]) camera.ProcessKeyboard(RIGHT, deltaTime); } // Is called whenever a key is pressed/released via GLFW void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) { if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); if (action == GLFW_PRESS) keys[key] = true; else if (action == GLFW_RELEASE) keys[key] = false; } void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } GLfloat xoffset = xpos - lastX; GLfloat yoffset = lastY - ypos; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); } #pragma endregion
从CPU到GPU时间长,我们需要尽可能减少它们之间数据传输的次数。
对于投影矩阵可以假定固定,只传一次。顶点数据也只需要传输一次每次渲染时绑定VAO即可。而观察矩阵只需要每次游戏循环每个着色器传入一次即可,最麻烦的是模型矩阵。每次循环渲染物体都要改变一次,我们使用顶点属性来使它一次性传输完成。
顶点着色器:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 2) in vec2 texCoords;
layout (location = 3) in mat4 instanceMatrix;
out vec2 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
gl_Position = projection * view * instanceMatrix * vec4(position, 1.0f);
TexCoords = texCoords;
}
要注意的是我们不再使用模型uniform变量,取而代之的是把一个mat4的顶点属性,送一我们可以将变换矩阵储存为一个实例数组(instanced array)。然而,当我们声明一个数据类型为顶点属性的时候,它比一个vec4更大,是有些不同的。顶点属性被允许的最大数据量和vec4相等。因为一个mat4大致和4个vec4相等,我们为特定的矩阵必须保留4个顶点属性。因为我们将它的位置赋值为3个列的矩阵,顶点属性的位置就会是3、4、5和6。
下一步我们再次获得网格的VAO,这次使用glDrawElementsInstanced进行绘制:
// Draw meteorites
instanceShader.Use();
for(GLuint i = 0; i < rock.meshes.size(); i++)
{
glBindVertexArray(rock.meshes[i].VAO);
glDrawElementsInstanced(
GL_TRIANGLES, rock.meshes[i].vertices.size(), GL_UNSIGNED_INT, 0, amount
);
glBindVertexArray(0);
}
主程序:
// Std. Includes #include <string> #include <algorithm> using namespace std; // GLEW #define GLEW_STATIC #include <GL/glew.h> // GLFW #include <GLFW/glfw3.h> // GL includes #include "Shader.h" #include "Camera.h" #include "Model.h" // GLM Mathemtics #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> // Other Libs #include <SOIL.h> // Properties GLuint screenWidth = 1920, screenHeight = 1080; // Function prototypes void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void Do_Movement(); void printError(); GLuint loadTexture(const GLchar* path); GLuint loadCubemap(vector<const GLchar*> faces); // Camera Camera camera(glm::vec3(0.0f, 40.0f, 185.0f)); bool keys[1024]; GLfloat lastX = 400, lastY = 300; bool firstMouse = true; GLfloat deltaTime = 0.0f; GLfloat lastFrame = 0.0f; // The MAIN function, from here we start our application and run our Game loop int main() { // Init GLFW glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed glfwMakeContextCurrent(window); // Set the required callback functions glfwSetKeyCallback(window, key_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); // Options glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // Initialize GLEW to setup the OpenGL Function pointers glewExperimental = GL_TRUE; glewInit(); glGetError(); // Debug GLEW bug fix // Define the viewport dimensions glViewport(0, 0, screenWidth, screenHeight); // Setup some OpenGL options glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_PROGRAM_POINT_SIZE); // Setup and compile our shaders Shader planetShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt" , "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt" ); Shader instanceShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\rockVertexShader.txt" , "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\rockFragmentShader.txt" ); Model rock("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Debug\\rock\\rock.obj"); Model planet("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Debug\\planet\\planet.obj"); glm::mat4 projection = glm::perspective(45.0f, (GLfloat)screenWidth / (GLfloat)screenHeight, 1.0f, 10000.0f); planetShader.Use(); glUniformMatrix4fv(glGetUniformLocation(planetShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); // Also of instance shader instanceShader.Use(); glUniformMatrix4fv(glGetUniformLocation(instanceShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); // Generate a large list of semi-random model transformation matrices GLuint amount = 10000000; glm::mat4* modelMatrices; modelMatrices = new glm::mat4[amount]; srand(glfwGetTime()); // initialize random seed GLfloat radius = 150.0f; GLfloat offset = 25.0f; for (GLuint i = 0; i < amount; i++) { glm::mat4 model = glm::mat4(1.0f); // 1. Translation: Randomly displace along circle with radius 'radius' in range [-offset, offset] GLfloat angle = (GLfloat)i / (GLfloat)amount * 360.0f; GLfloat displacement = (rand() % (GLint)(2 * offset * 100)) / 100.0f - offset; GLfloat x = sin(angle) * radius + displacement; displacement = (rand() % (GLint)(2 * offset * 100)) / 100.0f - offset; GLfloat y = -2.5f + displacement * 0.4f; // Keep height of asteroid field smaller compared to width of x and z displacement = (rand() % (GLint)(2 * offset * 100)) / 100.0f - offset; GLfloat z = cos(angle) * radius + displacement; model = glm::translate(model, glm::vec3(x, y, z)); // 2. Scale: Scale between 0.05 and 0.25f GLfloat scale = (rand() % 20) / 100.0f + 0.05; model = glm::scale(model, glm::vec3(scale)); // 3. Rotation: add random rotation around a (semi)randomly picked rotation axis vector GLfloat rotAngle = (rand() % 360); model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f)); // 4. Now add to list of matrices modelMatrices[i] = model; } // forward declare the buffer GLuint buffer; glGenBuffers(1, &buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW); // Set transformation matrices as an instance vertex attribute (with divisor 1) // NOTE: We're cheating a little by taking the, now publicly declared, VAO of the model's mesh(es) and adding new vertexAttribPointers // Normally you'd want to do this in a more organized fashion, but for learning purposes this will do. for (GLuint i = 0; i < rock.meshes.size(); i++) { GLuint VAO = rock.meshes[i].VAO; glBindVertexArray(VAO); // Set attribute pointers for matrix (4 times vec4) glEnableVertexAttribArray(3); glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)0); glEnableVertexAttribArray(4); glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)(sizeof(glm::vec4))); glEnableVertexAttribArray(5); glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)(2 * sizeof(glm::vec4))); glEnableVertexAttribArray(6); glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (GLvoid*)(3 * sizeof(glm::vec4))); glVertexAttribDivisor(3, 1); glVertexAttribDivisor(4, 1); glVertexAttribDivisor(5, 1); glVertexAttribDivisor(6, 1); glBindVertexArray(0); } // Game loop while (!glfwWindowShouldClose(window)) { // Set frame time GLfloat currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; std::cout << deltaTime << std::endl; // Check and call events glfwPollEvents(); Do_Movement(); // Clear buffers glClearColor(0.03f, 0.03f, 0.03f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Add transformation matrices planetShader.Use(); glUniformMatrix4fv(glGetUniformLocation(planetShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(camera.GetViewMatrix())); instanceShader.Use(); glUniformMatrix4fv(glGetUniformLocation(instanceShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(camera.GetViewMatrix())); // Draw Planet planetShader.Use(); glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(0.0f, -5.0f, 0.0f)); model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f)); glUniformMatrix4fv(glGetUniformLocation(planetShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); planet.Draw(planetShader); // Draw meteorites instanceShader.Use(); glBindTexture(GL_TEXTURE_2D, rock.textures_loaded[0].id); // Note we also made the textures_loaded vector public (instead of private) from the model class. for (GLuint i = 0; i < rock.meshes.size(); i++) { glBindVertexArray(rock.meshes[i].VAO); glDrawElementsInstanced(GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount); glBindVertexArray(0); } // Swap the buffers glfwSwapBuffers(window); } delete[] modelMatrices; glfwTerminate(); return 0; } void printError() { GLuint errorCode = glGetError(); if (errorCode) std::cout << errorCode << std::endl; } // Loads a cubemap texture from 6 individual texture faces // Order should be: // +X (right) // -X (left) // +Y (top) // -Y (bottom) // +Z (front) // -Z (back) GLuint loadCubemap(vector<const GLchar*> faces) { GLuint textureID; glGenTextures(1, &textureID); int width, height; unsigned char* image; glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); for (GLuint i = 0; i < faces.size(); i++) { image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); SOIL_free_image_data(image); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); return textureID; } // This function loads a texture from file. Note: texture loading functions like these are usually // managed by a 'Resource Manager' that manages all resources (like textures, models, audio). // For learning purposes we'll just define it as a utility function. GLuint loadTexture(const GLchar* path) { //Generate texture ID and load texture data GLuint textureID; glGenTextures(1, &textureID); int width, height; unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB); // Assign texture to ID glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D); // Parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); SOIL_free_image_data(image); return textureID; } #pragma region "User input" // Moves/alters the camera positions based on user input void Do_Movement() { // Camera controls if (keys[GLFW_KEY_W]) camera.ProcessKeyboard(FORWARD, deltaTime); if (keys[GLFW_KEY_S]) camera.ProcessKeyboard(BACKWARD, deltaTime); if (keys[GLFW_KEY_A]) camera.ProcessKeyboard(LEFT, deltaTime); if (keys[GLFW_KEY_D]) camera.ProcessKeyboard(RIGHT, deltaTime); } // Is called whenever a key is pressed/released via GLFW void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) { if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); if (action == GLFW_PRESS) keys[key] = true; else if (action == GLFW_RELEASE) keys[key] = false; } void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } GLfloat xoffset = xpos - lastX; GLfloat yoffset = lastY - ypos; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); } #pragma endregion
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。