К этому моменту мы благополучно “поженили” 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) загруженных текстур во избежание дублирования.
- Под 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; };
};
- Под 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, разблокируем, подключаем, разрешаем, компиляция и запуск.
Замечательно.