過去ログ(log2.htm)←過去ログIndexprogramming noteIndex

ここの駄文は特に駄文指数が高いです(^_^;)

2000/01/08 OpenGLでテクスチャレンダリング
1999/11/15 修正
1999/11/12 色々な当り判定
1999/10/23 privateメンバにアクセスする方法
1999/10/22 ライトマップ
1999/09/26 ワンスキンアニメーションを考える
1999/07/24 弾丸の軌道その1

 

 


OpenGLでテクスチャレンダリング 2000/01/08

最近、お年玉でOpenGLの本(オフィシャルマニュアル。リファレンスマニュアルあわせて、二万超えました(^_^;))を買いました。読んでみて思ったのですが、、OpenGLは感動するほど簡単ですねえ。なんかDirect3Dを使うのがあほくさくなってきました(^_^;)。とはいえ、Direct3Dは良くも悪くもM$が開発したものなので、無視するわけには行きませんしね。それに、Direct3Dが何も考えずバシバシ新機能を搭載するので、最新機能という点ではD3Dのほうが一枚上手です。その分ごちゃごちゃしていて、ただでさえ使いにくいのがさらに使いにくくなっているのですが・・・

それは置いといて、最近巷で話題になっている(?)テクスチャレンダリングを、OpenGLで実装してみたいと思います。応用したら、ソフトシャドウや複雑な面への映りこみ等々、表現の幅がぐっと広がるはずです。

先に書いておきますと、OpenGL1.1ではテクスチャに直接レンダリングする手段はありません。ので、フレームバッファに書き込んだ画像をテクスチャに転送するという、ちょっと回りくどい方法をとらないといけません。これはどのようにするかというと、実は関数一つ呼び出せばすんでしまうのですよね(^_^;)。見習ってくれ、D3D。

で、その関数というのが、glCopyTexImage2Dというものです。そのものずばり、フレームバッファの内容をテクスチャに転送するメソッドです(厳密に言うとちょっと違うようですけど)。使用上の注意としては、フレームバッファのよりも大きな範囲を指定したら、何が起きるかわからない、ということぐらいですかねえ。

ただ、このメソッドには少し問題点があります。現在のところ、この処理はすべてソフトウェアで処理されるため、遅いということです。(by Masaさん)むー、このままじゃゲームに使えませんねえ。将来改善されることを期待しておきましょう(消極的(^_^;))。

サンプルを用意しました。こちらです(ソース付き)。実行にはGLUTというライブラリが必要なので、ない方はこちらもDLしてください。

次はD3Dでテクスチャレンダリングをやってみたいと思います。これが結構面倒でして・・・


修正 1999/11/15

すいません、思ったとおり、Triangleにはバグが潜んでいました(-_-;)。修正しましたので、こちらをDLしてください。

持っていってください


色々な当り判定 1999/11/12

球と三角形、円と線分の当り判定はもう紹介したので、直線と三角形の当り判定&線分と三角形の当り判定を紹介します。

直線と三角形の当り判定

はっきりいって、この当り判定はものすごく簡単です。というのは、球と三角形の当り判定の様に図形的に解かなくても、数式だけで解けるからです(こちらの方が簡単と思うのは僕だけ?)。まあ、これは線分と三角形の当り判定への布石に過ぎませんけど。では、簡単なのでさっさとすませてしまいましょう。

直線は以下の式で表されます。
P=A*t+B (P、A、Bはベクトル。tは変数)
また、平面の式は以下のように表せます。
P・N+D=0(Nは平面の法線ベクトル。Dは定数)

この時Pは、どちらの図形にも含まれる点、つまり、両方の図形の交点なわけです。つまり、Pを求めればよいわけですね。Pを求める為には、sを求めればよいので、平面の式に直線の式を代入して、
(A*t+B)・N+D=0
Nx*(Ax*t+Bx)+Ny*(Ay*t+By)+Nz(Az*t+Bz)+D=0
t*(Nx*Ax+Ny*Ay+Nz*Az)+Nx*Bx+Ny*By+Nz*Bz+D=0
t*(N・A)+N・B+D=0
t=-(N・B+D) / (N・A)

したがって、PはA*( -(N・B+D) / (N・A) ) + Bとなるわけです。後は、この点が三角形の内部かどうかを判定して、終わりです。

線分と三角形の当り判定

先ほども書いたとおり、上の当り判定はこの当り判定の布石です。とはいえ、上の当り判定にPが線分上にあるかどうかの判定を追加しただけのようなものですが(^_^;)。では、説明をはじめましょう。

線分の両端をQ、R(両方とも位置ベクトル)と置くと、直線の式は、以下のように表されます。
P=(Q-R)*t + R
で、tはどうなるかというと、以下のようになります。
t=-(N・R+D) / ( N・(Q-R) )
そして、tが0以上1以下の時、Pは線分QR上に有ります。これはどうしてでしょうか。

hittest.gif (2964 バイト)

うまく言葉で説明しにくいので、またまた下手な図の登場です(^_^;)。Pというのは位置ベクトルRに長さtの方向ベクトル(Q-R)を足したものとして表されます。方向ベクトル(Q-R)は、Rから見たQの位置という意味です。これを踏まえた上で、図をよく眺めてみましょう。
もしもtが1より大きかったら、図の通りQを通りぬけてしまい、当然PはRQ上に存在しません。また逆に、tが0より小さかったら図のように当然RQの範囲外になってしまいます。tが0から1の間のとき、PはきれいにRQ上にいることになります。ん〜、わかりにくい説明ですね(^_^;)

整理してみると、線分QRと平面との交点Pは(Q-R)*t + R (ただし、 t=-(N・R+D) / ( N・(Q-R) ) )で、0<=t<=1の時に存在するということになります。後は、これが三角形内部にあるかどうかを判定するだけです。

C++で書いてみると、こうなります。

bool Triangle::hitTest(const D3DVECTOR& begin,const D3DVECTOR& end,D3DVECTOR& pos)
{
    //aはパラメータの係数。直線の定数はbeginで代用
    D3DVECTOR a=end - begin;
    //tは直線のパラメータ
    float t;

    //tを求めることで、交点を求める
    t    = -calcRyouiki(begin) / DotProduct(n,a);

    //0=<t=<1以外の場合、まず交差していないので抜ける
    if( t<0.0f || t>1.0f )return false;
    //面と線の交点を求める
    pos=a*t + begin;

    //三角形内外判定
    return checkInTriangle(pos);
}

なにか妙な感じがした人、正解です(^_^;)。これは、LightMapのテストプログラムを作っている最中に構築した、Triangleというクラスのメソッドを抜粋しているのです。Triangleクラスが欲しい人は、持っていってください注意:一応テストは済ませてあるつもりですが、ひょっとしたらバグが有るかもしれません。

しかし、こういうこと高校では教えてくれませんねえ。円周率は3.14から3になると聞きますし、これからの教育が心配です。おっと、僕は傍観者じゃないや(^_^;)


privateメンバにアクセスする方法 1999/10/23

C++で、privateメンバを外部からアクセスする方法。

#define private public
#include<foo.h>

(苦笑)


ライトマップ 1999/10/22

十月のはじめ頃に、CPUとビデオカードをフリーマーケットで譲ってもらったので、僕のPCが劇的に速くなりました(^_^)。もう昔と比べたら爆速です。今まで紙芝居かい!と突っ込みを入れたくなるほど遅かったゲーム(全部体験版ですが)が、さくさく動いたことには、本当に感動しました。誇張じゃ有りませんよ!

そういえば、譲ってもらった3Dカード、MonsterFusionには、二つのゲームが入っていました。一つはMS製のよくわからないバイクレーシングゲームと、もう一つはUnreal(11レベルまで)です。さすが発売当初各所で騒がれただけあるUnreal、グラフィックスには目を見張るものがあります。僕が特に印象に残ったのは、Unrealでの光の表現でした。

というわけで、Unrealを真似てみようと思います。よい技術はどんどん盗みましょう(^_^)。真似てみるのは、Unrealで一番最初に手に入るレーザーガンのようなものの弾丸です。この弾丸は、それ自体が発光していて、壁等を照らします。要するに点光源ですね。しかし、ここでDirect3Dの点光源を使うわけにはいきません。というのは、この光源は所詮シェーディングにしか使われないからです。もしUnrealのようにきれいな円状に壁を照らすためには、壁のポリゴンをかなり細かく分割しないといけません。これがパフォーマンスに多大な影響を与えることは想像に難くありません。

では、どうすればよいのでしょうか。答えは、実はDirectX6SDKのIMサンプル内にあります。ライトマップを使うのです。ライトマップが一体どんなものなのかは、ここここを見てください。読み終えましたか?それでは、ライトマップを利用して点光源をどのように実現するのかについて触れたいと思います。

とりあえず、毎回恒例の下手な図に登場してもらいましょうか(笑)

lightmap1.gif (2825 バイト)

黒い線が光が投影される面で、暗い黄色の丸が光源、青い円が光源から光が届く範囲だとすると、明るい黄色の線が光が描かれる場所ということになります。この明るい黄色の線の場所を算出できれば、後はライトマップを描くだけです。さて、その方法を考えてみましょう。

1.投影される面の法線ベクトルをZ軸にする行列を作成する。
え、そんな行列の作り方知らない?まさか知らないわけ無いでしょう、IMを使うのならば絶対扱う行列の作成方法と同じなのですから。任意のベクトルをZ軸にとる行列・・・そうです、視点変換行列です。視点の位置をゼロにして、回転角もゼロにします。後は、視点から見る方向を投影される面の法線ベクトルにすれば出来上がりです。これは、事前に計算できるので、メモリに余裕があれば先に計算しておきましょう。
*別に法線をX軸にする行列でもかまいません。僕はD3DUtil_SetViewMatrixを使いたいがため(つまり手抜き(^_^;))に法線をZ軸にする行列にしただけです。

2.行列を光源の座標と面の頂点にかける
これを行うと、こうなります。

lightmap2.gif (2703 バイト)

投影される面がXY平面に平行となるので、この後の計算が楽になります。

3.ライトマップを貼り付ける位置を決める
まず、平面と光源との距離を求めてみましょう。適当な面の頂点のZを取って、光源の座標のZとの差の絶対値が光源と平面との距離です。もし 距離>光が届く範囲 だったら、これから先の処理は無意味なので、とっととやめてしまいましょう。
そして、いよいよ貼り付ける位置を決めます。厳密にテクスチャの大きさを求めても良いのですが、厳密にやっても大して結果は良くなりそうに無いので、ここはやはり手抜きをしましょう。

テクスチャU座標 = (面の頂点のX座標 - 光源のX座標) / 光の届く範囲
テクスチャV座標 = (面の頂点のY座標 - 光源のY座標) / 光の届く範囲
光の強さ = | 面の頂点のZ座標 - 光源のZ座標 | / 光の届く範囲

これを面の各頂点ごとに行い、すべての頂点においてのテクスチャ座標を得ます。

4.描画する

あらかじめ断っておきますが、この部分はまだ試していないので、すべて予想で書いています(^_^;)。おそらく大丈夫だと思いますが、もしダメだった場合は見つけ次第僕が密かに修正するので、そのつもりでいてください(^_^;)

後はこの座標を元に描画するだけです。ライトマップにおいてシェーディングは必要ないので、ライトマップを描画するのにはD3DLVERTEXを使う方がよいです。光の強さを頂点カラーで代用することができますし(多分)。

さて、描画上の注意なのですが、まずZバッファ書き込みはオフにして、ZテストはD3DCMP_EQUALを指定します。そしてテクスチャのブレンドは加算合成でよいのでSrc、DestともにD3DBLEND_ONEにします。そして最後に、テクスチャアドレッシングモードをD3DTADDRESS_CLAMPにしましょう。テクスチャアドレッシングモードの変更は、SetRenderStateのD3DRENDERSTATE_TEXTUREADDRESS で変更できます。これは何を変更するのかというと、普通、テクスチャ座標が1.0を超えると同じテクスチャが繰り返されますが、これを指定すると、テクスチャ座標が0.0〜1.0の範囲を超えるとテクスチャの画像の周囲のピクセルを繰り返します。ここで注意なのですが、ライトマップに使う画像は周囲が黒で無いといけません。どうしてもテクスチャ画像の周囲のピクセルを黒にできない場合は、テクスチャアドレッシングモードをD3DTADDRESS_BORDERにして、繰り返す色を黒にしましょう。繰り返す色の指定は、SetRenderStateのD3DRENDERSTATE_BORDERCOLORの欄を見てください。うそを書いていないことを祈るばかりです(笑)

最後に一つ。この記事はFried Chicken氏の”テクスチャによる影の投影”をヒント(つーかパクリ?(汗))に書いています。なので、参考記事を読むことをお勧めします、というか読まないとわからないと思います(^_^;)。そちらのほうがここより何万倍もわかりやすいです。

 

理論だけにならないよう、現在サンプルプログラムを製作中です。いつものことですが、期待しないで待っていてください(^_^;)。


ワンスキンアニメーションを考える 1999/09/26

ワンスキンアニメーション・・・厳密な定義はよくわかりませんが、ここではとりあえず継ぎ目の無い3Dアニメーション、と定義しておきます。

これ、簡単なようで考えてみると奥が深そうです。実現する方法をちょっと考えてみます。

oneskin1.gif (2638 バイト)

赤い点を中心に青い頂点を回転させて、それを線でつないでいます。これがおそらく普通の方法でしょう。ここでのキーポイントは、赤い点と青、緑の頂点の取り方です。もし真ん中あたりにある緑の頂点をそろえてしまうと、青の頂点を回転させたときにへしゃげて(ってこれ方言かな?)しまいます。この方法は、実現するのは簡単ですね。
しかし、ここで問題が発生します。90度回転するぐらいならこれでOKですが、180度回転したりすると困るのです。赤い点を増やして滑らかにするという方法がありますが、頂点数が増えてしまうの上、計算負荷が増大するのであまり好ましくないです。場合わけするという方法もありますけど、これをしてしまうとモデラーさんに多大な負荷がかかってしまいます。

この問題の解決法の一つに、ボーンが有ります。これもまた色々な方法があるようですが、その中の一つがここで紹介されています。これを使えばいいですね。

・・・うーむ、これでは話が終わってしまいますね。折角なので、僕の考えた方法も紹介します。ボーンの”ねじり”に関する解決策の一つです。(注:例によって”実はよく知られた技術”という可能性が高いので、知っている人は笑って読んでください(^_^;)

細長い帯状の紙を考えてください。そして、その紙をピンと張って、両端をねじってみましょう。すると、きれいにねじれるはずです。・・・よくわからない説明ですね(^_^;)。つまり、一様な物体をねじると、一様にねじれていくということです。ますますわけがわからなくなったところで(^_^;)、図を見てみることにしましょう。

oneskin2.gif (2913 バイト)

①が初期の状態です。黒い四角がねじる細長い紙、青はボーンと考えてください。②で、ボーン上端に180°の回転を加えてみます。すると、③のような状態になります。非線形に曲がっているように見えますが、ちょっと待ってください。角度に注目してみると、線形なのです。つまり、ボーンの上端から離れるにしたがってねじれている角度を線形に減らしていき、ボーンの下端につくころにはねじれている角度は0°になっているようにすればよいのです。

さて、実現方法についてなのですが、ボーンを軸にした行列を作成して、その軸周りの回転行列を合成し、各頂点ごとにかけていく・・・という方法が考えられます。しかし、この方法、計算がとんでもなく多くなります。一つの頂点ごとに回転行列をつくりかえないといけないので、当然です。

残念なことに、僕はこれを解決するぐらいのスキルを持ち合わせていません(-_-;)。いい方法があったら教えてください(^_^;)。


弾丸の軌道その1 1999/07/24

ガリレイ(だったっけ?(^_^;))がピサの斜塔から重さの違う二つの玉を落として、落下速度と重さは関係無いことを証明したという有名な話がありますが、もし高層ビルから同じように二つの玉を落としたら、一体どちらが先に落ちるのでしょう?同時に落ちる?いえ、違います。重い方の玉が先に落ちるのです。

ところで、高いところから物体を落とした場合、大体毎秒9.8m加速します。しかし、雨を見てみると同じ速度で落ちているようにしか見えません。これは何故でしょうか?実は(というか当然?)、空気抵抗が存在するからです(これが無ければ、雨が降っただけで地球は滅亡してしまいます(^_^;))。この空気抵抗というのは、物体の速度が速くなるにつれて増大するという面白い性質を持っています。これをよく考えてみてください。まず最初は(図の①)当然加速する度合い(後の説明のためm x gと表記しています)の方が空気抵抗より大きいですが、だんだん速度が増してくると(図の②)、空気抵抗が大きくなっていき、ついには(図の③)加速する度合いと空気抵抗が等しくなってしまいます。これはもう加速しない、つまり等速直線運動(同じ速度で移動すること)をしているということです。雨もこの状態になっていて、同じ速度で落ちてきていたのですね。

kasoku.gif (2789 バイト)

ええと、上には加速度と書いていましたが、実はこの言葉は不適切です。というのは、空気抵抗は力であって加速の度合いではないからです。これを力に直すには、質量をかけてあげれば良いので、質量(m) x 加速の度合い(g = 9.8)となります。折角だから空気抵抗も式に直してみましょう。空気抵抗は速度に比例するので、空気抵抗(抵抗力R)=空気の状態によって定まる定数(k) x 物体の速度(v)となります。あれ、なんとなく③の時の速度がもとめられそうですねぇ。求めてみましょうか。
③の状態は空気抵抗=m x gの時でしたね。代入してみましょう。
k x v=m x g
これを整理して
v=m x g / k
出ましたね。この時のvの値を、専門用語で終端速度と言うそうなので、今後そう呼ぶことにします。

さて、話を戻しましょう。といっても、ここまで読み進めていたら何故重い玉の方が先に落ちるのかわかりますね。質量が軽い玉よりも大きいため終端速度が速いからです。ピサの斜塔からだと軽い玉が終端速度に達してなかったから同時に落ちる結果となったのでしょうが、高層ビルともなるとこの違いが顕著にあらわれますからね。

そういえば、表題は弾丸の軌道となってますね(^_^;)。そこまで話しを進めたいのですが、もう眠たいので(^_^;)次の機会ということで。

注意:
物理をまじめに受けた方にとってはチャンチャラおかしい内容&間違いだらけと思うでしょう(^_^;)。もし間違いを見つけた方は遠慮無く言ってください。それと、まだ物理を受けてない人!真に受けたらダメですよ!正規の授業を受けた人間が書いた文章ではないのですから(汗)