GPU Gems - Chapter 16に基づき, GLSLでシャドウマッピングと同様に光源からのデプスマップを使って不透明物体のレンダリングを行う.



不透明物体のレンダリングについて

完全に透明な物体と異なり,不透明な物体はその内部において光を散乱,吸収する. そのため,その表面色は光が物体内をどのような経路で進んだかに依存する. ここでは単純に物体内での吸収のみを考え(つまり,他の経路を通った光の散乱は考えない), また,物体内の特性は一定であるとする. そうすると,ある表面点での色は光源からその点に至るまでの経路における物体内深度から計算することができる. 下の図に置いて,sSUB{1};,sSUB{2};はそれぞれの点における物体内深度である.

translucent.jpg

この図を見てわかるように物体内深度はGLSLでシャドウマッピングの時と同様に, 光源を視点として作成したデプスマップから求めることができる. 詳しくはGPU Gems - Chapter 16を参照.

光源からの物体内深度の算出

基本的にはシャドウマップを生成するサイトおなじなのでGLSLでシャドウマッピングを参照. ただし,光源からのデプスマップはポリゴンの表面で生成する必要がある. 光線にほぼ平行な面では微妙な角度の差でもデプス値が大きく変化する. 境界面近くになると特にこの現象が顕著に現れる. シャドウマップではz-ファイティングを防ぐためにglPolygonOffsetを用いたが, glPolygonOffsetはデプス方向でしか変化させないので,光線にほぼ平行な面ではほとんど効果がない. そこで,GLSLで頂点を法線方向にオフセットさせ, 微少量膨張した状態でデプスマップを生成する.

デプスマップ生成時のGLSLの頂点シェーダは以下.

  1
  2
  3
  4
  5
  6
  7
uniform float offset;
 
void main(void)
{
    vec3 pos = vec3(gl_Vertex)+gl_Normal*offset;
    gl_Position = gl_ModelViewProjectionMatrix*vec4(pos, gl_Vertex.w);    // 頂点位置
}

フラグメントシェーダは単純に色を返すだけにする.

  1
  2
  3
  4
void main(void)
{    
    gl_FragColor = gl_Color;
}

物体内での吸収・散乱

デプスマップから得られた光源からの距離と注目表面点における光源からの距離の差をとることで, 物体内深度が求められる.この値に基づき表面の色を決定する. ここでは簡単にexp関数を用いる. つまり,物体内深度をsiとすると色を以下のように計算する.

exp(-si*sigma_t)*gl_FrontLightProduct[0].diffuse;

ここでは物体の色として拡散反射色を用いている.

siの算出も含めたフラグメントシェーダのコードを以下に示す.

  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
/*!
 * 物体内の光源ベクトル深度
 * @return 光源ベクトル深度
 */
float LightDepth(void)
{
    // 光源座標
    vec4 shadow_coord1 = vShadowCoord/vShadowCoord.w;
 
    // 光源からのデプス値(視点)
    float view_d = shadow_coord1.z;
    
    // 格納された光源からの最小デプス値を取得
    float light_d = texture2D(depth_tex, shadow_coord1.xy).z;
 
    // 光源からのデプス値と遮蔽を考慮したデプス値の差を求める
    return view_d-light_d;
}
 
/*!
 * 不透明物体のDepth Mapを利用したシェーディング
 * @return 表面反射色
 */
vec4 TranslucentShading(void)
{
    vec3 N = normalize(vNrm);            // 法線ベクトル
    vec3 L = normalize(gl_LightSource[0].position.xyz-vPos.xyz);    // ライトベクトル
 
    // 環境光の計算
    //  - OpenGLが計算した光源強度と反射係数の積(gl_FrontLightProduct)を用いる.
    vec4 ambient = gl_FrontLightProduct[0].ambient;
 
    // 拡散反射係数の計算
    float dcoef = max(dot(L, N), 0.0);
 
    // 光の透過効果
    float si = LightDepth();
    vec4 diffuse = exp(-si*sigma_t)*gl_FrontLightProduct[0].diffuse;
 
    // 鏡面反射の計算
    vec4 specular = vec4(0.0);
    if(dcoef > 0.0){
        vec3 V = normalize(-vPos.xyz);        // 視線ベクトル
 
        // 反射ベクトルの計算(フォン反射モデル)
        vec3 R = reflect(-L, N);
        float specularLight = pow(max(dot(R, V), 0.0), gl_FrontMaterial.shininess);
 
        specular = gl_FrontLightProduct[0].specular*specularLight;
    }
 
    return ambient+diffuse+specular;
}
 
void main(void)
{    
    gl_FragColor = TranslucentShading();
}

実行結果

実行結果のスクリーンショットを以下に示す(クリックで拡大).

translucent_result1.jpg

sigma_t=50にしてある. この方法では光源と表面の間に障害物があった場合,障害物表面から対象点までの距離を物体内深度にしてしまうので, 下の図に示すように影のようなものができてしまう (誤った深度によるものであり実際の影とは異なる色になっている).

translucent_result2.jpg

ソースコード

Visual Studio 2010用のソースコードを以下に置く(要freeglut,GLEW,rx_model).


添付ファイル: fileglsl_translucent.zip 978件 [詳細] filetranslucent_result2.jpg 1187件 [詳細] filetranslucent_result1.jpg 1168件 [詳細] filetranslucent.jpg 603件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2012-03-27 (火) 19:53:39 (3056d)