2012年07月26日

ラスタライズとZバッファ法について

車輪の再発明ですが、ラスタライズとZバッファ法を試してみました。

レンダリングを行う場合、以下の手順で行います。

ワールド座標系→視点座標系→正規化座標系→スクリーン座標系?

→の部分は、4x4のアフィン変換行列を掛ければできます。

まずは、ワールド座標系から視点座標系に変換する行列は、カメラの位置と向きを決めれば、カメラから見た座標系に変換するためのビュー行列を計算することができます。

次に、正規化座標系ということで、X,Y,Zそれぞれ[-1,1]の範囲に収まるように変換するプロジェクション行列が必要です。これも、視錐台の空間を[-1,1]の範囲に行列を掛けることで変換する方法ですので、視野角、アスペクト比、近/遠クリップ面までの距離が決まれば、プロジェクション行列を計算できます。

アフィン変換行列は合成ができますので、ビュー行列にプロジェクション行列を掛けたものが、ワールド座標から正規化座標へ変換するための行列になります。

この行列が求まれば、三角形の3頂点をワールド座標から正規化座標に変換できます。
(同次座標系で考えていますので、wで割る必要があります)

このあたりの行列の計算方法は、[1]を参考にさせていただいております。

画像のサイズが640×480であれば、正規化座標系からスクリーン座標系に変換するために、[-1,1]を[0,1]の範囲に変換して、その後640や480を掛ければ、画像のピクセル座標が求まります。

ラスタライズは、この求まったスクリーン座標系の3頂点を、スキャンラインで描画画素を辿っていく方法です[2]。
今回は、ポリゴンが横一直線上にある場合、3頂点のうち2頂点が同じY座標を持つ場合、3頂点ともY座標が異なる場合で場合分けしまして、3頂点ともY座標が異なる場合は、[2]を参考にして上半分のポリゴンと下半分のポリゴンに分割してラスタライズしました。

Zバッファ法は、隠面消去のための方法で、ピクセル座標の現在の深度値と3頂点の深度値から得られる深度値を比較して、より手前にあれば深度値を更新する方法です。

以上のような手順ですが、GPUなら高速に大量のポリゴンを処理してくれますので、OpenGL等が使えないような特殊な環境でないとやる意味は殆どないと思います。
ただ、アフィン変換行列を使って最終的な画像の位置と深度値を求めていくことができますので、線形代数やCGの勉強にはなるかもしれません。

実行結果

座標手打ちの立方体(12枚の三角ポリゴン)を上記の方法でレンダリングした結果です。

rasterize_001.png

一応アンチエイリアスもやっていますが、カクッとなっているところがあるのでバグがありそうです。
自動でやってくれることを自分でやろうとすると面倒な上に結果が微妙ですね。

参考文献
[1] http://marina.sys.wakayama-u.ac.jp/~tokoi/?date=20090821
[2] http://www.mm.media.kyoto-u.ac.jp/members/kameda/lecture/le4cg/html/node13.html

web拍手 by FC2
posted by シンドラー at 00:12 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2012年07月14日

Cascaded Shadow Mapsのテスト その2

続きです。

N分割した視錐台のシャドウマップがN枚できますので、シェーダ側でどのシャドウマップを使用するのか決める必要があります。
参考文献のサンプルプログラムでは複数枚使用しているようでしたが、とりあえず対応する距離のシャドウマップを1枚使ってやってみます。

視錐台の情報を元に分割し、N個の遠クリップ面までの距離がありますので、フラグメントシェーダでのz値で使用するシャドウマップを選択することができます。

// f[i].fardは視点から見た空間の遠クリップ面の距離です。
// カメラの座標系を使用してz値を計算することができます。
// cam_proj * (0, 0, f[i].fard, 1)^t 左の結果を[0; 1]の範囲に正規化します。(0.5倍+0.5)
far_bound[i] = 0.5f*(-f[i].fard*cam_proj[10]+cam_proj[14])/f[i].fard + 0.5f;

この計算結果をフラグメントシェーダに渡して比較を行います。

あとはシャドウマッピングを行います。
まず、フラグメントシェーダにワールド座標をバーテックスシェーダから渡しておきます。
このワールド座標を、対応する視錐台のModelViewProjectionCrop行列を掛けて[0; 1]に正規化すれば、光源座標系のシャドウマップでのx,y,zを求めることができます。
後は求めたz値とシャドウマップのz値を比較して、そこが影であるかどうかを判定します。

実行結果

fovy=45.0, aspect=1.33, zNear=5.0, zFar=200.0という視錐台に対して、1,2,4分割した結果です。
サンプルプログラムでかけていたので、少しPercentage Closer Filteringを掛けています。

csm_001.png csm_002.png csm_003.png

1分割(分割なし)の場合は、解像度が荒くなっています。静止画だとそうでもないですが、視点を動かすと影の揺らぎがひどいです。
2分割にした段階でかなり改善されていることがわかりました。
まぁ色々怪しいですが一応できたということにしたいと思います。

以下参考文献の適当和訳です。
続きを読む
web拍手 by FC2
posted by シンドラー at 12:31 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2012年07月11日

Cascaded Shadow Mapsのテスト その1

前回の続き?です。

屋外などの広い領域でシャドウマッピングを行おうとした場合、シャドウマップの解像度が悪くなってエイリアスなどが表示されやすくなってしまいます。

カメラから近い所にある物体は画像に占める割合が大きくなりますので、解像度を高くしたいですし、カメラから遠くにある物体は画像にしめる割合が小さいので、荒くても問題なくなります。

1枚のシャドウマップに、カメラから近い距離の物体に多くの画素を割り当てるのがPerspective Shadow Mapsで、視錘台を分割して、分割した視錘台にそれぞれ1枚ずつシャドウマップを生成するのがCascaded Shadow Mapsになります[1]。

今回はCasaded Shadow Mapsのテストで、前回のテクスチャアレイに、分割した視錘台のシャドウマップを格納します。

基本的には下記の流れになります。

1. 視錘台を分割する
2. 分割した視錘台の8頂点の座標を求める(近クリップ面の4頂点と遠クリップ面の4頂点)
3. 平行光源を仮定し、視錘台の8頂点がすっぽり入るようなModelViewProjection行列を計算する
4. テクスチャアレイに計算したModelViewProjection行列を使用してシャドウマップのレンダリングを行う
5. 視点からの描画を行い、それぞれのシャドウマップの情報から影かどうかの判定を行う

詳細は参考文献やNVIDIA OpenGL SDKにソースコードがあるので省略します。
2. については、昔の記事と同じやり方で計算するみたいです。

実行結果

一応4.までやってみたのですが、これで合ってるんでしょうか。何か怪しい気がしますね。



視錘台を3分割しています。光源は(-0.6,-0.5,-0.6)ぐらいの平行光源です。
左上が視点から見た深度値、右上が始点から一番近い視錘台のシャドウマップ、左下が二番目、右下が一番遠いシャドウマップになります。

後、確認のために近クリップ面と遠クリップ面を四角で描画しています。ちゃんと視錘台をすっぽり覆うシャドウマップが準備できていると思います。

1枚のシャドウマップで通常通りに生成した場合、右下のシャドウマップより解像度が下がったものになってしまいますので、分割の有効性が分かる気がします。

次はこれらの情報を使って実際に影付けまで試してみたいと思います。

[1] Rouslan Dimitrov: "Cascaded Shadow Maps", NVIDIA Corporation, 2007

web拍手 by FC2
posted by シンドラー at 21:26 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2012年07月05日

GL_TEXTURE_2D_ARRAYの使い方について

複数の光源を定義し、その影をシャドウマップ等で作ろうとした場合、光源の数だけデプスマップが必要になります。
これをまとめて2パス目で使用するためには、GL_TEXTURE_2D_ARRAYを使用すると便利そうでしたので、テクスチャアレイを試してみました。

今回、テクスチャアレイは、画像を読み込んで割り当てるわけではなく、光源から見た空間の深度情報を格納することになりますので、FBOを使用します。

  // FBO準備
glGenFramebuffersEXT(1, &fbo_id);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_id);
glDrawBuffer(GL_NONE);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

テクスチャアレイは、3Dテクスチャと考えることができます。例えば、256x256のテクスチャを10枚作るのであれば、256x256x10の3Dテクスチャとなります。ですので、テクスチャアレイは下記のように作成します。

  // 2d arrayの作成
glGenTextures(1, &texid);
glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid);

glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

// 2Dテクスチャアレイは、3Dテクスチャのように準備します。
// (fboSize×fboSizeのテクスチャを10枚)
glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_DEPTH_COMPONENT24,
fboSize, fboSize, 10, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

2Dテクスチャアレイに描画するためには、FBOのバインドと、テクスチャアレイのレイヤを指定して描画を行います。

// FBOのバインド
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_id);
...
// 各レイヤに描画
for (int i=0; i<10; i++)
{
// i番目のレイヤに描画を行う
glFramebufferTextureLayerEXT(GL_FRAMEBUFFER_EXT,
GL_DEPTH_ATTACHMENT_EXT, texid, 0, i);
...
// 描画
render();
...
}

シェーダ側で使用する場合には、テクスチャアレイとしてバインドします。

    // TEXTURE_2D_ARRAYのバインド
glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, texid);

フラグメントシェーダでは、下記のようにレイヤを指定して参照します。
(#version 120の場合はextensionの有効化が必要です)

  #version 120
#extension GL_EXT_texture_array : enable

uniform sampler2DArray texture0; // テクスチャアレイ
void main()
{
int layer = 0;
// zがレイヤの番号。0,1,...,レイヤ数-1
vec4 tex_coord = vec4(gl_TexCoord[0].st, layer, 1.0);
gl_FragColor = texture2DArray(texture0, tex_coord.xyz);
}

実行結果

視点の位置を15度刻みで回転させた10枚のテクスチャを表示しています。

tex_array_test_001.png

以下メインソースとシェーダソース
TextureArrayTest.cpp
tex_array.vert
tex_array.frag

テクスチャアレイの使い方は分かったので、次はこれを使ったCascaded Shadow Mapsを試してみたいと思います。

参考プログラム
[1] NVIDIA OpenGL SDK 10.6 Cascaded Shadow Mapsサンプルプログラム

web拍手 by FC2
posted by シンドラー at 19:02 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする