七、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;
}

5. 渲染器对象

6. 纹理对象

posted @ 2024-05-20 00:21  bok_tech  阅读(53)  评论(0)    收藏  举报