Hands-on C++ Game Animation Programming阅读笔记(四)
Chapter 6: Building an Abstract Renderer
In order to avoid getting caught up in any specific graphics APIs, in this chapter, you will build an abstraction layer on top of OpenGL.
本章没啥重点内容,就是OpenGL的那些东西,稍微新一点的几个OpenGL的api,之前没使用过:
glGetActiveUniform
glGetActiveAttrib
Shader类
定义Shader头文件
头文件如下:
#ifndef _H_SHADER_
#define _H_SHADER_
#include <map>
#include <string>
class Shader
{
private:
unsigned int mHandle;
std::map<std::string, unsigned int> mAttributes;
std::map<std::string, unsigned int> mUniforms;
private:
std::string ReadFile(const std::string& path);
// compile shader source code and return an OpenGL handle.
unsigned int CompileVertexShader(const std::string& vertex);
unsigned int CompileFragmentShader(const std::string& fragment);
// link two shaders into a shader program
bool LinkShaders(unsigned int vertex, unsigned int fragment);
// populate有填充的意思, 这俩函数是取OpenGL里的Attribs和Uniforms, 存到两个map里面
void PopulateAttributes();
void PopulateUniforms();
private:
// 通过私有的copy ctor和copy assignment operator禁止Shader对象的拷贝
Shader(const Shader&);
Shader& operator=(const Shader&);
public:
Shader();
// The overload constructor will call the Load method, which loads shaders from files and compiles them.
Shader(const std::string& vertex, const std::string& fragment);
// The destructor will release the OpenGL shader handle that the Shader class is holding on
~Shader();
void Load(const std::string& vertex, const std::string& fragment);
// Before a shader is used, it will need to be bound with the Bind function.
void Bind();
void UnBind();
// perform lookups in the appropriate dictionaries
unsigned int GetAttribute(const std::string& name);
unsigned int GetUniform(const std::string& name);
// returns the shader's OpenGL handle
unsigned int GetHandle();
};
#endif
Get All Uniforms And Attributes
一个Shader对象代表一个Shader Program,读取文件、编译和Link Shader都是我做过好多次的东西,这次唯一没做过的就是获取一个Shader Program的所有Uniforms和Attributes,存到Shader的两个map里,Shader的uniforms我比较清楚,而Attributes应该是使用glVertexArrayAttrib分配的顶点属性。
相关部分的代码如下:
// 准确的说, 这个类应该叫OpenGLShader
class Shader
{
std::map<std::string, unsigned int> mAttributes;
std::map<std::string, unsigned int> mUniforms;
// perform lookups in the appropriate dictionaries
unsigned int GetAttribute(const std::string& name)
{
// 遍历map. 找到命名为name的attribute对应的id
std::map<std::string, unsigned int>::iterator it = mAttributes.find(name);
if (it == mAttributes.end())
{
std::cout << "Retrieving bad attribute index: " << name << "\n";
return 0;
}
return it->second;
}
unsigned int GetUniform(const std::string& name)
{
// 遍历map. 找到命名为name的uniform对应的id
std::map<std::string, unsigned int>::iterator it = mUniforms.find(name);
if (it == mUniforms.end())
{
std::cout << "Retrieving bad uniform index: " << name << "\n";
return 0;
}
return it->second;
}
// 收集Shader Program里的active attribs, 加入到map里
void PopulateAttributes()
{
int count = -1;
int length;
char name[128];
int size;
GLenum type;
glUseProgram(mHandle);
// glGetProgramiv用于获取Shader Program的信息, 第二个参数是个枚举,可以为:
// GL_DELETE_STATUS, GL_LINK_STATUS, GL_VALIDATE_STATUS, GL_INFO_LOG_LENGTH,
// GL_ATTACHED_SHADERS, GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
// GL_ACTIVE_UNIFORMS, GL_ACTIVE_UNIFORM_MAX_LENGTH.
// 第三个参数是一个int*, 其含义根据输入的枚举不同而不一样, 这里的意思是active attribs的个数
glGetProgramiv(mHandle, GL_ACTIVE_ATTRIBUTES, &count);
// 遍历每一个顶点属性
for (int i = 0; i < count; ++i)
{
memset(name, 0, sizeof(char) * 128);
// 获取shader program的第i个active attrib的length、size、type和name
glGetActiveAttrib(mHandle, (GLuint)i, 128, &length, &size, &type, name);
// 获取对应attrib的id, 存到map里
int attrib = glGetAttribLocation(mHandle, name);
if (attrib >= 0)
mAttributes[name] = attrib;
}
glUseProgram(0);
}
// 收集Shader Program里的active uniforms, 加入到map里
void PopulateUniforms()
{
int count = -1;
int length;
char name[128];
int size;
GLenum type;
char testName[256];
glUseProgram(mHandle);
// GL_ACTIVE_UNIFORMS
glGetProgramiv(mHandle, GL_ACTIVE_UNIFORMS, &count);
// 遍历每个active uniform
for (int i = 0; i < count; ++i)
{
memset(name, 0, sizeof(char) * 128);
glGetActiveUniform(mHandle, (GLuint)i, 128, &length, &size, &type, name);
int uniform = glGetUniformLocation(mHandle, name);// Get Handle in OpenGL
// 额外加了个对uniform的处理
if (uniform >= 0)
{
std::string uniformName = name;
// 通过uniform的名字, 判断它是不是uniform array
std::size_t found = uniformName.find('[');
// 如果uniform是数组
if (found != std::string::npos)
{
// 清除string后面的'['开始的内容
uniformName.erase(uniformName.begin() + found, uniformName.end());
// Populate subscripted names too
unsigned int uniformIndex = 0;
// 以uniform的名字开始, 往后搜寻同一数组内的uniform
while (true)
{
memset(testName, 0, sizeof(char) * 256);
// 拼接新的字符串: uniformName[uniformIndex], 存到testName里
sprintf(testName, "%s[%d]", uniformName.c_str(), uniformIndex++);
int uniformLocation = glGetUniformLocation(mHandle, testName);
if (uniformLocation < 0)
break;
// 加入到mUniforms里, 这里的testName是带[]序号的
mUniforms[testName] = uniformLocation;
}
}
// 把不带'[]'的单纯的数组的名字, 或者uniform不为数组时的单独的uniform, 存入map
// 也就是说, 对于m个元素的uniform数组, map里存了m+1个相关的pair
mUniforms[uniformName] = uniform;
}
}
glUseProgram(0);
}
}
cpp文件
// 严格意义上说, 这里的Shader应该叫OpenGLShader, 因为这个cpp里用到了很多OpenGL的api, 并不是abstract的
#define _CRT_SECURE_NO_WARNINGS
#include "Shader.h"
#include "glad.h"
#include <fstream>
#include <sstream>
#include <iostream>
Shader::Shader()
{
mHandle = glCreateProgram();
}
Shader::Shader(const std::string& vertex, const std::string& fragment)
{
mHandle = glCreateProgram();
Load(vertex, fragment);
}
Shader::~Shader()
{
glDeleteProgram(mHandle);
}
// 利用ifstream和stringstream读取path文件里的内容, 老一套了
std::string Shader::ReadFile(const std::string& path)
{
std::ifstream file;
file.open(path);
std::stringstream contents;
contents << file.rdbuf();
file.close();
return contents.str();
}
unsigned int Shader::CompileVertexShader(const std::string& vertex)
{
unsigned int v_shader = glCreateShader(GL_VERTEX_SHADER);
const char* v_source = vertex.c_str();
glShaderSource(v_shader, 1, &v_source, NULL);
glCompileShader(v_shader);
int success = 0;
glGetShaderiv(v_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
char infoLog[512];
glGetShaderInfoLog(v_shader, 512, NULL, infoLog);
std::cout << "ERROR: Vertex compilation failed.\n";
std::cout << "\t" << infoLog << "\n";
glDeleteShader(v_shader);
return 0;
};
return v_shader;
}
//
unsigned int Shader::CompileFragmentShader(const std::string& fragment)
{
unsigned int f_shader = glCreateShader(GL_FRAGMENT_SHADER);
const char* f_source = fragment.c_str();
glShaderSource(f_shader, 1, &f_source, NULL);
glCompileShader(f_shader);
int success = 0;
// Check for errors with glGetShaderiv
glGetShaderiv(f_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
char infoLog[512];
glGetShaderInfoLog(f_shader, 512, NULL, infoLog);
std::cout << "ERROR: Fragment compilation failed.\n";
std::cout << "\t" << infoLog << "\n";
glDeleteShader(f_shader);
return 0;
};
return f_shader;
}
bool Shader::LinkShaders(unsigned int vertex, unsigned int fragment)
{
glAttachShader(mHandle, vertex);
glAttachShader(mHandle, fragment);
glLinkProgram(mHandle);
int success = 0;
glGetProgramiv(mHandle, GL_LINK_STATUS, &success);
if (!success)
{
char infoLog[512];
glGetProgramInfoLog(mHandle, 512, NULL, infoLog);
std::cout << "ERROR: Shader linking failed.\n";
std::cout << "\t" << infoLog << "\n";
glDeleteShader(vertex);
glDeleteShader(fragment);
return false;
}
glDeleteShader(vertex);
glDeleteShader(fragment);
return true;
}
void Shader::PopulateAttributes()
{
int count = -1;
int length;
char name[128];
int size;
GLenum type;
glUseProgram(mHandle);
glGetProgramiv(mHandle, GL_ACTIVE_ATTRIBUTES, &count);
for (int i = 0; i < count; ++i)
{
memset(name, 0, sizeof(char) * 128);
glGetActiveAttrib(mHandle, (GLuint)i, 128, &length, &size, &type, name);
int attrib = glGetAttribLocation(mHandle, name);
if (attrib >= 0)
mAttributes[name] = attrib;
}
glUseProgram(0);
}
void Shader::PopulateUniforms()
{
int count = -1;
int length;
char name[128];
int size;
GLenum type;
char testName[256];
glUseProgram(mHandle);
glGetProgramiv(mHandle, GL_ACTIVE_UNIFORMS, &count);
for (int i = 0; i < count; ++i)
{
memset(name, 0, sizeof(char) * 128);
glGetActiveUniform(mHandle, (GLuint)i, 128, &length, &size, &type, name);
int uniform = glGetUniformLocation(mHandle, name);
if (uniform >= 0)
{
std::string uniformName = name;
std::size_t found = uniformName.find('[');
if (found != std::string::npos)
{
uniformName.erase(uniformName.begin() + found, uniformName.end());
// Populate subscripted names too
unsigned int uniformIndex = 0;
while (true)
{
memset(testName, 0, sizeof(char) * 256);
sprintf(testName, "%s[%d]", uniformName.c_str(), uniformIndex++);
int uniformLocation = glGetUniformLocation(mHandle, testName);
if (uniformLocation < 0)
break;
mUniforms[testName] = uniformLocation;
}
}
mUniforms[uniformName] = uniform;
}
}
glUseProgram(0);
}
// 这个Load函数, 参数既可以是vs和fg的shader的路径
// 也可以是直接代表shader内容的长string@
void Shader::Load(const std::string& vertex, const std::string& fragment)
{
std::ifstream f(vertex.c_str());
bool vertFile = f.good();
f.close();
f = std::ifstream(vertex.c_str());
bool fragFile = f.good();
f.close();
std::string v_source = vertex;
if (vertFile)
v_source = ReadFile(vertex);
std::string f_source = fragment;
if (fragFile)
f_source = ReadFile(fragment);
unsigned int v_shader = CompileVertexShader(v_source);
unsigned int f_shader = CompileFragmentShader(f_source);
if (LinkShaders(v_shader, f_shader))
{
PopulateAttributes();
PopulateUniforms();
}
}
void Shader::Bind()
{
glUseProgram(mHandle);
}
void Shader::UnBind()
{
glUseProgram(0);
}
unsigned int Shader::GetHandle()
{
return mHandle;
}
unsigned int Shader::GetAttribute(const std::string& name)
{
std::map<std::string, unsigned int>::iterator it = mAttributes.find(name);
if (it == mAttributes.end())
{
std::cout << "Retrieving bad attribute index: " << name << "\n";
return 0;
}
return it->second;
}
unsigned int Shader::GetUniform(const std::string& name)
{
std::map<std::string, unsigned int>::iterator it = mUniforms.find(name);
if (it == mUniforms.end())
{
std::cout << "Retrieving bad uniform index: " << name << "\n";
return 0;
}
return it->second;
}
Working with buffers (attributes)
A vertex is made up of attributes. For example, a vertex has a position and a normal, which are both attributes.
The Attribute class declaration
创建Attribute.h
文件:
#ifndef _H_ATTRIBUTE_
#define _H_ATTRIBUTE_
#include <vector>
// 模板类, 可以存储任何顶点属性的数据
template<typename T>
class Attribute
{
protected:
unsigned int mHandle;
unsigned int mCount;
private:
// 同样是不允许Attribute对象复制, 因为它传给GPU就可以了
Attribute(const Attribute& other);
Attribute& operator=(const Attribute& other);
void SetAttribPointer(unsigned int slot);// 调用glVertexAttribIPointer
public:
Attribute();// 构造函数里调用glGenBuffers, 得到的handle赋给mHandle
~Attribute();
// Set函数Set的是GPU里的数据, 其实是Upload CPU数据到GPU
// 内部调用的是glBindBuffer和glBufferData
// Set函数接受两种形式的T类型的数组
void Set(T* inputArray, unsigned int arrayLength);
void Set(std::vector<T>& input);
// 底层应该是调用glVertexArrayAttrib
void BindTo(unsigned int slot);
void UnBindFrom(unsigned int slot);
unsigned int Count();// return mCount
unsigned int GetHandle();// return mHandle
};
#endif
Implementing the Attribute class
#include "Attribute.h"
#include "glad.h"
#include "vec2.h"
#include "vec3.h"
#include "vec4.h"
#include "quat.h"
template Attribute<int>;
template Attribute<float>;
template Attribute<vec2>;
template Attribute<vec3>;
template Attribute<vec4>;
template Attribute<ivec4>;
template Attribute<quat>;
template<typename T>
Attribute<T>::Attribute()
{
glGenBuffers(1, &mHandle);
mCount = 0;
}
template<typename T>
Attribute<T>::~Attribute()
{
glDeleteBuffers(1, &mHandle);
}
template<typename T>
unsigned int Attribute<T>::Count()
{
return mCount;
}
template<typename T>
unsigned int Attribute<T>::GetHandle()
{
return mHandle;
}
template<typename T>
void Attribute<T>::Set(T* inputArray, unsigned int arrayLength)
{
mCount = arrayLength;
unsigned int size = sizeof(T);
glBindBuffer(GL_ARRAY_BUFFER, mHandle);
glBufferData(GL_ARRAY_BUFFER, size * mCount, inputArray, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
template<typename T>
void Attribute<T>::Set(std::vector<T>& input)
{
Set(&input[0], (unsigned int)input.size());
}
// 这里直接利用模板特化, 写出了各种顶点数据类型调用的不同的glVertexAttribIPointer函数, 有:
// int, ivec4, float, vec2, vec3, vec4, quat
// int数组数据放到VAO的第slot号槽位
template<>
void Attribute<int>::SetAttribPointer(unsigned int slot)
{
glVertexAttribIPointer(slot, 1, GL_INT, 0, (void*)0);// 注意这里的IPointer
}
// ivec4数组数据放到VAO的第slot号槽位
template<>
void Attribute<ivec4>::SetAttribPointer(unsigned int slot)
{
glVertexAttribIPointer(slot, 4, GL_INT, 0, (void*)0);// 注意这里的IPointer
}
// float数组数据放到VAO的第slot号槽位
template<>
void Attribute<float>::SetAttribPointer(unsigned int slot)
{
glVertexAttribPointer(slot, 1, GL_FLOAT, GL_FALSE, 0, (void*)0);
}
// vec2数组数据放到VAO的第slot号槽位, 比如normal
template<>
void Attribute<vec2>::SetAttribPointer(unsigned int slot)
{
glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
}
template<>
void Attribute<vec3>::SetAttribPointer(unsigned int slot)
{
glVertexAttribPointer(slot, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
}
template<>
void Attribute<vec4>::SetAttribPointer(unsigned int slot)
{
glVertexAttribPointer(slot, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
}
template<>
void Attribute<quat>::SetAttribPointer(unsigned int slot)
{
glVertexAttribPointer(slot, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
}
template<typename T>
void Attribute<T>::BindTo(unsigned int slot)
{
glBindBuffer(GL_ARRAY_BUFFER, mHandle);
glEnableVertexAttribArray(slot);
SetAttribPointer(slot);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
template<typename T>
void Attribute<T>::UnBindFrom(unsigned int slot)
{
glBindBuffer(GL_ARRAY_BUFFER, mHandle);
glDisableVertexAttribArray(slot);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
创建uniform类
前面的Attribute类是有实例化的对象的,代表每个顶点的数据,这里的Uniform类也是个模板类,但是没有实例化对象,只有public static函数,因为它里面的数据是所有顶点共享的。
对于每种uniform的数据类型T而言,需要设置三个函数:
- 设置单独的一个uniform,设置一个T的值
- 设置T对应的uniform的数组的值
- 与2作用相同,但是接受的是vector数组(for convenience)
Uniform.h
如下:
#ifndef _H_UNIFORM_
#define _H_UNIFORM_
#include <vector>
template <typename T>
class Uniform
{
private:
// 私有构造函数, 防止类实例化(感觉是不是也可以用纯虚函数)
Uniform();
Uniform(const Uniform&);
Uniform& operator=(const Uniform&);
~Uniform();
public:
// 感觉用Upload的函数命名是不是比Set好一些
static void Set(unsigned int slot, const T& value);
static void Set(unsigned int slot, T* inputArray, unsigned int arrayLength);
static void Set(unsigned int slot, std::vector<T>& inputArray);
};
#endif
Uniform.cpp
如下:
#include "Uniform.h"
#include "glad.h"
#include "vec2.h"
#include "vec3.h"
#include "vec4.h"
#include "quat.h"
#include "mat4.h"
// 由于这些Uniform的模板实现在宏里, 为了使用到的时候通过编译, 需要加这些东西
template Uniform<int>;
template Uniform<ivec4>;
template Uniform<ivec2>;
template Uniform<float>;
template Uniform<vec2>;
template Uniform<vec3>;
template Uniform<vec4>;
template Uniform<quat>;
template Uniform<mat4>;
// 为tType类型实现Uniform的模板偏特化
#define UNIFORM_IMPL(gl_func, tType, dType) \
template<> \
void Uniform<tType>::Set(unsigned int slot, tType* data, unsigned int length) { \
gl_func(slot, (GLsizei)length, (dType*)&data[0]); \
}
//第一个int是uniform对应的数据类型, 第二个int是uniform对应的数据在内存里的类型
UNIFORM_IMPL(glUniform1iv, int, int)
// 上面这个宏等同于:
// template<> // int类型数据的模板特化
// void Uniform<int>::Set(unsigned int slot, int* data, unsigned int length)
// {
// glUniform1iv(slot, (GLsizei)length, (int*)&data[0]);
// }
// ivec4由4个int组成, 内存上本质上是int数组
UNIFORM_IMPL(glUniform4iv, ivec4, int)
UNIFORM_IMPL(glUniform2iv, ivec2, int)
UNIFORM_IMPL(glUniform1fv, float, float)
UNIFORM_IMPL(glUniform2fv, vec2, float)
UNIFORM_IMPL(glUniform3fv, vec3, float)
UNIFORM_IMPL(glUniform4fv, vec4, float)
UNIFORM_IMPL(glUniform4fv, quat, float)
// 上面的uniform上传函数都是三个参数, 所以统一用宏, 但是这里的mat4由四个参数, 所以单独特化
template<>
void Uniform<mat4>::Set(unsigned int slot, mat4* inputArray, unsigned int arrayLength)
{
glUniformMatrix4fv(slot, (GLsizei)arrayLength, false, (float*)&inputArray[0]);
}
// 保留只有两个参数的模板, 此时都是上传的单个Uniform数据
template <typename T>
void Uniform<T>::Set(unsigned int slot, const T& value)
{
Set(slot, (T*)&value, 1);
}
// 支持上传vector<T>的数组数据
template <typename T>
void Uniform<T>::Set(unsigned int slot, std::vector<T>& value)
{
Set(slot, &value[0], (unsigned int)value.size());
}
没啥稀奇的,我记得之前看的OpenGL教程里是把这块内容放到了Shader类里,这本书的代码里把它单独拆出来放到了Uniform类里。
创建Index Buffer类
与Attribute类类似:
// 头文件
#ifndef _H_INDEXBUFFER_
#define _H_INDEXBUFFER_
#include <vector>
class IndexBuffer
{
public:
unsigned int mHandle;
unsigned int mCount;
private:
// 不允许Copy
IndexBuffer(const IndexBuffer& other);
IndexBuffer& operator=(const IndexBuffer& other);
public:
IndexBuffer();// glGenBuffers(1, &mHandle)
~IndexBuffer();// glDeleteBuffers(1, &mHandle)
// Set就是把IndexBuffer上传给GPU, 就是调用glBindBuffer和glBufferData
void Set(unsigned int* inputArray, unsigned int arrayLengt);
void Set(std::vector<unsigned int>& input);
unsigned int Count();
unsigned int GetHandle();
};
#endif
// cpp文件
#include "IndexBuffer.h"
#include "glad.h"
IndexBuffer::IndexBuffer()
{
glGenBuffers(1, &mHandle);
mCount = 0;
}
IndexBuffer::~IndexBuffer()
{
glDeleteBuffers(1, &mHandle);
}
unsigned int IndexBuffer::Count()
{
return mCount;
}
unsigned int IndexBuffer::GetHandle()
{
return mHandle;
}
void IndexBuffer::Set(unsigned int* inputArray, unsigned int arrayLengt)
{
mCount = arrayLengt;
unsigned int size = sizeof(unsigned int);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mHandle);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * mCount, inputArray, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void IndexBuffer::Set(std::vector<unsigned int>& input)
{
Set(&input[0], (unsigned int)input.size());
}
Rendering geometry
作者划分的也太细了,居然还有个Draw类(感觉叫Renderer类不是更好么),讲道理感觉封装的过于细致了,没必要,Draw类就是把OpenGL的四个Draw Call函数给封装了起来,就是这四个:
glDrawArrays
: 直接按输入的Primitive类型绘制,不需要Index BufferglDrawElements
: 按照输入的Primitive类型和Index Buffer绘制glDrawArraysInstanced
glDrawElementsInstanced
代码如下:
// 头文件
#ifndef _H_DRAW_
#define _H_DRAW_
#include "IndexBuffer.h"
enum class DrawMode
{
Points,
LineStrip,
LineLoop,
Lines,
Triangles,
TriangleStrip,
TriangleFan
};
void Draw(IndexBuffer& inIndexBuffer, DrawMode mode);
void Draw(unsigned int vertexCount, DrawMode mode);
void DrawInstanced(IndexBuffer& inIndexBuffer, DrawMode mode, unsigned int instanceCount);
void DrawInstanced(unsigned int vertexCount, DrawMode mode, unsigned int numInstances);
#endif
// cpp文件
#include "Draw.h"
#include "glad.h"
#include <iostream>
static GLenum DrawModeToGLEnum(DrawMode input)
{
switch (input)
{
case DrawMode::Points:
return GL_POINTS;
case DrawMode::LineStrip:
return GL_LINE_STRIP;
case DrawMode::LineLoop:
return GL_LINE_LOOP;
case DrawMode::Lines:
return GL_LINES;
case DrawMode::Triangles:
return GL_TRIANGLES;
case DrawMode::TriangleStrip:
return GL_TRIANGLE_STRIP;
case DrawMode::TriangleFan:
return GL_TRIANGLE_FAN;
}
std::cout << "DrawModeToGLEnum unreachable code hit\n";
return 0;
}
void Draw(unsigned int vertexCount, DrawMode mode)
{
glDrawArrays(DrawModeToGLEnum(mode), 0, vertexCount);
}
void DrawInstanced(unsigned int vertexCount, DrawMode mode, unsigned int numInstances)
{
glDrawArraysInstanced(DrawModeToGLEnum(mode), 0, vertexCount, numInstances);
}
void Draw(IndexBuffer& inIndexBuffer, DrawMode mode)
{
unsigned int handle = inIndexBuffer.GetHandle();
unsigned int numIndices = inIndexBuffer.Count();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle);
glDrawElements(DrawModeToGLEnum(mode), numIndices, GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void DrawInstanced(IndexBuffer& inIndexBuffer, DrawMode mode, unsigned int instanceCount)
{
unsigned int handle = inIndexBuffer.GetHandle();
unsigned int numIndices = inIndexBuffer.Count();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle);
glDrawElementsInstanced(DrawModeToGLEnum(mode), numIndices, GL_UNSIGNED_INT, 0, instanceCount);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
Working with textures
此书里面所有的漫反射光都不是计算出来的,而都是从贴图里读取出来的,这里的图片都是png
格式的,用stb_image
库来读取进来。stb是一个文件库的集合(Stb is a collection of single-file public domain libraries),代码在https://github.com/nothings/stb.
把对应的stb_image.h
头文件加入到自己的项目里,然后创建一个对应的cpp文件,加cpp文件应该是为了让它被编译,代码如下:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
然后就可以创建自己的Texture类了:
// 头文件
#ifndef _H_TEXTURE_
#define _H_TEXTURE_
class Texture
{
protected:
unsigned int mWidth;
unsigned int mHeight;
unsigned int mChannels;
unsigned int mHandle;
private:
Texture(const Texture& other);
Texture& operator=(const Texture& other);
public:
Texture();// glGenTextures
Texture(const char* path);// glGenTextures and load image(call Load function)
~Texture();// glDeleteTextures(1, &mHandle);
void Load(const char* path);
// bind并把texture作为uniform传给GPU
void Set(unsigned int uniformIndex, unsigned int textureIndex);
// 绑定到GL_TEXTURE0的第0槽位
void UnSet(unsigned int textureIndex);
unsigned int GetHandle();
};
#endif
// cpp文件
#include "Texture.h"
#include "stb_image.h"
#include "glad.h"
Texture::Texture()
{
mWidth = 0;
mHeight = 0;
mChannels = 0;
glGenTextures(1, &mHandle);
}
Texture::Texture(const char* path)
{
glGenTextures(1, &mHandle);
Load(path);
}
Texture::~Texture()
{
glDeleteTextures(1, &mHandle);
}
// load, bind, set uv and channels
void Texture::Load(const char* path)
{
glBindTexture(GL_TEXTURE_2D, mHandle);
int width, height, channels;
// 4代表读取RGBA四个通道
unsigned char* data = stbi_load(path, &width, &height, &channels, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
// The Texture class will always load textures using the same mipmap level and wrapping parameters.
// For the samples in this book, that should be enough.(为啥mipmap等级会是相同的?)
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
mWidth = width;
mHeight = height;
mChannels = channels;
}
void Texture::Set(unsigned int uniformIndex, unsigned int textureIndex)
{
glActiveTexture(GL_TEXTURE0 + textureIndex);// 激活Texture槽位
glBindTexture(GL_TEXTURE_2D, mHandle);
glUniform1i(uniformIndex, textureIndex);
}
void Texture::UnSet(unsigned int textureIndex)
{
glActiveTexture(GL_TEXTURE0 + textureIndex);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
}
unsigned int Texture::GetHandle()
{
return mHandle;
}
添加具体的shaders文件
前面已经写好了Shader类了,以前学的OpenGL里的Shader是一个很多东西的类,这本书里把里面的Uniform、Texture类的东西都剥离出去了,这里补充vs和fs两个shader文件,就行了
// static.vert文件
// This shader can be used to display static geometry or CPU skinned meshes
#version 330 core
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
in vec3 position;
in vec3 normal;
in vec2 texCoord;
out vec3 norm;
out vec3 fragPos;
out vec2 uv;
void main()
{
// 注意, 这里的mvp是以uniform的形式存在的, 每个顶点的MVP是一样的
gl_Position = projection * view * model * vec4(position, 1.0);
fragPos = vec3(model * vec4(position, 1.0));
norm = vec3(model * vec4(normal, 0.0f));
uv = texCoord;
}
// lit.frag文件
// Some chapters will introduce new vertex shaders, but the fragment shader is always going to remain as this one.
#version 330 core
in vec3 norm;
in vec3 fragPos;
in vec2 uv;
uniform vec3 light;
uniform sampler2D tex0;
out vec4 FragColor;
void main()
{
vec4 diffuseColor = texture(tex0, uv);
vec3 n = normalize(norm);
vec3 l = normalize(light);
float diffuseIntensity = clamp(dot(n, l) + 0.1, 0, 1);
// 这里的输出颜色只跟贴图中得到的光照颜色, 以及法线与光线的夹角有关
// 没有环境光和高光
FragColor = diffuseColor * diffuseIntensity;
}
总结
这一章的内容本来没想单独写一篇文章的,但是代码量确实有点大。这篇文章里说到的,对渲染的抽象,在我看来,只是在头文件里没有用到任何OpenGL的API而已,说是abstraction layer实在是有点勉强… 总之,算是对OpenGL的复习和巩固吧
这一章的github仓库里额外提供了一个DebugDraw
类,可以帮助debug简单的图元,下一章会开始学习glTF这个文件格式。