|
前回大風呂敷を広げてしまったのですが、頂いたお手紙を拝見しますと、どうやら最初のスタートラインからして個々人でバラツキがあることが解りました。これではどうしようもないですから、ここで今一度、最初のスタートラインに立つまでのおさらいをしておこうと思います。
まず、従来の、そして現在、国内の3D関連書のほぼ全てを占領していると思われる概念、それが同次座標です。ですから日本語で掛かれた3D関連書で勉強するとまず間違いなく同次座標を使ったやりかたを頭に刷り込まれてしまうでしょう。
同次座標を使った計算が基本ですので、そこから話を始めましょう。
同次座標は四次元の考え方を部分的に取り入れたものですが、実際の座標は(x,y,z,1)というふうに、余分に1を加えるだけです。
これに対して、順番にワールド座標系への変換行列、視野座標系への変換行列(これは視野座標系をあらわす行列の逆行列です)、スクリーン座標系への変換行列を掛けていき、できあがった行列をAとします。
ポリゴンを構成する各頂点は行列Aに掛けるだけで、スクリーン同次座標(x,y,z,w)が求まりますから、あとは同次座標を(x/w,y/w,z/w)として変換してやるだけで一件落着というわけです。これにかかる計算コストは、4×4行列に対する乗算16回数と、同次座標変換にかかるコスト除算三回は除算一回、乗算三回に削減できます(除算より乗算の方が圧倒的に速いのです)。
そして合計すると乗算19回、除算一回のこのやり方こそが現在Direct3D RMが内部的に行っている操作です。
このプロセスを最適化しようと思うとき、大抵のひとが真っ先にとりかかるのは4×4行列の計算を3×3行列の計算と単純な足算で行うことです。こうするだけで16回かか行列計算部分が、9回に減ります。
しかしこのやり方の場合、Direct3Dが想定している同次座標ではなく、最初からスクリーン座標を求めてしまうことになりますから、rhwを別にもとめなくてはなりません。
こないだは気づかなかったのですが、普通の座標からDirct3D向きのZ値とrhwを求めるには下記のようにすればいいということがわかりました。
sz = 1-1/z;
rhw =1/sz;
このときzは普通の視野座標系におけるz座標、szはスクリーン座標系におけるz座標(zバッファリングのために必要です)、そしてrhwはその逆数です。
これをとりあえず16ビット精度におとしながらやってみたのが、昨日のサンプルの一部でした。
昨日のサンプルのままではまだ不十分ですが、少し工夫するだけで、行列演算のための乗算9回とスクリーン座標を求めるための乗算2回、除算が三回になります。これはなんとも邪悪な結末です。
しかし数回の試行錯誤の後、この計算は以下のようなやりかたでもいいことがわかりました。
rhw = 1/z;
sz =1-rhw;
これは当たり前です。なぜなら、最初のやり方ではszを展開するとrhw = 1/(1-1/z)となっていましたが、rhwにとって、1-1/zとzでは意味的には似たようなものです。こうすることによって、除算を二回に減らすことができました。除算の削減は、とりあえずここらへんが限界でしょう(としておきましょう)。
さて、先日のサンプルがどうして偏屈にも、16ビット化をここまで推し進めているかというと、それにはちゃんと理由があるのです。おかわりと思いますが、それはMMX命令を活用するためです。
これも知っておかなくてはいけない知識ですので敢えて説明しますが、現在のPentiumプロセッサでは、それまでの常識を覆し、整数よりも浮動小数の乗算の方が数段高速になっています。手持ちの表では、整数の乗算は11クロックかかるのに対し、浮動小数点演算はわずか3クロックです。つまり三倍速い。
いわば今のPentiumにとって浮動小数点乗算は赤い彗星なのです。
しかしアズナブル少佐がそうであったように、浮動小数点演算にも弱点があります。それは除算が遅いということです。浮動小数点除算は約40クロック、整数除算はそれぞれ除数が8ビットの場合17クロック、16ビットの場合25クロック、32ビットの場合41クロックですから、私が目的とする、32ビット/16ビットの除算は浮動小数点除算を使うより高速です。
|
|
|
近年開発された重大なテクノロジの一つに、MMX命令があります。
出た当初は「ほとんど変わってない!」とか「高いだけで速くない」など散々な言われようでしたが、実際にはMMXによる効果というのは、いまのところ消費者側よりも開発者側にとって重要なものとなっています。
既存のソフトがそれほど高速化したように感じないのはそのためです。それでもいくつかの工夫が取り入れられているので普通のPentiumよりはいくぶん高速なはずですが。
MMX命令の画期的なところは一つの命令で複数のデータを演算処理できるようになった点です。これをSIMDといいますが、実はSIMD自体は古くからある古典的な技法です。ただ、これが民生用のマシンに乗っかったということがすごい。
私にとって重要なのは、4つの16ビット数の乗算と加算の組み合わせが、わずか1クロックでできてしまうことです。
これはすごいことです。MMXによって浮動小数点と整数の立場は再び逆転しました。明らかに、MMXを使った方が高速なのです。MMXならばひとつにつき乗算コストは最大0.25クロック(!?)になるのですから。
これを利用すれば、乗算を多用する3Dグラフィックスの計算に非常に有効であることはいうまでもありませんよね。
しかしMMXにも困った問題があります。
それは、浮動小数点機能とMMX機能を同時には使えないことです。
これがどうして困るのかというと、Direct3Dは浮動小数点演算を多用するように設計されているので、どうしても途中で浮動小数点機能とMMX機能を切り替えなくてはなりません。ところが、その切り替えのためのemms命令は、なんと普通のMMX Pentiumで80クロック!PentiumIIでも20クロックも掛かってしまうという困ったちゃんなのです。
80クロックといったら単純に考えてもMMX命令の乗算が80回できるだけの時間です。
そして恐るべきことにDirect3Dの頂点構造体(TLVERTEXなど)に代入する値は全て浮動小数点値でなくてはなりません。
このことはつまり座標変換計算にMMXテクノロジを使用する上で無視できない問題となります。
なぜなら、いくら他の部分を計算しても、頂点ひとつにつき80クロックないし20クロックの重しがついてしまうのです。それならば最初から浮動小数点で計算したほうがずっと良いでしょう。
この矛盾を解決する方法はいくつかあります。ひとつはとりあえず頂点を整数で全て計算してしまってからemms命令を使い、順次変換していくやり方です。しかしこのやり方はいかにも泥臭く、利口なやり方とはおもえません。
もうひとつはemms命令も浮動小数点命令も使わずに、自力で浮動小数点形式へ変換してメモリに書き込むやり方です。
私は後者を選択することにしました。
int data,bias; //合成する浮動小数点数の実体
float *fp; //ポインタ
short int fixed,*integer; // 合成するもととなる数
// 31 23 0
// |------|-------|-------|-------|
// |-> bias <-;|-> fixed <-;|
fixed = 26;
bias = 23;
data = fixed;
char str[256];
while( (data & 0x800000) ==0){
data << =1;
bias -= 1;
sprintf(str,"[%x %x]\n", bias,data);
OutputDebugString(str);
}
data = data & 0x7FFFFF;
data |= (bias+0x7f)<< 23 ;
fp = (float *)&data;
sprintf(str,"float :%f int %x %x: %d %x\n", *fp ,bias,fixed,data,data);
OutputDebugString(str);
|
右が、とりあえず試験的に作ってみた浮動小数点合成アルゴリズムです。
fixedというのが、もととなる16ビットデータで、これを32ビット整数であるdataに代入し、シフト演算を繰り返しながら計算を行います。
こうしてみるとなんとも手間がかかって遅くなってしまいそうですが、整数演算のほとんどはペアリング(二個同時実行)できるので、よほど下手に作らなければemms命令を使うよりも効率が良いはずです。
また、486以降から追加されたBSR命令を使えば、ループは不要になり、シフトは一回ですみます。ついでにMMX命令を使えば、二つの32ビット値を同時に使えるのでより高速になるでしょう。
書き込みもMMXを利用することで、64ビット長アクセスが可能です。
なぜこんなプログラムになるかというのは、いろいろと難しい問題を含んでいます。
ずっとずっと昔ならば、浮動小数点演算というのは、そもそも自力で行うものでしたし、コプロセッサなどはなかったのですから、この程度の変換計算は誰でも簡単に行えたのでしょう。
しかし、いまや高級言語を使うことがあたりまえになり、コプロセッサが普及し、浮動小数点演算を何の労苦もなく行えるようになってしまったので、私は今日の今まで浮動小数点方式の細かい部分について自分は殆どしらないんだということを思い知らされました。
このプログラムを書くまでには、文献を何度も読み直し、探し、また読み、プログラムを組み、電卓を叩く・・・といった作業をなんどもなんども繰り返す必要がありました。しかしよくできたパズルと同じでわかってしまえばなんということのないことでした。
ですから、この部分は敢えて解説はせず、皆さんの楽しみのためにとっておくことにしましょう。
というわけで、次回は、この変換ルーチンを高速化する・・・かな?
|
|
|
|