OpenGl 4.抽象代码

这一节把之前写的顶点数组,顶点缓冲区,索引缓冲区,着色器等抽象到单独的类中,新建文件如下
在这里插入图片描述

顶点缓冲区

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

处理后代码清爽了很多,如果运行时报错,右键项目选择“重新生成”。

猜你喜欢

转载自blog.csdn.net/sinat_34014668/article/details/126909490
今日推荐