ディスプレースメントマッピング

レンダリング時にフラグメントシェーダにおいてテクスチャに格納された法線情報を参照してライティングを行うのがバンプマッピングである. バンプマッピングでは表面の細かな凸凹を少ないポリゴン数でも表現可能であるが, 一方,実際にジオメトリが凸凹になっているわけではないので輪郭などは基のポリゴンのままである. これに対して,テクスチャに格納されたハイトフィールドを用いて実際にジオメトリを変化させて凸凹を生成するのが ディスプレースメントマッピング(displacement mapping)である.

テッセレーション

ディスプレースメントマッピングを行う場合,まず,描画するポリゴンを細分割し, 生成された新しい頂点をテクスチャの値に応じてポリゴン法線方向に変化させるという処理を行う. これをテッセレーション(tessellation)と呼ぶ. テッセレーションはCPU側でやってもよいのだが, ここではジオメトリシェーダを用いてやってみる. ただし,前述(GLSLによるジオメトリシェーダ#qb36625f参照)のようにジオメトリシェーダの最大出力頂点数の制限があるため (各頂点が8要素を持つとして最大頂点数が128,この頂点数で三角形を生成した場合,一つの三角形の最大分割数は42となる), CPUである程度分割した後,GPUで再度分割するという風にしてみる.

ジオメトリシェーダでテッセレーションを行うために,三角形の分割方法を検討しなければならない. 三角形ポリゴンの分割には様々な方法が考えられるが,まず,三角形を以下の図のように(s,t)でパラメータ化する(parameterize).

tri_parameterize.gif

頂点v0を原点として,v1を(s,t)=(1,0),v2を(0,1)となるようにすると, 任意の(s,t)における座標値v(s,t)=v0+s(v1-v0)+t(v2-v0)が計算できる.

三角形の細分割方法として,ここでは元の三角形ポリゴンと同じ形状に4つに分割する方法をとる. 元の三角形ポリゴンのエッジの中点を結ぶ形で新しいポリゴンを4つ生成する.

tri_subdiv1.gif

上の図で数値は(s,t)のパラメータ値を表す.これをさらに分割すると,

tri_subdiv2.gif

となる.1回分割した状態をレベル1, 2回分割した状態をレベル2,...,L回分割した状態をレベルLと呼ぶ. レベル1の時の辺の分割数は2,レベル2で4,レベルLで2^Lとなる. また,生成される三角形の数はn=4^Lとなる.ジオメトリシェーダの最大出力要素数の制限が1024のとき, 生成できる三角形は42以下なので,レベル2が最大となる. また,色情報がなければレベル3までは分割可能である.

上の図において赤く塗りつぶした領域に注目すると,

  • s=0のエッジとs=0.25の頂点で構成される三角形が4個
  • s=0の頂点とs=0.25のエッジで構成される三角形が3個

で構成されていることが分かる.緑,青の領域についても同様に考えると,

  • s=i/nのエッジとs=(i+1)/nの頂点で構成される三角形が(n-i)個
  • s=i/nの頂点とs=(i+1)/nのエッジで構成される三角形が(n-i-1)個

となる.ここで,i=0,1,...,n-1であり,n=4^Lである. これをコードにすると,

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
    int layer = 1 << level;     
    float s0, s1, t0, t1;
    float ds = 1.0/float(layer);
    float dt = 1.0/float(layer);
    s0 = 0.0; s1 = s0+ds;
    t0 = 0.0; t1 = t0+dt;
 
    int n = layer;
 
    for(int i = 0; i < layer; ++i){            t0 = 0.0; t1 = t0+dt;
        for(int j = 0; j < n; ++j){                            ...
 
            if(j != n-1){
                                ...
            }
 
            t0 += dt; t1 += dt;
        }
 
        s0 += ds; s1 += ds;
        n--;
    }

となる.

シェーダコード

頂点シェーダ,フラグメントシェーダは頂点座標や色をパススルーするだけである.

  • 頂点シェーダ
      1
      2
      3
      4
      5
      6
      7
      8
      9
    
    #version 120
     
    void main(void)
    {
        gl_FrontColor = gl_Color;
     
        gl_Position = gl_Vertex;
        gl_TexCoord[0] = gl_TextureMatrix[0]*gl_MultiTexCoord0;
    }
  • フラグメントシェーダ
      1
      2
      3
      4
      5
      6
    
    #version 120
     
    void main(void)
    {
        gl_FragColor = gl_Color;
    }

ジオメトリシェーダでは新しい三角形ポリゴンを生成して,テクスチャの値に応じてその座標値を変更する.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
#version 120
#extension GL_EXT_geometry_shader4 : enable
 
uniform sampler2D HeightMap;
uniform int SubLevel;
uniform float Height;
 

void SetVertex(float s, float t)
{
    gl_TexCoord[0] = gl_TexCoordIn[0][0]+s*(gl_TexCoordIn[1][0]-gl_TexCoordIn[0][0])+t*(gl_TexCoordIn[2][0]-gl_TexCoordIn[0][0]);
    // gl_FrontColor = gl_FrontColorIn[0]+s*(gl_FrontColorIn[1]-gl_FrontColorIn[0])+t*(gl_FrontColorIn[2]-gl_FrontColorIn[0]);
 
    vec4 pos = gl_PositionIn[0]+s*(gl_PositionIn[1]-gl_PositionIn[0])+t*(gl_PositionIn[2]-gl_PositionIn[0]);
    pos.y = length(texture2D(HeightMap, gl_TexCoord[0].xy).xyz)*Height;
    gl_Position = gl_ModelViewProjectionMatrix*pos;
 
    EmitVertex();
}
 
void main(void)
{
 
    int level = SubLevel;
    int layer = 1 << level;     
        float s0, s1, t0, t1;
    float ds = 1.0/float(layer);
    float dt = 1.0/float(layer);
 
    s0 = 0.0;
    s1 = s0+ds;
 
    t0 = 0.0;
    t1 = t0+dt;
 
    int n;
    n = layer-1;
 
    for(int i = 0; i < layer; ++i){            t0 = 0.0;
        t1 = t0+dt;
        for(int j = 0; j < n; ++j){                SetVertex(s0, t0);
            SetVertex(s1, t0);
            SetVertex(s0, t1);
            EndPrimitive();
 
            SetVertex(s0, t1);
            SetVertex(s1, t0);
            SetVertex(s1, t1);
            EndPrimitive();
 
            t0 += dt;
            t1 += dt;
        }
 
        SetVertex(s0, t0);
        SetVertex(s1, t0);
        SetVertex(s0, t1);
        EndPrimitive();
 
        s0 += ds;
        s1 += ds;
 
        n--;
    }
}

元の三角形はx-z平面上にあり,高さ方向(法線方向)はy軸と平行としている.

SetVertex関数内のgl_FrontColorの行をコメントアウトしなければ新しい頂点の頂点色が線型補間で決定されるが, 生成できるポリゴン数は42に制限される.

シェーダのビルドと呼び出し

CPU側でもテッセレーションのコードを実装して,CPUである程度分割後,さらにGPUで分割して, ディスプレースメントマッピングをしている. CPU側のテッセレーションのコードは以下.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76

void SetVertex(float s, float t, Vec3 v0, Vec3 v1, Vec3 v2, Vec2 tc0, Vec2 tc1, Vec2 tc2)
{
    Vec3 v = v0+s*(v1-v0)+t*(v2-v0);
    Vec2 tc = tc0+s*(tc1-tc0)+t*(tc2-tc0);
    glTexCoord2dv(tc.data);
    glVertex3dv(v.data);
}
 

void DrawTriangle(Vec3 v0, Vec3 v1, Vec3 v2, Vec2 tc0, Vec2 tc1, Vec2 tc2, int level = 0)
{
    if(!level){
                glDisable(GL_LIGHTING);
        glBegin(GL_TRIANGLES);
        glTexCoord2dv(tc0.data); glVertex3dv(v0.data);
        glTexCoord2dv(tc1.data); glVertex3dv(v1.data);
        glTexCoord2dv(tc2.data); glVertex3dv(v2.data);
        glEnd();
    }
 
    int layer = 1 << level;     
        float s0, s1, t0, t1;
    float ds = 1.0/float(layer);
    float dt = 1.0/float(layer);
 
    s0 = 0.0;
    s1 = s0+ds;
 
    t0 = 0.0;
    t1 = t0+dt;
 
    int n;
    n = layer;
 
    for(int i = 0; i < layer; ++i){            t0 = 0.0;
        t1 = t0+dt;
        for(int j = 0; j < n; ++j){                glBegin(GL_TRIANGLES);
            SetVertex(s0, t0, v0, v1, v2, tc0, tc1, tc2);
            SetVertex(s1, t0, v0, v1, v2, tc0, tc1, tc2);
            SetVertex(s0, t1, v0, v1, v2, tc0, tc1, tc2);
            glEnd();
 
            if(j != n-1){
                glBegin(GL_TRIANGLES);
                SetVertex(s0, t1, v0, v1, v2, tc0, tc1, tc2);
                SetVertex(s1, t0, v0, v1, v2, tc0, tc1, tc2);
                SetVertex(s1, t1, v0, v1, v2, tc0, tc1, tc2);
                glEnd();
            }
 
            t0 += dt;
            t1 += dt;
        }
 
        s0 += ds;
        s1 += ds;
 
        n--;
    }
}

GLSLの呼び出しコードは以下.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31

void RenderScene(void)
{
    glColor3f(0.0, 0.0, 1.0);
 
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, g_uTexHeight);
 
        glUseProgram(g_GLSL.Prog);
    glUniform1i(glGetUniformLocation(g_GLSL.Prog, "HeightMap"), 0);
    glUniform1i(glGetUniformLocation(g_GLSL.Prog, "SubLevel"), 2);
    glUniform1f(glGetUniformLocation(g_GLSL.Prog, "Height"), 0.1);
 
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 
    glPushMatrix();
    glTranslatef(-0.5, 0.0, -0.5);
        DrawTriangle(Vec3(0.0, 0.0, 0.0), Vec3(1.0, 0.0, 0.0), Vec3(1.0, 0.0, 1.0), 
                 Vec2(0.0, 0.0), Vec2(1.0, 0.0), Vec2(1.0, 1.0), 2);
    DrawTriangle(Vec3(0.0, 0.0, 0.0), Vec3(1.0, 0.0, 1.0), Vec3(0.0, 0.0, 1.0), 
                 Vec2(0.0, 0.0), Vec2(1.0, 1.0), Vec2(0.0, 1.0), 2);
    glPopMatrix();
 
    glUseProgram(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

g_uTexHeightにはハイトフィールドとなるテクスチャが格納されている.

結果

ハイトフィールドとして以下の画像を用いた.

tex_sphere.png

CPU側で2レベル,GPU側で2レベル,計レベル4まで分割した結果を示す.

disp_map_1_1.jpg disp_map_2_1.jpg

左の画像が通常の結果,右はGLSLを無効にした場合である. ハイトフィールド画像の暗いところほど凹んでいる.

disp_map_3_1.jpg

この画像はジオメトリシェーダでの分割レベルを3にした場合である. 最大出力要素数の制限により出力されていない三角形があるのが見て取れる.

ソースコード

Visual Studio 2008用のソースコードを以下に置く.ビルド・実行にはGLUT,GLEW,OpenCVが必要である.


添付ファイル: filetri_subdiv1.gif 1329件 [詳細] filetri_subdiv2.gif 1434件 [詳細] filetri_parameterize.gif 1321件 [詳細] fileglsl_displacementmap.zip 1202件 [詳細] filedisp_map_2.jpg 711件 [詳細] filedisp_map_1_1.jpg 1434件 [詳細] filedisp_map_3.jpg 674件 [詳細] filedisp_map_3_1.jpg 1434件 [詳細] filedisp_map_2_1.jpg 1452件 [詳細] filetex_sphere.png 1373件 [詳細] filedisp_map_1.jpg 694件 [詳細]

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2022-11-30 (水) 13:48:07