OpenGLサンプル テクスチャ

OpenGLでテクスチャを表示する手順です。複数枚でブレンディングを有効にして表示してみます。

画像を読み込む

代表的な読み込み方は
・ライブラリを利用して直接読み込む( OpenCVのcvLoad, linpng, libjpgなど。または自作するなど)
フレームワーク付属のクラスを利用してraw dataを取り出す。
ここでは読み込んだと仮定して、画像を自動生成することにします。
画像のデータをまとめてグローバルに定義しておきます。

static const int RGB = 3;
static const int RGBA = 4;
struct image_t{
    int width;
    int height;
    int channels;
    void*buffer;
    ~image_t(){
        if(buffer)::free(buffer);
    }
};
image_t image1 = {64, 64, RGB, 0};
image_t image2 = {64, 64, RGBA, 0};  // ブレンディング対象

画像の読み込みの代わりの関数を作成します。左上端に目印を付けて表示が正しいか判断することにします。画像データの配列は、R,G,BあるいはR,G,B,Aの順で作成します。読み込むときも同様なので、BGR型の配列を読み込んだときは順序を変換しないといけません。

void *loadTexture(int width, int height, int channels, const char* filePathDummy){
    // 画像を読み込む(ここでは仮として自動生成する)
    typedef unsigned char* iterator_t;
    int image_size = width * height * channels * sizeof(unsigned char);
    void *image = ::malloc( image_size ); // nullチェック略
    iterator_t itr_dest = iterator_t(image);
    iterator_t itr_dest_end = itr_dest + image_size;
    //  ストライプ作成
    int pixel = 0; int stride = 3;
    while (itr_dest != itr_dest_end) {
        *itr_dest++ = 255-50 * pixel ;
        *itr_dest++ = 255-50 * pixel ;
        *itr_dest++ = 255-10 * pixel ;
        if (channels == RGBA) {
            *itr_dest++ = 100;  //  半透明
        }
        pixel = ++pixel % stride;
    }
    // 上左端に目印をつける
    itr_dest = iterator_t(image);
    for(int i = 0 ; i < width / 3; i++){
        *itr_dest++ = 50;  // cyan
        *itr_dest++ = 255;
        *itr_dest++ = 255;
        if (channels == RGBA) {
            *itr_dest++ = 100;
        }
    }
    return image;
}
int main(int argc, char **argv){
    //略

    // load image data
    image1.buffer = loadTexture(image1.width, image1.height, image1.channels, "icon1.png");
    image2.buffer = loadTexture(image2.width, image2.height, image2.channels, "icon2.png");
テクスチャの管理

各テクスチャはGLuint型の番号で管理します。複数枚のときはGLuint型の配列を用意しておきます。こちらも画像とセットでグローバルに定義しておきます。

const int TEXTURES = 2;
GLuint textureArray[TEXTURES];
テクスチャ領域の確保と解放

glGenTextures・glDeleteTexturesを使います。

int main(int argc, char **argv){
    //略...

    glEnable(GL_TEXTURE_2D);
    glGenTextures( TEXTURES, textureArray );
    // ...
}
void onExit(){
    glDeleteTextures(TEXTURES, textureArray);
    glDisable(GL_TEXTURE_2D);
}

先ほどの配列と対応した領域が内部に作成されます。今後特定のテクスチャを指定するときはGLuint型の番号textureArray[i]を用います。特定のテクスチャに関する処理を実行するときも引数にGLuint型の番号を入れます。

テクスチャの作成

glBindTextureで現在のテクスチャを指定、glPixelStorei、glTexParameterf、glTexEnviなどでテクスチャの各パラメータを指定、glTexImage2Dで画像からテクスチャを作成します。glTexImage2Dのの引数formatには内部フォーマットを、次の引数formatには画像のフォーマットを指定します。ブレンド処理に対応させるときは双方にGL_RGBAを入れておきます。

void textureFromImage(GLuint textureId, image_t* image){
    // IDによって現在のテクスチャを指定する
    glBindTexture(GL_TEXTURE_2D, textureId );
   // パラメータ設定(例)
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );	// texture environment
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    // 画像からテクスチャ作成
    GLenum format = GL_RGB;
    if(image->channels == RGBA){
        format = GL_RGBA;  //  4channelのときはGL_RGBAを引数にする
    }
    glTexImage2D(GL_TEXTURE_2D, 0, format, image->width, image->height, 0, format, GL_UNSIGNED_BYTE, image->buffer );
}
テクスチャの描画

glVertexPointerで描画位置を指定、glTexCoordPointerでテクスチャマッピング座標を指定します。その後にglDrawArraysで描画します。テクスチャマッピングを行う前に、glBindTextureでどのテクスチャに適用するのか指定しておきます。

void drawTexture(GLuint textureId, float x, float y, float width, float height ){
    static const int VERTEX_DIMENSION	= 2;
    static const int NUM_VERTICES	= 4;
    
    //	描画位置
    float vertexArray[ ] = {
        x, y,                 // 左下
        x + width, y,         // 右下
        x, y + height,        // 左上
        x + width, y + height // 右上
    };
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer( VERTEX_DIMENSION, GL_FLOAT, 0, vertexArray );
    
    //  texture mapping座標
    static const float textureCoordArray[] = {
		0.0f, 1.0f,				// 左下
		1.0f, 1.0f,				// 右下
		0.0f, 0.0f,				// 左上
		1.0f, 0.0f,				// 右上
    };
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glBindTexture(GL_TEXTURE_2D, textureId );
    glTexCoordPointer(2, GL_FLOAT, 0, textureCoordArray);
    // 描画
    glDrawArrays(GL_TRIANGLE_STRIP, 0, NUM_VERTICES);
    
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisable(GL_TEXTURE_2D);
}
ブレンディングの設定

ブレンディングの種類を予め指定しておきます。

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

以上で、画像1はそのまま、画像2が半透明で描画されます。
(参考)サンプル全体のコード。

#include <stdlib.h>
#include <GLUT/glut.h> // OSX上で実行
// テクスチャ
const int TEXTURES = 2;
GLuint textureArray[TEXTURES];
// 画像
static const int RGB = 3;
static const int RGBA = 4;
struct image_t{
    int width;
    int height;
    int channels;
    void*buffer;
    ~image_t(){ if(buffer){ ::free(buffer);} }
};
image_t image1 = {64, 64, RGB, 0};
image_t image2 = {64, 64, RGBA, 0};  // ブレンディング対象
// 関数
void *loadTexture(int width, int height, int channels, const char* filePath = "");
void drawTexture(GLuint textureId, float x, float y, float width, float height );
void textureFromImage(GLuint textureId, image_t* image);
void drawScene();
void onExit();

int main(int argc, char **argv){
    glutInit( &argc, argv );                
    glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE );
    glutInitWindowSize(500, 500);
    glutCreateWindow( "glut window" );
    glutDisplayFunc( drawScene );
    ::atexit(onExit);
    // init openGL
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // load image data
    image1.buffer = loadTexture(image1.width, image1.height, image1.channels, "icon1.png");
    image2.buffer = loadTexture(image2.width, image2.height, image2.channels, "icon2.png");
    // generate textures
    glEnable(GL_TEXTURE_2D);
    glGenTextures( TEXTURES, textureArray );
    // convert image to texture
    GLuint textureID1 = textureArray[0];
    GLuint textureID2 = textureArray[1];    
    textureFromImage(textureID1, &image1);
    textureFromImage(textureID2, &image2);

    glutMainLoop();
    return 0;
}
void onExit(){
    glDeleteTextures(TEXTURES, textureArray);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
}
// 描画
void drawScene(){
    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f);
    glClear( GL_COLOR_BUFFER_BIT );
    // draw textues
    GLuint textureID1 = textureArray[0];
    GLuint textureID2 = textureArray[1];
    drawTexture(textureID1, 0.1f, 0.1f, 0.2f, 0.2f);
    drawTexture(textureID2, -0.3f, -0.3f, 0.5f, 0.5f);
    
    glutSwapBuffers();
}
//  テクスチャ描画
void drawTexture(GLuint textureId, float x, float y, float width, float height ){
    static const int VERTEX_DIMENSION	= 2;
    static const int NUM_VERTICES	= 4;
    //	描画位置
    float vertexArray[ ] = {
        x, y,                 // 左下
        x + width, y,         // 右下
        x, y + height,        // 左上
        x + width, y + height // 右上
    };
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer( VERTEX_DIMENSION, GL_FLOAT, 0, vertexArray );
    //  texture mapping座標
    static const float textureCoordArray[] = {
		0.0f, 1.0f,				// 左下
		1.0f, 1.0f,				// 右下
		0.0f, 0.0f,				// 左上
		1.0f, 0.0f,				// 右上
    };
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glBindTexture(GL_TEXTURE_2D, textureId );
    glTexCoordPointer(2, GL_FLOAT, 0, textureCoordArray);
    // 描画
    glDrawArrays(GL_TRIANGLE_STRIP, 0, NUM_VERTICES);
    
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisable(GL_TEXTURE_2D);
}
void textureFromImage(GLuint textureId, image_t* image){
    // IDによって現在のテクスチャを指定する
    glBindTexture(GL_TEXTURE_2D, textureId );
    // パラメータ設定(例)
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    // 画像からテクスチャ作成
    GLenum format = GL_RGB;
    if(image->channels == RGBA){
        format = GL_RGBA;  //  4channelのときはGL_RGBAを引数にする
    }
    glTexImage2D(GL_TEXTURE_2D, 0, format, image->width, image->height, 0, format, GL_UNSIGNED_BYTE, image->buffer );
}
// 画像を読み込む(ここでは仮として自動生成する)
void *loadTexture(int width, int height, int channels, const char* filePathDummy){
    typedef unsigned char* iterator_t;
    int image_size = width * height * channels * sizeof(unsigned char);
    void *image = ::malloc( image_size ); // nullチェック略
    iterator_t itr_dest = iterator_t(image);
    iterator_t itr_dest_end = itr_dest + image_size;
    // ストライプ作成
    int pixel = 0; int stride = 3;
    while (itr_dest != itr_dest_end) {
        *itr_dest++ = 255-50 * pixel ; // r
        *itr_dest++ = 255-50 * pixel ; // g
        *itr_dest++ = 255-10 * pixel ; // b
        if (channels == RGBA) {
            *itr_dest++ = 100;  //  a:半透明
        }
        pixel = ++pixel % stride;
    }
    // 上左端に目印をつける
    itr_dest = iterator_t(image);
    for(int i = 0 ; i < width / 3; i++){
        *itr_dest++ = 50;  // cyan
        *itr_dest++ = 255;
        *itr_dest++ = 255;
        if (channels == RGBA) {
            *itr_dest++ = 100;
        }
    }
    return image;
}