Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the antispam-bee domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/ruwritingagame/public_html/wp-includes/functions.php on line 6121
Глава 12. Графический движок. Базовые классы – Игра в Написание Игры

Глава 12. Графический движок. Базовые классы

К этому моменту мы благополучно “поженили” desktop и мобильник, Visual Studio и OpenGL ES. И начинаем строить наш графический движок. Разумеется, это можно сделать по-разному.

Внимание: Весь дальнейший материал отражает мое личное видение этой темы.

Начнем с базовых классов. Наш текущий TheGame код рендрит 1 простой предмет, текстурированный прямоугольник. В реальной игре у нас будет МАССИВ предметов (для ясности назовем их game subjects, это могут быть например танки, ландшафт, деревья, элементы интерфейса, и т.д.). У каждого из них будут собственные модели поведения и инструкции как его рендрить. TheGame класс об этом заботиться не должен. Его задача – просканировать массив субъектов и запустить их соответствующие классы и функции, которые будут находиться вне TheGame класса.

Мы препарируемTheGame класс и построим набор базовых классов, которые пойдут в реальный Проект. Начнем с

Windows

1. В Windows File Explorer-е сделаем копию каталога a999hello и переименум ее (копию) в a998engine.

Запускаем VS. Открываем C:\CPP\a998engine\p_windows\p_windows.sln.


Texture class

Он должен содержать информацию о текстуре, типа размеров и GL texture id, плюс соответствующие функции. Также там будет список (vector) загруженных текстур во избежание дублирования.

  1. Под xEngine добавим новый header (.h) файл Texture.h

Location – C:\CPP\engine

Код:

#pragma once
#include <string>
#include <vector>

class Texture
{
public:
    //texture's individual descriptor:
    unsigned int GLid = -1; // GL texture id
    int size[2] = { 0,0 };  // image size
    std::string source; //file name
    //end of descriptor

    //static array (vector) of all loaded textures
    static std::vector<Texture*> textures;

public:
    static int loadTexture(std::string filePath);
    static int findTexture(std::string filePath);
    static int cleanUp();
    static unsigned int getGLid(int texN) { return textures.at(texN)->GLid; };
};


  1. Под xEngine добавим новый C++ (.cpp) файл Texture.cpp

Location – C:\CPP\engine

Код:

#include "Texture.h"
#define STB_IMAGE_IMPLEMENTATION  //required by stb_image.h
#include "stb_image.h"
#include "platform.h"

//static array (vector) of all loaded textures
std::vector<Texture*> Texture::textures;

int Texture::loadTexture(std::string filePath) {
    int texN = findTexture(filePath);
    if (texN >= 0)
        return texN;
    //if here - texture wasn't loaded
    //create Texture object
    Texture* pTex = new Texture();
    textures.push_back(pTex);
    pTex->source.assign(filePath);
    // load an image
    int nrChannels;
    unsigned char* imgData = stbi_load(filePath.c_str(),
        &pTex->size[0], &pTex->size[1], &nrChannels, 4); //"4"-convert to 4 channels -RGBA
    if (imgData == NULL) {
        mylog("ERROR in Texture::loadTexture loading image %s\n", filePath.c_str());
    }
    // generate texture
    glGenTextures(1, &pTex->GLid);
    glBindTexture(GL_TEXTURE_2D, pTex->GLid);
    // set the texture wrapping/filtering options (on the currently bound texture object)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // attach/load image data
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pTex->size[0], pTex->size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);
    glGenerateMipmap(GL_TEXTURE_2D);
    // release image data
    stbi_image_free(imgData);

    return (textures.size() - 1);
}
int Texture::findTexture(std::string filePath) {
    int texturesN = textures.size();
    if (texturesN < 1)
        return -1;
    for (int i = 0; i < texturesN; i++) {
        Texture* pTex = textures.at(i);
        if (pTex->source.compare(filePath) == 0)
            return i;
    }
    return -1;
}
int Texture::cleanUp() {
    int texturesN = textures.size();
    if (texturesN < 1)
        return -1;
    //detach all textures
    glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, 0);
    //release all textures
    for (int i = 0; i < texturesN; i++) {
        Texture* pTex = textures.at(i);
        glDeleteTextures(1, &pTex->GLid);
        delete pTex;
    }
    textures.clear();
    return 1;
}


Shader class

Сейчас у нас в нем просто набор из нескольких static функций, но должна быть вся относящаяся к шейдеру информация, такая как umvp_location, apos_location, atuv_location, и конечно shaderProgramId (мы назовем их немножко по-другому), плюс соответственный функционал.

4. Откроем Shader.h и заменим код на:

#pragma once
#include "platform.h"
#include <string>
#include <vector>

class Shader
{
public:
    //Shader program's individual descriptor:
    unsigned int GLid = -1; // GL shader id
    //common variables, "l_" for "location"
    int l_uMVP; // transform matrix (Model-View-Projection)
    int l_aPos; //attribute position (3D coordinates)
    int l_aTuv; //attribute TUV (texture coordinates)
    int l_uColor;
    int l_uTex0; //texture id
    //end of descriptor

    //static array (vector) of all loaded shaders
    static std::vector<Shader*> shaders;
    //common shader programs ("spN" - shader program number)
    static int spN_flat_ucolor;
    static int spN_flat_tex;

public:
    static int loadShaders();
    static int loadShader(std::string filePathVertexS, std::string filePathFragmentS);
    static int cleanUp();
    static unsigned int getGLid(int shN) { return shaders.at(shN)->GLid; };
    
    static int linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS);
	static int compileShader(const char* filePath, GLenum shaderType);
	static int shaderErrorCheck(int shaderId, std::string ref);
	static int programErrorCheck(int programId, std::string ref);
};


5. Откроем Shader.cpp и заменим код на:

#include "Shader.h"
#include "platform.h"
#include "utils.h"

extern std::string filesRoot;

//static array (vector) of all loaded shaders
std::vector<Shader*> Shader::shaders;
//common shader programs ("spN" - shader program number)
int Shader::spN_flat_ucolor = -1;
int Shader::spN_flat_tex = -1;

int Shader::loadShaders() {
    spN_flat_ucolor = loadShader("/dt/shaders/flat_ucolor_v.txt", "/dt/shaders/flat_ucolor_f.txt");
    spN_flat_tex = loadShader("/dt/shaders/flat_tex_v.txt", "/dt/shaders/flat_tex_f.txt");
    return 1;
}
int Shader::loadShader(std::string filePathVertexS, std::string filePathFragmentS) {
    //create shader object
    Shader* pSh = new Shader();
    shaders.push_back(pSh);
    pSh->GLid = linkShaderProgram((filesRoot + filePathVertexS).c_str(), (filesRoot + filePathFragmentS).c_str());
    //common variables. If not presented, = -1;
    pSh->l_uMVP = glGetUniformLocation(pSh->GLid, "uMVP"); // transform matrix (Model-View-Projection)
    pSh->l_aPos = glGetAttribLocation(pSh->GLid, "aPos"); //attribute position (3D coordinates)
    pSh->l_aTuv = glGetAttribLocation(pSh->GLid, "aTuv"); //attribute TUV (texture coordinates)
    pSh->l_uColor = glGetUniformLocation(pSh->GLid, "uColor");
    pSh->l_uTex0 = glGetUniformLocation(pSh->GLid, "uTex0"); //texture id

    return (shaders.size() - 1);
}
int Shader::cleanUp() {
    int shadersN = shaders.size();
    if (shadersN < 1)
        return -1;
    glUseProgram(0);
    for (int i = 0; i < shadersN; i++) {
        Shader* pSh = shaders.at(i);
        glDeleteProgram(pSh->GLid);
        delete pSh;
    }
    shaders.clear();
    return 1;
}

GLchar infoLog[1024];
int logLength;
int Shader::shaderErrorCheck(int shaderId, std::string ref) {
    //use after glCompileShader()
    if (checkGLerrors(ref) > 0)
        return -1;
    glGetShaderInfoLog(shaderId, 1024, &logLength, infoLog);
    if (logLength == 0)
        return 0;
    mylog("%s shader infoLog:\n%s\n", ref.c_str(), infoLog);
    return -1;
}
int Shader::programErrorCheck(int programId, std::string ref) {
    //use after glLinkProgram()
    if (checkGLerrors(ref) > 0)
        return -1;
    glGetProgramInfoLog(programId, 1024, &logLength, infoLog);
    if (logLength == 0)
        return 0;
    mylog("%s program infoLog:\n%s\n", ref.c_str(), infoLog);
    return -1;
}

int Shader::compileShader(const char* filePath, GLenum shaderType) {
    int shaderId = glCreateShader(shaderType);
    FILE* pFile;
    myFopen_s(&pFile, filePath, "rt");
    if (pFile != NULL)
    {
        // obtain file size:
        fseek(pFile, 0, SEEK_END);
        int fSize = ftell(pFile);
        rewind(pFile);
        // size obtained, create buffer
        char* shaderSource = new char[fSize + 1];
        fSize = fread(shaderSource, 1, fSize, pFile);
        shaderSource[fSize] = 0;
        fclose(pFile);
        // source code loaded, compile
        glShaderSource(shaderId, 1, (const GLchar**)&shaderSource, NULL);
        //myglErrorCheck("glShaderSource");
        glCompileShader(shaderId);
        if (shaderErrorCheck(shaderId, "glCompileShader") < 0)
            return -1;
        delete[] shaderSource;
    }
    else {
        mylog("ERROR loading %s\n", filePath);
        return -1;
    }
    return shaderId;
}
int Shader::linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS) {
    int vertexShaderId = compileShader(filePathVertexS, GL_VERTEX_SHADER);
    int fragmentShaderId = compileShader(filePathFragmentS, GL_FRAGMENT_SHADER);
    int programId = glCreateProgram();
    glAttachShader(programId, vertexShaderId);
    glAttachShader(programId, fragmentShaderId);
    glLinkProgram(programId);
    if (programErrorCheck(programId, "glLinkProgram") < 0)
        return -1;
    //don't need shaders any longer - detach and delete them
    glDetachShader(programId, vertexShaderId);
    glDetachShader(programId, fragmentShaderId);
    glDeleteShader(vertexShaderId);
    glDeleteShader(fragmentShaderId);
    return programId;
}


6. СоответствующийTheGame.cpp на данный момент. Откроем и заменим код на:

#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
#include "utils.h"
#include "Shader.h"
#include "Texture.h"

extern std::string filesRoot;

static const struct
{
    float x, y, z, tu, tv;
} vertices[4] =
{
    { -0.5f,  0.5f, 0.f, 0.f, 0.f }, //top-left
    { -0.5f, -0.5f, 0.f, 0.f, 1.f }, //bottom-left
    {  0.5f,  0.5f, 0.f, 1.f, 0.f }, //top-right
    {  0.5f, -0.5f, 0.f, 1.f, 1.f }  //bottom-right
};

Shader* pShader;
unsigned int vao_buffer, vertex_buffer;
float angle_z = 0;

int TheGame::getReady() {
    bExitGame = false;

    Shader::loadShaders();

    glGenVertexArrays(1, &vao_buffer);
    glBindVertexArray(vao_buffer);

    glGenBuffers(1, &vertex_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    pShader = Shader::shaders.at(Shader::spN_flat_tex);

    glEnableVertexAttribArray(pShader->l_aPos);
    glVertexAttribPointer(pShader->l_aPos, 3, GL_FLOAT, GL_FALSE,
        sizeof(vertices[0]), (void*)0);

    glEnableVertexAttribArray(pShader->l_aTuv);
    glVertexAttribPointer(pShader->l_aTuv, 2, GL_FLOAT, GL_FALSE,
        sizeof(vertices[0]), (void*)(sizeof(float) * 3));

    //load texture
    int textureN = Texture::loadTexture(filesRoot + "/dt/sample_img.png");
    int textureId = Texture::getGLid(textureN);
    //pass textureId to shader program
    glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
    glBindTexture(GL_TEXTURE_2D, textureId);
    // Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.    
    glUniform1i(pShader->l_uTex0, 0);

    return 1;
}
int TheGame::drawFrame() {
    myPollEvents();

    mat4x4 m, p, mvp;

    //glClearColor(0.0, 0.0, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    angle_z += 0.01f;
    mat4x4_identity(m);
    mat4x4_rotate_Z(m, m, angle_z);
    mat4x4_scale_aniso(m, m, 2.0, 1.0, 1.0);
    mat4x4_ortho(p, -screenRatio, screenRatio, -1.f, 1.f, 1.f, -1.f);
    mat4x4_mul(mvp, p, m);

    glUseProgram(pShader->GLid);
    glUniformMatrix4fv(pShader->l_uMVP, 1, GL_FALSE, (const GLfloat*)mvp);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    mySwapBuffers();
    return 1;
}
int TheGame::cleanUp() {
    Texture::cleanUp();
    Shader::cleanUp();
    return 1;
}
int TheGame::onScreenResize(int width, int height) {
    if (screenSize[0] == width && screenSize[1] == height)
        return 0;
    screenSize[0] = width;
    screenSize[1] = height;
    screenRatio = (float)width / (float)height;
    glViewport(0, 0, width, height);
    mylog(" screen size %d x %d\n", width, height);
    return 1;
}
int TheGame::run() {
    getReady();
    while (!bExitGame) {
        drawFrame();
    }
    cleanUp();
    return 1;
}

Компиляция и запуск. Отлично.


Теперь –

Android


7. Пере-запускаем VS. Открываем C:\CPP\a998engine\p_android\p_android.sln.

Добавим в Проект новые классы:

Под xEngine добавим Existing Item

Идем в C:\CPP\engine

Выбираем

  • Texture.cpp
  • Texture.h

Add.

Включаем Android, разблокируем, подключаем, разрешаем, компиляция и запуск.

Замечательно.


Leave a Reply

Your email address will not be published. Required fields are marked *