用 opengl 写一个小游戏 (1)

编程入门 行业动态 更新时间:2024-10-09 08:36:43

用 opengl 写一个<a href=https://www.elefans.com/category/jswz/34/1769974.html style=小游戏 (1)"/>

用 opengl 写一个小游戏 (1)

用 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 shadervoid    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 ShadersVertex = glCreateShader(GL_VERTEX_SHADER);glShaderSource(sVertex, 1, &vertexSource, NULL);glCompileShader(sVertex);checkCompileErrors(sVertex, "VERTEX");// Fragment ShadersFragment = 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 Programthis->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);// 获取指定的 shaderstatic 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;//加载 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 <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/VBOGLuint VBO;GLfloat vertices[] = {// Pos      // Tex0.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; // <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 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 <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/DestructorGame(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() {//加载 shadersResourceManager::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() {//初始化 glfwglfwInit();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);//初始化 glewglewExperimental = 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;}
}

运行结果

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

更多推荐

用 opengl 写一个小游戏 (1)

本文发布于:2024-03-23 20:49:03,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1742666.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:小游戏   opengl

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!