Pages

Wednesday, 25 March 2015

Texture atlas implementation in OpenGL ES 2.0 using shaders: texture coordinate to texture region coordinate transformation

Let me show a method of implementing texture atlas. Texture region is a sub area of the actual texture.
It is used for performance benefit, in that texture need not be switched.

With texture region also as a user we will be specifying the same texture coordinate(from (0, 0) to (1, 1)),
but the shader implementation internally will convert or transform it to the texture coordinate to match the region's.

How we do that is by simply transforming the texture coordinate in the fragment shader. We will create a 3x3 matrix
corresponding to the texture region and multiply it with the texture coordinates.

Texture Region class

This class creates the 3x3 matrix which transforms the original texture coordinates to texture region coordinates.

It is provided with the original texture size and a sub rectangle within the texture.

Source code for the complete class,

    1 #ifndef TEXTUREREGION_H
    2 #define TEXTUREREGION_H
    3 
    4 #include <algorithm>
    5 
    6 class TextureRegion {
    7 private:
    8     int mTextureWidth;
    9     int mTextureHeight;
   10     int mX;
   11     int mY;
   12     int mWidth;
   13     int mHeight;
   14     float mMatrix[3*3];
   15     
   16 public:
   17     TextureRegion()
   18     {}
   19     TextureRegion(int textureWidth, int textureHeight, int x, int y, int width, int height) {
   20         mTextureWidth = textureWidth;
   21         mTextureHeight = textureHeight;
   22         
   23         if (x < 0) {
   24             x = 0;
   25         }
   26         if (x < 0) {
   27             x = 0;
   28         }
   29         
   30         if (width > textureWidth) {
   31             width = textureWidth;
   32         }
   33         
   34         if (height > textureHeight) {
   35             height = textureHeight;
   36         }
   37 
   38         mX = x;
   39         mY = y;
   40         mWidth = width;
   41         mHeight = height;
   42 
   43         createMatrix();            
   44     }
   45     const float * getMatrix() {
   46         return mMatrix;
   47     }
   48 private:
   49     void createMatrix() {
   50         // translation factor
   51         float tx = (float)(mX - 0) / (float)mTextureWidth; 
   52         float ty = (float)(mY - 0) / (float)mTextureHeight;
   53         
   54         // scaling factor
   55         float sx = (float)mWidth / (float)mTextureWidth; 
   56         float sy = (float)mHeight / (float)mTextureHeight;
   57 
   58         // 3x3 matrix
   59         float mat[] = {
   60                 sx, 0,   0,
   61                 0,   sy, 0,
   62                 tx, ty, 1.0f
   63         };
   64         std::copy(mat, mat+9, mMatrix);
   65     }
   66 };
   67 
   68 #endif
   69 

Vertex shader


There is no additional logic in vertex shader from that of drawing a simple texture.

    1 attribute vec4 aPosition;
    2 attribute vec2 aTexCoord;
    3 varying vec2 vTexCoord;
    4 void main() {
    5     vTexCoord = aTexCoord;
    6     gl_Position = aPosition;
    7 }
    8 

Fragment shader


It is in the fragment shader that we use the texture matrix which transforms the original texture coordinates to the required coordinates.

    1 #ifdef GL_ES
    2 precision mediump float;
    3 #endif
    4 varying vec2 vTexCoord;
    5 uniform mat3 uTextureMatrix;
    6 uniform vec4 uColor;
    7 uniform sampler2D uTexture;
    8 void main() {
    9     vec3 texCoord = uTextureMatrix * vec3(vTexCoord.x, vTexCoord.y, 1.0f);
   10     gl_FragColor = uColor * texture2D(uTexture, texCoord.xy);
   11 }


As you are seeing we have declared the uniform variable uTextureMatrix for transforming the texture coordinate.
As you can see in line 9 instead directly passing the texture coordinate to texture2D function we are transforming it
using the matrix uTextureMatrix to the required coordinates.
We are doing it by multiplying the matrix uTextureMatrix with the texture coordinate, last value is 1.0 because
we are using homogeneous coordinate system in two dimension.

Complete source code is pasted below for your reference. You need glut to compile it.
    1 #include <GL/glut.h>
    2 #include <iostream>
    3 #include <string>
    4 
    5 #include "textureregion.h"
    6 
    7 bool init();
    8 void display();
    9 void reshape(int w, int h);
   10 int loadShader(int shaderType, std::string pSource);
   11 int createProgram(std::string vertexSource, std::string fragmentSource);
   12 
   13 static const std::string VertexShader =
   14     "attribute vec4 aPosition;\n"
   15     "attribute vec2 aTexCoord;\n"
   16     "varying vec2 vTexCoord;\n"
   17     "void main() {\n"
   18     "    vTexCoord = aTexCoord;\n"
   19     "    gl_Position = aPosition;\n"
   20     "}\n";
   21 
   22 static const std::string FragmentShader =
   23     "#ifdef GL_ES\n"
   24     "precision mediump float;\n"
   25     "#endif\n"
   26     "varying vec2 vTexCoord;\n"
   27     "uniform mat3 uTextureMatrix;\n"
   28     "uniform vec4 uColor;\n"
   29     "uniform sampler2D uTexture;\n"
   30     "void main() {\n"
   31     "    vec3 texCoord = uTextureMatrix * vec3(vTexCoord.x, vTexCoord.y, 1.0f);\n"
   32     "    gl_FragColor = uColor * texture2D(uTexture, texCoord.xy);\n"
   33     "}\n";
   34 
   35 int mProgram;
   36 
   37 GLuint mTextureId;
   38 
   39 int mPosLoc;
   40 int mTexCoordLoc;
   41 int mTexMatrixLoc;
   42 int mSampleLoc;
   43 int mColorLoc;
   44 
   45 float mVerticesBuffer[8];
   46 float mTexcoordBuffer[8];
   47 
   48 TextureRegion mTextureRegion;
   49 
   50 int loadShader(int shaderType, std::string pSource)
   51 {
   52     int shader = glCreateShader(shaderType);
   53     if (shader != 0) {
   54         const char *vsSource  = pSource.c_str();
   55         glShaderSource(shader, 1, &vsSource, 0);
   56         glCompileShader(shader);
   57         // not interested in checking status
   58     }
   59 
   60     return shader;
   61 }
   62 
   63 int createProgram(std::string vertexSource, std::string fragmentSource)
   64 {
   65     int vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource);
   66     if (vertexShader == 0) {
   67         return 0;
   68     }
   69 
   70     int pixelShader = loadShader(GL_FRAGMENT_SHADER, fragmentSource);
   71     if (pixelShader == 0) {
   72         return 0;
   73     }
   74 
   75     int program = glCreateProgram();
   76     if (program != 0) {
   77         glAttachShader(program, vertexShader);
   78         glAttachShader(program, pixelShader);
   79         glLinkProgram(program);
   80         // not interested in checking status
   81     }
   82     return program;
   83 }
   84 
   85 bool init()
   86 {
   87     glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
   88 
   89     mProgram = createProgram(VertexShader, FragmentShader);
   90 
   91     // retrieve locations
   92     glUseProgram(mProgram);
   93 
   94     mPosLoc = glGetAttribLocation(mProgram, "aPosition");
   95     mTexCoordLoc = glGetAttribLocation(mProgram, "aTexCoord");
   96     mSampleLoc = glGetUniformLocation(mProgram, "uTexture");
   97     mColorLoc = glGetUniformLocation(mProgram, "uColor");
   98     mTexMatrixLoc = glGetUniformLocation(mProgram, "uTextureMatrix");
   99 
  100     GLubyte texdata[] = {
  101         0xFF, 0x00, 0x00, 0xFF,
  102         0x00, 0xFF, 0x00, 0xFF,
  103         0x00, 0x00, 0xFF, 0xFF,
  104         0xFF, 0xFF, 0x00, 0xFF
  105     };
  106 
  107     glGenTextures(1, &mTextureId);
  108 
  109     glBindTexture(GL_TEXTURE_2D, mTextureId);
  110     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  111     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  112 
  113     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  114     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  115 
  116     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, texdata);
  117 
  118     // first color
  119     mTextureRegion = TextureRegion(2, 2, 1, 1, 1, 1);
  120 
  121     glEnable(GL_TEXTURE_2D);
  122 
  123     // create vertices buffer
  124     float vertices[] = {
  125         -0.75f, -0.75f,
  126         0.75f, -0.75f,
  127         0.75f, 0.75f,
  128         -0.75f, 0.75f
  129     };
  130 
  131     std::copy(vertices, vertices+8, mVerticesBuffer);
  132 
  133     // create texcoord buffer
  134     float texcoords[] = {
  135         0, 1,
  136         1, 1,
  137         1, 0,
  138         0, 0
  139     };
  140 
  141     std::copy(texcoords, texcoords+8, mTexcoordBuffer);
  142 
  143     glEnable(GL_BLEND);
  144 
  145     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  146 
  147     return true;
  148 }
  149 
  150 void display()
  151 {
  152     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  153 
  154     glUseProgram(mProgram);
  155 
  156     glActiveTexture(GL_TEXTURE0);
  157     glBindTexture(GL_TEXTURE_2D, mTextureId);
  158     glUniform1i(mSampleLoc, 0);
  159 
  160     glUniform4f(mColorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
  161 
  162     glVertexAttribPointer(mPosLoc, 2, GL_FLOAT, false, 0, mVerticesBuffer);
  163     glVertexAttribPointer(mTexCoordLoc, 2, GL_FLOAT, false, 0, mTexcoordBuffer);
  164 
  165     glUniformMatrix3fv(mTexMatrixLoc, 1, false, mTextureRegion.getMatrix());
  166 
  167     glEnableVertexAttribArray(mPosLoc);
  168     glEnableVertexAttribArray(mTexCoordLoc);
  169 
  170     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
  171 
  172     glutSwapBuffers();
  173 }
  174 
  175 void reshape(int w, int h)
  176 {
  177     glViewport(0, 0, w, h);
  178 }
  179 
  180 int main(int argc, char **argv)
  181 {
  182     glutInit(&argc, argv);
  183     glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  184     glutInitWindowPosition(100, 100);
  185     glutInitWindowSize(320, 420);
  186 
  187     glutCreateWindow("Texture region example");
  188 
  189     glutDisplayFunc(display);
  190     glutReshapeFunc(reshape);
  191 
  192     if (init()) {
  193         glutMainLoop();
  194     } else {
  195         std::cerr << "Initialization failed" << std::endl;
  196         return 1;
  197     }
  198 
  199     return 0;
  200 }
  201 
  202  


Source code is also available at  https://github.com/chikkutechie/glexamples

 

Compiling the sources

The source code can be compiled using the below command. main.cpp contals the above pasted code.
 
    g++ main.cpp -o textreg -DGL_GLEXT_PROTOTYPES -lGL -lglut 
 

No comments:

Post a Comment