1. Для начала нужно загрузить текстуру в программу. Какая-нибудь image-loading библиотечка, поддерживающая несколько популярных форматов кажется неплохим решением. Мне понравилась stb_image.h от Шона Барретта, особенно то, что это 1 h-файл, а не lib или dll.
Скачать можно здесь.
Сохраним ее в C:\CPP\engine folder (там же где и linmath.h ранее).
2. Потом нужна сама картинка.
Создадим для нее каталог: \dt под C:\CPP\a999hello (этот каталог должен принадлежать проекту, а НЕ движку (engine)).
ВАЖНО: размеры картинки ДОЛЖНЫ быть степенями двойки.
У нижеприведенной sample_img.png размер 512×256:
Сохраним ее в C:\CPP\a999hello\dt
Windows
3. Запускаем Visual Studio. Открываем C:\CPP\a999hello\p_windows\p_windows.sln
4. Добавим новый каталог /dt в Post-Build xcopy инструкции:
Открываем p_windows project Properties, All Configurations / x86, Build events -> Post-Build Event -> Command Line -> Edit.
Добавляем новую строку
xcopy "..\dt\*.*" "$(TargetDir)dt\" /E /R /D /y
так что теперь у нас 2 xcopy команды.
Ok, Apply, Ok.
5. Откроем TheGame.cpp. Нужно поменять кое-что:
- Нужна ссылка на stb_image.h.
- Немного другой набор переменных.
- Новая структура вертексов: 3D координаты + 2D координаты текстуры вместо просто координат.
- Понадобятся 4 вертекса вместо трех и примитив GL_TRIANGLE_STRIP вместо GL_TRIANGLES.
- Еще нужны чтение изображения и загрузка его в текстуру.
- И передача id текстуры и текстурные коодинаты по каждому вертексу в новую шейдер-программу.
ЗаменимTheGame.cpp код на:
#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
#include "utils.h"
#include "Shader.h"
extern std::string filesRoot;
#define STB_IMAGE_IMPLEMENTATION //required by stb_image.h
#include "stb_image.h"
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
};
unsigned int vao_buffer, vertex_buffer, shaderProgramId, textureId;
int umvp_location, apos_location, atuv_location;
float angle_z = 0;
int TheGame::getReady() {
bExitGame = false;
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);
shaderProgramId = Shader::linkShaderProgram((filesRoot + "/dt/shaders/flat_tex_v.txt").c_str(), (filesRoot + "/dt/shaders/flat_tex_f.txt").c_str());
umvp_location = glGetUniformLocation(shaderProgramId, "uMVP");
apos_location = glGetAttribLocation(shaderProgramId, "aPos");
glEnableVertexAttribArray(apos_location);
glVertexAttribPointer(apos_location, 3, GL_FLOAT, GL_FALSE,
sizeof(vertices[0]), (void*)0);
atuv_location = glGetAttribLocation(shaderProgramId, "aTuv");
glEnableVertexAttribArray(atuv_location);
glVertexAttribPointer(atuv_location, 2, GL_FLOAT, GL_FALSE,
sizeof(vertices[0]), (void*)(sizeof(float) * 3));
// loading an image
int imgWidth, imgHeight, nrChannels;
unsigned char* imgData = stbi_load((filesRoot + "/dt/sample_img.png").c_str(),
&imgWidth, &imgHeight, &nrChannels, 4); //"4"-convert to 4 channels -RGBA
if (imgData == NULL) {
mylog("ERROR loading image\n");
}
// generate texture
unsigned int textureId;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
// 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, imgWidth, imgHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);
glGenerateMipmap(GL_TEXTURE_2D);
// release image data
stbi_image_free(imgData);
//pass textureId to shader program
glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, textureId);
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(shaderProgramId);
glUniformMatrix4fv(umvp_location, 1, GL_FALSE, (const GLfloat*)mvp);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
mySwapBuffers();
return 1;
}
int TheGame::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;
}
- И, конечно, новые шейдеры:
Vertex shader:
#version 320 es
precision lowp float;
uniform mat4 uMVP; // transform matrix (Model-View-Projection)
in vec3 aPos; //attribute position (3D coordinates)
in vec2 aTuv; //attribute TUV (texture coordinates)
out vec2 vTuv; //varying TUV (pass to fragment shader)
void main(){
gl_Position = uMVP * vec4(aPos, 1.0);
vTuv = aTuv;
}
Копируем этот код в Текстовый редактор и сохраняем его как txt файл в
C:\CPP\engine\dt\shaders\flat_tex_v.txt
Fragment shader:
#version 320 es
precision lowp float;
uniform sampler2D uTex0; //texture id
in vec2 vTuv; //varying TUV (passed from vertex shader)
out vec4 FragColor; // output pixel color
void main(){
FragColor = texture(uTex0, vTuv);
}
Копируем этот код в Текстовый редактор и сохраняем его как txt файл в
C:\CPP\engine\dt\shaders\flat_tex_f.txt
7. Компиляция и запуск. Результат:
Теперь – на
Андроиде
На этот раз ВСЕ программные изменения были на cross-platform-енной стороне, так что здесь даже менять ничего не нужно… Кроме stb_image.h и xcopy post-build инструкции.
8. Пере-запускаем Visual Studio. Открываем C:\CPP\a999hello\p_android\p_android.sln.
9. Идем в p_android.NativeActivity project Properties, All Configurations / ARM64, Build Events -> Post-Build Event -> Command Line -> Edit
Добавляем новую строку:
xcopy "..\..\dt\*.*" "..\$(RootNamespace).Packaging\assets\dt\" /E /R /D /y
Ok, Apply, Ok.
9. Теперь, right-click на xEngine, Add -> Existing Item,
C:\CPP\engine\stb_image.h
Add.
10. Включаем Android, разблокируем, подключаем, разрешаем.
Re-build и запуск.
Отлично, как и ожидалось.
Кстати, если Вы – новичок в программировании, то теперь Вы можете скромно упомянуть в своем резюме, что в совершенстве владеете следующими технологиями:
- C++
- graphics
- 3D
- Visual Studio
- Cross-platform
- Android
- Windows
- OpenGL ES
- GLSL
Ну, “в совершенстве” – пока, может, и преувеличение, но все остальное – чистая правда.