Глава 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 *