这一节把之前写的顶点数组,顶点缓冲区,索引缓冲区,着色器等抽象到单独的类中,新建文件如下
顶点缓冲区
VertexBuffer.h
#pragma once
/// <summary>
/// 顶点缓冲区
/// </summary>
class VertexBuffer
{
private:
unsigned int m_RendererID;
public:
VertexBuffer(const void* data, unsigned int size);
~VertexBuffer();
//方法后面加const表示该方法是只读函数,不改变类的数据成员
void Bind() const;
void Unbind() const;
};
VertexBuffer.cpp
#include "VertexBuffer.h"
#include "Renderer.h"
VertexBuffer::VertexBuffer(const void* data, unsigned int size)
{
GLCall(glGenBuffers(1, &m_RendererID));
GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_RendererID));
GLCall(glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW));
}
VertexBuffer::~VertexBuffer()
{
GLCall(glDeleteBuffers(1, &m_RendererID));
}
void VertexBuffer::Bind() const
{
GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_RendererID));
}
void VertexBuffer::Unbind() const
{
GLCall(glBindBuffer(GL_ARRAY_BUFFER, 0));
}
顶点缓冲区布局
VertexBufferLayout.h
#pragma once
#include <vector>
#include "Renderer.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;
}
};
/// <summary>
/// 顶点缓冲区布局
/// </summary>
class VertexBufferLayout
{
private:
std::vector<VertexBufferElement> m_Elements;
unsigned int m_Stride; //步幅
public:
VertexBufferLayout() : m_Stride(0) {}
//count顶点由几部分组成
template<typename T>
void Push(unsigned int count)
{
//第一个参数表达式的值为真(true或者非零值),那么static_assert不做任何事情,就像它不存在一样,
//否则会产生一条编译错误,错误提示就是第二个参数提示字符串。
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)
{
//这里true是因为类型是字符型所以需要标准化为(0-1)的float类型
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; }
};
顶点数组
整合顶点缓冲区和顶点缓冲区布局
VertexArray.h
#pragma once
#include "VertexBuffer.h"
//直接引用造成循环引用,会报错
//#include "VertexBufferLayout.h"
class VertexBufferLayout;
/// <summary>
/// 顶点数组
/// </summary>
class VertexArray
{
private:
unsigned int m_RendererID;
public:
VertexArray();
~VertexArray();
void AddBuffer(const VertexBuffer& vb, const VertexBufferLayout& layout);
void Bind() const;
void Unbind() const;
};
VertexArray.cpp
#include "VertexArray.h"
#include "VertexBufferLayout.h"
#include "Renderer.h"
VertexArray::VertexArray()
{
GLCall(glGenVertexArrays(1, &m_RendererID)); /* 生成顶点数组 */
}
VertexArray::~VertexArray()
{
GLCall(glDeleteVertexArrays(1, &m_RendererID));
}
void VertexArray::AddBuffer(const VertexBuffer& vb, const VertexBufferLayout& layout)
{
//绑定顶点数组
Bind();
//绑定顶点缓冲区
vb.Bind();
//定义在内存中的布局
const auto& elements = layout.GetElements();
unsigned int offset = 0;
for (unsigned int i = 0; i < elements.size(); i++)
{
const auto& element = elements[i];
GLCall(glEnableVertexAttribArray(i)); /* 启用指定索引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_RendererID));
}
void VertexArray::Unbind() const
{
GLCall(glBindVertexArray(0));
}
索引缓冲区
IndexBuffer.h
#pragma once
/// <summary>
/// 索引缓冲区
/// </summary>
class IndexBuffer
{
private:
unsigned int m_RendererID;
unsigned int m_Count; //有多少个索引
public:
//size表示字节数,count表示元素数
IndexBuffer(const unsigned int* data, unsigned int count);
~IndexBuffer();
void Bind() const;
void Unbind() const;
inline unsigned int GetCount() const { return m_Count; }
};
IndexBuffer.cpp
#include "IndexBuffer.h"
#include "Renderer.h"
IndexBuffer::IndexBuffer(const unsigned int* data, unsigned int count)
:m_Count(count) //初始化列表
{
ASSERT(sizeof(unsigned int) == sizeof(GLuint));
GLCall(glGenBuffers(1, &m_RendererID));
GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID));
GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(unsigned int), data, GL_STATIC_DRAW));
}
IndexBuffer::~IndexBuffer()
{
GLCall(glDeleteBuffers(1, &m_RendererID));
}
void IndexBuffer::Bind() const
{
GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID));
}
void IndexBuffer::Unbind() const
{
GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
着色器
Shader.h
#pragma once
#include <string>
#include <unordered_map>
struct ShaderProgramSource
{
std::string VertexSource;
std::string FragmentSource;
};
class Shader
{
private:
std::string m_FilePath;
unsigned int m_RendererID;
//cache
std::unordered_map<std::string, int> m_UniformLocationCache;
public:
Shader(const std::string& filepath);
~Shader();
void Bind() const;
void Unbind() const;
void SetUniform1f(const std::string& name, float value);
void SetUniform4f(const std::string& name, float v0, float v1, float v2, float v3);
private:
ShaderProgramSource ParseShader(const std::string& filepath);
unsigned int CompileShader(unsigned int type, const std::string& source);
unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader);
int GetUniformLocation(const std::string& name);
};
Shader.cpp
#include "Shader.h"
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include "Renderer.h"
Shader::Shader(const std::string& filepath)
: m_FilePath(filepath), m_RendererID(0)
{
ShaderProgramSource source = ParseShader(filepath);
m_RendererID = CreateShader(source.VertexSource, source.FragmentSource);
}
Shader::~Shader()
{
GLCall(glDeleteProgram(m_RendererID));
}
ShaderProgramSource Shader::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() };
}
unsigned int Shader::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);
int 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 ? "vertex" : "fragment") << " shader!" << std::endl;
std::cout << message << std::endl;
glDeleteShader(id);
return 0;
}
return id;
}
unsigned int Shader::CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
unsigned int program = glCreateProgram();
unsigned int vShader = CompileShader(GL_VERTEX_SHADER, vertexShader);
unsigned int fShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);
glAttachShader(program, vShader);
glAttachShader(program, fShader);
glLinkProgram(program);
glValidateProgram(program);
glDeleteShader(vShader);
glDeleteShader(fShader);
return program;
}
void Shader::Bind() const
{
GLCall(glUseProgram(m_RendererID));
}
void Shader::Unbind() const
{
GLCall(glUseProgram(0));
}
void Shader::SetUniform1f(const std::string& name, float value)
{
GLCall(glUniform1f(GetUniformLocation(name), value));
}
void Shader::SetUniform4f(const std::string& name, float v0, float v1, float v2, float v3)
{
GLCall(glUniform4f(GetUniformLocation(name), v0, v1, v2, v3));
}
int Shader::GetUniformLocation(const std::string& name)
{
if (m_UniformLocationCache.find(name) != m_UniformLocationCache.end())
return m_UniformLocationCache[name];
GLCall(int location = glGetUniformLocation(m_RendererID, name.c_str()));
if (location == -1)
std::cout << "Warning: uniform " << name << " doesn't exist" << std::endl;
m_UniformLocationCache[name] = location;
return location;
}
渲染器
整合顶点数组,索引缓冲区,着色器
Renderer.h
#pragma once
#include <GL/glew.h>
#include "VertexArray.h"
#include "IndexBuffer.h"
#include "Shader.h"
//设置一个断言,__debugbreak是编译器本身的函数
#define ASSERT(x) if(!(x)) __debugbreak();
//x是我们要运行的函数,‘\’是换行符,#x会将x转变为一个字符串
//先清空所有的错误,然后运行函数x,检查错误
#define GLCall(x) GLClearError();\
x;\
ASSERT(GLLogCall(#x, __FILE__, __LINE__))
//申明方法
void GLClearError();
bool GLLogCall(const char* function, const char* file, int line);
/// <summary>
/// 渲染器
/// </summary>
class Renderer
{
public:
void Clear() const;
void Draw(const VertexArray& va, const IndexBuffer& ib, const Shader& shader) const;
};
Renderer.cpp
//注意用双引号
#include "Renderer.h"
#include <iostream>
void GLClearError()
{
//当代码发生错误时,会有错误标记(error flag),多个错误就有多个标记,
//glGetError会返回其中任意一个标记并将其重置,所以要在循环中调用。这里方法体不需要写
while (glGetError() != GL_NO_ERROR);
}
bool GLLogCall(const char* function, const char* file, int line)
{
while (GLenum error = glGetError())
{
std::cout << "[OpenGL Error] (" << error << "): " << function << " "
<< file << ":" << line << std::endl;
return false;
}
return true;
}
void Renderer::Clear() const
{
glClear(GL_COLOR_BUFFER_BIT);
}
void Renderer::Draw(const VertexArray& va, const IndexBuffer& ib, const Shader& shader) const
{
shader.Bind();
va.Bind();
ib.Bind();
GLCall(glDrawElements(GL_TRIANGLES, ib.GetCount(), GL_UNSIGNED_INT, nullptr));
}
抽象后的代码
Application.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "Renderer.h"
#include "VertexBuffer.h"
#include "VertexBufferLayout.h"
#include "IndexBuffer.h"
#include "VertexArray.h"
#include "Shader.h"
int main(void)
{
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
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);
glfwSwapInterval(1);
GLenum err = glewInit();
if (GLEW_OK != err)
std::cout << err << std::endl;
std::cout << glGetString(GL_VERSION) << std::endl;
//添加一个作用域,作用域结束时,会调用VertexBuffer和IndexBuffer的析构函数,
//不添加作用域的话,需要在glfwTerminate之前显式删除两个缓冲区,
//因为glfwTerminate会破坏OpenGL上下文,导致glGetError返回一个错误
{
float positions[] = {
-0.5f, -0.5f, //0
0.5f, -0.5f, //1
0.5f, 0.5f, //2
-0.5f, 0.5f, //3
};
unsigned int indices[] = {
0, 1, 2,
2, 3, 0,
};
VertexArray va;
VertexBuffer vb(positions, 4 * 2 * sizeof(float));
VertexBufferLayout layout;
layout.Push<float>(2);
va.AddBuffer(vb, layout);
IndexBuffer ib(indices, 6);
Shader shader("res/shaders/Basic.shader");
shader.Bind();
shader.SetUniform4f("u_Color", 0.8f, 0.3f, 0.8f, 1.0f);
va.Unbind();
vb.Unbind();
ib.Unbind();
shader.Unbind();
Renderer renderer;
float r = 0.0f;
float increment = 0.01f;
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
renderer.Clear();
renderer.Draw(va, ib, shader);
shader.SetUniform4f("u_Color", r, 0.3f, 0.8f, 1.0f);
if (r > 1.0f)
increment -= 0.01f;
else if (r < 0.0f)
increment += 0.01f;
r += increment;
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
//作用域结束后会调用所有析构函数
}
glfwTerminate();
return 0;
}
处理后代码清爽了很多,如果运行时报错,右键项目选择“重新生成”。