04/09/05 | 乱数+Rnd関数解析編[★★◎◎◎] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(いつもそうですが)どう考えても自分の趣味で突っ走った今回の戯言です。 日ごろゲームなり統計なりのプログラムを作ろうとすると、乱数が登場する場面は非常に多いです。 こんなとき、C言語ならrand、VBならRnd関数を使うことが多いと思います。 しかし、使ってて「Rndって何やってるの?」って思ったことはないでしょうか? 「いや、別にどうでもいいよ」と言われてしまえばそれまでですが、まぁ興味を持ったので調べて見ました。 乱数の基礎 擬似乱数 C言語では何を使ってる? いよいよVBのRnd関数 ランタイムの解析 Randomizeの謎 (2007/08追記)VBでRand/Randomizeを実装 まとめ 乱数の基礎 一応教科書にあるような話ですが乱数の基礎。 ここで言う乱数とは一様乱数を表すものとします。 一様乱数は範囲内の値をすべて同じ確率で返すような乱数です。 もうひとつの条件として、これまでの値から次の値が予測できないことが必要です。 たとえばサイコロの目やコインの裏表はそれぞれ各値は同じ確率で出ますし、次の値が予測できないので、一様乱数と言えます。 コンピュータは与えられた計算を高速に正しく行う機器であるため、乱数とは相性が悪いです。 もし何らかの計算式で一見ランダムな数列を計算できるとしても、計算で求められる以上何かしら偏りが出るとか、次の値が予測できてしまいます。 1つの乱数の生成法としては、コンピュータの外部から値を持ってきて加工すると言うものがあります。 代表的な物は以下のような物があります。
物理現象も厳密にはそのときの空気やら配線の状況がわかれば(人間やコンピュータが計算しきれるかどうかは置いておいて)生成される音や温度は予測可能だからです。 それこそ完全に真の意味で乱数を取ろうとするのであれば、量子力学がうんぬんとか言い出す事になります。 擬似乱数 だいぶ脱線しましたが、まぁそこまでやらんでもいいだろう、と言うことで良く使われるのが擬似乱数です。 上記のとおり、完全な乱数は生成するのが難しいため、計算によって乱数っぽく見える値を生成します。 (暗号論的には、長さに対して多項式時間で法則性が見つからなければ擬似乱数) 非常にシンプルな手法として、以下の2つを紹介します。
ここまで挙げてきたものでは、線形合同法がわかりやすいし次の値が予測しにくく、結局広く使われているようです。 (乗算器や除算器を持たない簡単な回路ならLFSRの方が実装しやすいでしょうが。) より質の高い(周期が長い、偏りが少ない)乱数生成アルゴリズムとしてはメルセンヌ・ツイスタ等が注目されています。 さすがに線形合同法ほどシンプルで高速なわけではありませんが、他の(もう少し質が高くて使われている乱数生成ルーチンに比べて)高速、プログラムもシンプルなようです。 いずれも共通しているのは、初期値を同じ値にすると同じ乱数列を出力するということです。 先ほども書いたように「正確に計算を行う」機械である以上当然ですが。 この性質はゲームのリプレイなどを保存するとき役に立ちます。 乱数の初期値だけ覚えておけば内部で乱数を使うゲームもリプレイを再現できるので。 C言語では何を使ってる? C言語と言うか、C言語のライブラリではどんな手法を使っているのかをちょっと見て行きたいと思います。 最終的にはVBのRnd関数が目的なわけですが、ここでC言語に寄り道する理由として、「VB以上に広く使われている物だけにそれなりな乱数生成ルーチンが使われているのでは?」と言うのもありますが、最も大きいのが「ソースファイルが公開されている物もある」と言うものです。 ここで重要なのは、C言語の規格(ANSI-C)では、乱数については以下のことしか決まっていないと言うことです。
実際にVC++、Cygwin、Fedore Core(Linux)の3つについて比較して見ます。 とりあえず先頭から10個の乱数を生成して見ました。 2行目はRAND_MAXの値です。
みごとにバラバラです。(なお、MingwはVC++と一致した。) Cygwinの1回目はなぜか0になってしまいました。 後述の計算式だと、最初の値が1であればの次に1481765933が出るので、1が出るならまだわかるのですが… では、具体的にソースを見てみた結果です。 (Fedore Coreってglibcを使っていると言うことでいいんでしょうか?今回glibcのソースを見ました。 もし違っていたらすいません。ブート時にrundom number generatorが云々っていうメッセージもあった気がする・・・) 結果ですが、VisualC++とCygwinは線形合同法、glibcはパラメータ指定で線形合同法と他の手法を入れ替えられる、という状態でした。 なお、glibcはデフォルトでは線形合同法でないパラメータになっていました。 その線形合同法でないパラメータはなんだかよくわからず。 それぞれ以下のような計算をしているようです。 いずれも初期値は1と言うのは共通。(Cygwinで最初0が出たのは謎)
いよいよVBのRnd関数 C言語では線形合同法を用いたライブラリがまだ現役で動いている環境もあることがわかりました。 んで、ようやくVBのRnd関数を見て見ることにする。 まず、VBのマニュアルからRnd関数の仕様を確認すると・・・
まぁ引数が0の時は直前の乱数ということで置いておいて・・・ まず、負の場合を考えて見ます。 負の場合は引数だけで返り値が決まり、前の値とかを考慮する必要がないからです。 とりあえず、ためしに-0.01〜-1、-1〜-100までを入力した時の乱数一覧を求めて見ます。 もし線形合同法であれば、簡単な線形の関係が出てくるはずです。 下の表では一部省略をしています。
これを見て、はっきりと癖がわかるのが左側の-1〜-100での変化の場合です。 差の値に注目して見ると・・・ 「入力する値が変化するにつれ、差が段々半分になっていってる」 「差が変化する間隔が倍々になっていってる」 境目はノイズっぽい値が混ざってはいますが、両方の性質が見て取れないでしょうか。 右側の-0.01〜-1.00での場合も、差の変化する位置は違うものの、両方の性質が見られます。 -1〜-100の例では、どうも-4、-8、-16、-32、-64あたりで差が変化してるような気がします。 さて、ここからRnd関数の法則を少しでも探って見ることにします。 問題は、「差が段々半分」「その間隔は倍々」と法則性があるんだかないんだかわからないこの差です。 もしかしたら検討のついた人がいるかも知れません。 それは、「Single値とか言っておきながら、実際のビットパターンに比例するんじゃないの?」と言うことです。 以後の話を理解する上で、小数がコンピュータ内でどう表現されているかを知っておく必要があります。 以下に簡単に書いておきます。 知ってる人は飛ばしても大丈夫です。 (これに関してはGoogleで検索すると色々出てくるのでそちらを確認してもいいかも。。
上のことを考えて見ると、指数部は確かに-2、-4、-8、-16、-32、-64で変化するので、そこで差が変化するのは納得できるし、指数部が大きくなってくると、値が-1ずつ変化した時の仮数部の変化が段々小さくなります。 このことから、「ビットパターンそのものに比例するんじゃないの?」と予測。 小数の値を一定間隔で変化させるのではなく、ビットパターンを一定間隔で変化させて見ました。 左側はビットパターンを16進数で80000001から1ずつ増加、右側は80100000から100000ずつ増加させて見た場合です。 最初が8になっているのは、浮動小数で先頭にある符号ビットを1にするためです。
ただ、右側を見ると0x1000000ごとにちょっと値がずれていることがわかります。 しかもその幅(-0.385681と-0.375000の差)は左側の差の-0.010681と一致します。 一体これは何なんでしょうか・・・ ランタイムの解析 上の様子を見る限り、ほぼ線形合同法っぽいですが、一部よくわからないところもあります。 そこで、ランタイムを実際に解析して見ました。 VB5の場合は、msvbvm50.dll内のrfcRandomNextと言う関数がそれに該当します。 この関数ですが、最初の方は何をやっているかわかりませんでした。 (一応、一番最初は引数が省略されている場合はビットパターン3F800000 = Singleで1を引数の来るべきだった場所に入れる、という処理を行っています。引数省略時と、引数が正の場合は同じ処理を行うのでこれはマニュアルどおりと言えるでしょう。) もしかしたらRandomizeステートメントの影響がここに出てくるのかも知れません。 その後の処理は以下のようになっているようです。 「現在の値」と言うのは内部で保持している32bitの整数です。 (注意する事として、入力した値を生のビットパターンで解釈したり、小数の値として解釈したりしているのを理解する必要があります。 例えば3F800000は16進数の整数だと見ると1065353216になりますが、小数を表してるととると1.0になります。 これは内部でCPUがどの命令で該当するデータを処理しようとしているかに依存します。)
3の式でも4の式でも最後に00FFFFFFとのANDをとっているため、「現在の値」は実質24bitの整数となります。 上記の通り、Single型の精度は24bitとなっているので、これにあわせたんでしょう。 24bitなら0〜224-1の値をとれるので、最後に2-24を掛けることで0〜1未満の値を返すことができます。 これで計算の式がわかったことになります。 4を見てわかる通り、線形合同法まんまですね。(FFFFFFとのANDは1000000でMODとるのと同じだし) 3を見ると、入力値が1000000変わる毎に出力がちょっとずれるのもわかります。 ちょっと気になるのが、VC++なんかは内部的に32bit持って置いて、16bit分だけ返り値を返しているのですが、こちらは内部的にも24bit、返り値も24bitの精度で値を保持することになります。 せっかく8bit余ってるんだから、内部的に32bitで持って置けば前のRndの返り値から次の値を予測できない(256通りになる)と思うんだけどなぁ・・・ Randomizeの謎 (2007/08にRandomizeの式について、後述のVBでRand/Randomizeを実装に付け足しました。) ここまで避けていた話題に、Randomizeステートメントがあります。 C言語の場合、乱数のシード値を指定するsrandとrandの関係は単純です。 srandはrandで利用する「現在の値」をそのまま更新するだけの物です。 VBでは、Rnd関数を負の値を引数として呼ぶと「現在の値」を更新できるので、これはsrandと近い性質を持っています。 Randomizeは引数をとるのですが、同じ引数を入れても後の乱数が必ずしも同じにならないという点があります。 同じ乱数列を再現するにはRandomizeは向きません。(最初に負の値でRndを呼んだ後、あとは引数を省略するのが良い) とりあえずマニュアルを見ると以下のようにかかれています。 乱数ジェネレータを初期化 (乱数系列を再設定) する数値演算ステートメントです。原因はDLLを解析しても良くわかりませんでした。 とりあえず実際にRandomizeを呼んで見てわかったこと。 同じ値をRandomizeに入れても、続くRnd関数の数列は異なる物になる。 これはC言語のsrandとは明らかに違います。 ただし、異なるとは言え、そのRandomizeまでに呼んだRnd関数の履歴が同じならば同じ数列を生成するというのがあります。 言い換えると、「これまでのRandomize関数の呼び出し方には依存しない。(最後に呼んだRandomizeの引数のみが影響する」「過去のRnd関数お呼び出し回数やその引数が異なると、Randomize関数の引数が同じでもRandomize後のRnd関数は異なる値を返す。」と言うことがあります。 結局「よくわからん」というオチです。 別にRandomizeで線形合同法のパラメータが変わるわけでもないですし・・・ もしRandomizeについて詳しい方がいたら教えていただけないでしょうか。 VBでRnd/Randomizeを実装 (2007/08加筆) よくわからなかったRandomizeですが、いっちゃんさんより解析結果をいただきました。 まず、Cで書くと以下のようになるとのことです。
これをRnd/Randomizeともに自分なりにVBにしたところ、以下のようになりました。 VBではUnsigned型がない上、そのままではオーバーフローしてしまうのでだいぶいじりました…
まとめ コンピュータに置ける乱数生成法は色々ありますが、中でも線形合同法は実装も簡単で一様な乱数を高速に作る事ができます。 そのため、特にソフトウェアで乱数生成ルーチンを実装する際に良く使われるようです。 実際、いくつかのC言語の環境でも使われていましたし、今回のVBも線形合同法と言っていいでしょう。 (2007/08修正) Randomizeがすっきりしないのが不満…だったのですが、いっちゃんさんの解析により中身が判明しました。 Rnd関数に比べるとRandomizeはだいぶ複雑ですね。 |
04/07/20 | 2度目の自作編 | ||||||||||||||||||||||||||||||||||||||
マシンを買い換えました。 まぁ一応自分の買った製品名とかの備忘録ということでだらだら書いてきます。 もしかしたら近いうちに安めの値段でPC買う人の参考には・・・ならないかな(^^; 前回は雑誌を読んでほぼそのとおりに構成したものの、今回はほぼネットの情報だけでチャレンジ。 とりあえずチップセットの名前とかぜんぜんわかんないのでそこら辺を1週間程度調べた。 パーツ購入 予算は7万円ということで土日秋葉原を回ってきました。 買ったもの・金額・店は以下のとおり。
無事に予算内。 TUKUMO exでは開店前から並ぶと3000ポイント還元のに並んだので、3000円分それも残った。 まぁ3000ポイント還元は3000円引きとは違うけど。 ケースが重いので2往復必要ということになり、まず土曜にケース目当てでTUKUMO exに朝から並んだ。 ケースはいいとして、買うべきものはどのパーツも約1万円。 TUKUMOの今回のポイント還元は2万円以上購入で3000ポイント還元だったので、あと12020円分何か買わないといけない。 他のものは他の場所の方が安く買える場所も多く、できるだけ他店と価格差が少なく、かつ2万円を大きく超えないということで、結局グラフィックボードにした。 GeForceFX5700LEぐらいかなと思ってたけど、GeForceFX5700(ただし、玄人志向のものはメモリクロックが通常55MHzに対し400MHzと少な目)のものを買った。 午後からは用事があったのでこの日はケースとVGAだけで終了。 で、2日目。 今度はいろいろ回るので午後からゆっくり。 人が多いし暑いしで疲れました。 まずCPU、これは大体の店で9000円台後半でした。 Athlonリテールのファンはうるさいと言うことだったんですが、2500+あたりからそうでもないということでBOXでいくことに。 ちなみに、PC-Success2号店では最後の1個でした・・・ WinXPもここがかなり安かったのでここで購入。 次にメモリ。 どうもOVERTOPがかなり安いという情報があったものの、偶然通りかかった道に同じく安いという情報のあったテクノハウス東映があったので見てみた。 バルク品としては評判のいいらいいInfineon純正のものが10400円だった。 ただ相性保障とかついてるかどうか忘れたんで、結局OVERTOPでSanMax=Hynixを購入。 全体的にどの店もバルクでないものはSamsung製のものが多かった。 最近のSamsung製はいまいちという話があったので避けましたが。 意外に迷ったのがHDD。 ATA133なのかATA100なのか書いてない店が多い。 書いてある店で型番を確認しつつ、ATA133、7200rpm、キャッシュ8MBのHDDを買いました。 後でわかったことだけどPC-Successの方が8円安かった。 HDDはSofmapやTUKUMOみたいなところでも100円とかしか変わらないから、ポイントカードあるならそっちでもよかったかも。 10000円超えてる店はほとんどなくてみんな9000円後半。 最後にマザーボード。 値段と安定性ということでKT600のものを探してたけど、Athlonは半分が64向け、残りがnForce2とKT880チップセットのものが多かった。 小さめの店だとKT600モノは扱ってないね。 とりあえず目をつけてたAK77-600Nを売ってたのがSofmapが7770円、PC-Successが7800円で、TUKUMOは7780円だったんだけどタイムサービス3%引きだったので7547円で購入。 この3%引きのおかげで最終的に予算内に収まりました。 2日ともTUKUMOに並んでた方がよかったかな・・・ ただ、HDDはともかく、CPU・OSは3000円ポイントもらってもWinXP Homeが14000円越えじゃPC-Successの方が安いからなぁ。 組み立て・OSのインストール とりあえず組み立て。 組み立てていてしまったと思ったのはケースのST-5688。 小さいのがいいかなと思って6688でなく5688にしたが、CPUのファンのすぐ上を半分電源が覆う。 結局CPU・SYSTEMの温度は50度程度なんでまぁ平気といえば平気かもしれないが、クロックアップとかクーラーを変えるとかは厳しそう。 中の配線もその分かなりやりにくい。 これは調査不足だったな・・・店頭で中身が見えなかったのもまずかったかも知れない。 6688もほとんど値段変わらないしな・・・ まぁ電源周りに苦労しつつも順調に組み立て。 電源を入れる・・・がBIOSが表示されない。 一応DVDドライブとかはしばらくなんかやってるので、画面に出ないだけで起動はしている模様。 マザーボードが悪いのかVGAが悪いのかわからなかったが、今までのGF2MXでも表示されない。 30分ぐらいいろいろやって、結局CMOSクリアをちゃんとやったら表示された。 最初FSBが100の設定になってたので、AthlonXP 1100MHzとか表示されてびっくり。 まぁすぐ166にしたけど。 しかし、上にあったようにAthlonのリテールファンは(最近マシとはいえ)うるさめと聞いていたが、今までよりよっぽど静かだ・・・ まぁ前は5400rpm、今回は3200rpmだから当たり前といえば当たり前だけど。 で、次はOS。 時間はかかるけど特に難しいことはない。 ここで、アカウント名を日本語でつけてしまって後で後悔した。 環境の移行 今までのメインHDDを付けて片っ端からファイルを移す。 設定もいろいろいじって結局今までのWin98とほぼ同じ環境に。 で、まず判明したのがグラフィックボード周りのバグ。 まずDirect3Dを使ってるソフトを動かすと三角形の点が飛んだり、テクスチャにごみが入ったりして画面が非常に乱れる。 これはドライバをごちゃごちゃ入れ替えてたら直った。 さらに、またドライバを元のにしても直ってた(^^; 次に開発環境の移行。 VB5はXP非対応だがとりあえずインストールは順調に終了。 ただし、ボタンなどのカスタマイズがなぜか毎回リセットされてしまうな・・・ 次にVC++6、これが参った。 「oledb32.dllがレジストリに登録できませんでした」とかいってインストールが成功しない。 ほかにもデータベース関連の機能をインストールしようとするといくつかのファイルで同じ現象が発生した。 とりあえず開発環境の起動とかはできるんだが、インストールが完了してくれないとSP6を入れることができない。 結局CDの中身をいったんHDDにすべて移し、インストールの手順らしきものが記述されているテキストファイルをいじってoledb32.dllを処理しないようにしてインストール完了。 インストール手順がテキストファイルでよかったな。 で、そこらへんやってたらVS.NET the Spokeのプロダクトキーが来たのでインストール。 MSDN Libraryと合わせてCD5枚あるだけあって時間かかるな・・・ C#やVB.NETはしばらくおいといて、VC++.NETに移行するか検討中。 ソースを出さないものは移行してもいいかな?(今VC6とVC.NETのどっちが使われてるんだろ) ほかごちゃごちゃソフトをインストール。 以前のWin98のレジストリの内容をテキスト出力してきたので、ソフトの設定はそれをそのままコピーして利用できるものもある。 で、上で日本語のアカウント名を付けて失敗したなと思ったのが、一部の英語のソフトだとGetTempFileNameで一時ファイルを作成するとき、Win98と違い、Documents and setting\アカウント名の下にtempディレクトリがあるため、そこでこける。 しかもあとでアカウント名を変更してもこのディレクトリ名は変更できないのね・・・ Cygwinとかでもなんか挙動がおかしかった気がする。 結局今のアカウントは消して新しくアルファベットでのアカウントを作り、そっちにいろいろと移行。 一部のゲーム(まぁMicro Warもそうなんだが)タイマーの精度がWin98と違い、ウェイトが正しくかからないのね・・・ timeGetTimeの精度がWinXPだと10msらしく、ウェイトを17msにして約60FPSにしようとしたらタイマーの精度のせいで実質20ms→50FPSとなってしまった。 近いうちに直す予定。 とかくWin98とWinXPの違いに戸惑ってます。 (大学とかではWinXP使ってるけど、やはり無理にWin98の環境を持ち込もうとしているのが無理があるんだろう・・・) ベンチマーク とりあえずHDBENCHをやってみた。 上は今回作ったPC、下はこれまで使ってたAthlon950MHz+GeForce2MX。 BitBlt・DirectDrawはあまり参考にならないとして、CPU・メモリは倍近くなっている。
ほかにも3DMark2001を動かしたりしてみたけど、木や草のある画面がかなりスムーズに動いていい感じ。 とはいえ、(メモリクロックが低いこともあるんだろうけど)3DMark03だと遅くなる部分もある。 N-bench3もGeForce2GTSの倍程度のスコアしかでないしな・・・ 久々に最近のデモでもいろいろダウンロードしてみようかなとか考えてます。 |
04/06/15 | VBでOpenGL編−1(初期化と簡単な描画)[★★◎◎] | |||||||||||||||||||||||||||||||||||||||
(VBでOpenGLとありますが、VCでもほとんど同じ方法で利用出来ます。定数名とかが多少違う程度。) 過去様々な3Dレンダリングエンジンがありましたが、なんだかんだでWindows用ではDirectXが広がってます。 ただ、DirectXはcomで構成されていることもあり、利用できる言語も多少選ぶし、バージョンアップが激しいし初期化が面倒(かなり改善されて来てますが)ということがあります。 そこで、ちょっとした3DのプログラムならDirectXよりずっととっかかりが簡単ということでVBでOpenGLをいじる方法を調べてみました。 Direct3Dとの比較 関連ライブラリ 必要なファイルの前準備 OpenGLの初期化・終了 ちょっと描画してみる Direct3Dとの比較 まずOpenGLをWindowsで使おうとすると、Direct3Dが気になります。 一応簡単な比較をしておきます。(結構主観が入ってますが)
OpenGLでのプログラミングでは、C++/GLUTという環境用のものですがOpenGLプログラミングコースというわかりやすいテキストがPDFで公開されています。 多少C言語もわかると言う方は、このPDFを見ると3Dに必要な概念なども細かく解説されていて分かりやすいのでオススメです。 (と言うか、今回はGLUTで隠されてる初期化回りをやるのでともかく、次回のマテリアルとかはこのマニュアルで十分にわかる) 関連ライブラリ 先にOpenGL関連ライブラリの話について書いておきます。 OpenGLのコアであるGLは最低限の描画機能のみを提供しています。(関数名の最初がglで始まる) 通常の動作環境では描画回りを少し拡張したGLUライブラリが利用できます。(gluで始まる) OpenGLの下にはプラットフォーム依存の処理を行うライブラリがプラットフォームごとに準備されています。 (X Window SystemならGLX、WindowsならWGL、OS/2ならPGL・・・今回はWindowsなのでWGLのみ関連) いずれも描画回りの処理までしか持っていないと言うことで、ウインドウ処理などを行えるライブラリということでAUXが開発され、さらに実用的なものということでGLUTが開発されました。 これらはOSの差異を吸収してくれますが、逆に各OSの最大公約数の処理しか行えないことになります。 C言語でのOpenGLの学習には最適ですが、Windowsならではの機能を用いたアプリケーションを作ろうとしていて、かつ自分で(直APIにしろMFCにしろ)ウインドウ処理なども行える人はGLUTなしでもOKでしょう。 GLUTはウインドウの処理などを行うものなので、VBではそれほど利点はありません。 また、今回は関係ありませんが、GLUT上でテキストボックスや視点の変更のようなコントロールを実現するためのライブラリとして、GLUIがあります。 GLUTを使いつつある程度ユーザーインターフェースも備えたい方はこちらを使うといいでしょう。 大雑把に図にすると以下の感じです。
Windows95ではOpenGLをサポートしていないので、OpenGLランタイムを導入する必要がありますが、Windows98以上では最初から導入されています。 ここで注意するのは、Windows98以上でデフォルトで使えるのはOpenGL+GLU+WGLまでということです。 GLUTは別に入手する必要があります。 Windows用のGLUTはGLUT for Win32でダウンロードできます。 GLUTはDLLで提供されているため、GLUTを利用する場合はDLLごと配布する必要があります。 幸い、「freely distributable without licensing fees.」とあるように再配布は自由です。 VBではGLUTはあまり意味がありませんが、一部は利用可能な命令もあります。 GLUTの命令一覧はThe OpenGL Utility Toolkit (GLUT) Programming Interface API Version 3で見ることが出来ますが、大半がウインドウ・メッセージ制御関連の機能です。 一部glutSolidSphere等のような描画回りの機能がありますが、このためにDLLを追加するかどうかは迷うところ。 後述のタイプライブラリにはGLUTの宣言も含まれていますが、実際にGLUTの命令を呼んでいなければ開発時・実行時にはDLLは必要ありません。 必要なファイルの前準備 OpenGLはDLL呼びだしで利用するのでDeclare文の定義が必要です。 また、Declare文の定義の替わりにタイプライブラリを使う方法もあります。 一応両方の入手先を書いておきます。
OpenGLの初期化・終了 今回は後者のタイプライブラリを利用します。 上記のサイトから「VBOpenGL 1.2 for Microsoft」をダウンロードし解凍して出てくる「vbogl.tlb」をc:\windowsでもVBのプロジェクトと同じ所でもいいので置いておきます。 VBでプロジェクトを作る際、「プロジェクト」→「参照設定」し、「参照」で先ほどのvbogl.tlbを読みこみ、「VB OpenGL API 1.2」を使えるようにしておきます。 この状態だと、オブジェクトブラウザで関数も見ることが出来ますし、プログラム時に関数の引数リスト表示などの支援機能も働いていい感じです。
で、OpenGLは描画先を「コンテキスト」と呼びます。(GDIにおけるHDCとほぼ同じ) この「コンテキスト」は通常HDCに対して1つ作ります。 なので、描画先はHDCを持つことのできる、ピクチャーボックスやウインドウになります。 初期化及び終了の処理は以下の通りです。(各構造体やAPIに関しては、MSDNライブラリに書いてあります)
PIXELFORMATDESCRIPTORは色々設定項目がありますが、後は引数もわずかです。 DirectDrawやDirect3Dよりははるかに初期化は簡単だと思います。 具体的には、以下のような感じ。 ここでは、ピクセルフォーマットはカラー32bit(RGBAそれぞれ8bit)、Zバッファ16bitを指定しています。 PIXELFORMATDESCRIPTORでは、他にアキュムレーションバッファやステンシルバッファも指定できますが、ここでは利用しません。 (PIXELFORMATDESCRIPTORの詳細は、MSDN Library等で見てください。)
上記のプログラムは描画先が1つの場合です。 複数描画先を持ちたい場合は、描画先のHDCに対して同じだけHRCを作り、描画の際に適宜wglMakeCurrentで対象となるHDC・HRCを切り替えつつ描画することも出来ます。 ちょっと描画してみる せっかくなんでちょっと描画してみます。 3Dについては今回は置いておいて、2Dで簡単な図形を描いて見ます。 OpenGL初期化後、何もいじくらなければXY座標の-1〜1がそれぞれ見えている状態になります。 この座標はあくまで数学の座標系と同じで、上に行くほどY座標は大きくなるため、一般のコンピュータの座標とは上下逆です。 それがどの領域に描画されるかを指定するにはglViewportを使用します。 ここではPicture1全面を使うとして、以下の様に指定します。(もちろん上のInitOpenGLでPicture1.hDCを引数に送っていないと行けませんが)
例えば画面の1部分はステータスなどを2Dで描画して残りを3Dの画面を表示するとか、2人用のゲームを作る場合などにglViewportで領域の半分ずつ指定してそれぞれ描画したりすることが出来ますが、今回はその必要もないので全面使います。 描画は、glBegin命令からglEndの間で頂点をどんどんglVertex**で指定していきます。 glBeginの引数でbmLineとかbmPolygonとかを指定することで描画するものを直線・連続した直線・三角形など指定することが出来ます。 (C言語ではGL_LINEやGL_POLYGON等と定数名が違うので注意)
描画の前には、glClearでバッファをクリアしておきます。 引数にクリアするバッファを指定できるので、ここではカラーとZバッファ(使わないけど)を指定します。 クリアする色はglClearColorで指定できます。 デフォルトだと黒で初期化されるのでそれでいいでしょう。 最後にSwapBuffersで画面に反映されます(DirectDrawのFlipと同じで、バッファを入れ替えている)。 プログラムは下みたいな感じ。 タイマーなどで下の関数が定期的に呼ばれる様にします。 下のプログラムではX軸・Y軸が白い幅2ピクセルの線で引かれた後、回転する3角形が描画されます。 図形の回転はOpenGLの機能で行うことも出来ますが、今回はとりあえず自分で計算。 3角形の各頂点は赤・緑・青が指定されており、その間は自動的に線形補間された色が指定されます。 各頂点は-1〜1の間なので、整数でなく小数で指定したいため、glVertex2fを使います。 色指定はglColor3fだとRGBの3色を0〜1で指定できます。(glColor3bだと0〜255で指定できる)
これを実行すると、以下のようになります。 DirectXに比べるとはるかに簡単にとっかかれるような気がしませんか? とにかく画面に表示するまでに憶えなければ行けないことが少ないのはいいですね。 今回はこんなところで。 実際に動くコードはまた今度載せようと思います。 次回は簡単な3Dの物体の描画をして見ます。 マテリアル・ライティングあたり。 テクスチャも行けるかな・・・? |