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

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;
}
 

vec4 TranslucentShading(void)
{
    vec3 N = normalize(vNrm);                vec3 L = normalize(gl_LightSource[0].position.xyz-vPos.xyz);     
            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).


添付ファイル: filetranslucent.jpg 892件 [詳細] filetranslucent_result1.jpg 1825件 [詳細] fileglsl_translucent.zip 1506件 [詳細] filetranslucent_result2.jpg 1831件 [詳細]

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