本节代码 github
基本的环境搭建可以参考我之前的文章在 Eclipse或CLion 中集成 opengl 环境 (windows+mingw)
在这里我们还需要另外两个包,freetype 和 soil。
FreeType 库是一个完全免费(开源)的、高质量的且可移植的字体引擎,它提供统一的接口来访问多种字体格式文件。我们可以借助它实现文字渲染。可在此网站下载源代码或直接下载二进制文件。
SOIL是简易OpenGL图像库(Simple OpenGL Image Library)的缩写,它支持大多数流行的图像格式,使用起来也很简单。它可以帮助我们在openGL项目中加载图片以实现纹理。这是SOIL库的主页。
由于C/C++标准库中没有几何数学库,这样造成在开发一个三维系统之初往往都需要自行实现一个实用的几何数学库,这样太费时费力了。GLM的出现可以很好的解决这个问题。GLM设计上遵照OpenGL Shading Language风格,使用开放的MIT授权协议。会GLSL的人可以很快上手。因采用了数据结构与函数方法分离的方式,可以很容易扩充函数方法而不改变原文件(增加新的头文件即可,不过得在不同的头文件中找函数方法比较费力)。
下载地址
注意这个库不需要 link。只要要包含头文件即可
注意 link 时的顺序,有些包的前后顺序不能倒置。我的顺序是
target_link_libraries(game glew32s glfw3 gdi32 freetype soil opengl32)
game 是游戏程序。后面是需要用到的包。
在写游戏的过程中我们需要频繁的使用两个部分:shader 和 texture,即着色器和纹理。我们可以将其封装为两个类。
.h 文件
#ifndef GAME_SHADER_H
#define GAME_SHADER_H
#include
#define GLEW_STATIC
#include
#include
#include
class Shader{
public:
GLuint id;
//构造函数
Shader(){};
//使用此程序
Shader &use();
//编译 shader 代码, 可选的 geometry shader
void Compile(const GLchar *vertexSource, const GLchar *fragmentSource, const GLchar *geometrySource = nullptr);
//设置参数
void SetFloat (const GLchar *name, GLfloat value, GLboolean useShader = false);
void SetInteger (const GLchar *name, GLint value, GLboolean useShader = false);
void SetVector2f (const GLchar *name, GLfloat x, GLfloat y, GLboolean useShader = false);
void SetVector2f (const GLchar *name, const glm::vec2 &value, GLboolean useShader = false);
void SetVector3f (const GLchar *name, GLfloat x, GLfloat y, GLfloat z, GLboolean useShader = false);
void SetVector3f (const GLchar *name, const glm::vec3 &value, GLboolean useShader = false);
void SetVector4f (const GLchar *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w, GLboolean useShader = false);
void SetVector4f (const GLchar *name, const glm::vec4 &value, GLboolean useShader = false);
void SetMatrix4 (const GLchar *name, const glm::mat4 &matrix, GLboolean useShader = false);
private:
// 检查编译的错误
void checkCompileErrors(GLuint object, std::string type);
};
#endif //GAME_SHADER_H
.cpp 文件
#include "shader.h"
#include
Shader &Shader::use()
{
glewExperimental = GL_TRUE;
glewInit();
glUseProgram(this->id);
return *this;
}
void Shader::Compile(const GLchar* vertexSource, const GLchar* fragmentSource, const GLchar* geometrySource)
{
GLuint sVertex, sFragment, gShader;
// Vertex Shader
sVertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(sVertex, 1, &vertexSource, NULL);
glCompileShader(sVertex);
checkCompileErrors(sVertex, "VERTEX");
// Fragment Shader
sFragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(sFragment, 1, &fragmentSource, NULL);
glCompileShader(sFragment);
checkCompileErrors(sFragment, "FRAGMENT");
// Geometry Shader (可选)
if (geometrySource != nullptr)
{
gShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(gShader, 1, &geometrySource, NULL);
glCompileShader(gShader);
checkCompileErrors(gShader, "GEOMETRY");
}
// Shader Program
this->id = glCreateProgram();
glAttachShader(this->id, sVertex);
glAttachShader(this->id, sFragment);
if (geometrySource != nullptr)
glAttachShader(this->id, gShader);
glLinkProgram(this->id);
checkCompileErrors(this->id, "PROGRAM");
// 删除 shader 当 link 成功时
glDeleteShader(sVertex);
glDeleteShader(sFragment);
if (geometrySource != nullptr)
glDeleteShader(gShader);
}
void Shader::SetFloat(const GLchar *name, GLfloat value, GLboolean useShader)
{
if (useShader)
this->use();
glUniform1f(glGetUniformLocation(this->id, name), value);
}
void Shader::SetInteger(const GLchar *name, GLint value, GLboolean useShader)
{
if (useShader)
this->use();
glUniform1i(glGetUniformLocation(this->id, name), value);
}
void Shader::SetVector2f(const GLchar *name, GLfloat x, GLfloat y, GLboolean useShader)
{
if (useShader)
this->use();
glUniform2f(glGetUniformLocation(this->id, name), x, y);
}
void Shader::SetVector2f(const GLchar *name, const glm::vec2 &value, GLboolean useShader)
{
if (useShader)
this->use();
glUniform2f(glGetUniformLocation(this->id, name), value.x, value.y);
}
void Shader::SetVector3f(const GLchar *name, GLfloat x, GLfloat y, GLfloat z, GLboolean useShader)
{
if (useShader)
this->use();
glUniform3f(glGetUniformLocation(this->id, name), x, y, z);
}
void Shader::SetVector3f(const GLchar *name, const glm::vec3 &value, GLboolean useShader)
{
if (useShader)
this->use();
glUniform3f(glGetUniformLocation(this->id, name), value.x, value.y, value.z);
}
void Shader::SetVector4f(const GLchar *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w, GLboolean useShader)
{
if (useShader)
this->use();
glUniform4f(glGetUniformLocation(this->id, name), x, y, z, w);
}
void Shader::SetVector4f(const GLchar *name, const glm::vec4 &value, GLboolean useShader)
{
if (useShader)
this->use();
glUniform4f(glGetUniformLocation(this->id, name), value.x, value.y, value.z, value.w);
}
void Shader::SetMatrix4(const GLchar *name, const glm::mat4 &matrix, GLboolean useShader)
{
if (useShader)
this->use();
glUniformMatrix4fv(glGetUniformLocation(this->id, name), 1, GL_FALSE, glm::value_ptr(matrix));
}
void Shader::checkCompileErrors(GLuint object, std::string type)
{
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM")
{
glGetShaderiv(object, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(object, 1024, NULL, infoLog);
std::cout << "| ERROR::SHADER: Compile-time error: Type: " << type << "\n"
<< infoLog << "\n -- --------------------------------------------------- -- "
<< std::endl;
}
}
else
{
glGetProgramiv(object, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(object, 1024, NULL, infoLog);
std::cout << "| ERROR::Shader: Link-time error: Type: " << type << "\n"
<< infoLog << "\n -- --------------------------------------------------- -- "
<< std::endl;
}
}
}
.h 文件
#ifndef GAME_TEXTURE_H
#define GAME_TEXTURE_H
#include
class Texture2D
{
public:
GLuint id;
// Texture 图片尺寸
GLuint width, height;
// Texture 格式
GLuint internalFormat; // 纹理对象的格式
GLuint imageFormat; // 加载图片的格式
// 纹理设置
GLuint wrapS;
GLuint wrapT;
GLuint filterMin;
GLuint filterMax;
Texture2D();
// 生成纹理
void generate(GLuint width, GLuint height, unsigned char* data);
// 绑定纹理
void bind() const;
};
#endif //GAME_TEXTURE_H
.cpp 文件
#include
#include "texture.h"
Texture2D::Texture2D()
: width(0), height(0), internalFormat(GL_RGB), imageFormat(GL_RGB), wrapS(GL_REPEAT), wrapT(GL_REPEAT), filterMin(GL_LINEAR), filterMax(GL_LINEAR)
{
glGenTextures(1, &this->id);
}
void Texture2D::generate(GLuint width, GLuint height, unsigned char* data)
{
this->width = width;
this->height = height;
// 创建纹理
glBindTexture(GL_TEXTURE_2D, this->id);
glTexImage2D(GL_TEXTURE_2D, 0, this->internalFormat, width, height, 0, this->imageFormat, GL_UNSIGNED_BYTE, data);
// 设置纹理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, this->wrapS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, this->wrapT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, this->filterMin);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, this->filterMax);
// 解绑纹理
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture2D::bind() const
{
glBindTexture(GL_TEXTURE_2D, this->id);
}
在上面两个类中仅仅是纹理和着色器的生成,我们还需要从文件中加载相应的资源。为了方便,我们可以写一个静态的资源加载类。
.h 文件
#include
#include
#define GLEW_STATIC
#include
#include "utility/texture.h"
#include "utility/shader.h"
class ResourceManager
{
public:
static std::map<std::string, Shader> shaders;
static std::map<std::string, Texture2D> textures;
// 加载 shader 程序
static Shader loadShader(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile, std::string name);
// 获取指定的 shader
static Shader getShader(std::string name);
// 从文件中加载纹理
static Texture2D loadTexture(const GLchar *file, GLboolean alpha, std::string name);
// 获取指定的 纹理
static Texture2D getTexture(std::string name);
static void clear();
private:
ResourceManager() { }
static Shader loadShaderFromFile(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile = nullptr);
static Texture2D loadTextureFromFile(const GLchar *file, GLboolean alpha);
};
#endif //GAME_RESOURCEMANAGER_H
.cpp 文件
#include "resourceManager.h"
#include
#include
#include
#include
// 实例化静态变量
std::map<std::string, Texture2D> ResourceManager::textures;
std::map<std::string, Shader> ResourceManager::shaders;
//加载 shader
Shader ResourceManager::loadShader(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile, std::string name)
{
shaders[name] = loadShaderFromFile(vShaderFile, fShaderFile, gShaderFile);
return shaders[name];
}
//获取指定的 shader
Shader ResourceManager::getShader(std::string name)
{
return shaders[name];
}
//加载 texture
Texture2D ResourceManager::loadTexture(const GLchar *file, GLboolean alpha, std::string name)
{
textures[name] = loadTextureFromFile(file, alpha);
return textures[name];
}
//获取指定的 texture
Texture2D ResourceManager::getTexture(std::string name)
{
return textures[name];
}
//清理
void ResourceManager::clear()
{
for (auto iter : shaders)
glDeleteProgram(iter.second.id);
for (auto iter : textures)
glDeleteTextures(1, &iter.second.id);
}
Shader ResourceManager::loadShaderFromFile(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile)
{
std::string vertexCode;
std::string fragmentCode;
std::string geometryCode;
try
{
//打开文件
std::ifstream vertexShaderFile(vShaderFile);
std::ifstream fragmentShaderFile(fShaderFile);
std::stringstream vShaderStream, fShaderStream;
//读入文件
vShaderStream << vertexShaderFile.rdbuf();
fShaderStream << fragmentShaderFile.rdbuf();
//关闭文件
vertexShaderFile.close();
fragmentShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
if (gShaderFile != nullptr)
{
std::ifstream geometryShaderFile(gShaderFile);
std::stringstream gShaderStream;
gShaderStream << geometryShaderFile.rdbuf();
geometryShaderFile.close();
geometryCode = gShaderStream.str();
}
}
catch (std::exception e)
{
std::cout << "ERROR::SHADER: Failed to read shader files" << std::endl;
}
const GLchar *vShaderCode = vertexCode.c_str();
const GLchar *fShaderCode = fragmentCode.c_str();
const GLchar *gShaderCode = geometryCode.c_str();
// 生成 shader 对象
Shader shader;
shader.Compile(vShaderCode, fShaderCode, gShaderFile != nullptr ? gShaderCode : nullptr);
return shader;
}
Texture2D ResourceManager::loadTextureFromFile(const GLchar *file, GLboolean alpha)
{
// 生成纹理对象
Texture2D texture;
if (alpha)
{
texture.internalFormat = GL_RGBA;
texture.imageFormat = GL_RGBA;
}
// 加载图片
int width, height;
unsigned char* image = SOIL_load_image(file, &width, &height, 0, texture.imageFormat == GL_RGBA ? SOIL_LOAD_RGBA : SOIL_LOAD_RGB);
// 生成纹理
texture.generate(width, height, image);
// 释放数据
SOIL_free_image_data(image);
return texture;
}
这是一个简单的渲染器,用来渲染纹理。
spriteRenderer.h
#ifndef SPRITE_RENDERER_H
#define SPRITE_RENDERER_H
#define GLEW_STATIC
#include
#include
#include
#include "../utility/texture.h"
#include "../utility/shader.h"
class SpriteRenderer
{
public:
SpriteRenderer(Shader &shader);
~SpriteRenderer();
// 渲染纹理,注意,这里的 pos 坐标是纹理的左上角点的坐标
void drawSprite(Texture2D texture, glm::vec2 position, glm::vec2 size = glm::vec2(10, 10), GLfloat rotate = 0.0f, glm::vec3 color = glm::vec3(1.0f));
private:
Shader shader;
GLuint quadVAO;
// 初始化
void initRenderData();
};
#endif //SPRITE_RENDERER_H
spriteRenderer.cpp
#include "spriteRenderer.h"
#include "../resourceManager.h"
SpriteRenderer::SpriteRenderer(Shader &shader)
{
this->shader = shader;
this->initRenderData();
}
SpriteRenderer::~SpriteRenderer()
{
glDeleteVertexArrays(1, &this->quadVAO);
}
void SpriteRenderer::drawSprite(Texture2D texture, glm::vec2 position, glm::vec2 size, GLfloat rotate, glm::vec3 color)
{
//shader.Use();
glm::mat4 model;
//平移变换
model = glm::translate(model, glm::vec3(position, 0.0f));
//旋转变换
model = glm::translate(model, glm::vec3(0.5f * size.x, 0.5f * size.y, 0.0f));
model = glm::rotate(model, rotate, glm::vec3(0.0f, 0.0f, 1.0f));
model = glm::translate(model, glm::vec3(-0.5f * size.x, -0.5f * size.y, 0.0f));
//放缩变换
model = glm::scale(model, glm::vec3(size, 1.0f));
this->shader.SetMatrix4("model", model);
this->shader.SetVector3f("spriteColor", color);
glActiveTexture(GL_TEXTURE0);
texture.bind();
glBindVertexArray(this->quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
void SpriteRenderer::initRenderData()
{
// 设置 VAO/VBO
GLuint VBO;
GLfloat vertices[] = {
// Pos // Tex
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f
};
glGenVertexArrays(1, &this->quadVAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindVertexArray(this->quadVAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
还有其需要的 shader
vertex shader
#version 330 core
layout (location = 0) in vec4 vertex; //
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 projection;
void main()
{
TexCoords = vertex.zw;
gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0);
}
fragment shader
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D image;
uniform vec3 spriteColor;
void main()
{
color = vec4(spriteColor, 1.0) * texture(image, TexCoords);
}
在此类中来加载资源和进行渲染。
Game.h
纹理可以在文章开始的 github 链接里找到。
#ifndef GAME_GAME_H
#define GAME_GAME_H
#define GLEW_STATIC
#include
#include
#include "resourceManager.h"
#include "component/spriteRenderer.h"
enum gameState {
GAME_ACTIVE,
GAME_MENU,
GAME_WIN
};
class Game
{
public:
// 游戏状态
gameState state;
GLuint level;
GLboolean keys[1024];
GLuint width, height;
// Constructor/Destructor
Game(GLuint width, GLuint height);
~Game();
// 初始化 - 加载纹理、着色器等
void init();
//控制类操作
void processInput(GLfloat dt);
//更新数据
void update(GLfloat dt);
//渲染
void render();
private:
//void DoCollisions();
};
#endif //GAME_GAME_H
Game.cpp
#include "game.h"
#include
SpriteRenderer *renderer;
Game::Game(GLuint width, GLuint height)
: state(GAME_ACTIVE), keys(), width(width), height(height) {}
Game::~Game() {
delete renderer;
}
void Game::init() {
//加载 shaders
ResourceManager::loadShader("../shaders/sprite.vs.glsl", "../shaders/sprite.frag.glsl", nullptr, "sprite");
// 投影矩阵
glm::mat4 projection = glm::ortho(0.0f, static_cast(this->width), static_cast(this->height), 0.0f,
-1.0f, 1.0f);
ResourceManager::getShader("sprite").use().SetInteger("image", 0);
ResourceManager::getShader("sprite").SetMatrix4("projection", projection);
//加载一个纹理
ResourceManager::loadTexture("textures/car.png", GL_TRUE, "car");
Shader spriteShader = ResourceManager::getShader("sprite");
renderer = new SpriteRenderer(spriteShader);
}
void Game::processInput(GLfloat dt) {
}
void Game::update(GLfloat dt) {
}
void Game::render() {
//渲染纹理
if (this->state == GAME_ACTIVE) {
renderer->drawSprite(ResourceManager::getTexture("car"), glm::vec2(0, 0),
glm::vec2(this->width, this->height/3*2));
}
}
这里可以参考之前的文章
main.cpp
#define GLEW_STATIC
#include
#include
#include "game.h"
#include "resourceManager.h"
//按键回调函数
void key_callback(GLFWwindow* wwindow, int key,int scancode, int action, int mode);
//屏幕宽度
const GLuint SCREEN_WIDTH = 800;
//屏幕高度
const GLuint SCREEN_HEIGHT = 600;
Game game(SCREEN_WIDTH, SCREEN_HEIGHT);
int main() {
//初始化 glfw
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "game", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
//初始化 glew
glewExperimental = GL_TRUE;
glewInit();
glGetError();
//opengl 设置
glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
game.init();
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
game.state = GAME_ACTIVE;
//游戏循环
while(!glfwWindowShouldClose(window))
{
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
glfwPollEvents();
game.processInput(deltaTime);
game.update(deltaTime);
//设置屏幕颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
game.render();
glfwSwapBuffers(window);
}
ResourceManager::clear();
glfwTerminate();
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key < 1024)
{
if (action == GLFW_PRESS)
game.keys[key] = GL_TRUE;
else if (action == GLFW_RELEASE)
game.keys[key] = GL_FALSE;
}
}
准备工作大致完成,下一节将真正开始写游戏。