用 opengl 写一个小游戏 (1)

来源:互联网 发布:mysql 性能监控软件 编辑:程序博客网 时间:2024/04/29 07:02

用 opengl 写一个小游戏 (1)

  • 用 opengl 写一个小游戏 1
    • 环境搭建
      • freetype
      • soil
      • glm
      • 注意事项
    • 基本组件
      • shader
      • texture
      • 资源加载
    • 渲染器
    • 主游戏类
    • 游戏窗口

本节代码 github

环境搭建

基本的环境搭建可以参考我之前的文章在 Eclipse或CLion 中集成 opengl 环境 (windows+mingw)
在这里我们还需要另外两个包,freetype 和 soil。

freetype

FreeType 库是一个完全免费(开源)的、高质量的且可移植的字体引擎,它提供统一的接口来访问多种字体格式文件。我们可以借助它实现文字渲染。可在此网站下载源代码或直接下载二进制文件。

soil

SOIL是简易OpenGL图像库(Simple OpenGL Image Library)的缩写,它支持大多数流行的图像格式,使用起来也很简单。它可以帮助我们在openGL项目中加载图片以实现纹理。这是SOIL库的主页。

glm

由于C/C++标准库中没有几何数学库,这样造成在开发一个三维系统之初往往都需要自行实现一个实用的几何数学库,这样太费时费力了。GLM的出现可以很好的解决这个问题。GLM设计上遵照OpenGL Shading Language风格,使用开放的MIT授权协议。会GLSL的人可以很快上手。因采用了数据结构与函数方法分离的方式,可以很容易扩充函数方法而不改变原文件(增加新的头文件即可,不过得在不同的头文件中找函数方法比较费力)。
下载地址
注意这个库不需要 link。只要要包含头文件即可

注意事项

注意 link 时的顺序,有些包的前后顺序不能倒置。我的顺序是

target_link_libraries(game glew32s  glfw3 gdi32 freetype soil opengl32)

game 是游戏程序。后面是需要用到的包。

基本组件

在写游戏的过程中我们需要频繁的使用两个部分:shader 和 texture,即着色器和纹理。我们可以将其封装为两个类。

shader

.h 文件

#ifndef GAME_SHADER_H#define GAME_SHADER_H#include <string>#define GLEW_STATIC#include <GL/glew.h>#include <glm/glm.hpp>#include <glm/gtc/type_ptr.hpp>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 <iostream>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;        }    }}

texture

.h 文件

#ifndef GAME_TEXTURE_H#define GAME_TEXTURE_H#include <GL/glew.h>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 <iostream>#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 <map>#include <string>#define GLEW_STATIC#include <GL/glew.h>#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 <iostream>#include <sstream>#include <fstream>#include <SOIL/SOIL.h>// 实例化静态变量std::map<std::string, Texture2D>    ResourceManager::textures;std::map<std::string, Shader>       ResourceManager::shaders;//加载 shaderShader ResourceManager::loadShader(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile, std::string name){    shaders[name] = loadShaderFromFile(vShaderFile, fShaderFile, gShaderFile);    return shaders[name];}//获取指定的 shaderShader ResourceManager::getShader(std::string name){    return shaders[name];}//加载 textureTexture2D ResourceManager::loadTexture(const GLchar *file, GLboolean alpha, std::string name){    textures[name] = loadTextureFromFile(file, alpha);    return textures[name];}//获取指定的 textureTexture2D 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 <GL/glew.h>#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#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 corelayout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords>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 corein 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 <GL/glew.h>#include <GLFW/glfw3.h>#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 <tuple>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<GLfloat>(this->width), static_cast<GLfloat>(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 <GL/glew.h>#include <GLFW/glfw3.h>#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;    }}

运行结果
渲染结果

准备工作大致完成,下一节将真正开始写游戏。

0 0
原创粉丝点击