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而言,需要设置三个函数:

  1. 设置单独的一个uniform,设置一个T的值
  2. 设置T对应的uniform的数组的值
  3. 与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 Buffer
  • glDrawElements: 按照输入的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这个文件格式。

posted @ 2021-10-28 13:39  弹吉他的小刘鸭  阅读(79)  评论(0编辑  收藏  举报