Глава 11. Текстуры

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;
}


  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

Ну, “в совершенстве” – пока, может, и преувеличение, но все остальное – чистая правда.


Leave a Reply

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