用 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; }}
运行结果
准备工作大致完成,下一节将真正开始写游戏。
- 用 opengl 写一个小游戏 (1)
- 用 opengl 写一个小游戏 (2)
- 用WPF写的一个小游戏
- 用Python写一个猜数字小游戏
- stencyl 写的一个小游戏
- 一个python写的小游戏
- 一个贪吃蛇小游戏 用 C WINAPI 写的
- 以前自己用SDL写的一个小游戏:QuadraPop
- 用Html+js自己写了一个小游戏
- 如何用Python写一个小游戏(1)
- 一个java写的弹球小游戏
- 写了一个小游戏 还可以
- 从无到有写一个C#弹球小游戏(一)
- 从无到有写一个C#弹球小游戏(二)
- 写一个三子棋小游戏的感悟
- C语言写的一个2048小游戏
- 如何用Cocos2d-android写一个小游戏
- 使用Java写一个猜数字小游戏
- Golang- import 导入包的语法(转载)
- 【斐波拉契数列】杭电2046-骨牌铺方格
- sed语法格式
- AnyviewC编程作业系统——支持程序可视化运行、调试和测评
- opencv:直方图与匹配
- 用 opengl 写一个小游戏 (1)
- JavaScript高级程序设计读书笔记(第三章)(二)
- opencv:机器学习
- maven提示invalid LOC header (bad signature)的解决办法
- 洛谷 P1363 幻想迷宫
- PAT BASIC 1019 数字黑洞
- 川普撞脸希拉里(基于 OpenCV 的面部特征交换)-1
- opencv:Dirctory
- [Android]HTTP请求