«第2回 Gouraud シェーディングと Phong シ.. 最新 第4回 GLSL によるバンプマッピング»

床井研究室

※このブログは遅くとも 2027 年 3 月に管理者の定年退職により閉鎖します (移転先は管理者本人共々模索中)

■ 2005年10月08日 [OpenGL][GLSL] 第3回 テクスチャの参照

2023年04月05日 11:38更新

昭和テイスト

研究室のミーティングで学生さんに対して「当たり前田のクラッカー!」っていう具合に突っ込みを入れたら,「昭和テイストだなぁー」とか「世代が違いますよ,世代が」とか散々な言われ方をしました.私,やっぱり旧い人なんでしょうか?(聞くまでもないか)そういえば以前,奥さんに「おさじとって」と言ったら,「はい,スプーン」と訂正されてしまいました.昭和は遠くなったもんだ(違

テクスチャを参照する

シェーダプログラムの中でもテクスチャを参照することができます.もちろん,マルチテクスチャが使えます.複数のテクスチャを組み合わせた処理を手続きで書けるってあたりが,シェーダプログラミングの醍醐味でしょう.

とりあえず,前回のプログラムにテクスチャに使う画像を読み込む処理を追加します.テクスチャには,以前に使ったこの画像を使います.なお本題とは関係ありませんが,以下のプログラムでは OpenGL 1.4 で標準機能となったミップマップの自動生成機能を使用しています.

・・・
 
/*
** テクスチャ
*/
#define TEXWIDTH  256                      /* テクスチャの幅    */
#define TEXHEIGHT 256                      /* テクスチャの高さ   */
static const char texture1[] = "dot.raw";  /* テクスチャファイル名 */
 
/*
** 初期化
*/
static void init(void)
{
  /* シェーダプログラムのコンパイル/リンク結果を得る変数 */
  GLint compiled, linked;
  
  /* テクスチャの読み込みに使う配列 */
  GLubyte texture[TEXHEIGHT][TEXWIDTH][4];
  FILE *fp;
  
  ・・・
  
  /* シェーダプログラムの適用 */
  glUseProgram(gl2Program);
  
  /* テクスチャ画像の読み込み */
  if ((fp = fopen(texture1, "rb")) != NULL) {
    fread(texture, sizeof texture, 1, fp);
    fclose(fp);
  }
  else {
    perror(texture1);
  }
  
  /* テクスチャ画像はバイト単位に詰め込まれている */
  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
  glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
  
  /* テクスチャを拡大・縮小する方法の指定 */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  
  /* テクスチャの繰り返し方法の指定 */
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  /* テクスチャの割り当て */
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
    GL_RGBA, GL_UNSIGNED_BYTE, texture);
  
  /* 初期設定 */
  glClearColor(0.3, 0.3, 1.0, 0.0);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  
  ・・・

図形描画の際にテクスチャ座標を設定しておきます.シェーダプログラムを使う場合は,glEnable(GL_TEXTURE_2D); を実行する必要はありません.

・・・
 
/*
** シーンの描画
*/
static void scene(void)
{
  static const GLfloat diffuse[] = { 0.6, 0.1, 0.1, 1.0 };
  static const GLfloat specular[] = { 0.3, 0.3, 0.3, 1.0 };
  
  /* 材質の設定 */
  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, diffuse);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
  glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 100.0f);
  
#if 1
  /* 1枚の4角形を描く */
  glNormal3d(0.0, 0.0, 1.0);
  glBegin(GL_QUADS);
  glTexCoord2d(0.0, 1.0);
  glVertex3d(-1.0, -1.0,  0.0);
  glTexCoord2d(1.0, 1.0);
  glVertex3d( 1.0, -1.0,  0.0);
  glTexCoord2d(1.0, 0.0);
  glVertex3d( 1.0,  1.0,  0.0);
  glTexCoord2d(0.0, 0.0);
  glVertex3d(-1.0,  1.0,  0.0);
  glEnd();
#else
  glutSolidTeapot(1.0);
#endif
}
 
・・・

これでテクスチャマッピングの準備は完了です.試しに関数 init() の最後にある glUseProgram(gl2Program); をコメントアウトしてプログラムをコンパイル・実行し,テクスチャが正しく貼れるか確かめてください(確かめたらプログラムを元に戻してください).

  /* シェーダプログラムの適用 */
  glUseProgram(gl2Program);

シェーダプログラムの作成

シェーダプログラムは前回作成した Phong シェーディングのものをもとにして作成します.phong.vert と phong.frag をそれぞれ texture.vert と texture.frag というファイル名にコピーしてください.

バーテックスシェーダ

バーテックスシェーダでは,テクスチャ変換行列 gl_TextureMatrix[0] を処理対象の頂点のテクスチャ座標 gl_MultiTexCoord0 に掛けて,組み込み varying 変数 gl_TexCoord[0] に代入します.gl_MultiTexCoord0 はテクスチャユニット0に設定されたテクスチャ座標です.gl_TexCoord は配列変数になっていますが,添え字の番号とテクスチャユニットは無関係(ただし総数は同じ)なので,gl_TexCoord のどの要素にどのテクスチャユニットのテクスチャ座標を入れても構いません.

// texture.vert
 
varying vec4 position;
varying vec3 normal;
 
void main(void)
{
  position = gl_ModelViewMatrix * gl_Vertex;
  normal = normalize(gl_NormalMatrix * gl_Normal);
  
  gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
  gl_Position = ftransform();
}

なお,テクスチャ変換行列を使わない場合は,gl_TextureMatrix[0] を gl_MultiTexCoord0 にかける必要はありません.

フラグメントシェーダ

2次元テクスチャは GLSL の組み込み関数 texture2DProj() を使ってサンプリングします.変数 texture は sampler2D 型の uniform 変数で,どのテクスチャユニットからどういう方法でテクスチャをサンプリングするかを指定します.この変数 texture の値(すなわちテクスチャユニット番号)の設定は,アプリケーションプログラム側で行います.

以下のプログラムでは,gl_TexCoord[0] に格納されているテクスチャ座標をテクスチャ変換行列 gl_TextureMatrix[0] により変換し,その結果を使って texture で指定されたテクスチャユニットが保持するテクスチャをサンプリングします.そして,サンプリングした色 color を使って拡散反射光強度と環境光の反射光強度を計算します.

// texture.frag
 
uniform sampler2D texture;
 
varying vec3 position;
varying vec3 normal;
 
void main (void)
{
  vec4 color = texture2DProj(texture, gl_TexCoord[0]);
  vec3 light = normalize((gl_LightSource[0].position * position.w - gl_LightSource[0].position.w * position).xyz);
  vec3 fnormal = normalize(normal);
  float diffuse = max(dot(light, fnormal), 0.0);
  
  vec3 view = -normalize(position.xyz);
  vec3 halfway = normalize(light + view);
  float specular = pow(max(dot(fnormal, halfway), 0.0), gl_FrontMaterial.shininess);
  gl_FragColor = color * gl_LightSource[0].diffuse * diffuse
                + gl_FrontLightProduct[0].specular * specular
                + color * gl_LightSource[0].ambient;
}

もちろん,gl_LightSource[0].diffuse と color * gl_LightSource[0].ambient は color でくくることができます.

// texture.frag
 
uniform sampler2D texture;
 
varying vec3 position;
varying vec3 normal;
 
void main (void)
{
  vec4 color = texture2DProj(texture, gl_TexCoord[0]);
  vec3 light = normalize((gl_LightSource[0].position * position.w - gl_LightSource[0].position.w * position).xyz);
  vec3 fnormal = normalize(normal);
  float diffuse = max(dot(light, fnormal), 0.0);
  
  vec3 view = -normalize(position.xyz);
  vec3 halfway = normalize(light + view);
  float specular = pow(max(dot(fnormal, halfway), 0.0), gl_FrontMaterial.shininess);
  gl_FragColor = color * (gl_LightSource[0].diffuse * diffuse + gl_LightSource[0].ambient)
                + gl_FrontLightProduct[0].specular * specular;
}

uniform 変数への値の設定

uniform 変数の値はアプリケーションプログラムで設定します.これは,まずglGetUniformLocation() 関数を使ってプログラムオブジェクト内から値を設定する uniform 変数を探し出し,その識別子を得ます.そして glUniform*() 関数を使って,その識別子に対して値を設定します.ここではフラグメントシェーダプログラムの texture という uniform 変数に値を設定しますから,次のような手続きになります.

・・・
 
/*
** 初期化
*/
static void init(void)
{
  ・・・
  
  /* シェーダオブジェクトの作成 */
  vertShader = glCreateShader(GL_VERTEX_SHADER);
  fragShader = glCreateShader(GL_FRAGMENT_SHADER);
  
  /* シェーダのソースプログラムの読み込み */
  if (readShaderSource(vertShader, "texture.vert")) exit(1);
  if (readShaderSource(fragShader, "texture.frag")) exit(1);
  
  ・・・
  
  /* シェーダプログラムの適用 */
  glUseProgram(gl2Program);
  
  /* テクスチャユニット0を指定する */
  glUniform1i(glGetUniformLocation(gl2Program, "texture"), 0);
  
  /* テクスチャ画像の読み込み */
  if ((fp = fopen(texture1, "rb")) != NULL) {
    fread(texture, sizeof texture, 1, fp);
    fclose(fp);
  }
  else {
    perror(texture1);
  }
   
  ・・・

以上によりシェーダプログラムの中でテクスチャを参照することが可能になります.

Phong シェーディングによるテクスチャを貼った四角形 Phong ーシェーディングによるテクスチャを貼った四角形を斜めから見たところ
Phong シェーディングによるテクスチャを貼ったティーポット
コメント(12) [コメントを投稿する]
鴨居 2015年05月27日 19:44

毎度質問ばかりですみません。<br><br>glslで、テクスチャの有無判定についてアドバイス頂けますと助かります。<br>例えば、シーンの中に2つの物体があり、一方はテクスチャ有り、他方はテクスチャ無し、だったとします。<br>昔ながらのOpenGLでは問題なく描画できます。<br><br>しかし、フラグメントシェーダではテクスチャの有無に関係なく同じ演算をするので、テクスチャが無くてもテクスチャの演算が行われ、正しく描画されなくなってしまいます。<br><br>どのようにすれば良いでしょうか?<br><br>uniform 変数で、OpenGL本体からテクスチャの有無フラグを送れば分岐処理ができそうですが、if文が入ると処理が重くらしいですし、正しいやり方なのか悩んでおります。<br><br>よろしくお願い致します。

とこ 2015年09月27日 19:09

鴨居さま、すみません、コメントいただいていたことに気づいていませんでした。<br>亀レスというか浦島レスですが…<br><br>シェーダ内での動的な分岐がパフォーマンスに大きく影響するというのは事実ですが、<br>uniform 変数による分岐のように描画単位内で分岐先が変化しない場合は、<br>分岐予測が効いてパフォーマンスにあまり影響しないと聞いています。<br>これ、以前に何かのドキュメントで読んだんですけど、ソースが見つかりません。すみません…

てー 2017年10月05日 15:35

このプログラムではテクスチャのデータは(.raw)形式のものを使用していますが、(.jpg)や(.png)などでもテクスチャとして使用することは可能ですか?

とこ 2017年10月06日 09:09

てーさん,コメントありがとうございます.<br><br>RAW を使ってるのは,読み込み用のプログラム(ライブラリ)が必要ないからです.JPEG なら libjpeg http://libjpeg.sourceforge.net/ など,PNG なら libpng http://www.libpng.org/pub/png/libpng.html などが使えます.glpng http://www.fifi.org/doc/libglpng-dev/glpng.html というものもあります.私はめんどくさいので OpenCV http://opencv.org/ をよく使います.ビデオのキャプチャなんかもできますし.

一木 2019年08月22日 00:12

質問失礼いたします、gl_TexCoord[0]は現在fragment shaderが計算しようとしているピクセルの座標に相当するオブジェクトのuv座標というイメージでしょうか?<br>よろしくお願いします

とこ 2019年08月29日 12:14

一木さま,コメントありがとうございます.お返事が遅くなり,申し訳ありません.<br><br>おっしゃる通り,オブジェクトの uv 座標と考えていただいて差し支えないと思います.<br>バーテックスシェーダの<br><br> gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;<br><br>という処理は,gl_MultiTexCoord0 に格納されている頂点のテクスチャ座標 (uv 座標) にテクスチャ座標の変換行列をかけたものを,ラスタライザに送っています.<br><br>ラスタライザはこれを補間しますので,フラグメントシェーダの gl_TexCoord[0] には,頂点のテクスチャ座標の補間値が入っています.それを使ってテクスチャをサンプリングします.<br><br> vec4 color = texture2DProj(texture, gl_TexCoord[0]);<br><br>したがって,たとえばバーテックスシェーダにおいて gl_TexCoord[0] に頂点座標自体を代入することもできます.<br><br> gl_TexCoord[0] = gl_Vertex;<br><br>そうすると,フラグメントシェーダの gl_TexCoord[0] には,そのフラグメント(において見えているポリゴン上の点)のローカル座標上の位置を得ることができます.

ナナ 2020年06月30日 23:20

質問です。xファイルをopenglで解析しているんですが、ループ回数が統一できるようglTexcoord3fを使おうと思いました。glTexcoord3fは使い方をよく知らないのですが、xファイルはテクスチャのインデックスがないので、テクスチャのインデックスを頂点のインデックスで再利用できるかと思いました。初歩的な質問ですが、間違えてるかもしれませんがglTexCoord2f(Texture[2*c[2]+0],Texture[2*c[2]+1]);なので横の値をとりたいのに縦の列を見てきてしてしまうときがあります。いままであまり考えてなかったのですが、行のデータを取り込むことがあるかもです。ひとまず テクスチャを処理するにはどうしたらいいですかc[2]などはインデックスが入っています。しりたいことは、横のデータを取り込むです。わかりにくいと思いますがどうぞよろしくお願いいたします。

ナナ 2020年07月01日 22:01

自己解決しました。Mesh_TextureCoodsの読み方を変えてみたところ、行を数えているのではなく、0.551713-0番目0.158481-1番目-----と数えるとぴったし合いました。ただうまくいってるとはいっていない。<br><br>glTexcoord3fの使い方がわからず、これってどうすればテクスチャを読むのでしょうか。

ナナ 2020年08月27日 15:46

glTexcoordでやりました。/* テクスチャの割り当て */<br> glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,<br> GL_RGBA, GL_UNSIGNED_BYTE, texture);の自分の構文が間違えていてテクスチャがでていないことに気がつきました<br>。<br>

ナナ 2020年09月02日 11:37

>2020年07月01日 22:01<br>>自己解決しました。Mesh_TextureCoodsの読み方を変えてみたところ、行を数えているのではなく、0.551713-0番目0.158481-1番目-----と数えるとぴっ<br>>たし合いました。ただうまくいってるとはいっていない。<br><br>>glTexcoord3fの使い方がわからず、これってどうすればテクスチャを読むのでしょうか。<br><br>gltexcoord2fで0.551713,0.158481を1行で見るであっていました。(コメントがじゃまになっていたら上のコメントの削除お願いします)

サイチ 2022年12月15日 11:40

glutSolidTeapot(1.0);のところを glutSolidTorus(0.5, 1.0, 100, 400);に変更すると、テクスチャ<br>の貼り付けがされていないトーラスが表示されます、なぜですか?

とこ 2023年04月05日 11:38

サイチさま、亀レス(死語)で申し訳ありません。glutSolidTorus() には、残念ながらテクスチャ座標が設定されていません。


編集 «第2回 Gouraud シェーディングと Phong シ.. 最新 第4回 GLSL によるバンプマッピング»