[CG実習 > テクスチャマッピング]

テクスチャマッピング

物体の表面には,さまざまな模様がついていることが少なくありません. 模様が見えるのは,表面の各部分で光の反射が均一でないためです. そのような物体を描くにはどうすればよいでしょうか. 一つの方法として,たとえば,表面を小さなポリゴンに分割して, それぞれのポリゴンのマテリアルを適宜設定することが考えられます. しかし,このような方法は大変面倒です.

別の方法として,模様を画像として表現して,それを物体表面に貼りつけることが考えられます. このような手法をテクスチャマッピング(texture mapping)とよびます. ここでは,OpenGLでテクスチャマッピングを行う方法について説明します.

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

テクスチャマッピングは,テクスチャ(画像)を物体の表面に貼りつける手法です. 物体に画像を「貼りつける」ことは,物体の面を構成する各ピクセルに関して, 対応するテクスチャの点を決めることで実現できます. 次に挙げる例では,テクスチャを平行四辺形の平面物体に貼りつけています.

テクスチャ座標

テクスチャには画像を用います. OpenGLでテクスチャ内の点は(s,t)で指定します. ここでs,tは,それぞれ0.0から1.0の間の値を取ります. テクスチャはピクセルの集まりではなく1辺が長さ1の正方形として扱われます. 画像は正方形である必要はありません. 正方形でない場合,s方向とt方向では単位長が異なることになります. なおテクスチャを構成するピクセルのことをとくにテクセル(texel)といいます.

OpenGLのテクスチャには,1次元のテクスチャ,2次元のテクスチャ,3次元のテクスチャがありますが,ここでは,一般的に用いられる2次元のテクスチャだけを扱います.

サンプルプログラム(1)

まずテクスチャを貼る簡単なサンプルプログラム(texture2d.rb)を示します. このプログラムは,次のように画像を指定して実行します.

$ ruby texture2d.rb 画像ファイル名

実行すると,平行四辺形に画像が貼り付けられたウインドウが現われます.


001  # coding: utf-8
002  require 'opengl'
003  require 'glu'
004  require 'glut'
005  require 'cg/gfc'
006  
007  # ウインドウサイズ
008  WSIZE  = 600
009  WIDTH  = WSIZE
010  HEIGHT = WSIZE
011  
012  ### 描画コールバック ########
013  display = Proc.new {
014    w = 0.5; h = 0.7; a = 0.2; b = 0.1
015    GL.Clear(GL::COLOR_BUFFER_BIT)
016    # テクスチャを貼った平行四辺形の描画
017    GL.Begin(GL::POLYGON)
018      GL.TexCoord(0.0,0.0) # 頂点v0(-w+a,h-b)  ⇔ テクスチャ座標(0,0)
019      GL.Vertex(-w+a,h-b)
020      GL.TexCoord(0.0,1.0) # 頂点v1(-w-a,-h-b) ⇔ テクスチャ座標(0,1)
021      GL.Vertex(-w-a,-h-b)
022      GL.TexCoord(1.0,1.0) # 頂点v2(w-a,-h+b)  ⇔ テクスチャ座標(1,1)
023      GL.Vertex(w-a,-h+b)
024      GL.TexCoord(1.0,0.0) # 頂点v3(w+a,h+b)   ⇔ テクスチャ座標(1,0)
025      GL.Vertex(w+a,h+b)
026    GL.End()
027    GL.Flush()
028  }
029  
030  ### キーボード入力コールバック ########
031  keyboard = Proc.new { |key,x,y| 
032    exit 0 if key == 'q' or key.ord == 0x1b # [q],[ESC]で終了
033  }
034  
035  ### ウインドウサイズ変更コールバック ########
036  reshape = Proc.new { |w,h|
037    GL.Viewport(0,0,w,h)
038    GL.LoadIdentity()
039    x=w.to_f/WIDTH
040    y=h.to_f/HEIGHT
041    GLU.Ortho2D(-x,x,-y,y)
042    GLUT.PostRedisplay()
043  }
044  
045  ### テクスチャの設定 ########
046  def setup_texture(iname)
047    # 2次元テクスチャを使用可能にする.
048    GL.Enable(GL::TEXTURE_2D)
049  
050    # テクスチャに使用する画像の読み込み
051    g = Gfc.load(iname)
052    width,height = g.size
053  
054    # テクスチャ画像生成
055    #  GL::TEXTURE_2D    テクスチャの種別(target)
056    #  0                 テクスチャレベル(level)
057    #  GL::RGB           テクスチャ画像のフォーマット(iformat)
058    #  width,height      テクスチャ画像のサイズ(width,height)
059    #  0                 (テクセル)縁の幅(border)
060    #  GL::RGB           構築するテクセルのフォーマット(format)
061    #  GL::UNSIGNED_BYTE テクセルデータタイプ(type)
062    #  g0.get_bytes      画像データオブジェクト(texels)
063    #
064    #  typeとformatであるwidth×heightのtexelsの2次元テクスチャを構築する
065    #  画像データは,iformatの形式であると仮定する.
066    GL.TexImage2D(GL::TEXTURE_2D,0,GL::RGB,width,height,0,GL::RGB,
067  		GL::UNSIGNED_BYTE,g.get_bytes)
068  
069    # テクスチャ座標に対するパラメタ指定
070    #  GL::CLAMP  0.0以下が指定されたら0.0,1.0以上が指定されたら1.0とする
071    #  GL::REPEAT テクスチャがくり返しタイルされているとして座標を決める
072    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_S,GL::REPEAT)
073    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_T,GL::REPEAT)
074  
075    # ピクセルに対応するテクスチャの値の決定
076    # ピクセルの中心点をテクスチャ空間の点に移して,そのピクセルに対応する
077    # テクスチャの値を決める
078    #  GL::NEAREST  点にもっとも近いテクセルの値を使う
079    #  GL::LINEAR   点の周囲のテクセル値の平均を使う
080    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MAG_FILTER,GL::NEAREST)
081    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MIN_FILTER,GL::NEAREST)
082  end
083  
084  ##############################################
085  # main
086  ##############################################
087  
088  if ARGV.size == 0
089    STDERR.puts 'テクスチャ画像が指定されていません'
090    exit 1
091  end
092  
093  GLUT.Init()
094  GLUT.InitWindowSize(WIDTH,HEIGHT) 
095  GLUT.CreateWindow('Textured Quadrilateral')
096  GLUT.DisplayFunc(display)  
097  GLUT.KeyboardFunc(keyboard)
098  GLUT.ReshapeFunc(reshape)  
099  setup_texture(ARGV.shift)
100  GLUT.MainLoop()

テクスチャ画像の構築

OpenGLで画像をテクスチャとして利用するためには,GL.TexImage2Dを利用します.


054    # テクスチャ画像生成
055    #  GL::TEXTURE_2D    テクスチャの種別(target)
056    #  0                 テクスチャレベル(level)
057    #  GL::RGB           テクスチャ画像のフォーマット(iformat)
058    #  width,height      テクスチャ画像のサイズ(width,height)
059    #  0                 (テクセル)縁の幅(border)
060    #  GL::RGB           構築するテクセルのフォーマット(format)
061    #  GL::UNSIGNED_BYTE テクセルデータタイプ(type)
062    #  g0.get_bytes      画像データオブジェクト(texels)
063    #
064    #  typeとformatであるwidth×heightのtexelsの2次元テクスチャを構築する
065    #  画像データは,iformatの形式であると仮定する.
066    GL.TexImage2D(GL::TEXTURE_2D,0,GL::RGB,width,height,0,GL::RGB,
067  		GL::UNSIGNED_BYTE,g.get_bytes)

このメソッドは,合計9個の引数をとります.

テクスチャ座標の指定

すでに述べた通り,物体の面にテクスチャを貼りつけるには,物体の面を構成する各ピクセルについて,それに対応するテクスチャの座標を決める必要があります. この対応関係を直接に決めるには,GL.TexCoordで,対象となる物体の各頂点のテクスチャ座標を指定します. 頂点以外については補間で決められます. 座標を与える際には,テクスチャ座標系と物体の座標系との関係に注意が必要です.


016    # テクスチャを貼った平行四辺形の描画
017    GL.Begin(GL::POLYGON)
018      GL.TexCoord(0.0,0.0) # 頂点v0(-w+a,h-b)  ⇔ テクスチャ座標(0,0)
019      GL.Vertex(-w+a,h-b)
020      GL.TexCoord(0.0,1.0) # 頂点v1(-w-a,-h-b) ⇔ テクスチャ座標(0,1)
021      GL.Vertex(-w-a,-h-b)
022      GL.TexCoord(1.0,1.0) # 頂点v2(w-a,-h+b)  ⇔ テクスチャ座標(1,1)
023      GL.Vertex(w-a,-h+b)
024      GL.TexCoord(1.0,0.0) # 頂点v3(w+a,h+b)   ⇔ テクスチャ座標(1,0)
025      GL.Vertex(w+a,h+b)
026    GL.End()

テクスチャパラメタ

テクスチャマッピングに関するパラメタに次の二つがあります.


069    # テクスチャ座標に対するパラメタ指定
070    #  GL::CLAMP  0.0以下が指定されたら0.0,1.0以上が指定されたら1.0とする
071    #  GL::REPEAT テクスチャがくり返しタイルされているとして座標を決める
072    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_S,GL::REPEAT)
073    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_T,GL::REPEAT)
074  
075    # ピクセルに対応するテクスチャの値の決定
076    # ピクセルの中心点をテクスチャ空間の点に移して,そのピクセルに対応する
077    # テクスチャの値を決める
078    #  GL::NEAREST  点にもっとも近いテクセルの値を使う
079    #  GL::LINEAR   点の周囲のテクセル値の平均を使う
080    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MAG_FILTER,GL::NEAREST)
081    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MIN_FILTER,GL::NEAREST)

テクスチャ座標パラメタ

テクスチャ座標は0.0〜1.0の範囲で指定する必要があります. この範囲外の値が指定された場合にどのように処理するかについて, いくつかの選択肢が提供されています. ここではCLAMPとREPEATを紹介します.

テクスチャ値パラメタ

テクスチャを貼りつける面の形状により,テクスチャのテクセルと描画するピクセルとの関係はさまざまに変わります. いくつかのピクセルが同一のテクセルに対応する場合もあれば, 一つのピクセルにいくつかのテクセルが対応する場合もあります. このとき,テクセルを拡大したり縮小したりすることが必要になります. 各ピクセルに対するテクスチャの値を決める方法は2通りあります. これらは,テクセルの拡大(MAGNIFICATION),縮小(MINIFICATION)のそれぞれの場合について独立に指定できます.

サンプルプログラム(2)

次のプログラム(texture_teapot.rb)では,指定した画像をテクスチャとして,ティーポットに貼りつけます. このプログラムでは,面に貼りつけるテクスチャ座標をどのように決めるかが主なポイントになります. プログラムは次のように実行します.

$ ruby texture_teapot.rb 画像ファイル名

なお,次のような操作ができます.


001  # coding: utf-8
002  require 'opengl'
003  require 'glu'
004  require 'glut'
005  require 'cg/camera'
006  require 'cg/light'
007  require 'cg/gfc'
008  
009  WSIZE  = 800       # ウインドウサイズ
010  INIT_THETA =  0.0  # カメラの初期位置
011  INIT_PHI   =  0.0  # カメラの初期位置
012  INIT_DIST  = 10.0  # カメラの原点からの距離の初期値
013  DT = 3             # 回転角単位
014  DZ = 0.125         # カメラの原点からの距離変更の単位
015  LIGHT0_POS = [45.0,90.0]   # 光源0の位置
016  LIGHT1_POS = [135.0,-90.0] # 光源1の位置
017  
018  ## カメラ
019  __camera = Camera.new(INIT_THETA,INIT_PHI,INIT_DIST)
020  
021  ### 描画コールバック ########
022  display = Proc.new {
023    GL.Clear(GL::COLOR_BUFFER_BIT|GL::DEPTH_BUFFER_BIT)
024    LightSource.deploy(0,LIGHT0_POS,LightSource::POLAR)
025    LightSource.deploy(1,LIGHT1_POS,LightSource::POLAR)
026  
027    GL.Material(GL::FRONT,GL::AMBIENT,  [0.4,0.4,0.4])
028    GL.Material(GL::FRONT,GL::DIFFUSE,  [1.0,1.0,1.0])
029    GL.Material(GL::FRONT,GL::SPECULAR, [0.1,0.1,0.1])
030    GL.Material(GL::FRONT,GL::SHININESS,64.0)
031  
032    # テクスチャ座標を自動生成するための式の設定
033    #  通常 s = ax + by + cz + d
034    #  GL::OBJECT_PLANE,[a,b,c,d]  物体座標系を基準とした式で指定
035    GL.TexGen(GL::S,GL::OBJECT_PLANE,[1.0,0.0,0.0,0.0])
036    GL.TexGen(GL::T,GL::OBJECT_PLANE,[0.0,0.0,-1.0,0.0])
037  
038    GLUT.SolidTeapot(2.0)
039    GLUT.SwapBuffers()
040  }
041  
042  #### キーボード入力コールバック ########
043  keyboard = Proc.new { |key,x,y| 
044    case key
045    # [j],[J]: 経度の正方向/逆方向にカメラを移動する
046    when 'j','J'
047      __camera.move((key == 'j') ? DT : -DT,0,0)
048    # [k],[K]: 緯度の正方向/逆方向にカメラを移動する
049    when 'k','K'
050      __camera.move(0,(key == 'k') ? DT : -DT,0)
051    # [l],[L]: 
052    when 'l','L'
053      __camera.move(0,0,(key == 'l') ? DT : -DT)
054    # [z],[Z]: zoom in/out
055    when 'z','Z'
056      __camera.zoom((key == 'z') ? DZ : -DZ)
057    # [r]: 初期状態に戻す
058    when 'r'
059      __camera.reset
060    # [q],[ESC]: 終了する
061    when 'q',"\x1b"
062      exit 0
063    end
064    
065    GLUT.PostRedisplay()
066  }
067  
068  #### ウインドウサイズ変更コールバック ########
069  reshape = Proc.new { |w,h|
070    GL.Viewport(0,0,w,h)
071    __camera.projection(w,h) 
072    GLUT.PostRedisplay()
073  }
074  
075  ### テクスチャの設定 ########
076  def setup_texture(iname)
077    ## テクスチャ画像の読み込み
078    g = Gfc.load(iname)
079    width,height = g.size
080  
081    ## テクスチャ生成
082    GL.TexImage2D(GL::TEXTURE_2D,0,GL::RGB,width,height,0,GL::RGB,
083  		GL::UNSIGNED_BYTE,g.get_bytes)
084  
085    ## テクスチャ座標に対するパラメタ指定
086    #GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_S,GL::CLAMP)
087    #GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_T,GL::CLAMP)
088    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_S,GL::REPEAT)
089    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_T,GL::REPEAT)
090  
091    ## ピクセルに対応するテクスチャの値の決定
092    
093    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MAG_FILTER,GL::NEAREST)
094    GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MIN_FILTER,GL::NEAREST)
095    #GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MAG_FILTER,GL::LINEAR)
096    #GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MIN_FILTER,GL::LINEAR)
097  
098    ## テクスチャの環境(表示方法)の指定
099    # GL::MODULATE テクスチャ色と物体色を掛け合わせて表示する
100    # GL::REPLACE  テクスチャ色をそのまま表示する
101    GL.TexEnv(GL::TEXTURE_ENV,GL::TEXTURE_ENV_MODE,GL::MODULATE)
102    #GL.TexEnv(GL::TEXTURE_ENV,GL::TEXTURE_ENV_MODE,GL::REPLACE)
103  
104    ## s方向についてテクスチャ座標を自動生成する
105    ## (※ t方向についても同様にGL::Tでテクスチャ座標の自動生成ができる)
106    # GL.TexGen(GL::S,GL::TEXTURE_GEN_MODE,func)
107    # func: テクスチャ座標自動生成の方法の指定
108    # GL::OBJECT_LINEAR 物体座標系を基準とした1次式を用いる
109    GL.TexGen(GL::S,GL::TEXTURE_GEN_MODE,GL::OBJECT_LINEAR)
110    GL.TexGen(GL::T,GL::TEXTURE_GEN_MODE,GL::OBJECT_LINEAR)
111  
112    GL.Enable(GL::TEXTURE_2D)    # 2次元テクスチャを使用可能にする
113    GL.Enable(GL::TEXTURE_GEN_S) # s方向のテクスチャ座標の自動生成を有効にする
114    GL.Enable(GL::TEXTURE_GEN_T) # t方向のテクスチャ座標の自動生成を有効にする
115  end
116  
117  #### シェーディングの初期化 ########
118  def init_shading
119    LightSource.init(2) # 光源x2を用意する.  
120    LightSource.switch(0,LightSource::ON) # 0 -> ON
121    LightSource.switch(1,LightSource::ON) # 1 -> ON
122  end
123  
124  ##### main ##############################################
125  if ARGV.size == 0
126    STDERR.puts 'テクスチャ画像が指定されていません'
127    exit 1
128  end
129  
130  GLUT.Init()
131  GLUT.InitDisplayMode(GLUT::RGB|GLUT::DOUBLE|GLUT::DEPTH)
132  GLUT.InitWindowSize(WSIZE,WSIZE) 
133  GLUT.CreateWindow('Textured Teapot')
134  GLUT.DisplayFunc(display)
135  GLUT.KeyboardFunc(keyboard)
136  GLUT.ReshapeFunc(reshape)
137  GL.ClearColor(0.4,0.4,1.0,0.0)
138  GL.Enable(GL::DEPTH_TEST)
139  setup_texture(ARGV.shift) # テクスチャの設定
140  init_shading()            # シェーディングの設定
141  __camera.set              # カメラを配置する
142  GLUT.MainLoop()

テクスチャの環境(表示方法)の設定

テクスチャを貼りつけたときの物体の表面の色の決め方には,いくつかの方法があります. これは,GL.TexEnv(GL::TEXTURE_ENV,GL::TEXTURE_ENV_MODE,mode)のように指定します. 単純にテクスチャ値に置き換えるには,modeにGL::REPLACEを用います. GL::MODULATEにすることで,もともと物体に指定した色とテクスチャ値を掛け合わせることもできます. さらに,GL::DECALGL::BLENDにより, 透明度(α値)を利用して,テクスチャ値と物体の色を混合することもできます.


098    ## テクスチャの環境(表示方法)の指定
099    # GL::MODULATE テクスチャ色と物体色を掛け合わせて表示する
100    # GL::REPLACE  テクスチャ色をそのまま表示する
101    GL.TexEnv(GL::TEXTURE_ENV,GL::TEXTURE_ENV_MODE,GL::MODULATE)

テクスチャ座標の自動生成

物体の面を構成するピクセルとテクスチャの対応関係を決めるのにGL.TexCoordで直接テクスチャ座標を指定する代わりに,テクスチャ座標を決める式を与えることもできます. これを行うには,GL.TexGenを利用します.

具体的には,たとえば,テクスチャ座標の値を物体を基準とした座標に関する1次式で指定することができます(他にも指定方法はあります). 式はs方向(GL::S)とt方向(GL::T)について独立に設定することができます. この場合の指定方法のついては次のコードを見てください.


032    # テクスチャ座標を自動生成するための式の設定
033    #  通常 s = ax + by + cz + d
034    #  GL::OBJECT_PLANE,[a,b,c,d]  物体座標系を基準とした式で指定
035    GL.TexGen(GL::S,GL::OBJECT_PLANE,[1.0,0.0,0.0,0.0])
036    GL.TexGen(GL::T,GL::OBJECT_PLANE,[0.0,0.0,-1.0,0.0])
:
104    ## s方向についてテクスチャ座標を自動生成する
105    ## (※ t方向についても同様にGL::Tでテクスチャ座標の自動生成ができる)
106    # GL.TexGen(GL::S,GL::TEXTURE_GEN_MODE,func)
107    # func: テクスチャ座標自動生成の方法の指定
108    # GL::OBJECT_LINEAR 物体座標系を基準とした1次式を用いる
109    GL.TexGen(GL::S,GL::TEXTURE_GEN_MODE,GL::OBJECT_LINEAR)
110    GL.TexGen(GL::T,GL::TEXTURE_GEN_MODE,GL::OBJECT_LINEAR)

テクスチャ処理のON/OFF

陰影処理Zバッファなどと同様にテクスチャ処理についても,設定を行うとともに,GL.Enableにより,明示的に処理を有効にする必要があります. 逆に無効化するにはGL.Disableを実行します.


112    GL.Enable(GL::TEXTURE_2D)    # 2次元テクスチャを使用可能にする
113    GL.Enable(GL::TEXTURE_GEN_S) # s方向のテクスチャ座標の自動生成を有効にする
114    GL.Enable(GL::TEXTURE_GEN_T) # t方向のテクスチャ座標の自動生成を有効にする

サンプルプログラム(3)

次のプログラム(texture_cube.rb)では, 立方体の各面に別々のテクスチャを貼りつけて表示します. 実行時には,6枚の画像を指定する必要があります. このプログラムでは,複数のテクスチャを内部でどのように扱うかがポイントになります. プログラムは次のように実行します.

$ ruby texture_cube.rb 画像ファイル名1 画像ファイル名2 ... 画像ファイル名6

なお,次のような操作ができます.


001  # coding: utf-8
002  require 'opengl'
003  require 'glu'
004  require 'glut'
005  require 'cg/camera'
006  require 'cg/light'
007  require 'cg/colored_cube'
008  require 'cg/gfc'
009  
010  WSIZE  = 800       # ウインドウサイズ
011  INIT_THETA =  0.0  # カメラの初期位置
012  INIT_PHI   =  0.0  # カメラの初期位置
013  INIT_DIST  = 10.0  # カメラの原点からの距離の初期値
014  DT = 3             # 回転角単位
015  DZ = 0.125         # カメラの原点からの距離変更の単位
016  LIGHT0_POS = [45.0,90.0]   # 光源0の位置
017  LIGHT1_POS = [135.0,-90.0] # 光源1の位置
018  
019  SZ=2.0 # 立方体のサイズ
020  # 立方体(頂点=(±SZ,0,0),(0,±SZ,0),(0,0,±SZ))
021  CUBE = ColoredCube.new(SZ,[0,0,0])
022  N_FACES = 6 # 面の枚数
023  
024  ## 状態変数
025  __camera = Camera.new(INIT_THETA,INIT_PHI,INIT_DIST)
026  __texname = nil # テクスチャオブジェクトを管理するための変数
027  __texture_on = true
028  
029  # s方向の各面に対するテクスチャ座標自動生成式
030  TEXPLANE_S = [           # 対象とする立方体の面の式
031    [ 0.5/SZ,0.0,0.0,0.5], # z == 1 
032    [-0.5/SZ,0.0,0.0,0.5], # z == -1
033    [0.0,0.0,-0.5/SZ,0.5], # x == 1 
034    [0.0,0.0, 0.5/SZ,0.5], # x == -1
035    [ 0.5/SZ,0.0,0.0,0.5], # y == 1 
036    [ 0.5/SZ,0.0,0.0,0.5]  # y == -1
037  ]
038  
039  # t方向の各面に対するテクスチャ座標自動生成式
040  TEXPLANE_T = [           # 対象とする立方体の面の式
041    [0.0,-0.5/SZ,0.0,0.5], # z == 1 
042    [0.0,-0.5/SZ,0.0,0.5], # z == -1
043    [0.0,-0.5/SZ,0.0,0.5], # x == 1 
044    [0.0,-0.5/SZ,0.0,0.5], # x == -1
045    [0.0,0.0, 0.5/SZ,0.5], # y == 1 
046    [0.0,0.0,-0.5/SZ,0.5]  # y == -1
047  ]
048  
049  #### 描画コールバック ########
050  display = Proc.new {
051    GL.Clear(GL::COLOR_BUFFER_BIT|GL::DEPTH_BUFFER_BIT)
052    LightSource.deploy(0,LIGHT0_POS,LightSource::POLAR)
053    LightSource.deploy(1,LIGHT1_POS,LightSource::POLAR)
054  
055    draw_cube(__texname)
056    GLUT.SwapBuffers()
057  }
058  
059  #### 立方体の描画 ########
060  def draw_cube(texname)
061    # 各面を描く
062    N_FACES.times do |i|
063      # i番目のテクスチャオブジェクトを利用する
064      # (i番目のテクスチャ画像とテクスチャパラメタを使用する)
065      GL.BindTexture(GL::TEXTURE_2D,texname[i])
066      # テクスチャ座標の自動生成
067      GL.TexGen(GL::S,GL::OBJECT_PLANE,TEXPLANE_S[i])
068      GL.TexGen(GL::T,GL::OBJECT_PLANE,TEXPLANE_T[i])
069      # i番めの面の描画
070      CUBE.draw_face(i) 
071    end
072  end
073  
074  #### キーボード入力コールバック ########
075  keyboard = Proc.new { |key,x,y| 
076    case key
077    # [j],[J]: 経度の正方向/逆方向にカメラを移動する
078    when 'j','J'
079      __camera.move((key == 'j') ? DT : -DT,0,0)
080    # [k],[K]: 緯度の正方向/逆方向にカメラを移動する
081    when 'k','K'
082      __camera.move(0,(key == 'k') ? DT : -DT,0)
083    # [l],[L]: 
084    when 'l','L'
085      __camera.move(0,0,(key == 'l') ? DT : -DT)
086    # [z],[Z]: zoom in/out
087    when 'z','Z'
088      __camera.zoom((key == 'z') ? DZ : -DZ)
089    when 't','T'
090      if __texture_on
091        GL.Disable(GL::TEXTURE_2D)  # テクスチャマッピングOFF
092        GL.Disable(GL::LIGHTING)    # シェーディングOFF
093      else
094        GL.Enable(GL::TEXTURE_2D)   # テクスチャマッピングON
095        GL.Enable(GL::LIGHTING)     # シェーディングON
096      end
097      __texture_on = (not __texture_on)
098    # [r]: 初期状態に戻す
099    when 'r'
100      __camera.reset
101    # [q],[ESC]: 終了する
102    when 'q', "\x1b"
103      exit 0
104    end
105  
106    GLUT.PostRedisplay()
107  }
108  
109  #### ウインドウサイズ変更コールバック ########
110  reshape = Proc.new { |w,h|
111    GL.Viewport(0,0,w,h)
112    __camera.projection(w,h) 
113    GLUT.PostRedisplay()
114  }
115  
116  ### テクスチャの設定 ########
117  def setup_texture(inames)
118    teximages=create_texture_images(inames) # テクスチャ画像の配列を構築する
119    texname = GL.GenTextures(N_FACES)       # テクスチャオブジェクトのIDの確保(6面分)
120  
121    # 立方体の各面に貼るテクスチャの設定
122    N_FACES.times do |i|
123      GL.BindTexture(GL::TEXTURE_2D,texname[i])
124  
125      ## テクスチャ画像生成
126      img,width,height = teximages[i]
127      GL.TexImage2D(GL::TEXTURE_2D,0,GL::RGB,width,height,0,GL::RGB,
128  		  GL::UNSIGNED_BYTE,img)
129  
130      ## テクスチャ座標に対するパラメタ指定
131      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_S,GL::REPEAT)
132      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_T,GL::REPEAT)
133  
134      ## ピクセルに対応するテクスチャの値の決定
135      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MAG_FILTER,GL::NEAREST)
136      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MIN_FILTER,GL::NEAREST)
137    end
138  
139    ## テクスチャの環境(表示方法)の指定(GL::REPLACE;テクスチャ値をそのまま使う)
140    GL.TexEnv(GL::TEXTURE_ENV,GL::TEXTURE_ENV_MODE,GL::REPLACE) 
141  
142    ## テクスチャ座標自動生成方法の指定→オブジェクトを基準とする
143    GL.TexGen(GL::S,GL::TEXTURE_GEN_MODE,GL::OBJECT_LINEAR)
144    GL.TexGen(GL::T,GL::TEXTURE_GEN_MODE,GL::OBJECT_LINEAR)
145  
146    GL.Enable(GL::TEXTURE_2D)    # 2次元テクスチャを使用可能にする
147    GL.Enable(GL::TEXTURE_GEN_S) # s方向のテクスチャ座標の自動生成を有効にする
148    GL.Enable(GL::TEXTURE_GEN_T) # t方向のテクスチャ座標の自動生成を有効にする
149  
150    texname
151  end
152  
153  ## テクスチャ画像の読み込み
154  # inames: テクスチャ画像ファイル名の配列
155  # 読みこんだ画像のデータ配列を返す[[pixels_0,width_0,height_0],...]
156  def create_texture_images(inames)
157    images=[]
158    inames.each do |iname|
159      g = Gfc.load(iname)
160      width,height = g.size
161      images.push([g.get_bytes,width,height])
162    end
163    images
164  end
165  
166  #### シェーディングの初期化 ########
167  def init_shading
168    LightSource.init(2) # 光源x2を用意する.  
169    LightSource.switch(0,LightSource::ON) # 0 -> ON
170    LightSource.switch(1,LightSource::ON) # 1 -> ON
171  end
172  
173  ##### main ##############################################
174  if ARGV.size < N_FACES
175    STDERR.puts "テクスチャ画像を#{N_FACES}枚指定して下さい"
176    exit 1
177  end
178  # 画像ファイルの配列を作る
179  ifiles=ARGV[0,N_FACES]
180  
181  GLUT.Init()
182  GLUT.InitDisplayMode(GLUT::RGB|GLUT::DOUBLE|GLUT::DEPTH)
183  GLUT.InitWindowSize(WSIZE,WSIZE) 
184  GLUT.CreateWindow('Fully Textured Cube')
185  GLUT.DisplayFunc(display)        
186  GLUT.KeyboardFunc(keyboard)      
187  GLUT.ReshapeFunc(reshape)
188  GL.ClearColor(0.4,0.4,1.0,0.0)   
189  GL.Enable(GL::DEPTH_TEST)
190  __texname=setup_texture(ifiles) # テクスチャの構築
191  init_shading()                  # シェーディングの設定
192  __camera.set                    # カメラを配置する
193  GLUT.MainLoop()

テクスチャオブジェクト

OpenGLでのテクスチャマッピング処理は,「現在のテクスチャ」を対象に行われます. これは,色,マテリアル,モデル・ビュー変換行列などが「現在のもの」を使って処理されるのと,まったく同様です. したがって,複数のテクスチャを使い分けるためには,その都度テクスチャを切り替える必要があることになります. ところが,テクスチャを扱うには,大量なデータの処理が必要となりえますので,毎回ゼロからテクスチャを構築するのは,あまり効率的ではありません.

そこで,複数のテクスチャを使い分ける場合には,テクスチャオブジェクトを利用するのが効果的です. テクスチャオブジェクトは,テクスチャ画像テクスチャパラメタをまとめて保持しておくための仕組みを提供します. 予めテクスチャオブジェクトを構築しておけば,テクスチャの切り替えは,「現在のテクスチャ」に既存のテクスチャオブジェクトを対応させるだけの操作で実現できるようになります. ただし,その効果は,OpenGLでテクスチャ用に使用できるメモリの量に依存します. メモリが十分に利用できない場合,効果は限定的なものとなります.

テクスチャオブジェクトの生成と管理

テクスチャオブジェクトはGL.GenTextures(n)で確保します. これによりn個(n>0)のテクスチャオブジェクトのIDが確保されます. これによりオブジェクトを指し示すIDの配列が返されます. テクスチャオブジェクトは,このIDを利用して管理することになります.


116    texname = GL.GenTextures(6)             # テクスチャオブジェクトのIDの確保(6面分)

テクスチャオブジェクトの設定

テクスチャオブジェクトのデータ,すなわちテクスチャ画像テクスチャパラメタ は,(データ未設定の)テクスチャオブジェクトを「現在のテクスチャ」にしておいてから設定します. 「現在のテクスチャ」をテクスチャオブジェクトと対応づけるには,GL.BindTextureを実行します. 引数としてテクスチャの種別(GL::TEXTURE_2D)とテクスチャオブジェクトのIDを指定します.

なお,テクスチャオブジェクトのデータは,テクスチャ画像テクスチャパラメタであり,テクスチャの環境,あるいはテクスチャ座標自動生成のデータは,オブジェクトでは保持されないことに注意して下さい


121    # 立方体の各面に貼るテクスチャの設定
122    N_FACES.times do |i|
123      GL.BindTexture(GL::TEXTURE_2D,texname[i])
124  
125      ## テクスチャ画像生成
126      img,width,height = teximages[i]
127      GL.TexImage2D(GL::TEXTURE_2D,0,GL::RGB,width,height,0,GL::RGB,
128  		  GL::UNSIGNED_BYTE,img)
129  
130      ## テクスチャ座標に対するパラメタ指定
131      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_S,GL::REPEAT)
132      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_WRAP_T,GL::REPEAT)
133  
134      ## ピクセルに対応するテクスチャの値の決定
135      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MAG_FILTER,GL::NEAREST)
136      GL.TexParameter(GL::TEXTURE_2D,GL::TEXTURE_MIN_FILTER,GL::NEAREST)
137    end

テクスチャオブジェクトの切り替え

テクスチャオブジェクトを実際に「現在のテクスチャ」として使うには,オブジェクトのデータを設定した後に,GL.BindTextureを実行します.


059  #### 立方体の描画 ########
060  def draw_cube(texname)
061    # 各面を描く
062    N_FACES.times do |i|
063      # i番目のテクスチャオブジェクトを利用する
064      # (i番目のテクスチャ画像とテクスチャパラメタを使用する)
065      GL.BindTexture(GL::TEXTURE_2D,texname[i])
066      # テクスチャ座標の自動生成
067      GL.TexGen(GL::S,GL::OBJECT_PLANE,TEXPLANE_S[i])
068      GL.TexGen(GL::T,GL::OBJECT_PLANE,TEXPLANE_T[i])
069      # i番めの面の描画
070      CUBE.draw_face(i) 
071    end
072  end

[CG実習 > テクスチャマッピング]