文章目录
- 1. 顶点
- 2. 着色器(Shader)
1. 顶点
OpenGL本质是一个状态机,点作为表示图形最基本的元素,如何告诉OpenGL点的基本信息就是我们要做的事情
1.1VertexArray
VertexArray是所有顶点的集合,我们可以将VertexBuffer分成很多组,每组采取不同的着色方式
之后再解释Addlayout()
class VertexArray
{
public:
VertexArray()
{
//创建一个VertexArray
glGenVertexArrays(1, &m_vao);
//绑定
glBindVertexArray(m_vao);
}
~VertexArray()
{
glDeleteVertexArrays(1, &m_vao);
}
void Addlayout(const VertexBuffer& vb, const VertexBufferLayout& layout);
void Bind()const;
void UnBind()const;
private:
unsigned int m_vao;
};
void VertexArray::Addlayout(const VertexBuffer& vb, const VertexBufferLayout& layout)
{
vb.Bind();
const auto& elements = layout.GetElements();
unsigned int offset = 0;
for (int i = 0; i < elements.size(); i++)
{
const auto& element = elements[i];
//之后再解释
glVertexAttribPointer(i, element.size, element.type, element.normalized, element.stride, (const void*)offset);
glEnableVertexAttribArray(i);
offset += VertexBufferElement::GetSizeofType(element.type) * element.size;
}
}
void VertexArray::Bind()const
{
glBindVertexArray(m_vao);
}
void VertexArray::UnBind()const
{
glBindVertexArray(0);
}
1.2VertexBuffer
VertexBuffer就是将所有需要的点的数据进行缓存,但OpenGL仅仅只是知道了所有的数据,多少个字节表示一个点,OpenGL还是不知道
就像上图,整个大长方形表示我们所有点的内存总和,每个小长方形表示每一个点占的内存,这时OpenGL只是知道了存储这些点需要多少内存,但不知道有多少个点,每个点的每个字节的意义
class VertexBuffer
{
public:
VertexBuffer(const void* data,unsigned int size);
~VertexBuffer();
void Bind() const;
void Unbind() const;
private:
unsigned int m_RendererID;
};
VertexBuffer::VertexBuffer(const void* data, unsigned int size)
{
glGenBuffers(1, &m_RendererID);
glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
}
VertexBuffer::~VertexBuffer()
{
glDeleteBuffers(1, &m_RendererID);
}
void VertexBuffer::Bind()const
{
glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
}
void VertexBuffer::Unbind()const
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
1.3VertexBufferLayout
VertexBufferLayout描述VertexBuffer中每个点占的字节数多少和每个点内存的划分是怎样的
例如这个点,一共4个字节,我需要前两个字节表示坐标,后两个字节表示颜色,VertexBufferLayout就可分配字节,前两个字节为一组,后两个字节为一组,如果要具体的表示坐标与颜色后文会介绍
struct VertexBufferElement
{
int size;
int type;
unsigned char normalized;
static unsigned int stride;
static unsigned int GetSizeofType(int type)
{
switch (type)
{
case GL_FLOAT:
{
return 4;
}
case GL_UNSIGNED_INT:
{
return 4;
}
case GL_UNSIGNED_BYTE:
{
return 1;
}
case GL_BYTE:
{
return 1;
}
default:
{
break;
}
}
return false;
}
};
unsigned int VertexBufferElement::stride = 0;
class VertexBufferLayout
{
public:
template<class T>
void Push(int size)
{
static_assert(false);
}
template<>
void Push<float>(int size)
{
m_Elements.push_back({ size,GL_FLOAT,GL_FALSE });
VertexBufferElement::stride += VertexBufferElement::GetSizeofType(GL_FLOAT) * size;
}
template<>
void Push<unsigned int>(int size)
{
m_Elements.push_back({ size,GL_UNSIGNED_INT,GL_FALSE });
VertexBufferElement::stride += VertexBufferElement::GetSizeofType(GL_UNSIGNED_INT) * size;
}
template<>
void Push<char>(int size)
{
m_Elements.push_back({ size,GL_BYTE,GL_FALSE });
VertexBufferElement::stride += VertexBufferElement::GetSizeofType(GL_BYTE) * size;
}
template<>
void Push<unsigned char>(int size)
{
m_Elements.push_back({ size,GL_UNSIGNED_BYTE,GL_FALSE });
VertexBufferElement::stride += VertexBufferElement::GetSizeofType(GL_UNSIGNED_BYTE) * size;
}
inline const std::vector<VertexBufferElement>& GetElements() const { return m_Elements; }
private:
std::vector<VertexBufferElement> m_Elements;
};
1.4解释1.1中的Addlayout
解释之前理解下glVertexAttribPointer
glVertexAttribPointer 是一个 OpenGL 函数,用于将当前的顶点属性与顶点缓冲对象(VBO)关联起来。它的原型如下:
void glVertexAttribPointer(GLuint index, GLint size, GLenum type,
GLboolean normalized, GLsizei stride, const GLvoid *pointer);
index 指定要配置的顶点属性的编号。 size 指定每个顶点属性的分量数(1、2、3 或 4,就像向量的维度一样)。
type指定每个分量的数据类型,可以是GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT或 GL_DOUBLE。 normalized1 指定是否将数据归一化到 [0,1] 或 [-1,1] 范围内。
stride(步长)指定连续两个顶点属性间的字节数。如果为 0,则表示顶点属性是紧密排列的。 pointer
指向缓冲对象中第一个顶点属性的第一个分量的地址。(offset的作用)
知道了这个之后再看看Addlayout通过VertexBufferLayout将每个顶点的布局进行加载
void VertexArray::Addlayout(const VertexBuffer& vb, const VertexBufferLayout& layout)
{
vb.Bind();
const auto& elements = layout.GetElements();
unsigned int offset = 0;
for (int i = 0; i < elements.size(); i++)
{
const auto& element = elements[i];
glVertexAttribPointer(i, element.size, element.type, element.normalized, element.stride, (const void*)offset);
glEnableVertexAttribArray(i);
offset += VertexBufferElement::GetSizeofType(element.type) * element.size;
}
}
2. 着色器(Shader)
我们已经知道了顶点的信息,但目前而言,顶点的信息只是一串数,让OpenGL如何解析这些数,就是Shader要做的事情
OpenGL需要在另一个文件单独写一个着色方法,因此本文只介绍怎么创建着色器
struct ShaderProgramSource
{
std::string Vertex;
std::string Fragment;
};
class Shader
{
public:
Shader(const std::string& filepath);
~Shader();
void SetUniform4f(const std::string& name, float v1, float v2, float v3, float v4);
void SetUniform1i(const std::string& name, int v1);
void Bind()const;
void Unbind() const;
private:
int GetUniformLocation(const std::string& name);
ShaderProgramSource ParseShader(const std::string& filepath);
unsigned int ComplieShader(const std::string& source, unsigned int type);
unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader);
private:
unsigned int m_program;
std::string m_filepath;
std::unordered_map<std::string,int> m_UniformLocations;
};
Shader::Shader(const std::string& filepath)
:m_filepath(filepath)
,m_program(0)
{
ShaderProgramSource shaderProgram = ParseShader(m_filepath);
m_program = CreateShader(shaderProgram.Vertex, shaderProgram.Fragment);
glUseProgram(m_program);
}
Shader::~Shader()
{
glDeleteProgram(m_program);
}
void Shader::SetUniform4f(const std::string& name, float v0, float v1, float v2, float v3)
{
glUniform4f(GetUniformLocation(name), v0, v1, v2, v3);
}
void Shader::SetUniform1i(const std::string& name, int v0)
{
glUniform1i(GetUniformLocation(name), v0);
}
void Shader::Bind()const
{
glUseProgram(m_program);
}
void Shader::Unbind() const
{
glUseProgram(0);
}
int Shader::GetUniformLocation(const std::string& name)
{
if (m_UniformLocations.find(name) != m_UniformLocations.end())
{
return m_UniformLocations[name];
}
GLCall(int location = glGetUniformLocation(m_program, name.c_str()));
if (location == -1)
{
std::cout << "fail glGetUniformLocation" << std::endl;
return 0;
}
else
{
m_UniformLocations[name] = location;
return location;
}
}
ShaderProgramSource Shader::ParseShader(const std::string& filepath)
{
enum ShaderTypeEnum
{
NONE = -1,
VERTEX = 0,
FRAGMENT = 1
};
std::ifstream stream(filepath);
std::string line;
std::string ss[2];
ShaderTypeEnum type = NONE;
while (std::getline(stream, line))
{
if (line.find("#shader") != std::string::npos)
{
if (line.find("vertex") != std::string::npos)
{
type = VERTEX;
}
else
{
type = FRAGMENT;
}
}
//没有#shader
else
{
ss[type] += line;
ss[type] += '\n';
}
}
return { ss[0],ss[1] };
}
unsigned int Shader::ComplieShader(const std::string& source, unsigned int type)
{
unsigned int id = glCreateShader(type);
const char* str = source.c_str();
glShaderSource(id, 1, &str, nullptr);
glCompileShader(id);
int result;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
if (result == false)
{
int length;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
char* message = new char[length];
glGetShaderInfoLog(id, length, &length, message);
std::cout << "Failed to complie:" << ((type == GL_VERTEX_SHADER) ? "vertex" : "fragment") << 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 vs = ComplieShader(vertexShader, GL_VERTEX_SHADER);
unsigned int fs = ComplieShader(fragmentShader, GL_FRAGMENT_SHADER);
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
// glValidateProgram(program);
glDeleteShader(vs);
glDeleteShader(fs);
return program;
}