七、OpenGL 简单开始 --- 抽象工作
1. 缓冲区抽象
1.1 索引缓冲区对象
IndexBuffer.h
class IndexBuffer {
private:
unsigned int m_RendererID;
unsigned int m_Count;
public:
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));
// 1. 生成缓冲区 (缓冲区个数 和 缓冲区编号)
GLCall(glGenBuffers(1, &m_RendererID));
// 2. 绑定缓冲区 (缓冲区数组 和 缓冲区编号)
GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID));
// 3. 存入数据 (缓冲区数组 数据大小 数据 使用方式)
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));
}
1.2 顶点缓冲区对象
VertextBuffer.h
class VertextBuffer {
private:
unsigned int m_RendererID;
public:
VertextBuffer(const void* data, unsigned int size);
~VertextBuffer();
void Bind() const;
void Unbind() const;
};
VertextBuffer.cpp
#include "VertexBuffer.h"
#include "Renderer.h"
VertextBuffer::VertextBuffer(const void* data, unsigned int size)
{
// 1. 生成缓冲区 (缓冲区个数 和 缓冲区编号)
GLCall(glGenBuffers(1, &m_RendererID));
// 2. 绑定缓冲区 (缓冲区数组 和 缓冲区编号)
GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_RendererID));
// 3. 存入数据 (缓冲区数组 数据大小 数据 使用方式)
GLCall(glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW));
}
VertextBuffer::~VertextBuffer()
{
// 删除缓冲区
GLCall(glDeleteBuffers(1, &m_RendererID));
}
void VertextBuffer::Bind() const
{
// 绑定缓冲区 (缓冲区数组 和 缓冲区编号)
GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_RendererID));
}
void VertextBuffer::Unbind() const
{
// 绑定缓冲区 (缓冲区数组 和 缓冲区编号)
GLCall(glBindBuffer(GL_ARRAY_BUFFER, 0));
}
2. 属性布局对象
VertextBufferLayout.h
#pragma once
#include <GL/glew.h>
#include <vector>
#include "Renderer.h"
using namespace std;
/* 顶点属性信息 */
struct VertextBufferElement
{
unsigned int type; // 类型
unsigned int count; // 维度
unsigned char normalized; // 是否标准化到 [0,1] (一般是处理颜色时需要)
VertextBufferElement(unsigned int type, unsigned int count, bool normalized)
: type(type), count(count), normalized(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 VertextBufferLayout {
private:
vector<VertextBufferElement> m_Element; // 顶点属性数组
unsigned int m_Stride; // 顶点偏移量
public:
VertextBufferLayout() : m_Stride(0) {}
// 添加新属性信息
template<typename T>
void Push(unsigned int count) {
static_assert(true);
}
template<>
void Push<float>(unsigned int count) {
m_Element.emplace_back(VertextBufferElement(GL_FLOAT, count, GL_FALSE));
m_Stride += VertextBufferElement::GetSizeOfType(GL_FLOAT) * count;
}
template<>
void Push<unsigned int>(unsigned int count) {
m_Element.emplace_back(VertextBufferElement(GL_UNSIGNED_INT, count, GL_FALSE));
m_Stride += VertextBufferElement::GetSizeOfType(GL_UNSIGNED_INT) * count;
}
template<>
void Push<unsigned char>(unsigned int count) {
m_Element.emplace_back(VertextBufferElement(GL_UNSIGNED_BYTE, count, GL_TRUE));
m_Stride += VertextBufferElement::GetSizeOfType(GL_UNSIGNED_BYTE) * count;
}
inline unsigned int GetStride() const { return m_Stride; }
inline const vector<VertextBufferElement>& GetElements() const { return m_Element; }
};
3. 顶点数组对象
VertextArray.h
#pragma once
#include "VertexBuffer.h"
#include "VertextBufferLayout.h"
class VertextArray {
private:
unsigned int m_RendererID;
public:
VertextArray();
~VertextArray();
void Bind() const;
void Unbind() const;
void AddBuffer(const VertextBuffer& vb, const VertextBufferLayout& layout);
};
VertextArray.cpp
#include "VertextArray.h"
#include "Renderer.h"
VertextArray::VertextArray()
{
// 生成数组
GLCall(glGenVertexArrays(1, &m_RendererID));
}
VertextArray::~VertextArray()
{
GLCall(glDeleteVertexArrays(1, &m_RendererID));
}
void VertextArray::Bind() const
{
GLCall(glBindVertexArray(m_RendererID));
}
void VertextArray::Unbind() const
{
GLCall(glBindVertexArray(0));
}
void VertextArray::AddBuffer(const VertextBuffer& vb, const VertextBufferLayout& layout) const
{
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));
/*
设置属性布局
1. 属性位置索引,代表第几个属性
2. 几个数值表示该属性(维度,如位置(x, y)就是二维)
3. 数值类型
4. 是否标准化到 [0,1] (一般是处理颜色时需要)
5. 顶点偏移量,某顶点移动到下个顶点所需字节数(仅有位置属性,float x + float y = 2 * 4 = 8 byte)
6. 属性偏移量,某属性移动到下个同属性所需字节数(单一属性,不用偏移)。
如有 0 位置:(float x, float y); 1 法线:float normal,
则在取法线时,需要越过位置,即偏移量为 2 * 4 = 8 byte。参数传入(const void*) 8。
*/
GLCall(glVertexAttribPointer(i, element.count, element.type, element.normalized,
layout.GetStride(), (const void*)offset));
offset += element.count * VertextBufferElement::GetSizeOfType(element.type);
}
}
4. 着色器对象
Shader.h
#pragma once
#include <iostream>
#include <unordered_map>
using namespace std;
// 程序源码(同时返回多个参数的载体)
struct ShaderProgramSource
{
string VertexSource;
string FragmentSource;
};
class Shader
{
private:
string m_FilePath;
unsigned int m_RendererID;
unordered_map<string, int> m_UniformLocationCache; // 方便查询,而不是每次设置 Uniform 都要找重新找 Location
public:
Shader(const string& filepath);
~Shader();
// 绑定相关
void Bind() const;
void Unbind() const;
// 设置 Uniforms
void SetUniform4f(const string& name, float v0, float v1, float v2, float v3);
private:
// 从文件提取源码
ShaderProgramSource ParseShader(const string& filePath);
// 编译 Shader(Shader 对象)
unsigned int CompileShader(unsigned int type, const string& source);
// 创建 Shader(Programm 对象)
unsigned int CreateShader(const string& vertextShader, const string& fragmentShader);
// 获取 UniformLocation
int GetUniformLocation(const string& name);
};
Shader.cpp
#include "Shader.h"
#include "Renderer.h"
#include <fstream>
#include <sstream>
Shader::Shader(const string& filepath)
: m_FilePath(filepath), m_RendererID(0)
{
// 源代码
ShaderProgramSource source = ParseShader(filepath);
// 创建
m_RendererID = CreateShader(source.VertexSource, source.FragmentSource);
}
Shader::~Shader()
{
// 删除 Program 对象
GLCall(glDeleteProgram(m_RendererID));
}
ShaderProgramSource Shader::ParseShader(const string& filePath)
{
// 着色器枚举类
enum class ShaderType
{
NONE = -1, VERTEX = 0, FRAGMENT = 1
};
// 默认类型定义
ShaderType type = ShaderType::NONE;
// 文件流输入流
ifstream stream(filePath);
// 字符串输入输出流 (字符串数组 大小为 2)
stringstream ss[2];
// 按行获取文件内容
string line;
while (getline(stream, line))
{
// 获取着色器类型
if (line.find("#shader") != string::npos)
{
if (line.find("vertex") != string::npos) type = ShaderType::VERTEX;
else if (line.find("fragment") != 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 string& source)
{
// 1. Creates a shader object (指定着色器类型:vertex shader)
GLCall(unsigned int id = glCreateShader(type));
// Shader 源码数组
const char* src = source.c_str();
/*
2. 着色器源代码替换
指定着色器
指定字符串数组和长度数组中元素的个数。
指定指向字符串的指针数组,字符串中包含要加载到着色器中的源代码。
指定字符串长度的数组
(如果 length 为 NULL,则每个字符串都被假定为空值结束
如果 length 值不是 NULL,它将指向一个数组,其中包含字符串中每个相应元素的字符串长度)
*/
GLCall(glShaderSource(id, 1, &src, nullptr));
// 3. 编译 正式生成 新 Shader
GLCall(glCompileShader(id));
// 4. 错误处理
int result;
GLCall(glGetShaderiv(id, GL_COMPILE_STATUS, &result)); // 获取错误状态
if (result == GL_FALSE)
{
int length;
GLCall(glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length)); // 获取错误信息长度
char* message = (char*)malloc(length * sizeof(char)); // 申请同大小堆空间,存放错误信息
GLCall(glGetShaderInfoLog(id, length, &length, message)); // 根据长度,空间,程序提取出 错误信息
// 输出
cout << "编译" << (type == GL_VERTEX_SHADER ? "顶点" : "片元") << "着色器失败!" << endl;
cout << message << endl;
return 0;
}
// 返回着色器编号
return id;
}
unsigned int Shader::CreateShader(const string& vertextShader, const string& fragmentShader)
{
// 1. Creates a program object (程序对象是一个可以附加着色器对象的对象)
GLCall(unsigned int program = glCreateProgram());
// 2. 编译着色器 (指定着色器类型,源代码)
GLCall(unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertextShader));
GLCall(unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader));
// 3. 附加着色器对象
GLCall(glAttachShader(program, vs));
GLCall(glAttachShader(program, fs));
// 4. 链接程序对象(被用于创建在之前附加的着色器上运行的可执行文件)
GLCall(glLinkProgram(program));
// 5. 检查程序中包含的可执行文件是否能在当前 OpenGL 状态下执行。
GLCall(glValidateProgram(program));
// 6. 绑定生成后,删除之前临时的文件 (不用 detach 原因:留下的源代码可执行文件占用空间少,而且经常用)
GLCall(glDeleteShader(vs));
GLCall(glDeleteShader(fs));
// 返回程序对象
return program;
}
void Shader::Bind() const
{
GLCall(glUseProgram(m_RendererID));
}
void Shader::Unbind() const
{
GLCall(glUseProgram(0));
}
void Shader::SetUniform4f(const string& name, float v0, float v1, float v2, float v3)
{
GLCall(glUniform4f(GetUniformLocation(name), v0, v1, v2, v3));
}
int Shader::GetUniformLocation(const 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) cout << "Warning: uniform '" << name << "' doesn't exist!" << endl;
m_UniformLocationCache[name] = location;
return location;
}

浙公网安备 33010602011771号