感覚メディア研究室/OpenGL


テクスチャマッピングの基礎

テクスチャマッピングとは,ポリゴンに対して画像(テクスチャ)を貼り付け(マッピング)することです.

元々,textureには「生地,てざわり,質感」といった意味があります.物体の表面的な質感をだすために,表面の画像をポリゴンに貼り付けることによって,よりリアルな描写を目指すものです.

一方,リアルな描写を目的とするだけでなく,ポリゴンオブジェクトの近似に使うこともあります.レンガ造りの壁面を描きたい場合には,単一ポリゴンにレンガの写真を貼りつけることにより,レンガひとつ一つを描画することなしに表示できます(下図).

caption,レンガのテクスチャを使った昔のスクリーンセーバ
 

このテクスチャマッピングはさまざまな可能性を持っています.シーンのリアルさを演出するためだけでなく,映像の歪み補正といった本来の目的とは異なる利用も模索されています.この手の研究は現在も最先端でおこなわれていますが,基本的な手法は以下に集約されます.

  1. テクスチャとなる画像を準備する
  2. 準備した画像をテクスチャバッファに格納する
  3. ポリゴンの幾何学的形状を指定して描画する
  4. ポリゴンの描画と同時にテクスチャバッファでの座標を指定する
     
    texturemapping1.png

ところで,ここまではテクスチャマッピングは極めて表現力の高い技法でいいことずくめのようですが,実は処理がとても複雑のためコンピュータに高度な処理能力を要求します.近年ではテクスチャマッピングをハードウエアで実現し,より高速,高解像度で描画できるようになってきていますが,廉価なハードウエアでは目も当てられないほど遅いものもあり,ハードウエアによって得意不得意が分かれます.テクスチャマッピングを多用する場合には,少なくともテクスチャマッピング機能を持ったビデオカードを使用し,テクスチャメモリの容量を把握しておくことが賢明です.

では,テクスチャマッピングの基本的手法について,以下の節により手順を詳しく見ていきます.

テクスチャファイル

テクスチャとなる画像は,画像はBMPやJPEGなどの外部ファイルを使用するのが通常です.画像には2次元の画素情報が含まれており,それぞれの画素には三原色であるRGB要素が含まれています.画像によってはアルファ値を含んだものもあります.

通常,画像ファイルを使用すると述べましたが,市松模様のようなプログラムで記述できるようなテクスチャはわざわざ画像ファイルを使用するまでもありません.このような幾何学的に定義できるテクスチャは,テクスチャバッファに直接データを書き込む(演習参照)ことで,外部ファイルの読み込みを避けることもできます.

ちなみにOpenGLで使用できるテクスチャバッファの解像度は,縦横ともに2のn乗に準じた数でなければなりません.つまり,128x128や512x256といった解像度でなければ動きません.あらかじめ画像ファイルの解像度を変換しておくか,テクスチャバッファを大き目に作っておいて画像ファイルを配列の一部分に読み込む必要があります.要注意.

OpenGLで外部ファイルを使用する場合は,画像ファイルの読み込み部分を自前で作ってあげる必要があります.したがって,画像フォーマットを熟知しておかなければなりません.知らないフォーマットの画像ファイルはテクスチャとして利用することは困難です.無難なところでは,無圧縮で比較的ファイル読み込みのしやすいTIFF,TGAファイルに変換したテクスチャを使用することをすすめます.

テクスチャバッファへの格納

テクスチャバッファとは,フレームバッファやデプスバッファと同じような,画素値を格納する2次元配列です.この配列内の1つ1つの点はピクセルと呼ばず,テクセル(Texel)と呼びます.

テクスチャバッファの格納という主題がついていますが,要するにこの手順では画像ファイルを読み取って,2次元配列にデータを書き込むことをしています.外部ファイルを読み込む場合には,ファイルを読み取って各画素の値をテクスチャバッファにコピーしていきます.テクスチャが幾何学模様の場合には,その幾何学的法則に基づいてテクスチャバッファにデータを書き込んで行きます.

テクスチャバッファは縦横の画素数を持つ2次元配列ですが,それぞれの画素にはRGBのデータ,場合によってはα値が含まれます.アルファ値を含んだテクスチャマッピングでは,そのテクスチャが透明になります.

配列にデータを書き込んだあとは,この配列がテクスチャだよ,という指示が必要です.ここまででテクスチャマッピングの準備が出来ました.

テクスチャの貼り付け

テクスチャマッピングの過程で要となる部分はこの貼り付け処理です.テクスチャの貼り付けには,テクスチャバッファの切り出し点,ポリゴンへの貼り付け点を指定してそれぞれ対応づけます.

下の図はテクスチャバッファを三角形に切り出して,三角形ポリゴンに貼り付けている場合の模式図です.テクスチャバッファ側でどの点を切り出し,ポリゴン側でどの位置に貼り付けるか,という組み合わせは幾通りもあります.思い通りのマッピングをするためには,指定方法をよく理解しておくことが肝要です.

#ref(): File not found: "texturedetach.png" at page "感覚メディア研究室/OpenGL/テクスチャマッピング"

テクスチャバッファ側の切り出し点を指定するために,OpenGLではテクスチャ座標が用いられます.この座標系は横軸をs,縦軸をtとする座標系で張られており(図),テクスチャ座標s,tはそれぞれ[0.0:1.0]の範囲をとります.

配列番号が若いほど左下になります.このため画像は左下から右上に格納されて行きます.上下左右の極性がある画像を使用する場合は注意して下さい.

texturemapping2.png

テクスチャを切り出す場合は,このsとtを指定することで行います.テクスチャ座標の指定には

#highlight(c++:nogutter:nocontrols){{ glTexCoord2f(0.0, 0.0); }} というコマンドを使いますが,引数にs値,t値を指定します.この場合,テクスチャ切り出し点は左下の原点になります.

切り出し点の指定とともにテクスチャ貼り付け点の指定をしなければなりません.これは今まで通りのポリゴン描画と同じで,2次元(3次元)の位置指定をするだけでかまいません.すなわち,

#highlight(c++:nogutter:nocontrols){{

 glVertex3f(-5.0,-5.0, 0.0);

}} とすると,直前に呼んだglTexCoord2fに対応した貼り付け点が指定されます.たとえば,

#highlight(c++:nogutter:nocontrols){{

 glBegin(GL_QUADS);
 glTexCoord2f(0.0, 0.0); glVertex3f(-5.0,-5.0, 0.0);
 glTexCoord2f(0.0, 1.0); glVertex3f(-5.0, 5.0, 0.0);
 glTexCoord2f(1.0, 1.0); glVertex3f( 5.0, 5.0, 0.0);
 glTexCoord2f(1.0, 0.0); glVertex3f( 5.0,-5.0, 0.0);
 glEnd();

}} と指定すると,下の図のような表示になります.

texturemapping3.png

テクスチャ座標s,tの指定を[0.0:1.0]の範囲を超える場合には,テクスチャが反復されます.

#highlight(c++:nogutter:nocontrols){{

 glBegin(GL_QUADS);
 glTexCoord2f(0.0, 0.0); glVertex3f(-5.0,-5.0, 0.0);
 glTexCoord2f(0.0, 1.0); glVertex3f(-5.0, 5.0, 0.0);
 glTexCoord2f(2.0, 1.0); glVertex3f( 5.0, 5.0, 0.0);
 glTexCoord2f(2.0, 0.0); glVertex3f( 5.0,-5.0, 0.0);
 glEnd();

}} と指定すると,下の図のようにs方向に2回反復表示になります.

texturemapping4.png

また,負の数を使用すると反転表示になります.

演習問題

テクスチャマッピング

サンプルプログラムを用いて、テクスチャマッピングの手順を追っていきましょう。

  1. 次のサンプルプログラムに目を通して下さい.以下で簡単に説明しています.

    #highlight(c++:nogutter:nocontrols){{

    #include <GL/glut.h>

    #include <stdlib.h>

    #include <stdio.h>

#define TEX_HEIGHT 16

#define TEX_WIDTH 16 static GLubyte image[TEX_HEIGHT][TEX_WIDTH][4];

void initTexture(void) {

 int i, j, c;
 for (i=0;i<TEX_HEIGHT;i++) {
   for (j=0;j<TEX_WIDTH;j++) {
     c = ( ((i&0x01)==0)^((j&0x01)==0) );
     image[i][j][0]= image[i][j][1]= image[i][j][2]=c*255;
     image[i][j][3]=255;
   }
 }

}

void displayTexPolygon(void) {

 glEnable(GL_TEXTURE_2D);
 glBegin(GL_QUADS);
 glTexCoord2f(0.0, 0.0); glVertex3f(-5.0,-5.0, 0.0);
 glTexCoord2f(0.0, 1.0); glVertex3f(-5.0, 5.0, 0.0);
 glTexCoord2f(1.0, 1.0); glVertex3f( 5.0, 5.0, 0.0);
 glTexCoord2f(1.0, 0.0); glVertex3f( 5.0,-5.0, 0.0);
 glEnd();
 glDisable(GL_TEXTURE_2D);

}

void display(void) {

 static float spin=0.0;
 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 glPushMatrix();
 {
   glTranslatef(0.0, 0.0,-20.0);
   glRotatef(spin, 0.0, 1.0, 0.0);
   glColor3f(1.0, 1.0, 1.0);
   displayTexPolygon();
 }
 spin+=1.0;
 glPopMatrix();
 glFlush();
 glutSwapBuffers();

}

void init(void) {

 glClearColor(0.0, 0.0, 0.0, 0.0);
 glDepthFunc(GL_LEQUAL);
 glEnable(GL_DEPTH_TEST);
 initTexture();
 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_WIDTH, TEX_HEIGHT, 
	       0, GL_RGBA, GL_UNSIGNED_BYTE, image);

}

void reshape(int w, int h) {

 glViewport(0, 0, (GLsizei) w, (GLsizei) h);
 glMatrixMode(GL_PROJECTION);			
 glLoadIdentity();				
 glFrustum(-5.0, 5.0,-5.0, 5.0, 5.0, 500.0); 
 glMatrixMode(GL_MODELVIEW);			
 glLoadIdentity();				

}

int main(int argc, char** argv) {

 glutInit(&argc, argv);			
 glutInitDisplayMode (GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA);
 glutInitWindowSize (250, 250);		
 glutInitWindowPosition (100, 100);		
 glutCreateWindow (argv[0]);			
 init();					
 glutIdleFunc(display);			
 glutDisplayFunc(display);			
 glutReshapeFunc(reshape);			
 glutMainLoop();				
 return 0;					

} }}

  1. テクスチャの準備
     
    テクスチャの準備initTexture関数の中でおこなわれています. テクスチャバッファの2次元配列には,GLubyte型(unsigned char)でWIDTHxHEIGHTx4個となっています.1画素にRGBAの4バイトを割り当てていることになります.テクスチャのサイズは縦横とも2の累乗になっていなければならないので,ここでは16x16ですね.
     
    initTexture関数の内部では、配列にチェック模様を格納していきます. 白いチェック模様を描くために,配列番号の下位1ビットで白黒を判定しています.つまり2で割り切れる場合は1,そうでなければ0をcに代入しています.最終的に配列には0か255の値が代入されます.
     
  2. テクスチャの格納
     
    配列をテクスチャバッファに読みこみます.テクスチャの格納もプログラム初期化時にinit関数にて実行されています.
     
    glPixelStorei()は,テクスチャバッファに格納される際の配列の並びを指定します.image[]という名前のついた配列をこれからテクスチャバッファに格納しようとするわけですが,この配列は並びが連続していますよ,という指定をしています.
     
    glTexParameteriはテクスチャバッファを利用する際の設定をおこないます.テクスチャの反復やクランプ,拡大縮小時の描画に関する制御が可能です.
     
    glTexImage2Dは,配列の内容をテクスチャバッファに格納する実質的なコマンドです.このコマンドでは以下の引数を持ちます.
    void glTexImage2D(GLenum target, GLint level, GLint component,
    			  GLsizei width, GLsizei height, GLint border,
    			  GLenum format, GLenum type, const GLvoid *pixels)
     
    頻繁に使うパラメータはwidth,height,pixelです.width,heightはテクスチャバッファのサイズです.それぞれ2の累乗であることが必要です.pixelにはテクスチャが格納されている配列へのポインタ(ここではimage)を示します.
     
  3. テクスチャの貼りつけ
     
    テクスチャの切り取り,貼り付け指定は,物体の描画時におこなわれます.ここではdisplayTexPolygon()関数で行われています.
     
    重要なのは,テクスチャマッピングを有効にするために,glEnable(GL_TEXTURE_2D)をコールすることです.glDisable(GL_TEXTURE_2D)を呼ぶとテクスチャマッピングを無効にします.この切り替えを上手にしないと,表示されるポリゴンすべてにテクスチャマッピングが適用される恐れがあります.
     
  4. サンプルプログラムをよく読んだ上でコンパイルし,実行して下さい.
     
    ensyu7-1.png
     
  5. テクスチャの解像度を16x16から64x64に変更してみて下さい.
     
    ensyu7-2.png
     
  6. テクスチャの解像度を2x2に変更してみて,盤の目が粗くなることを確認して下さい.縦横に8回反復させることによって,16x16と同じになることを確認しましょう.

外部ファイルによるテクスチャマッピング

外部ファイルを使用したテクスチャマッピングをしてみましょう.

  1. デジタルカメラやスマートフォンでテクスチャにしたい画像を撮影します.
  2. この画像ファイルの形式を変換します.演習ではTGAファイルを扱いますので,TGA形式(RGBA各8bitの32bit)に変換して下さい.この時,解像度を縦横ともに2の累乗になるように合わせること.
  3. 写真がなければ,filetest.tga を使ってください.
  4. テクスチャマッピングのプログラムを作成します.最初の課題で使用したプログラムを流用してみましょう.まず,自分の画像の解像度にあわせて,

    #highlight(c++:nogutter:nocontrols){{

    #define HEIGHT 16

    #define WIDTH 16 }} の値を適宜変更しましょう.

     
    画像ファイルを読み込み,配列に格納するようにinitTexture関数を変更します. TGAフォーマットについては,ここに資料がありますので参考にして下さい.ファイル読み込み部の例を以下に記述しますが,なぜこうするのかは自分で解釈してください.また下の例ではファイル名が"test.tga"になっていますので自分のファイル名に変更が必要です.

    #highlight(c++:nogutter:nocontrols){{ void initTexture(void) {

     FILE *fp;
     int x, z;
     
     /* texture file open */
     if((fp=fopen("test.tga", "rb"))==NULL){
       fprintf(stderr, "texture file cannot open\n");
       return;
     }
     fseek(fp, 18, SEEK_SET);
     for(x=0;x<TEX_HEIGHT;x++){
       for(z=0;z<TEX_WIDTH;z++){
         image[x][z][2]=fgetc(fp);/* B */
         image[x][z][1]=fgetc(fp);/* G */
         image[x][z][0]=fgetc(fp);/* R */
         image[x][z][3]=fgetc(fp);/* alpha */
       }
     }
     fclose(fp);
    } }}
    ensyu7-3.png
     
  5. テクスチャ取り込み点や貼り付け点を各自で変更し,指定方法が表示にどう影響するのか,自分で試してみましょう.
  6. 物体の色を変更してみましょう.テクスチャにどう影響をあたえるでしょうか.
  7. 物体の形をいろいろ変更してみましょう.glutSolidTeapotは実はテクスチャマッピングに対応しています.試してみましょう.
     
    ensyu7-4.png

自習問題

ここで勉強したOpenGLの関数は,

#highlight(c++:nogutter:nocontrols){{ glTexCoord2f() glPixelStorei() glTexParameteri() glTexImage2D() }} です.赤本やGLUTのマニュアルを利用して,復習しておきましょう.

#highlight(end)


Last-modified: 2024-03-27 (水) 18:10:22