2013年04月24日

コードレビュー。そして、Phabricator

こんにちは、タマキです。

ソフトウェアの品質を高めるために、開発にコードレビューを取り入れている話はよく耳にします 耳

コードレビューを簡単に言えば、『あるソースコードに対して、作者とは別の人(and/or ツール)がチェックを行うこと』、になるでしょうか。

形態としても様々あり、
 ・新人のソースコードに絞って、会議室などで先輩が集まって、レビュー
 ・VCSで、コード内にコメントを挿入しコミット。対象者はそのコメントを見て修正しコミット
など。

また、ペアプログラミングをしているというところもあるかと思います。


以前では、このコードレビューを行うために、多くのリソースが消費されていました ふらふら

例えば、以前のMicrosoftでは必ず
 1.コードを書き、単体テスト
 2.旧ソースとのdiffをチームメンバーに配り,メールベースでレビュー
 3.レビューは1行1行細かく行う
 4.全員がOKと判断したら、再度テストを行いコミット
といったことを行なっていたとのこと。

「レビューを必ず行う」というところは、とても見習うべき点ですね わーい(嬉しい顔)

ですが、『diffを配り、メールベースでレビュー』というところが、今では効率的でない感じがします。


これを効率的に行う目的で、コードレビューツールといったものがあります ひらめき

現在では、有名な会社はもちろん、OSSも含めた多くの開発でコードレビューツールが導入され、品質の向上と効率化が図られています。
(有名なところでは、Google, Microsoft, Facebook, Twitter, VMware, Dropbox, Quora, ...など)

ツールも、『Web上でレビューを行う』といったものが増えてきました。

ヘキサドライブでも、レビュー自体の恩恵を受けつつ、レビュー時間の削減や効率化を狙い、コードレビューツールを利用しています パンチ

このツールを用いたコードレビューの基本的な流れは、
 1.ソースコードを追加・修正・削除(複数のソースファイルもok)
 2.コードレビューツールにアップ
  ソースコードで変更があった部分が色付けでわかりやすく表示される
 3.設定したレビュワー(複数人ok)が添削し、OK/NGなどを設定。コメント(行単位でも可)なども追記
  もちろん、ツール上だけでなく、口頭などでも相談や提案などが行われます。
  レビュワー以外の人がコメントなどをするのもok。
 4.OKならコミットし、クローズ



そして、ヘキサのいくつかのプロジェクトでは、Phabricator(http://phabricator.org/)を使用しています。 手(チョキ)

Phabricatorを選択した理由を、いくつか挙げると、
 ・主要なVCSに対応している(Git, Subversion, Mercurial)
 ・主要なクライアントに対応している(Mac, Linux, Windows)
 ・導入時にPhabricator自身のソースコードをあまりいじらなくても良い
  初めに検証したものは、ソースコード内部をいろいろ書き換えてやっと動いたという状態。。。
  機能的にも不満ではあったが、それを抜きにしても、バージョンアップなどで追いかけるにはメンテナンスコストが高かった。。。
 ・レビューをブラウザで行うことができ、差分の箇所がわかりやすい。ソース一行に対しコメントを挿入することできる
 ・レビュー履歴が残る(レビューされるコードを修正しアップしなおしても、以前のものも残りつつ、アップしなおしたものも確認できる)
 ・UTF-8対応されている
でしょうか。

もちろん気になる点もありましたが。。


Phabricatorだけでなく、様々なコードレビューツールが存在しますので、要件・導入コスト・習得コスト、そして好みも含め、合うものを選択すれば良いと思います。

個人的には、GitHubのPull Requestは好みです わーい(嬉しい顔)

posted by 管理人 at 17:40 | 研究・開発

2012年11月20日

視神経モデル網膜シミュレーション -Retina simulation of Human Visual System-

気がつけば冬の到来雪。皆様風邪などひいてないでしょうか?
こんにちは、イワサキです。

以前からだいぶ間が空いてしまいまして、久々に技術デモを掲載してみようと思います。
今までもコンピュータグラフィックス以外の他の分野にも目を向けながら技術デモを紹介してきていますが、
今回は医学の分野に少し触れてから、その切り口でゲームCGを考えてみようと思います。

写実的なリアリティを目指していこうとすると、最終的に行き着くのは「本物の再現」になります。
今後DirectX11世代からはその考えがゲームCGでも加速していくのは間違いないと思いますが。そうなってくると、シーンの計算が終わった後、最終的な画面への反映の方法も重要になってきます。
現実世界には光を受けて映像化するデバイスは数あれど、主に次のどれかに分類できると思います。
カメラフィルム
カメラセンサー
目肉眼

感光フィルムは昔ながらのスチルカメラカメラで感光剤を塗布したフィルムを感光させる方式です。
感光センサーはCCDセンサーなどの光の強さを数値化することができるもののことです。
肉眼は普段日常で皆さんが目で見ている景色がまさにそれです目
カメラ的な表現はゲームCGの中でもToneMappingとしてすでに多くの作品がHDR表現として取り入れていますが、今回は人体に備わっている感光センサー「網膜」にフォーカスしてみたいと思います。
そんなわけで「網膜シミュレーション」を今回のお題にしたいと思います。

…と、その前に、原理の解説が長くなるためにまず先にデモを紹介します。
ダウンロードして実際に実行することができます。

2010-10-14-0.png


このデモは以下からダウンロードすることができます。
今回もWindowsXPでも閲覧実行可能にするためにDirectX9でデモを作成しました。
実際にはDirectX11で実装することで実行効率を上げ、さらに速度を向上させることができるようになります。

2012-11-20-0.jpg

2012-11-20-1.jpg2012-11-20-2.jpg

◎動作可能な環境

 <動作条件>
 Windows XP/Vista/7 DirectX9.0c
 ShaderModel 3.0 以降
 ※注意※ 今回のデモは大変複雑なシェーダーになっています。
 ShaderModel 3.0 以降対応でもドライバの問題などで動作しない場合があります。
 なるべく最新版のグラフィックドライバをご利用ください。
 尚、デモの中でfp16浮動小数点バッファを使用していますので、SM3.0対応GPUでもfp16に非対応のGPUでは起動できません。


Download
HexaRetina.zip (約5.9MB)

【動作確認済ハードウェア】
・NVIDIA GeForce GT 520
・AMD Radeon HD 7750
【操作方法】
左マウスクリック+ドラッグ…視点を回転


このデモでは、残像現象を主に表現しています。
その効果がわかりやすいように残像を2倍強調されて出るようにしています。
(実際にはこの半分の残像量になります)

今後網膜上の明順応暗順応が実装されれば、一般的なTonemappingとは少し違った見え方で表現できそうです。

見え方をよりそれらしくするために次の技法も併用しています。

ひらめき散乱グレア
ひらめき周辺減光
ひらめきHDRモーションブラー(カメラを回した時に効果が確認できます)

続きを読む

posted by 管理人 at 15:07 | 研究・開発

2012年05月02日

浮動小数点のカラクリ5

晴れゴールデンウィークも終わり、今年入社した新人はそろそろ五月病を
発症している頃ではないでしょうか。私といえば季節の変わり目で
風邪を引き、病気に対して抵抗力が落ち込んでいるコンドウふらふらですバッド(下向き矢印)

さて第5回となりました浮動小数点シリーズですが、今回は四則演算最後の
除算について解説させていただきます。

最初から読まれていない方はまず、こちらからご覧いただきますとより理解が深まります。

浮動小数点のカラクリ
浮動小数点のカラクリ2
浮動小数点のカラクリ3
浮動小数点のカラクリ4


今回は除算です。除算は精度さえ気にしなければ除数の逆数での乗算に置き換える事ができます。
つまり、A÷B ⇒ A×(1÷B)に置き換えて計算すればいい訳です。
(逆数はニュートン法を用いると除算使わないで計算できます。)
しかし精度が必要な場合は除算がどうしても必要になります。
それでは除算方法について考えてみます。

除算は乗算に反対の処理を行う感じで進めます。
具体的な手順は以下の通りです。


@ 指数部同士を引く
A 仮数部同士を割る
B Aの結果を正規化し指数部に反映
C 符号の処理


順番に説明します。1540 ÷ 14 を使って計算します。


1540 = (1+0.50390625) x 210 = 指数部10、仮数部1.50390625
14 = (1+0.75) x 23 = 指数部3、仮数部1.75


@は+127のバイアスが入っているので実際には137(10+127)と130(3+127)になります。
指数部同士を単純に引くと137-130=7ととても小さくなるので、本来なら


  137-127=10 (バイアス引く)
  130-127=3 (バイアス引く)
  10-3=7 (指数部の引き算)
  7+127=134 (結果にバイアス足す)


という手続きになりますが、


  137-130=7 (バイアス引く前の段階で引く)
  7+127=134 (結果にバイアス分足す)


でも同じ結果になりますのでこちらの計算を採用します。


Aは仮数部を取り出した状態の時には+1が省略されているので加算の時と同じように+1します。


1540の仮数部 1.50390625 = C08000(16進数) = 110000001000000000000000(2進数)
14の仮数部 1.75 = E00000(16進数) = 111000000000000000000000(2進数)


このまま普通に除算してしまうと0か1にしかならなくなるので
本来は計算結果に対して23bit左シフト(小数点の位置が23bit目にあるので)するところを
計算前の被除数に対して23bit左シフトして精度を保ちます。

2進数で表記すると桁が多くなるので16進数で計算します。


(0xC08000<<23) / 0xE00000 =
0x604000000000 / 0xE00000 = 0x6E0000


Bですが、0x6E0000は0.859375で1未満となるので正規化の必要があります。
  仮数部 0x6E0000 << 1 = 0xDC0000
  指数部 134 - 1 = 133

最後にCの符号処理は乗算と同じく同じ符号であればプラスに、異なる符号ならマイナスになりますので
排他的論理和とれば簡単に再現できます。上記の場合はどちらもプラスなので結果もプラスになります。
結果は


  符号 0
  指数 133
  仮数 0x5C0000(1.0が加算されているので引いて、0xDC0000 - 0x800000 = 0x5C0000)


となり、(1+0.71875) x 2(133-127) = 110

と晴れて答えが求まりました。

いつものようにプログラム作ってみました。プログラムではGRSビットも考慮された処理になっています。
正規化処理は前回と同じです。



#define FRAC_LEN 23
#define GRS_LEN 3
#define BIAS 127

typedef unsigned __int64 uint64;
typedef signed __int64 sint64;

union FLOATING_VALUE{
float FloatValue;
struct {
unsigned int Fraction:FRAC_LEN;
unsigned int Exp:31-FRAC_LEN;
unsigned int Sign:1;
}s;
};

// 正規化
void normalize( int *exp, unsigned int *frac )
{
unsigned int value;
int Exp = *exp, Frac = *frac;
// 1.0〜1.99999…に収まるよう正規化
if(Frac == 0){ // 0?
Frac = 0;
Exp = 0;
}else{
if( Frac >= (1<<(FRAC_LEN+GRS_LEN+1))){ // 桁あふれ (2.0以上)
do{
value = Frac & 1; // シフトによって捨てられる値
Frac >>= 1;
Frac |= value; // スティッキーbitの補充
Exp++;
}while(Frac >= (1<<(FRAC_LEN+GRS_LEN+1)) && Exp < 255);

}else if(Frac < (1<<(FRAC_LEN+GRS_LEN))){ // 桁借り(1.0未満)
do{
value = Frac & 1;
Frac = (Frac - value) << 1;
Frac |= value; // スティッキーbitの補充
Exp--;
}while(Frac < (1<<(FRAC_LEN+GRS_LEN)) && Exp > 0);
}
if(Exp>=255){ // オーバーフロー?
// 無限大とする
Exp=255;
Frac=0;
}else if(Exp<0){ // アンダーフロー?
// 非正規値とする
Exp=0;
}
}
*exp = Exp;
*frac = Frac;
}

float FDiv(float a, float b)
{
FLOATING_VALUE va,vb,vc;
int value;

va.FloatValue = a;
vb.FloatValue = b;
unsigned int kasuuA,kasuuB;
// aが0なら計算せず0を返す
if(va.s.Fraction==0 && va.s.Exp==0){
vc.s.Fraction = 0;
vc.s.Exp = 0;
vc.s.Sign = va.s.Sign ^ vb.s.Sign; // 符号の処理
return vc.FloatValue;
}
// bが0なら計算せずエラーを返す(divied by 0)
if(vb.s.Fraction==0 && vb.s.Exp==0){
// 無限大を返す
vc.s.Fraction = 0;
vc.s.Exp = 255;
vc.s.Sign = va.s.Sign ^ vb.s.Sign; // 符号の処理
return vc.FloatValue;
}
kasuuA = va.s.Fraction + (1<<FRAC_LEN); // 省略されている1.0(1<<23)を加える
kasuuB = vb.s.Fraction + (1<<FRAC_LEN); // 省略されている1.0(1<<23)を加える
kasuuA <<= GRS_LEN; // GRSビット分桁上げ
kasuuB <<= GRS_LEN; // GRSビット分桁上げ
int exp = va.s.Exp - vb.s.Exp + BIAS; // 指数部の計算
// 仮数部の計算(GRSビット分底上げ)
uint64 kasuuL = (static_cast<uint64>(kasuuA)<<(FRAC_LEN+GRS_LEN)) / static_cast<uint64>(kasuuB);
kasuuA = static_cast<unsigned int>(kasuuL);
normalize( &exp, &kasuuA ); // 正規化
value = kasuuA & ((1<<GRS_LEN)-1); // GRSビット抽出
if(value >= 4){ // 0b100以上なら切り上げ、0b011以下なら切り下げ
kasuuA += 1<<GRS_LEN; // +1切り上げ
normalize( &exp, &kasuuA ); // 再度正規化
}
kasuuA >>= GRS_LEN; // GRSビット捨てる
// 結果を格納
vc.s.Fraction = kasuuA;
vc.s.Exp = exp;
vc.s.Sign = va.s.Sign ^ vb.s.Sign; // 符号の処理
return vc.FloatValue;
}


除数が0の場合、計算不能になるので事前に初期段階ではじいています。仮数部同士の除算では0チェック
していませんが、両方とも24bit目が必ず1になるので0チェックは省いています。
被除数が0の場合も除数の値に関わらず必ず0になるので、事前に0を返しています。






除算は加算や乗算のようにパイプライン化できない演算(同じ計算回路を何度も再利用しないといけない為)
なので非常に時間のかかる処理になってしまいます。しかもパイプライン構造でないので1回の除算が
完了するまで次の除算が計算できず待たされてしまうなどデメリットが非常に大きい計算です。
その為、可能であれば冒頭でも書きました逆数の乗算に置き換える等工夫する必要があります。
除算がどの位遅いかというとプロセッサにもよりますが乗算に比べると数倍〜十数倍(実計算時間)の差があります。
例えば透視変換で、XとYをZで割る処理の場合
  SX = X / Z;
  SY = Y / Z;
となりますが、これを
  INVZ = 1 / Z;
  SX = X * INVZ;
  SY = Y * INVZ;
に置き換えてもまだこっちの方が早かったりします。2個以上の除算は検討する必要がありますね。
最適化かけたCコンパイラなら定数の除数の場合は自動的に逆数の乗算などに置き換えてくれたりします。
しかし除数が変数の場合はそのまま除算命令になるので手動で最適化する必要があります。

さて5回に渡って浮動小数点の仕組みについて説明してきましたが、これらの計算部分はほとんど最適化
が施されていませんので実用的ではありません。あくまで浮動小数点の仕組みを理解する事が趣旨であり
分かりやすくした結果、速度が犠牲になっています。もしちゃんと実用的なロジックでシステムに組み込
みたいって思っている(よっぽど古いプロセッサ以外は必要ありませんが)方は是非gcc(gnuのcコンパイラ)
のソースを入手して解析してみて下さい。このシリーズでは説明していない浮動小数点⇒10進数表示など
も調べる事ができるかと思います。製作されるならビット長128bitなど超精度浮動小数点など面白いかも
しれませんね。
今回の浮動小数点ネタは私自身も深く理解する事が出来(資料作るにあたって色々調べました)とても楽しく
進める事ができました。またこのようなネタがありましたら私自身の勉強も兼ねて公表したいと思います。
次回も何かプログラムネタを探したいと思います。ではexclamation


posted by 管理人 at 14:22 | 研究・開発

2012年03月23日

浮動小数点のカラクリ4

やっと晴れ春らしくなってきつつある今日この頃、いかがお過ごしでしょうか?コンドウです。

最近は家で暖房器具に電源入れる機会もぐっと減りエコひらめきしていますが、
まだまだ片づけるには早いですねあせあせ(飛び散る汗)

さて第4回となりました浮動小数点シリーズですが、今回は乗算について解説させていただきます。

最初から読まれていない方はまず、こちらからご覧いただきますとより理解が深まります。

浮動小数点のカラクリ
浮動小数点のカラクリ2
浮動小数点のカラクリ3


以前にも書いたかと思うのですが、実は乗算って加減算よりもずっと簡単です。

例えば、1234 × 528 について考えてみます。

まずは浮動小数点と同じように指数部と仮数部に分離します。
(分かりやすくする為に10進数表記で説明します)


  1234 = 1.234 × 103
  528 = 5.28 × 102

  1234 × 528 = (1.234×5.28) × 10(3+2) = 6.51552 × 105 = 651552


という感じで計算します。ここで仮数部と指数部に分離しましたが、加減算のように指数部合わせの
フローが不要となり単純に処理できます。以上をまとめますと、


@ 指数部同士を足す
A 仮数部同士を掛ける
B Aの結果を正規化し指数部に反映
C 符号の処理


順番に説明します。上と同じ、1234 × 528 を使って計算します。


  1234 = (1+0.205078125) x 210 = 指数部10(+127)、仮数部1.205078125
  528 = (1+0.03125) x 29 = 指数部9(+127)、仮数部1.03125


@は+127のバイアスが入っているので実際には137(10+127)と136(9+127)になります。
指数部同士を単純に足すと137+136=273ととても大きくなるので、本来なら


  137-127=10 (バイアス引く)
  136-127=9 (バイアス引く)
  10+9=19 (指数部の足し算)
  19+127=146 (結果にバイアスかける)


という手続きになりますが、


  137+136=273 (バイアス引く前の段階で足す)
  273-127=146 (結果からバイアス分引く)


でも同じ結果になりますのでこちらの計算を採用します。


Aは仮数部を取り出した状態の時には+1が省略されているので加算の時と同じように+1します。


1234の仮数部 1.205078125 = 9A4000(16進数) = 100110100100000000000000(2進数)
528の仮数部 1.03125 = 840000(16進数) = 100001000000000000000000(2進数)


2進数で表記すると桁が多くなるので16進数で計算します。


  0x9A4000 × 0x840000 = 0x4F8900000000


で、23bit目に小数点があるので小数点位置を合わせる為に結果を23bit右シフトします。
この時に前回説明しましたGRSビットへの反映も内部では忘れず行います。


  0x4F8900000000 >> 23 = 0x9F1200 = 100111110001001000000000(2進数)



Bですが、結果は23bitで収まっているので正規化の必要はありません。23bitをオーバー
している場合は適宜指数部への反映を行います。

最後にCの符号処理ですが乗算は同じ符号であればプラスに、異なる符号ならマイナスに
なりますので排他的論理和とれば簡単に再現できます。上記の場合はどちらもプラスなので
結果もプラスになります。
結果は


  符号 0
  指数 146
  仮数 0x1F1200(小数点では0.24273681640625)


となり、(1+0.24273681640625) x 2(146-127) = 651552

と晴れて答えが求まりました。

いつものようにプログラム作ってみました。前回作った加算でGRSビットからの切り上げが
発生した時に正規化する処理を忘れていましたので今回は正規化する処理を関数化しました。



#define FRAC_LEN 23
#define GRS_LEN 3
#define BIAS 127

typedef unsigned __int64 uint64;
typedef signed __int64 sint64;

union FLOATING_VALUE{
float FloatValue;
struct {
unsigned int Fraction:FRAC_LEN;
unsigned int Exp:31-FRAC_LEN;
unsigned int Sign:1;
}s;
};

// 正規化
void normalize( int *exp, unsigned int *frac )
{
unsigned int value;
int Exp = *exp, Frac = *frac;
// 1.0〜1.99999…に収まるよう正規化
if(Frac == 0){ // 0?
Frac = 0;
Exp = 0;
}else{
if( Frac >= (1<<(FRAC_LEN+GRS_LEN+1))){ // 桁あふれ (2.0以上)
do{
value = Frac & 1; // シフトによって捨てられる値
Frac >>= 1;
Frac |= value; // スティッキーbitの補充
Exp++;
}while(Frac >= (1<<(FRAC_LEN+GRS_LEN+1)) && Exp < 255);

}else if(Frac < (1<<(FRAC_LEN+GRS_LEN))){ // 桁借り(1.0未満)
do{
value = Frac & 1;
Frac = (Frac - value) << 1;
Frac |= value; // スティッキーbitの補充
Exp--;
}while(Frac < (1<<(FRAC_LEN+GRS_LEN)) && Exp > 0);
}
if(Exp>=255){ // オーバーフロー?
// 無限大とする
Exp=255;
Frac=0;
}else if(Exp<0){ // アンダーフロー?
// 非正規値とする
Exp=0;
}
}
*exp = Exp;
*frac = Frac;
}

float FMul(float a, float b)
{
FLOATING_VALUE va,vb,vc;
int value;

va.FloatValue = a;
vb.FloatValue = b;
// どちらかが0なら計算せず0を返す
va.s.Fraction==0 && va.s.Exp==0) || (vb.s.Fraction==0 && vb.s.Exp==0)){
return 0.0f;
}
unsigned int kasuuA,kasuuB;
kasuuA = va.s.Fraction + (1<<FRAC_LEN); // 省略されている1.0(1<<23)を加える
kasuuB = vb.s.Fraction + (1<<FRAC_LEN); // 省略されている1.0(1<<23)を加える

int exp = va.s.Exp + vb.s.Exp - BIAS; // 指数部の計算
// 仮数部の計算(32bitで計算するとオーバーフローするので64bit型で計算)
uint64 kasuuL = static_cast<uint64>(kasuuA) * static_cast<uint64>(kasuuB);
// 右シフトによって捨てられるbit分
unsigned int stub = kasuuL & (( 1 << (FRAC_LEN - GRS_LEN)) - 1 );
kasuuL >>= (FRAC_LEN - GRS_LEN); // 小数点位置に合わせる(GRSビットは残しておく)
kasuuA = static_cast<unsigned int>(kasuuL | (stub != 0)); // スティッキーbitの処理

normalize( &exp, &kasuuA ); // 正規化
value = kasuuA & ((1<<GRS_LEN)-1); // GRSビット抽出
if(value >= 4){ // 0b100以上なら切り上げ、0b011以下なら切り下げ
kasuuA += 1<<GRS_LEN; // +1切り上げ
normalize( &exp, &kasuuA ); // 再度正規化
}
kasuuA >>= GRS_LEN; // GRSビット捨てる
// 結果を格納
vc.s.Fraction = kasuuA;
vc.s.Exp = exp;
vc.s.Sign = va.s.Sign ^ vb.s.Sign; // 符号の処理
return vc.FloatValue;
}


どちらかが0の場合は最初の方で0をリターンする処理が入っています。これは0を0として処理しなかった
場合は1.0×2-127 の限りなく0に近いけど0で無い値として処理されてしまうからです。






CPUに付いている浮動小数点コプロセッサの中には、積和計算(乗算+加算)や積差計算(乗算+減算)を
1つの命令で行うものがあります。しかも大抵は加算や乗算と同じ所要クロックで行える為、ちょっと
お得な命令です。(乗算の結果に対して加算を行うので同じクロックで終えるのは謎です)
この積和命令って、内部では多bit状態(GRSビットなどを保持)のまま、計算し続けているので乗算と
加算を別々で行った場合に比べて計算精度が高かったりするコプロセッサもあります。2つの計算を
同時にしてくれてかつ精度が高いとはとてもお得感がありますね。但しIEEE754の仕様と異なる
(IEEE754には積和計算など無い)のでCのコンパイルオプションでは選択できるようになっているものも
あります。(浮動小数点の高速計算ON/OFFとか)
コプロセッサには便利な命令(例えば逆数を計算したり、平方根計算等)が積んでいたりするので使用の
コンパイラで組み込み関数が無いかもしくはアセンブラで直接書くすると色々と発見できます。

次回は四則演算最後の除算の予定です。ではexclamation

posted by 管理人 at 14:13 | 研究・開発

2012年03月16日

ShaderModel2.0でGPU曲面分割

こんにちは、イワサキです。わーい(嬉しい顔)

先日、サンフランシスコでGDC2012も開催され、ゲーム業界の開発話題も
活気だっていますね。特にDirectX11のGPUコンピューティングの話題が去年よりも増えていて、技術トレンドも徐々に移っているのが感じられます。

今回は技術的なお話というよりかは実験的なおはなしですひらめき
近年のGPUでは曲面/再分割が可能になったり、一般的な計算も可能になったりと表現力のポテンシャルはどんどん向上しています。
DirectX11/OpenGL4はできる事が増えた分、より高度で面白いプログラムが書けるようになっています。

新しい環境は大歓迎したいところではありますが、そこで問題になるのは「今までの環境とどう互換を取っていくのか」という部分です。
特にPC環境では、ユーザーによってPC性能や機能の差が大きく異なるため柔軟にサポートしていく必要があります。

次々とGPUに機能が搭載されていく中、過去の世代のGPUでテセレータを搭載していないものでもGPU支援で
曲面や再分割できたりしたらDirectX11っぽくて面白そうだなひらめき、と思い立って作成してみました。

DirectX9 ShaderModel2.0世代でGPUポリゴン分割レンダリング」が今回のお題です。

GPUで実行するからにはCPU負荷をかけないことを目標にします。




このテセレーション自体はかなり以前の世代のShaderModel2.0でも動作可能です。
法線生成など他の実装を含めるとテンポラリレジスタ数制限によってオーバーしてしまいましたので今回はSM3.0と2種類で実装してみました。
SM2.0対応GPUではテセレートとテクスチャマッピングが実行され、SM3.0以降対応GPUならさらに高品質に法線生成して描画されます。

下記よりダウンロード可能です。

◎動作可能な環境

 <動作条件>
WindowsXP以降 DirectX9 ShaderModel2.0以上対応のGPU


00.png01.png
【左】高精細な分割 【右】 粗い分割


02.png03.png
【左】SM30の実行結果 【右】 SM20の実行結果


Download
HexaTessellation.zip (約2.47MB)

【動作確認済ハードウェア】
NVIDIA GeForce GT 420

【操作方法】
マウス左クリック&ドラッグ……視点回転
Z ……曲面パッチコントロールポイント表示 ON/OFF
X ……ジオメトリワイヤーフレーム表示 ON/OFF
C or V ……テセレーション係数増減(分割数が変化)



2010-10-14-0.png



◎実装のポイント

04.png

当初からテセレータを搭載していたGPUは存在しており、DX9内でも限定的に使用できるものもありましたが、すべてのGPUが対応していたかというと実はそうではなく、各個別に対応する必要があることと非搭載の環境では互換をどのようにするのか。という課題がありました。
DX11からは共通のGPU仕様として導入されていますので安心して使えますよね。
DX11はテセレートされたあとのジオメトリは通常のポリゴンとして取り扱えますのでDomainShader上でさらなる加工も可能です。

ポリゴンを分割することをCG用語でTessellation (テセレーション/テッセレーション)と呼びます。
英単語的意味は「モザイク細工、モザイク配列、 埋め尽くし」などの意味があります。
頂点シェーダではテセレータユニットのように分割ができませんので文字通りポリゴンを並べて埋め尽くそう!というのが今回のアイデアです。

DX9環境ではこれらの実装を全て頂点シェーダー内でやってしまいます。

05-1.png
06.png

ここで実装のポイントとなるのがGeometryShaderやテセレータの機能である
頂点座標をGPU内で新規生成して三角形データとして投入する」機能がDX9世代には存在していないことです。
なかでもATI(現AMD) RadeonなどはDX9世代でもテセレータ対応していたりします。

そこで、予め生成される予定の最大個数分の頂点を頂点バッファ上に作成しておき、必要個数分をDrawPrimitiveの描画プリミティブ数に指定することで増減させます。
ここは最大個数分を頂点バッファ上に持つことで冗長性が出てしまいます。
CPUを使わずに全てGPUのみで完結させることを目標にしましたが、この描画ポリゴン数だけシェーダー側からは動的にはできないためなるべく負荷をかけないようにCPU上でおおまかに計算しています。

07.png

ここはCPU負荷を最小にとどめるため、厳密に数を計算せずに大雑把な精度で求めています。多少の範囲外頂点は頂点シェーダー内でクリップします。
(そのぶん若干頂点シェーダー後のカリングに負荷がかかりますが…)
この頂点の持たせ方によって、様々なテセレーションアルゴリズムが表現可能です。

08.png09.png

プリミティブ用の頂点には曲面位置のUV値に相当するパラメータを事前に格納しておきます。
頂点シェーダー内で頂点座標を生成するためここには頂点座標情報は不要です。


0a.png

曲面パッチ情報は頂点バッファの中に仕込みます。
これがDX11とは異なり冗長なデータになってしまう部分の一つです。
SM3.0ではHWインスタンシングなどを使えば効率的に共有データを保持できます。
SM2.0では定数レジスタにセットして使用するかしなければレジスタ個数が不足します
ので描画が曲面パッチ単位になったりとさらなる制限がつきます。

2010-10-14-0.png

◎適応型テセレーション (Adaptive Tessellation)

せっかくなので、分割数を適応化分割してみます。
テセレーションすると、曲面パッチ同士で繋ぎ目の分割数が異なると隙間(クラック)が発生してしまいます。
これを回避するために隣接する曲面の境界線は分割数を一致させることで対応できます。
今回はコントロールポイントの位置のビュー空間のZ座標で分割数をコントロールしました。

0b.png

これによって距離によってのポリゴンあたりの面積が均一になり、効率的なLODが可能になります。
この実装はDX11でもSDKサンプルのDetailTessellation11などでも見ることができますペン




一部の辺の境界線に分割数適応化を実装しています。シームレスにジオメトリが接続されています。
今回のデモのポットだと側面の部分に適用されています目

0c.png

今回のデモではテセレート処理が完全な実装にはなっていないため、分割数適応されていない部分で分割数が異なる部分には
縮退ポリゴンを挿入してクラックが見えないように対処してあります。
これは頂点シェーダーの分割アルゴリズムを改良すれば解決できそうです。後日実験してみようかなと思います。
法線方向を見てシルエットになる部分だけを高精細にする適応化なども対応できそうです。

2010-10-14-0.png


今回はDirectX11を使わずにGPUテセレーションを実装してみましたぴかぴか(新しい)
テセレータ非搭載のGPUでも動作しますので何かの表現に使えるかもしれませんね。ひらめき

DirectX11ではこれらすべてがハードウェア化され更に高効率になり、分割数もこれよりもさらに細かくしても低負荷でワイヤーフレーム表示が真っ白な塊になって見えるレベルまで再分割できます。
ポストエフェクトフィルタも従来のピクセルシェーダーではなくComputeShaderで実行できますので、よりスマートに高速に作成できます。

DirectX11での最新技術のデモも今後公開していく予定です。

それでは次回以降も話題を提供したいと思います。ぴかぴか(新しい)
お楽しみに! わーい(嬉しい顔)手(パー)


posted by 管理人 at 18:21 | 研究・開発

2012年02月08日

浮動小数点のカラクリ3

しばらくの間、休暇を頂いていましたのでご無沙汰になります。コンドウです。

健康の為、普段は極力歩くよう心掛けているのですが
寒すぎてついつい車車(セダン)に乗ってしまう怠け者ですもうやだ〜(悲しい顔)

さて前回から浮動小数点関係のネタで攻めていますが、
ここまでいったら四則演算までいっちゃおうかと
決めましたのでこのネタ終わるまでお付き合いの程宜しくお願いしますグッド(上向き矢印)

最初から読まれていない方はまず、こちらからご覧いただきますとより理解が深まります。

浮動小数点のカラクリ
浮動小数点のカラクリ2


実は前回、前々回共にさらーっと書いてた計算による桁落ち処理なんですが、詳しく調べたところ
もうちょっと複雑な仕組みになっているようですのでその辺を含めてまずは説明します。
これらははみ出た端数をどう扱うかの丸め処理にも大きく関係してきます。


・ガードビット、ラウンドビット、スティッキービット

IEEE754では計算の精度を高める為に仮数部の下位に3bitを追加して計算するようです。
それぞれのビットに名前があり、上位からガードビット、ラウンドビット、
スティッキービットと命名されています。

仮数部
1.00000000000000000000000GRS

"1."は隠れている+1.0です。
"00000000000000000000000"は仮数部本体です。
"G"はガードビット
"R"はラウンドビット
"S"はスティッキービット
全部で1+23+1+1+1=27bitになります。


ガードビット(Guard bit)・ラウンドビット(Round bit)

ガードビットとラウンドビットは仮数の最下位ビットから加算や減算などの計算の為に
こぼれてしまった捨てられてしまうビットを一時的に貯蔵する為のビットです。


スティッキービット(Sticky bit)

スティッキービットは先の2bitとはちょっと性質が異なり、ラウンドビットから右シフト
されて落ちた1を保持し続けるビットです。一度1が入ると0に戻ることはありません。
最初は0で初期化されます。

1.00000000000000000110110 を右シフトしていくと以下のようなイメージになります。

1.00000000000000000110110 0 0 0
0.10000000000000000011011 0 0 0
0.01000000000000000001101 1 0 0
0.00100000000000000000110 1 1 0
0.00010000000000000000011 0 1 1
0.00001000000000000000001 1 0 1
0.00000100000000000000000 1 1 1


今回は前回の加算処理にこの3ビットを加味し最近値への丸め(*1)で処理したプログラムに
改造してみたいと思います。
折角改造するなら前回さらーっと端折った負の数や0との加算や無限大などのエラー処理も
付け加えたいと思います。


#define FRAC_LEN 23 // 仮数部のbit数
#define GRS_LEN 3 // 付加される3bit

union FLOATING_VALUE{
float FloatValue;
struct {
unsigned int Fraction:FRAC_LEN;
unsigned int Exp:31-FRAC_LEN;
unsigned int Sign:1;
}s;
};

float FAdd(float a, float b)
{
FLOATING_VALUE va,vb,vc;
int kasuuA,kasuuB;
int shift,maxExp,value;

va.FloatValue = a;
vb.FloatValue = b;
vc.s.Sign = 0;
kasuuA = (va.s.Fraction + (1<<FRAC_LEN)) << GRS_LEN; // 省略されている1.0(1<<23)を加える
kasuuB = (vb.s.Fraction + (1<<FRAC_LEN)) << GRS_LEN; // 省略されている1.0(1<<23)を加える
if(va.s.Sign) kasuuA = -kasuuA; // 負の数なら仮数部を符号反転
if(vb.s.Sign) kasuuB = -kasuuB; // 負の数なら仮数部を符号反転
if(va.s.Exp > vb.s.Exp){ // aの方が大きい
maxExp = va.s.Exp;
shift = va.s.Exp - vb.s.Exp;
if(shift >= (1+FRAC_LEN+GRS_LEN)){ // 仮数部が無くなる程のシフトなら
kasuuB = (kasuuB != 0); // 1のbitが1つでもあればスティッキービットを1に
}else{
value = kasuuB & ((1<<shift)-1);// シフトによって捨てられる値
kasuuB >>= shift; // 仮数部の指数部合せ
kasuuB |= (value!=0); // スティッキーbitの補充
}
}else{ // bの方が大きい
maxExp = vb.s.Exp;
shift = vb.s.Exp - va.s.Exp;
if(shift >= (1+FRAC_LEN+GRS_LEN)){ // 仮数部が無くなる程のシフトなら
kasuuA = (kasuuB != 0); // 1のbitが1つでもあればスティッキービットを1に
}else{
value = kasuuA & ((1<<shift)-1);// シフトによって捨てられる値
kasuuA >>= shift; // 仮数部の指数部合せ
kasuuA |= (value!=0); // スティッキーbitの補充
}
}
kasuuA += kasuuB; // 仮数部の加算
if(kasuuA < 0){ // 負の数になっていたら
kasuuA = -kasuuA; // 正の数化
vc.s.Sign=1; // 符号ビット立てる
}

// 1.0〜1.99999…に収まるよう正規化
if(kasuuA == 0){ // 0?
vc.s.Sign=0;
kasuuA = 0;
maxExp = 0;
}else{
if( kasuuA >= (1<<(FRAC_LEN+GRS_LEN+1))){ // 桁あふれ (2.0以上)
do{
value = kasuuA & 1; // シフトによって捨てられる値
kasuuA >>= 1;
kasuuA |= value; // スティッキーbitの補充
maxExp++;
}while(kasuuA >= (1<<(FRAC_LEN+GRS_LEN+1)) && maxExp < 255);

}else if(kasuuA < (1<<(FRAC_LEN+GRS_LEN))){ // 桁借り(1.0未満)
do{
value = kasuuA & 1;
kasuuA = (kasuuA - value) << 1;
kasuuA |= value; // スティッキーbitの補充
maxExp--;
}while(kasuuA < (1<<(FRAC_LEN+GRS_LEN)) && maxExp > 0);
}
if(maxExp>=255){ // オーバーフロー?
// 無限大とする
maxExp=255;
kasuuA=0;
}else if(maxExp<0){ // アンダーフロー?
// 非正規値とする
maxExp=0;
}
}
value = kasuuA & ((1<<GRS_LEN)-1); // GRSビット抽出
kasuuA >>= GRS_LEN; // GRSビット捨てる
if(value >= 4){ // 0b100以上なら切り上げ、0b011以下なら切り下げ
kasuuA++;
}
// 結果を格納
vc.s.Fraction = kasuuA;
vc.s.Exp = maxExp;
return vc.FloatValue;
}

float FSub(float a, float b)
{
FLOATING_VALUE vb;

vb.FloatValue = b;
vb.s.Sign ^=1;
return FAdd(a,vb.FloatValue);
}


今回は減算(FSub)も用意しました。減算は加算処理の仮数部同士の計算のところを
+から-にするだけで対応できますが、上記のプログラムでは引く値を符号反転して
加算に置き換えています。
今回丸め処理をもうちょっとまじめにやってみましたが、まだWindowsの浮動小数点とは
最大1bitの誤差が出てしまうようです。(丸め処理のところかな?)
その辺も含めて自身への宿題にしたいと思います。
次回こそは乗算したいと思います。乗算計算でもGRSビットが活躍しますexclamation
乞うご期待下さいexclamation×2

*1 丸め処理の1つで正しい値に近い方へ丸める方法です。他にもゼロ方向への丸め、
正の無限大方向への丸め、負の無限大方向への丸めなどがあります。



posted by 管理人 at 18:14 | 研究・開発

2012年01月12日

ゲームデザイン研究会に参加してきました

こんにちは。せりざわです。

新たな年、2012年を迎えてみなさん仕事や学業で新たな気持ちでスタートされたと思います。
ヘキサドライブもアクセル全開スタートダッシュ車(セダン)で走り出しましたexclamation

去年の話になってしまうのですが
日本デジタルゲーム学会(DiGRA JAPAN)様で、12月4日に開催された
「第2回ゲームデザイン研究会」に参加してきました。
「ゲームデザインのモデル化」という企画職の自分にとって、とても興味深いテーマでした。

講演内容はゲームデザインモデル化の入門編として
MDA Frameworkの紹介がありました。

MDA Frameworkとは

「Mechanics」 アルゴリズムレベルのゲームの根本的な仕組み
「Dynamics」  インタラクションによって生まれる展開
「Aesthetics」 プレイヤーに生まれる感情

という3段階のプロセスをゲームデザインの評価、分析、研究へつなげる
フレームワーク・デザイン手法です。
それぞれのプロセスを言語化、モデル化する事により
明確に本質を表す事が出来ます。

言語化のメリットとして開発者間での打ち合わせなどのコミニュケーションのベースになるため
イメージの伝達、意思の疎通がスムーズになり効率化が図れたり
プランニング担当者のみに蓄積されがちな経験、分析結果を残していく事が出来
以後のゲーム開発に活用する事が出来ます。

これはいろいろな業種で活用されている
PDCAサイクル(Plan,Do,Check,Act)に近いのかなと思いました。

まだ研究はこれからも進んでいくとの事で
ゲームデザインの技術論として、今後の発表が楽しみです!

ゲームデザインの書籍として
「ルールズ・オブ・プレイ(上) ゲームデザインの基礎」
が去年翻訳されました。
アナログ、デジタル問わずゲームのルールが分析されていて、こちらも大変参考になります。

そして大事なのは蓄積されたモデル、技術論を活用しつつ、新しいモデルを生み出すクリエイティビティと
ゲームデザインの核爆弾となるぐつぐつかわいいアツくむかっ(怒り)煮えたぎるいい気分(温泉)どんっ(衝撃)の如き「コンセプト」が
モデルから突き抜けるぐらいエッジを効かせる事だと思いましたexclamation×2

今年もプレイヤーのみなさんのハート黒ハートに突き刺さるゲームを作っていけるようにがんばっていきますexclamation×2


posted by 管理人 at 18:54 | 研究・開発

2011年11月16日

浮動小数点のカラクリ2

こんにちはexclamationコンドウです。秋も深まり若干肌寒く感じる季節になってきました。
手洗い手(パー)うがいわーい(嬉しい顔)励行しているにも関わらず、風邪ダッシュ(走り出すさま)ぎみで我ながら抵抗力の無さを嘆いておりますたらーっ(汗)


さて、前回に「次回はプログラムネタします」と言いましたがいいネタが思いつかず
またもや浮動小数点ネタで申し訳ありませんがいってみたいと思います。

前回の 浮動小数点のカラクリ です。
まだ読まれていない方は先にこちらを読むと理解が深まります。


今回は前回にやろうとしていた計算(まずは足し算)についてやろうかと思います。

その前に前回のおさらいとしてまずは10進数から浮動小数点に変換する仕組みをさらっと説明します。
10進数で115を、正規化で表してみたいと思います。

やり方は簡単です。まず115を2進数に変換します。

115(10進数) = 1110011(2進数)

次に仮数部を求めます。仮数部は前回も書きました1〜1.999999…の範囲となりますのでまず
この範囲になるよう小数点の位置を決定させます。1〜1.999999…の範囲に収めるには最上位bitの
右隣に小数点を持ってくればできます。

1110011 → 1.110011

小数点は6つ左に移動したので指数部は26(=64)に決まります。
仮数部 1.110011 は 1のところだけを抽出して10進数化して、1 + 1/2 + 1/4 + 1/32 + 1/64 で 1.796875 です。

指数部と仮数部掛け合せると 26 x 1.796875 = 115

となります。簡単ですね。もし小数点のある実数 68.3542 の場合なら1〜1.9999…の範囲になるまで
2で割り続けます。

68.3542 / 2 = 34.1771
34.1771 / 2 = 17.08855
17.08855 / 2 = 8.544275
8.544275 / 2 = 4.2721375
4.2721375 / 2 = 2.13606875
2.13606875 / 2 = 1.068034375

6回続けたので指数部は26、仮数部は1.068034375となりました。
指数部と仮数部掛け合せると 26 x 1.068034375 = 68.3542
具体的な浮動小数点にするには

 符号部 +なので 0
 指数部 127(バイアス値 *1) + 6 = 133
 仮数部 1+0.068034375 の 0.068034375 を固定小数点化 → 0.068034375 x 223 = 570713.7024 → 小数部四捨五入 → 570714

16進数で表現すると、0x4288B55A
もし、0以上1未満の値なら逆に1〜1.9999…の範囲になるまで2倍ずつ行っていき計算した回数分だけ
指数部のバイアス値127から引けばいい訳です。

これで手動にて浮動小数点化できるようになりました。



次はいよいよ加算計算です。まず計算を行う為には以下のルールで進めていきます。


・計算する値の指数部を合せる(結果は仮数部に反映させる)
・指数部を合わせたら仮数部同士を足す
・もし仮数部のbit数をオーバー(オーバーフロー)したら指数部にそれを反映させる

です。実際に行ってみます。

値は何でもいいのですが、246.912(以下A) + 1975.28(以下B) にします。

Aを浮動小数点化すると、0x4376E979
Bを浮動小数点化すると、0x44F6E8F6
更に2進数化してみます。(分かりやすくする為 符号部、指数部、仮数部で "_" 挟みます)

A = 0x4376E979 = 0_10000110_11101101110100101111001
B = 0x44F6E8F6 = 0_10001001_11101101110100011110110

ここで仮数部で省略されている+1.0を設定する為、仮数部のMSBに1を追加し24bitにします。

A = 0_10000110_111101101110100101111001
B = 0_10001001_111101101110100011110110

先のルールに従い指数部を揃えます。揃える場合はどちらか大きい方に揃えます。
Aの指数部は 10000110(2進数) = 134(10進数)
Bの指数部は 10001001(2進数) = 137(10進数)
137 - 134 = 3の差があるのでAの指数部に3を加えて揃えます。3を加えた事で仮数部もそれに合せて調整する必要があります。指数部が23=8倍になったので仮数部は1/8します。1/8は3bit右シフトになりますので

A = 0_10001001_000111101101110100101111

となります。この時溢れた3bitの内の最上位bitが1なら+1して丸め処理しておきます。
これで指数部が合いました。

A = 0_10001001_000111101101110100101111
B = 0_10001001_111101101110100011110110

次に仮数部同士の足し算です。

A = 000111101101110100101111(2進数) = 2022703(10進数)
B = 111101101110100011110110(2進数) = 16181494(10進数)

2022703 + 16181494 = 18204197 = 1000101011100011000100101

仮数部が24bitをオーバーしていなければそのまま仮数部の下位から詰めていきます。オーバーしている場合は1bit右シフトしてから下位から詰めます。

10.00101011100011000100101 ← この場合は24bitオーバーしているので右に1bitシフトした
00010101110001100010010 を仮数部に詰めます。

また仮数部の24bit目以降が0で無ければその分指数部に加味(+1)します。

0_10001010_00010101110001100010010

これで完成しました。


01000101000010101110001100010010 ≒ 211 x (1+0.085054636) = 2222.191894528

10進数レベルで246.912+1975.28=2222.192ですが10進数→2進数→10進数変換した際の情報落ちが出ている為、若干精度悪いですねたらーっ(汗)

足し算のルール見ると分かりますが、指数部に大きな開きのある値同士で計算すると小さい値の仮数部情報がどんどん無くなってしまう事が想像できますね。即ち、「大きい値」に「小さい値」を足しても正しい精度が
得られないという事です。

例えば、1.5 x 213 と 1.5 x 2-12 の足し算では指数部に25の開きがあるので23+1bitの仮数部は0になってしまいます。

ここが浮動小数点の弱点でもあるところです。

以上の作業をプログラム化したものがこれです。


union FLOATING_VALUE{
float FloatValue;
struct {
unsigned int Fraction:23;
unsigned int Exp:8;
unsigned int Sign:1;
}s;
};

float FAdd(float a, float b)
{
FLOATING_VALUE va,vb,vc;
unsigned int kasuuA,kasuuB,kasuuC;
int shift,maxExp;

va.FloatValue = a;
vb.FloatValue = b;
vc.s.Sign = 0;
kasuuA = va.s.Fraction | (1<<23);
kasuuB = vb.s.Fraction | (1<<23);
if(va.s.Exp > vb.s.Exp){
maxExp = va.s.Exp;
shift = va.s.Exp - vb.s.Exp;
kasuuC = kasuuB >> shift; // 指数部の合せ
if(shift){
kasuuC += (kasuuB >> (shift-1)) & 1; // 情報落ちするbitの四捨五入
}
kasuuB = kasuuC;
}else{
maxExp = vb.s.Exp;
shift = vb.s.Exp - va.s.Exp;
kasuuC = kasuuA >> shift; // 指数部の合せ
if(shift){
kasuuC += (kasuuA >> (shift-1)) & 1; // 情報落ちするbitの四捨五入
}
kasuuA = kasuuC;
}
kasuuA += kasuuB; // 仮数部の加算
vc.s.Fraction = kasuuA;
if(kasuuA >= (1<<24)){ // 桁あふれ
vc.s.Fraction = (kasuuA >> 1);
}
kasuuA>>=24; // オーバーフローした分
vc.s.Exp = maxExp + kasuuA;
return vc.FloatValue;
}
注意) 処理を分かりやすくする為、負の数や0に対する計算及びNaNや∞などは一切考慮しておりません。
これをWindowsマシン上で実行すると分かりますがコプロセッサで計算された答えと比較した時仮数部で
最大1bitの誤差があるようです。

***


どうですか?謎だった浮動小数点が少し理解できた気がしませんかexclamation&question
この手の内容はなかなか知り得にくいところだと思われます。
因みに引き算も同じような要領で計算する事で行えます。(アンダーフロー発生時に指数から引けばいけます)
次回もネタに困りそうなら乗算でもやろうかなと思います。乗算は加算よりも簡単です。
ではまたexclamation×2

*1 バイアス(bias) 下駄履きとも言ったりします。(例 下駄を履かせる)


posted by 管理人 at 14:57 | 研究・開発

2011年10月12日

浮動小数点のカラクリ

つい最近まで半袖ブティックで過ごしておりましたが、10月からは肌寒く感じ
衣替えしました近藤です。ご無沙汰しております。

本日で4回目の書き込みになります。今日はあまりご存知ないかもし
れない浮動小数点ネタを書こうかと思います。

最近のコンピュータでは浮動小数点を計算する為のコ・プロセッサが
標準で搭載されておりますが、組み込み系システムなどではいまだ
未実装だったりします。システムで浮動小数点計算が必要無ければ
それで構わないと思います。
が、3Dゲームなどはこれ無しではろくに開発出来ないほど重要な
ファクターになっております。

現在主に使われている浮動小数点は32bit型のfloat、64bit型のdouble
ですが、シェーダーなどでは16bit型などもあったりします。
どの型もbit幅が異なるだけで本質的には同じです。

内部の構造を大きく分けて3つに分類できます。


  ・符号
  ・指数部
  ・仮数部


【符号】

符号は+と−を表すものでどの型であっても1bit幅で最上位bitに
割り当てられます。0の場合が正の数、1の場合が負の数となっています。

【指数部】

指数部は2のn乗のnの部分に該当します。例えば16を表現する場合、
24で4となります。
32bit型の場合は8bit分、64bit型の場合は11bit分割り当てられています。

【仮数部】

指数部だけで表せる数は大雑把(2の累乗単位)になりますので、
その間を補間する為に用意されています。例えば仮数が8bit幅だった場合、
9bit目が1で残りが0の数値を1.0と見なした細かい数値が固定小数として
格納されています。(8bit幅の場合,0x100=1.0で0x80なら0.5といった感じです)
この場合、数式で表すと


          0≦仮数部<1


になります。最大でも1にはなりません。32bit型の場合は23bit分、
64bit型の場合は52bit分割り当てられています。

それぞれの構造での数値表現は以下のようになっています。


    値=(2指数部) × (1.0+仮数部)


指数部は2倍ずつ変化し、12倍までの範囲は仮数部で補われると
いった感じです。できた値に対して符号=1なら負の数に、符号=0なら
正の数になります。
例えば3の場合は (21)×(1+0.5)=3で指数部は1、仮数部は0.5になります。
5の場合は (22)×(1+0.25)=5となり、指数部は2、仮数部は0.25となります。
0の場合は、、、後ほど説明します。

1より小さい小数数値の場合はどう表現するのでしょうか?例えば0.0125など。
実は指数部に細工があります。指数部は32bit型の場合、8bitの幅があります。
8bitだと0〜255まで表現できます。これをそのまま2の指数にすると
20〜2255
が扱えますが指数からだいたい中央の127を引く事で2-127〜2128まで
表現できるようになる訳です。但し、指数部の0と255は特別な意味が
割り当てられており使える範囲は2-126〜2127となります。
2-126は10進数で表すと1.175494×10-38のだいたい小数点以下38桁
ぐらいまで扱えます。
2127は10進数で表すと1.701412×1038の小数値と同じく
だいたい38桁ぐらいです。
32bit型浮動小数点でもかなり大きい値を取り扱う事ができます。
しかし実際の有効な精度といえば仮数の幅に依存するので223
精度で10進数でいうと7桁ぐらいです。

指数部の0と255について説明します。
指数部0で仮数部も0の場合数値は0という扱いになります。
指数部0で仮数部が0以外の場合は非正規化数、指数部255で仮数部0の場合は
無限大、指数部255で仮数部0以外の場合NaN(Not a Number)という特別な
扱いになっています。
表にすると以下の通りです。

種類    指数部   仮数部
------------------------------
ゼロ      0      0
非正規化数  0      0以外
正規化数  1〜254   任意
無限大    255      0
NaN     255      0以外

お気づきかもしれませんが実はゼロを表現する方法が2種類あります。

 符号0、指数0、仮数0の +0
 符号1、指数0、仮数0の −0

どちらも同じ0です。cなどで

if(a == 0.0f)

という判定を行うとどちらも同じ真になるはずです。

64bitのdouble型の場合もそれぞれの幅が広がっているぐらいでほとんど
同じ仕組みになっています。

浮動小数点の仕組みが分かると色々と面白い事ができます。例えば0〜1
(正確には0.9999…)までのランダムな数値を簡単に作る事ができます。

32bit型の場合で説明します。方法はまず仮数部23bit分をランダムな
数値で埋めます。(rand()を使用する場合は注意が必要です。大抵の
処理系では0〜32767までしか返さない為15bit分しかなく残り8bitも
何か設定しないといけません。)
次に指数部は20になるよう127をセットします。これで
(20)×(1+ランダム)になり、1〜1.99999…の値が生成できる
ようになりました。
あとは出来た数値から1を引けば出来上がりです。Cで書くと以下のような
感じです。


union FLOATING_VALUE{
float FloatValue;
struct {
unsigned int Fraction:23;
unsigned int Exp:8;
unsigned int Sign:1;
}s;
};

float GetRandom()
{
FLOATING_VALUE v;
v.s.Fraction = rand() | ((rand() & 0xff)<<15);
v.s.Exp = 127;
v.s.Sign = 0;
return v.FloatValue - 1.0f;
}


※ビッグエンディアンのシステムではbit-fieldの上位と下位が逆になる場合があります。
ターゲットがWindowsの場合は変更の必要はありません。

ついでにNaNの検出する関数は以下のような感じです。


bool IsNaN(float a)
{
FLOATING_VALUE b;
b.FloatValue = a;
if(b.s.Exp==255 && b.s.Fraction!=0){
return true;
}
return false;
}


他にも絶対値計算や符号反転など簡単にできちゃいます。グッド(上向き矢印)

今回は実際の四則演算なども説明したかったのですがかなり長くなりそうなので
省略させていただきました。

浮動小数点仕様はIEEE754(IEEEは「あいとりぷるいー」と読む)にて仕様が規格化されています。
興味ある方は参照してみて下さい。次回はプログラムネタを扱いたいと思います。ではexclamation


posted by 管理人 at 16:25 | 研究・開発

2011年08月24日

大気散乱シミュレーション -Atmospheric Scattering-

こんにちは、イワサキですわーい(嬉しい顔)
さて、今日は技術的な話題ということで『大気散乱シミュレーション』のお話をしてみたいと思います。
技術資料がWEB上に公開されていますので、その内容を技術デモと共に紹介します。


2011-08-24-0.jpg
(C) 写真素材 足成

屋外のゲームシーンで最もよく目にするであろう『青空』。
一見単純そうで、実はシーンの空気感に影響がある無限遠背景です。
ゲームの中で「天球」を表現する際には、従来の古典的な手法では半球ドームなどの遠景で覆えるモデルに青空テクスチャをぺたっと貼ってそれを天空としていました。
青空の本物っぽさというものはデザイナーの技量と感性によって再現されることが多かったと思います。場合によっては実際に空の写真撮影を行ってその色味を再現したりすることもあるかと思います。

これを実際に物理現象として起きている状況をリアルタイムシミュレーションで再現してゲーム内に活用してみよう!というのが今回のお題です。

この技術を導入することで次の点に特にメリットが出ます。
デザイナーが苦労することなく、簡単に実写品質を得ることができるようになりますぴかぴか(新しい)ぴかぴか(新しい)ぴかぴか(新しい)

2010-10-14-0.png


1昼夜の動的な変化、時間の移り変わりがリアルタイムで表現できる
  次項有時間の概念があるゲームでは必須になってくる表現を太陽の方角を指定するだけでシーンの情景を作りだすことができます。

2地平線付近の夕焼け、朝焼けの微妙な色味が計算で再現できる(見た目が実写により近くなる)
  次項有大気中の微細粒子(塵)のシミュレートによって実写の空気感が出ます。
 又、火星の大気や未知の惑星の大気など、地球外の惑星の大気組成も再現できます。
 そして、標高・高度によって眼下の視界にも霞がかかります。
 今までフェイクで実装していたフォグ実装が不要になりますぴかぴか(新しい)

3昼間になると星空が太陽光によってかき消されて見えなくなる現象が表現できる。
  次項有フェードイン・フェードアウトでフェイクで昼夜入れ替えるのではなく、
実際の光学的な現象で星空が消えるようになります。

4大気圏を表現することで付加価値が生まれる。
  次項有宇宙空間と地上とをシームレスに行き来することができ、金環現象などの光学的現象もリアルタイムで見ることができます。

2010-10-14-0.png


オープンフィールドのゲームを制作する際に昼夜の概念があったりするとシーンごとに時間帯が異なって屋外の風景も太陽の位置によって大きく変化します。
又、天候などでさらに豊かな表情を表現していくことで、よりリアリティをもたせて没入感を高めます。
今回は時間帯によって空の色が変化していく様子にクローズアップしてみます。
それではまずは恒例のデモプログラムを公開します。




このデモは以下からダウンロードすることができます。
今回もWindowsXPでも閲覧実行可能にするためにDirectX9でデモを作成しました。
実際にはDirectX11で実装することで実行効率を上げ、さらに速度を向上させることができるようになります。

太陽光晴れが大気圏内で多重散乱する現象をシミュレートします。


◎動作可能な環境

 <動作条件>
 Windows XP/Vista/7 DirectX9.0c
 ShaderModel 3.0 以降
 ※注意※ 今回のデモは大変複雑なシェーダーになっています。
 ShaderModel 3.0 以降対応でもドライバの問題などで動作しない場合があります。
 なるべく最新版のグラフィックドライバをご利用ください。
 尚、デモの中でfp16浮動小数点バッファを使用していますので、SM3.0対応GPUでもfp16に非対応のGPUでは起動できません。


2011-08-24-1.jpg2011-08-24-2.jpg
2011-08-24-3.jpg2011-08-24-4.jpg
2011-08-24-5.jpg2011-08-24-6.jpg


Download
HexaSky.zip (約1.36MB)

【動作確認済ハードウェア】
NVIDIA GeForce GT 420
NVIDIA GeForce 9800 GT

【操作方法】
十字キー↑↓…位置を移動
左マウスクリック+ドラッグ…視点を回転
右マウスクリック+ドラッグ…全天の姿勢を回転
Zキー  … 大気散乱ON/OFF




今回のデモは見た目の品質も確保できるように大気散乱シミュレーションの他に
次の技法を併用しています。

ひらめき浮動小数点バッファHDR
ひらめきグレア表現(ブルーム、カメラ絞りの回折9枚羽根)
ひらめきレンズ表現(周辺減光ビネッティング、フレア)
ひらめきトーンマッピング(※今回は評価測光は無く、動的ではなく静的なものです)

屋外光散乱ではなく大気散乱シミュレーションを選択した理由は、
大気散乱の実装によって屋外光散乱も全て実現できてしまうことにあります。
これによって宇宙からも地上からも自在に操れるようになります。
このデモでは春分点で高緯度(北極)でのカメラ視点にしていますので、太陽はさほど高く昇らず、そして夜も薄く地平線が明るくなっているのが観察できます。
十字キーで大気圏を出入りすることで、そこに大気の層を感じることができます。
操作の関係上、地球を固定して相対的に全天を回転させていますが、本来は天動説ではなく地動説ですよね。


デモで使用したテクスチャは次の2つです。それぞれ下記リンク先の素材を使用しています。
ペン地球の地表テクスチャ 32k Virtual Earth Textures for Celestia
   http://celestia.h-schmidt.net/earth-vt/
ペン宇宙全天テクスチャ Axel Mellinger's Milky Way Panorama 2.0
  http://home.arcor-online.de/axel.mellinger/

2010-10-14-0.png

このデモについて詳しい解説・続きを読む

posted by 管理人 at 21:02 | 研究・開発