1. To use textures, we first need to load them into the application. Using an image-loading library that supports several popular formats looks like a good solution. I like stb_image.h by Sean Barrett. Especially I love that it's a single h-file, not a lib or dll.
You can download it here.
Save it in C:\CPP\engine folder (the same place where we saved linmath.h earlier).
2. Then we'll need an image itself.
Create a folder for it: \dt under C:\CPP\a999hello (this folder should belong to the project, NOT to engine).
IMPORTANT: the image sizes MUST be powers of 2.
The following sample_img.png is 512x256:
Save it in C:\CPP\a999hello\dt
Windows
3. Start Visual Studio. Open C:\CPP\a999hello\p_windows\p_windows.sln
4. Add new /dt folder to Post-Build xcopy instruction:
Open p_windows project Properties, All Configurations / x86, Build events -> Post-Build Event -> Command Line -> Edit.
Add new line
xcopy "..\dt\*.*" "$(TargetDir)dt\" /E /R /D /y
so now we have 2 xcopy commands.
Ok, Apply, Ok.
5. Open TheGame.cpp. We'll need to change it a bit:
- We need a reference to stb_image.h.
- A bit different variables set.
- Different vertex structure: (3D coordinates + 2D texture coordinates) instead of just coordinates.
- We will use 4 vertices instead of 3 and GL_TRIANGLE_STRIP primitive instead of GL_TRIANGLES
- Also we need to implement reading/loading an image and converting it into the texture.
- And pass texture id and per-vertex texture coordinates to a new shader program.
So, replaceTheGame.cpp code by following:
#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;
}
- And, of course, new shaders:
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;
}
Copy-paste this code to a text editor and save it as a txt file in
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);
}
Copy-paste this code to a text editor and save it as a txt file in
C:\CPP\engine\dt\shaders\flat_tex_f.txt
7. Build and run. Result:
Now - on
Android
This time ALL code changes were on a cross-platform side, so we don't even need to change anything… Except stb_image.h and xcopy post-build instruction.
8. Re-start Visual Studio. Open C:\CPP\a999hello\p_android\p_android.sln.
9. Go to p_android.NativeActivity project Properties, All Configurations / ARM64, Build Events -> Post-Build Event -> Command Line -> Edit
Add new line:
xcopy "..\..\dt\*.*" "..\$(RootNamespace).Packaging\assets\dt\" /E /R /D /y
Ok, Apply, Ok.
9. Now, right-click on xEngine, Add -> Existing Item,
C:\CPP\engine\stb_image.h
Add.
10. Plug in Android (as usual).
Re-build and run.
Nice, just as expected.
BTW, if you are a newbie to programming, now you can modestly indicate in your resume that you are fluent in:
- C++
- graphics
- 3D
- Visual Studio
- Cross-platform
- Android
- Windows
- OpenGL ES
- GLSL
Well, maybe "fluent" is still an exaggeration at this point, but everything else is a pure truth.