缓冲区布局抽象
首先我们抽象顶点数组的目的是什么?
对我们来说,顶点数组需要做的是将顶点缓冲区与某种布局联系在一起,所以顶点缓冲区就是有数据的缓冲区,它们没有实际的概念比如前三个浮点数是位置,没有类型或者大小之类的概念,它只是实际数据的普通缓冲区。每个字节是什么、这些顶点有多大等等才是顶点数组真正代表的,它应该把缓冲区和实际布局联系在一起。
顶点数组对象是 OpenGL 存储那种状态的方式,那么当我们考虑创建这个接口时,我们需要做的是需要一些东西来创建一个顶点数组。
顶点数组布局 VertexBufferLayout 类
#pragma once#include <vector>
#include <GL/glew.h>
#include "Render.h"struct VertexBufferElement {unsigned int type;unsigned int count;unsigned char normalized;static unsigned int GetSizeOfType(unsigned int type) {switch (type){case GL_FLOAT: return 4;case GL_UNSIGNED_INT: return 4;case GL_UNSIGNED_BYTE: return 1;}ASSERT(false);return 0;}
};class VertexBufferLayout {
private:std::vector<VertexBufferElement> m_Elements;unsigned int m_Stride;
public:VertexBufferLayout():m_Stride(0) {}template<typename T>void Push(unsigned int count) {//static_assert(false);}template<>void Push<float>(unsigned int count) {m_Elements.push_back({GL_FLOAT,count,GL_FALSE});m_Stride += VertexBufferElement::GetSizeOfType(GL_FLOAT) * count;}template<>void Push<unsigned int>(unsigned int count) {m_Elements.push_back({ GL_UNSIGNED_INT,count,GL_FALSE });m_Stride += VertexBufferElement::GetSizeOfType(GL_UNSIGNED_INT) * count;}template<>void Push<unsigned char>(unsigned int count) {m_Elements.push_back({ GL_UNSIGNED_BYTE,count,GL_TRUE });m_Stride += VertexBufferElement::GetSizeOfType(GL_UNSIGNED_BYTE) * count;}inline const std::vector<VertexBufferElement>& GetElements()const {return m_Elements;}inline unsigned int GetStride() const {return m_Stride;}
};
顶点数组类
.h #pragma once#include "VertexBuffer.h"
#include "VertexBufferLayout.h"class VertexArray {
private:unsigned int m_RenderID;
public:VertexArray();~VertexArray();void AddBuffer(const VertexBuffer& vb,const VertexBufferLayout& layout);void Bind() const;void UnBind() const;
};.cpp#include "VertexArray.h"#include "Render.h"VertexArray::VertexArray() {GLCall(glGenVertexArrays(1, &m_RenderID));GLCall(glBindVertexArray(m_RenderID));
}VertexArray::~VertexArray() {GLCall(glDeleteVertexArrays(1, &m_RenderID));
}void VertexArray::AddBuffer(const VertexBuffer& vb, const VertexBufferLayout& layout) {Bind();vb.Bind();const std::vector<VertexBufferElement>& elements = layout.GetElements();unsigned int offset = 0;for (unsigned int i = 0; i < elements.size();i++) {const auto& element = elements[i];GLCall(glEnableVertexAttribArray(i));GLCall(glVertexAttribPointer(i, element.count, element.type, element.normalized, layout.GetStride(), (const void*)offset));offset += element.count * VertexBufferElement::GetSizeOfType(element.type);}
}void VertexArray::Bind() const {GLCall(glBindVertexArray(m_RenderID));
}
void VertexArray::UnBind() const {GLCall(glBindVertexArray(0));
}
应用 Application 类
#include <GL/glew.h>
#include <GLFW/glfw3.h>#include <iostream>
#include <fstream>
#include <string>
#include <sstream>#include "Render.h"
#include "IndexBuffer.h"
#include "VertexBuffer.h"
#include "VertexBufferLayout.h"
#include "VertexArray.h"struct ShaderProgramSource {std::string VertexSource;std::string FragmentSource;
};//从文件中将着色器代码读取出来
static ShaderProgramSource ParseShader(const std::string& filepath) {std::ifstream stream(filepath);enum class ShaderType {NONE = -1, VERTEX = 0, FRAGMENT = 1};std::string line;std::stringstream ss[2];ShaderType type = ShaderType::NONE;while (getline(stream, line)) {if (line.find("#shader") != std::string::npos) {if (line.find("vertex") != std::string::npos) {type = ShaderType::VERTEX;}else if (line.find("fragment") != std::string::npos) {type = ShaderType::FRAGMENT;}}else {ss[(int)type] << line << '\n';}}return { ss[0].str(),ss[1].str() };
}static unsigned int CompileShader(unsigned int type, const std::string& source) {unsigned int id = glCreateShader(type);const char* src = source.c_str();glShaderSource(id, 1, &src, nullptr);glCompileShader(id); //Syntax error handlingint result;glGetShaderiv(id, GL_COMPILE_STATUS, &result);if (result == GL_FALSE) { //编译失败 获取错误信息int length;glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);char* message = (char*)alloca(length * sizeof(char));glGetShaderInfoLog(id, length, &length, message);std::cout << "Failed to compile" << (type == GL_VERTEX_SHADER ? "vertexShader" : "fragmentShader ") << " shader!" << std::endl;std::cout << message << std::endl;glDeleteShader(id);return 0;}return id;
}//我们向OpenGL提供我们实际的着色器源代码,我的着色器文本。我们想让OpenGL编译那个程序,将这两个链接到一个
//独立的着色器程序,然后给我们一些那个着色器返回的唯一标识符,我们就可以绑定着色器并使用
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) {unsigned int program = glCreateProgram();unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);//将这两个着色器附加到我们的程序上glAttachShader(program, vs);glAttachShader(program, fs);//链接程序glLinkProgram(program);glValidateProgram(program);//删除一些无用的中间文件glDeleteShader(vs);glDeleteShader(fs);return program;
}int main(void)
{GLFWwindow* window;/* Initialize the library */if (!glfwInit())return -1;glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //指定opengl版本,3glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //指定opengl次要版本 0.3 即3.3 //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); //兼容版本glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 核心版本/* Create a windowed mode window and its OpenGL context */window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);if (!window){glfwTerminate();return -1;}/* Make the window's context current */glfwMakeContextCurrent(window);if (glewInit() != GLEW_OK) { //should after MakeContextCurrent std::cout << "Error" << std::endl;}std::cout << glGetString(GL_VERSION) << std::endl;{float positions[] = {-0.5f, -0.5f, //00.5f, -0.5f, //10.5f, 0.5f, //2-0.5f,0.5f, //3};unsigned int indices[] = {0,1,2,2,3,0};VertexArray va;// 顶点缓冲区VertexBuffer vb(positions, 8 * sizeof(float));VertexBufferLayout layout;layout.Push<float>((unsigned int)2);va.AddBuffer(vb,layout);//索引缓冲区IndexBuffer ib(indices, 6);ShaderProgramSource source = ParseShader("res/Shaders/Basic.shader");unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);//绑定着色器glUseProgram(shader);int location = glGetUniformLocation(shader, "u_Color");ASSERT(location != -1);//将所有绑定解绑va.UnBind();glUseProgram(0);glBindBuffer(GL_ARRAY_BUFFER, 0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);float r = 0.0f;float increment = 0.05f;/* Loop until the user closes the window */while (!glfwWindowShouldClose(window)){/* Render here */glClear(GL_COLOR_BUFFER_BIT);GLClearError();//为了正确绘制图形,重新进行绑定glUseProgram(shader);//glBindBuffer(GL_ARRAY_BUFFER, buffer);//glEnableVertexAttribArray(0);//glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8, 0);va.Bind();ib.Bind();glUniform4f(location, r, 0.0f, 1.0f, 1.0f);//Draw callGLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));if (r > 1.0f) {increment = -0.0001f;}else if (r <= 0.0f) {increment = 0.0001f;}r += increment;/* Swap front and back buffers */glfwSwapBuffers(window);/* Poll for and process events */glfwPollEvents();}//删除着色器glDeleteProgram(shader);
}glfwTerminate();return 0;
}
着色器抽象
着色器需要什么?
第一步首先我们希望能够传递一个文件或者字符串,把它作为着色器来编译;第二步我们希望能够绑定和解绑着色器;第三步则是我们需要能够设置着色器中各种不同的统一变量,这可能就是我们现在正在研究的东西
参考博文:Cherno OpenGL 教程 | Fl0w3r
视频地址:S13.抽象OpenGL成类_哔哩哔哩_bilibili