C言語のInvader
[Home]

 C言語のInvader

 最初に

Invader(インベーダー)とは何かと言えば、下の画面を見れば一目瞭然でしょう。(2019年10月13日 Windows 10 64bit)
とは言っても、最近の若い人は全く知らない方も多くなったと思いますが。一言で言えば、それは空前絶後のブームを巻き起こしたコンピュータゲームです。
ここでは、 Space Invaders OpenGL を(個人的なC言語の学習のために)利用させていただいたものです。

インベーダー画面1

これも一昔前(1975年8月)のCMの話なのですが、インスタント食品のコマーシャル(https://www.youtube.com/watch?v=q7LfLhsqdlA)があって、「私作る人、ボク食べる人」のフレーズが「男女の役割分担を固定化するもの」と問題になったことがあります。
当時、参議院議員の市川房枝が「食事づくりはいつも女性の仕事という印象を与え、男女の役割分担を固定化してしまうものだ」と抗議しました。
しかも、「1ヵ月以内に放送を中止しない場合には不買運動も含めた対抗手段を検討する」とメーカーに通告したのです。
ここから、このCMが社会問題にまで広がり、ハウス食品側は検討を進め、10月27日に「社会的影響なども無視できない」として放送中止を決断しました。

正直な所、CMを見直してみても、食事づくりが女性の仕事という印象を与え、男女の役割分担を固定化してしまうとまで(過剰に)感じる人は皆無に近いと思うのですが、メーカーは CM に敏感になって、それ以降は同様な印象を与え兼ねないフレーズがCMに登場する事は一切無くなりました。
今の時代なら、抗議は滑稽だと言われるだけでなく、国会議員の職権乱用だと非難されるでしょう。(現在でも、NHKに関与したくて、うずうずしている大臣もおります)

話がInvaderと逸(そ)れたのは、コンピュータゲームもプログラムを作る人と、単にゲームをやるだけの人との2種類の人が居り、ここではコンピュータゲームのプログラムを作る人を対象にしているからです。単にゲームをやるだけの方には、(以降の内容は全く理解が出来ないために)つまらない内容になっております。
そんな方は、私がこのプログラムをコンパイルした Space Invader.zip を用意しましたので、これを持ってさっさとお帰りください。(但し、動作に少々不具合があります)

私がなぜ「作る人と食べる人」の話をしたのかと言いますと、以前にエクセルでインベーダーゲームを組んだ事がありますが、それは単に他人の作成したプログラムの改造でした。
1から造るのに比べれば、赤子の手を捻るような簡単なものだったのですが、それでも改造には数日を要しました。
そんなエクセルのインベーダーゲームを、 昔のゲームをむやみにフルHD化してはいけない、ということがよく分かる動画 だと評価している人がおります。
この人の言いたいのは、昔と今では画面の解像度が異なるので、画面が「阿鼻叫喚の嵐が吹き荒れる有様となっている。」からとのようです。

私の乏しい知識では阿鼻叫喚(あびきょうかん)の読み方さえ知らなかったので、意味を調べたら以下のような解説が見つかりました。
非常な辛苦の中で号泣し、救いを求めるさま。非常に悲惨でむごたらしいさま。地獄に落ちた亡者が、責め苦に堪えられずに大声で泣きわめくような状況の意から。
「阿鼻」は仏教で説く八熱地獄の無間むけん地獄。現世で父母を殺すなど最悪の大罪を犯した者が落ちて、猛火に身を焼かれる地獄。
「叫喚」は泣き叫ぶこと。一説に八熱地獄の一つの大叫喚地獄(釜かまゆでの地獄)の意。


この人は最後に、「貴重な時間をドブに捨てたい人はぜひ挑戦してみることをオススメします。」と書いているので、このエクセルで作成したインベーダーゲームはゴミ以下の存在だと言いたい(偉ぶっている)ようです。(咎められると、ブログの内容はジョークだとでも言い訳するつもりでしょうか)
グーグルで「excel インベーダー」で検索すると、このブログは4番目に登場(2019年10月)しますので、多くの人の共感を得ているようですが、私はこの手の人間が大嫌いです。
自分の知識をひけらかし、自分では何もできない癖に、人のやる事にはああだこうだと文句ばかり付けたがります。
会社の管理職クラスに多いタイプですが、そんな会社は伸びませんし、私が以前務めていた会社のように潰れる羽目になる事が多いです。(私が辞めてから7、8年後に潰れました)

良くある例では、官僚の天下りタイプで田舎の県知事(確か山形と福島)だったと思いますが、県庁ではマイクロソフトエクセルを大量に購入しますので予算が膨大に掛かります。
代替えの無料のエクセルを導入すると予算が数分の一で済むので、「鶴の一声」で県庁で導入したのですが、1年も持つか持たないかで(当たり前ですが)問題が算出して、マイクロソフトエクセルに戻したようです。(戻さざるを得なかった) そんな導入に無理があるのは、この話に技術者が絡んでいれば直ぐに指摘できるのですが、官僚あがりの馬鹿人間には直接的な損得の判断しか出来ません。 マイクロソフトエクセルに戻す時も、知事の下で働く下っ端役人は知事の顔を潰さないようにと、戻した理由を尤もらしい屁理屈をつけて説明しておりました。 そんな知事がいるから山形や福島は(永久に)発展出来ないのですが、日本では使い物にならない高学歴者を救済する場所(ど田舎)が必要なのでしょう。


 なぜC言語でインベーダーを作成するのか

一言で申し上げれば、C言語はプログラムの基本言語だと思っているからです。アセンブラで組めば最速で動作するのは分かっておりますが、普通の人が使用するには無理です。
VB(VB6)でも遜色なく動くかも知れませんが、所詮は誰でも使えるような簡易型言語で、痒い所に手が届きませんから、どうしてもプログラムで組むには限界が生じます。
エクセルでインベーダーを組んだ事(他人の作成したプログラムの改造)もありますが、良くここまで組んだと感心は致しますが、安定度を考慮すると使い物になりません。
その他の言語は詳しくは知りませんが、(個人的には)C言語に勝る言語は無いと思っています。

この言語の欠点は、理解にとてつもなく時間が掛かる点でしょう。プログラムの作成には多くの経験も不可欠で、恐らく9割近くの人は途中で挫折してしまいます。(私も同様)
マイクロソフトでは、C#と言う新しいプログラム言語の提供を行ってから数年以上になりますが、C#がCやC++より勝っているのかとなると、私は疑問を感じております。
本格的にC#の勉強をしたことがありませんので、偉そうな事は言えないのですが、最近は終わったと言われていたCやC++が見直されてきているとの話も聞きます。
パターン化されている業務のプログラムは、C#で十分でしょうが、ゲームのような独創性が要求されるプログラムでは、CやC++が必要ではないでしょうか。


 なぜC言語を取得出来ない人が多いのか

偉そうなタイトルを付けておりますが、私もC言語は取得出来ておりません。ソフトウェアハウスでも、仮にプログラマーが20人居たとしても、C言語を取得出来ていると言える人は2,3人がやっとでしょう。 他の人は補助的な作業をやるのが精一杯ではないかと思っています。
今はどうか知りませんが、プログラマーは自分の仕事を囲い込む人が多いらしくて、昔は自分の開発しているソフトの入ったHDDを持ち帰る人まで居たそうです。
自分の仕事を他人に奪われたくないからでしょうが、こんなスケールの小さい人は、プログラマーとしては大成しませんね。ひょっとしたら、これは日本人の独特な行動かも知れません。そう言えば、日本では「技は見て盗め」とか言われておりました。 C言語の解説でも、「Hello World !」と表示させて解説を終わりにしている人が多いのも日本ぐらいです。
この場合は知識の出し惜しみではなく、書いている人が低レベルなだけなのですが。そんなサイトに限って広告が溢れており、サイトの表示がやたら遅いのは共通しております。

どうでも良い話はここまでにして、「Space Invaders OpenGL」はソースの解説も多くて、私のような低レベルの人でも理解出来る部分が多いです。
ソフトのレベルも高くて、画像処理やサウンド、インターネット通信まで行っています。そしてゲームですので(中身を理解するのに)飽きないです。
こんなソフトが多ければ、C言語を取得出来る人は多くなるのですが、残念ながら日本では見かける事が極めて少ないです。日本では、囲い込んでいるのでしょうか。
私は「Space Invaders OpenGL」はVS2005でコンパイルしましたが、不具合が取れた(ほば無くなった)状態になれば、(当然ながら)ソースは公開致します。

現在の段階ですが、「Space Invaders OpenGL」のソースを、やっとの事で再コンパイル出来てから数日経過した程度なのですが、動作途中で固まったり、動作しないOSがあったりして見直しをしております。 このソフトの動作を理解出来た暁には、C言語の理解が一段と深まりそうだと自分に期待しています。ちなみに、私のC言語のレベルは小学4年生程度で、やっと大人の入り口にたどり着いた程度ですが、現在幼稚園レベルの人には再コンパイルでさえ無理でしょう。 そんな人は、自分が理解出来ない事を認めたくないので、ソースにバグがあるとか、ソースが古過ぎるだとか難癖をつけるのでしょうが、単に自分の頭が悪いだけですので、親の遺伝子を嘆きましょう。 それが悔しいなら、努力をしなさい。


 まず何から始めるか

「Space Invaders OpenGL」はC++6で作成されておりますから、これをVS2005に対応するようにプログラムの変換を行います。VS2005にはその機能がありますから、幼稚園レベルの方でも造作ないでしょう。 それで一件落着するソフトも中にはありますが、2004年頃に造られたソフトがそんなに容易く動く事はまずありません。大量のエラーとワーニングが溢れる事になります。 それを今まで得ている知識と経験で消していきます。ワーニングもいきなり無視設定にしないで、確認しながら(直せるものは直して)行うべきでしょう。
私は(自分の能力の向上のためにも)だいたいそうしております。

このソフトの場合は、制作者がフランス人のようですので、文字コードが変換できないとかのワーニングが大量に出ました。ユニコードに変更してくださいとかの警告なのですが、私はマルチバイト文字セットを使いました。 エラー関係は、for文の i の単位の指定が無いとかのエラーが多かったですが、この程度のエラーなら(知られておりますので)幼稚園レベルの人でも直せるでしょう。 ソフト自体は、英語がメインで、フランス語、スペイン語対応になっておりますが、私は最終的には日本語と英語対応にしたいと思っています。
私が苦労したのはリンク時に出るエラーだったのですが、まずはVS2005が、リンク操作をする段階まで到達するように頑張ってください。
その程度の段階まででも、幼稚園レベルの人では、7,8割の人が到達できないのではないでしょうか。いくら何でも馬鹿にし過ぎたかな。


 リンク時のエラー

リンク時にlibpng関係(らしい)依存が解決できないとのエラーがでて、これが本当に解決出来なくて調べまくりました。
リンクの「入力」の追加の依存ファイルは、(最終的に動作させて調べた限りでは)以下の13点(必須)があれば問題ないようです。
msvcrt.libに関しては、コンパイル時に2重登録ですと警告が出る場合は、削除しても大丈夫です。(削除しなくても動きます)
fmodvc.lib
msvcrt.lib
delayimp.lib
glpng.lib
odbc32.lib
odbccp32.lib
opengl32.lib
glu32.lib
wsock32.lib
msacm32.lib
winmm.lib
wininet.lib
comctl32.lib

問題が解決したのは、 png形式の画像 様の記載です。要は、サイトに記載している通りで、以下の理由によるものです。
glpngは2000年にリリースされた後に、メンテナンスがされていない模様で、VS2005やVS2008では?「ファイル LIBC.lib を開くことができません」?「外部シンボル "__iob" は未解決です」などエラーをはいて使う事が出来ない。そこで、VS2005や、VS2008でライブラリを再ビルドして「glpng.lib」,「glpngd.lib」を作成する必要がある。
このサイトに記載している通りに再ビルドを行った結果、リンクエラーは出なくなりました。ありがたや、ありがたやです。
このサイトも、最終更新が2009年10月と古いので、私のサイトへ内容をコピーしたいのですが、著作権も絡むので考慮中です。(私はもう必要ありませんので)
御提供しているソースには、再ビルドを行ったglpng.libを添付しております。


 Windows10で動かないのを追及する

基(著作権者)のサイトにある「Space Invaders OpenGL.exe」は、最新のWindows10(ver 1903)でも動くのに、最新のファイルを揃えてコンパイルした(私の)ソフトが動かないので、原因を調べてみました。 最初は、ソフト内部でOSのバージョンチェックでもしているのだろうと軽く考えていたのですが、そのような部分は見当たらず、基のソースを制作者が使用した C++6 でコンパイルしてみました。

私はC++6も所有しておりますので、コンパイルする環境は整っているのですが、ワーニングは0でしたが、エラーが14個も発生して、この対処にも苦労しました。
「error C2065: 'TTM_SETTITLE' : 定義されていない識別子です。」だとか、もう忘れましたが、何とかの前に(')')が必要ですとかのトンチンカンなエラー内容でした。ちょっと調べたら、私のレベルでも解決できる内容でしたので対処法は省略します。 この程度が解決できないようでは、「 Space Invaders OpenGL 」のソースを勉強するのは10年早いです。

C++6でもコンパイルが完了したので、Windows10(ver 1903)で動くのかと動作させてみると、やはり動きません。
現象は同じで、最初の動作設定のウィンドウは表示するのですが、スタートボタンを押すと、OpenGL の真っ黒い画面だけが1秒程度出て、プログラムが終了してしまいます。
エラーでも出て呉れると、少しは対処の方法も分かると思うのですが、全く出ない所を見ると、OSにはじかれているか、外部依存のプログラムではじかれているのではないでしょうか。さすがに小学4年生程度の(私の)レベルでは、(忍耐の)限界を感じてきました。Windows 7で動くのだから、もう良いではないかと「悪魔の囁き」が聞こえてきます。

対処方法が尽きて、もうあきらめ掛けていた時に、OSの互換モードを使ってWindows Xpで動作させても動かなかった時にピンときました。
Windows 10で動かないのは、「管理者として実行」していないからではないかと思えたのです。私の環境に於いてはですが、あっさりと動きました。
使用しているパソコンのアカウントは管理者になっておりますので、このような発想は全く起きませんでした。これで(Windows 10 で動かない問題は)完全に解決したのかどうかと、基のソフトではなぜ動くのかについて研究したいと思います。 ソフト上で管理者として実行する設定が出来るのであれば、Windows 10 は何をやっているのかとなります。

【注記】 世の中そう甘くはなかったようで、フルスクリーン設定時ではローディング途中で止まってしまう場合があるようです。(パソコンをリセットするしかないかも)
その場合は、基(著作権者)のソフトを起動してフルスクリーンで動作させてから再度起動すると回復する場合もあり訳が分からないです。(これを不安定な状態と言いますが)
ソフトのブロック(管理者のみ実行可)が一度解除されると、その後は動作するような気もしますが、小学4年生程度の(プログラム)レベルの素人には分かりません。

フルスクリーン時にWindows 10 ではローディング終了の手前で止まってしまう現象は、Loding.cppの// RECUPERATION DU HISCORE以降の呼び出しのいくつかを止めると、止まらなくなる雰囲気があります。取り敢えず、LOG(" ")をコメントアウトして、スコアの保存先をHKEY_LOCAL_MACHINEから、使用例の多いHKEY_CURRENT_USERに変えてみました。 もし、関係があるとしても、呼び出しの際の微妙なタイミング程度ですが。(変更はしましたが、レジストリのどこに記憶するのかは、無関係のようです)


 起動時にローデング終了間際で進行しなくなる

私の場合、Windows 10 (Ver1903)でローデングが9割程度終わった時点から先に進まない現象が出ました。
Windows 7 (32bit,64bit)では、このような現象は出ないので、OSにインストールされているDLLなどが存在しない(又は壊れている)影響ではないかと思うのですが、OPEN GL関係のファイルが多くて、私のレベルでは原因が掴めません。 恐らくは、画像の読み込みや音楽の読み込み時に先に進まなくなるのだろうと思いますが、私は怪しいと思っているのは音楽再生で使用しているfmod.dllです。 これを無いとのエラーが出た事もあって、実行ファイルのある場所に提供ソースにあったfmod.dllをコピーしているのですが、これが、Windows 10 (Ver1903)に適合しているのかどうかです。

fmod.dllを削除して動作を見たりはしておりますが、断定的な答えは出せないでおります。サイト上には同名のファイルがいくらでも見つかるのですが、素性がはっきりしない点やVersionが何種類もあったりで、安易にインストールして試す気持ちにはなりません。
特に、Windows 10 はバージョンアップが激しくて、何もしなくても動くようになっていたりする場合も多いので、実に困り物のOSです。
現在はソースのローディングの部分いじったりして対処しておりますが、Windows 10 (Ver1903)でも環境によっては動くようになったりするので、対応に難儀しております。

ローデング終了間際で進行しなくなる原因は、si_Loading.cppに記載のinit_particle()をコメントアウトしているからではないかと思われます。
バージョン0.6.0ではコメントアウトしていないのに、バージョン0.6.1でなぜコメントアウトしているのかは分かりません。


 ESCキーで終了しない

フルスクリーンモードの時に(それ以外でも)終了する場合は、「ESC」キーで終了するのですが、OSによってや、押すタイミングによっては終了出来ない場合があります。
このプログラムの場合は、ウィンドウを閉じるだけでなく、画像処理や音声処理も停止してメモリーを解放する必要があります。その時に解放に失敗(無限ループ)しているか、解放に異常な時間が掛かっていると思われます。 「ESC」キーなどの処理は、Main.cpp(550行付近)で行っておりますので、その部分を見直せば良いのですが、作者も苦労をしたらしく、バージョンによっては、あちこちをコメントアウトをしたりしており、動作を理解していないといじれません。

兎に角、Windows 98の時代から動作をしているプログラムですので、全てのOSで動作させるには、並大抵の苦労ではなかったと思います。
今なら、古くてもWindows Xpからの対応で十分ですし、一般的にはWindows 7から動作するようにすれば、全く問題ないと考えています。
口で言うのは容易いのですが、実際にプログラムのどの部分をどうすれば良いのかとなると、私のような素人にはきついです。
時間を掛ければ解決は出来ると思いますが、それまで根気が続くかどうかです。

終了を妨げているのは、どうやらBGM(MUSIC)のサウンドの解放が上手く出来ないからのようで、BGMをオフ(Mキー)にすれば終了出来るようです。
引き続き、サウンドの解放方法を調べます。BGMをオフ(Mキー)にするのは、単にボリュームを0にするか、ボリュームを240にするかだけですね。
この操作が終了に関係しているのか、更に調べます。FSOUND_Close()の前にmusic_on = falseを入れると終了出来るような気がしてきました。


 ソースファイルの御提供

まだ問題点は多いですが、お約束の私がコンパイルした「Space Invaders OpenGL」のソースファイル SpaceInvadersOpenGL-0.6.1-Source.zip を御提供します。
以前は、自機への当たり判定がおかしい最新のバージョン0.6.1ではなく SpaceInvadersOpenGL-0.6.0-Source.zip のみを供給致しましたが、今回はソースを見直して修正しましたので、両バージョンを公開致します。動作が不安定なので、現在は内容を頻繁に変更(2019年10月21日の更新が最新)しております。
両バージョンを動作させてみましたが、特に差異は感じませんでしたので、 SpaceInvadersOpenGL-0.6.0-Source.zip は、2Dタイプ専用に改造しました。
その方がどこを変更しているのかが分かりますので、ご覧になる方も便利だと思います。(当サイトは学校ではありませんので、その程度は自分で探してください)

但し、両バージョン共に、供給元のソースにも存在している問題点のいくつかは残っています。(取り除く努力はしております)
又、両バージョン共に、画像数点を自分好みに変更しておりますが、オリジナルの方が良い方は入れ替えてください。(行き詰った時に気分転換に行ったものです)
尚、御提供のソースはVS2005を使用すれば、そのままコンパイル出来るようにしてあります。
今時、VS2005を使っておられる方は皆無に近いと思いますが、最新のVSには上位互換性がありますのでコンパイルは可能と思います。
自動変換が出来なかったとしても、「空のプロジェクトファイル」を作ってソースをコピーしても、動作する事を確認しております。

必死になって集中的に作業をしてまいりましたが、正直な所、面倒臭くなりましたので、これで終わるかも知れません。
まぁ、私としてはやれるだけの事はやったとの思いがありますので、 これで終わっても悔いはありません。
このソースがC言語を学んでおられる方に、多少なりでもお役に立てるなら、この上ない喜びです。
重ねて「Space Invaders OpenGL」の作者様には心から感謝を申し上げます。
最後に、私の成果を御披露致します。(2019年10月11日 Windows 7 32bit)

インベーダー画面2

正真正銘インチキ無しの点数です。私がサイトに飾りたいために、必死になって高得点を目指して頑張りました。(普段、ゲームばかりやっている訳ではおりませんよ)
画面が3Dタイプになっておりますが、うっかり3Dタイプにしてしまったもので、この直前までは2Dタイプでやっておりました。3Dタイプでは高得点は出せません。
それと、フルスクリーンの方がやり易くて高得点を出せるのですが、「Print Screen」では真っ黒な画像しか映りませんでしたので、Windowsタイプで行いました。
私は運動神経はかなり鈍いので、残念ながら記録的な価値は無いと思います。

台風が上陸すると言うのに、今日も懲りずにやってしまいました。(2019年10月12日 Windows 7 32bit)
Windows 7 Proの32bit版ですが、64bit版でも問題無く動きます。癖があるのはWindows 10で、ローディング途中で止まってしまったりします。(動く環境もあります)
インベーダー画面3

最高得点を記録した時の画面が欲しくて頑張ってみました。7万点を越えたので「Print Screen」を押しましたが、それからもしばらくは戦いは続きますので、実際の得点はもう少し高いです。 半端ではない攻撃を受けますので、バンカーの下に隠れて「Print Screen」を押さないと、やられてしまいます。年のせいか相手の弾が見にくいので、もう少し白っぽい色に変えた方が良いかもしれません。 ひょっとしたら、動体視力が低下しているせいなのかも知れません。稀に、やられていないのにやられた事にされてしまう場合があるような気もします。 60歳を超えた人で7万点を越えられる人は、ザラには居ないだろうと思っているのですが、どうでしょうか。

昨日と同じパソコンを使っているのですが、なぜか画面の解像度が異なります。2Dタイプのせいかも知れません。
集中出来ないので、BGMは切っておりますので、サウンドのスペクトラム表示はされておりません。他の音は出ております。
一応、記録更新の予定は達成出来ましたので、(馬鹿みたいと言われそうなので)もう当分やりません。

競馬で負けた腹いせに(と言う理由にして)今日も最高得点を目指して頑張りました。(2019年10月14日 Windows 7 32bit)
インベーダー画面5

相手の弾が見難かったので、白色系にしたら効果抜群で、1,2万点なら鼻歌まじりで操作しても達成できるようになりました。
今まではやられていないのにやられたと思っていましたが、とんでもない所から弾が飛んできているのが分かるようになりました。
ただ、最高得点を目指すのは気も使いますし、達成するまで時間も掛かりますので、もう本当に気が向いた時しかやりません。


 Space Invaders OpenGLの解析

Space Invaders OpenGLでそこそこは遊んできましたが、やはり不満点があります。 1つは、ずっと2Dタイプで遊びたいのに、上から2Dタイプから3Dタイプへ変更させるためのアイコン(画像)が降りてきて、それを避けながらゲームをしなければならない点です。
インベーダーの弾を避けるのも大変なのに、余計な切り換え用の画像も避けるために、攻撃の対応が遅れてしまいます。
それでは面白くないとお感じの方は、2Dタイプ専用版の Space Invader2D.zip を用意しましたので、こちらをダウンロードしてください。

もう一つの不満点は、1画面をクリアすると別の画面に切り替わるのですが、切り替わる一瞬に画面が真っ白になってちらつきます。
時間にすれば0.1秒にも満たない時間でしょうが、気になるだけでなく、眼にも悪影響を与えるでしょう。これも何としても修正しなければなりません。
このプログラムの作成時(2004年頃)は、ディスプレイはブラウン管でしたから、反応が遅くてちらつきを感じなかったのではないでしょうか。
色は黒が起点のはずですから、何もしなくても黒になると思うのですが、この考えは間違っているでしょうか。
【注記】
切り替わる一瞬に画面が真っ白になってちらつく問題は、2019年10月18日に対処出来ました。(active_splashの部分をコメントアウトするだけで良さそうです)
早速、ソースファイルと実行ファイルを対処したものと取り換えました。この現象はわざと(アクセントとして)発生させているようです。

現在まで解析している部分の御説明を致します。(やり始めたばかりですので不正確です)
個々のコントロールの動きは、si_World.cppが受け持っているようです。
インペーダーの動きは、display_ship(int i)で、iが位置情報のようです。
自機はdisplay_player()で、位置はプレイヤーが方向キーで、任意の位置(横だけ)に移動できます。2Dタイプと3Dタイプで形状が異なります。
赤い円盤はdisplay_supershipです。こちらも2Dタイプと3Dタイプで形状が異なります。

弾丸はdisplay_shoot(int k, bool viewport2)のようです。2Dタイプと3Dタイプで形状が異なります。
他に、3Dタイプの時に出てくるdisplay_bonus_3dのボーナス表示、display_bonus_2d_to_3d()もあります。
ガードバンカーの表示は、display_bunkers()です。2Dタイプと3Dタイプがあります。
他に、display_score_hiscore_lives()のスコア表示と生存数表示、display_background(bool viewport2)のバックグラウンド表示などもあります。
バックグラウンドは、game_levelによって5種類あり、fond oil→plane(土星)→earth-moon(地球と月)→galaxy1→skyboxの順に遷移します。3Dの場合は昼と夜の2種類。


 OpenGLを学ぶ

不満点の一つに画面をクリアして、別の画面に切り替わる時に、ほんの一瞬ですが画面が真っ白になってちらつくのがありました。これが気にならない方は居ないと思います。
これを解決するのにSpace Invaders OpenGLの膨大なプログラムを直接いじるのは、私のような低レベルの能力しか持ち合わせていない人間には、どうあがいても無理です。
そもそも、私はOpenGLが全くと言ってよいほど理解出来ていないのです。早速、 OpenGLとは のサイト様を伺って勉強させていただく事にしました。
低レベルの能力しか持ち合わせていない人間が、人並の能力を持つためには、ひたすら先人の教えを乞うしか道はありません。
このサイト様のお蔭によりとまでは言いませんが、最終的に画面が真っ白になってちらつく問題には対処出来ました。

以下に(万一の場合になりますが)サイトが閉鎖された場合を考慮して、OpenGL の基本を抜粋させていただきました。
抜粋とは言っても、基本部分は省略できず、ほぼ丸写しになります。要するに基本は学校の教科書に該当しますので、中身を省略するのが難しいのです。
著名な、 猫でもわかるプログラミング のサイトもそうですが、基本的な事だけを教えているのに「当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。」などと(厳しい転載条件を)記載しています。(転載したくなるような内容はありませんが)

恐らく、サイトに掲載しているのは、日本人への(C言語の)教育的な目的ではなく、自分の書いた本を売りたいだけなのでしょう。 それが悪いとまでは言いませんが、スケールの小さい人だとは思います。 おまけに、日本人には「どこそこのサイトは、他サイトの著作権を侵害している」などと騒ぎ立てる人がおりますから、私のような臆病者は躊躇してしまいます。 しかし、有益なサイトがリンク不能になって、(知識が取得できず)何度も泣かされてきた思いから、自分の知識をひけらかし、自分では何もできない癖に、人のやる事にはああだこうだと文句ばかり付けたがる人間(の屑)は無視する事にしました。 ただ、自分を正当化するつもりは毛頭ありませんので、同調できない方は、ここでお帰りください。
私の行為は、(恐らくですが)法律を犯しているだけでなく、著作権者にも多大な損害を与えている可能性があります。(そこまで影響力があれば、本当は嬉しいのですが)

OpenGL はすばらしいグラフィックス・インターフェイスですが、機能がシステムから完全に分離されているため、システムとの連携ができません。
そこで、ウィンドウ・システムと連携するためのライブラリがいくつか存在します。例えば OpenGL Utility Toolkit、通称 GLUT を用いることで解決することもできます。
GLUT は Mark Kilgard 氏が作成したウィンドウ・ツールキットです。まず、OpenGL コマンドはすべてが "gl" ではじまります。
定義済み定数は GL_ ではじまり、すべてが大文字となります。

また、OpenGL は従来の C 言語の標準型に対して typedef 名を定義しています。
OpenGL の型と C 言語の型は次のような対応になっています。

データ型C 言語OpenGL
8ビット整数signed charGLbyte
8ビット符号なし整数unsigned charGLubyte, GLboolean
16ビット整数shortGLshort
16ビット符号なし整数unsigned shortGLushort
32ビット整数int, longGLint, GLsizei
32ビット符号なし整数unsigned int, unsigned longGLuint, GLenum, GLbitfield
32ビット浮動少数floatGLfloat, GLclampf
64ビット浮動少数doubleGLdouble, GLclampd

OpenGL のコマンドは OpenGL で定義されている型を用いるので慣れる必要があります。OpenGL のコマンドや定義済み定数を使うには GL.H ヘッダファイルをインクルードしてください。 ウィンドウを表示させるには GLUT を初期化しなければなりません。GLUT の初期化には glutInit() 関数を使います
void glutInit(int *argcp, char **argv); argcp には main() 関数の argc パラメータへのポインタを、argv には main() 関数の argv パラメータへのポインタを指定します

次に、ウィンドウの位置とサイズを設定しましょう。ウィンドウの位置は glutInitWindowPosition() 関数で、サイズは glutInitWindowSize() 関数で設定します。
void glutInitWindowPosition(int x, int y); x にはウィンドウの X 座標を、y には Y 座標をそれぞれ指定します。
void glutInitWindowSize(int width, int height); width にはウィンドウの幅を、height には高さを指定します。これらの値は、ピクセル単位です。

最後に、GLUT のディスプレイモードを設定します。ウィンドウモードは、ウィンドウのカラーモデルやバッファの設定を行うためのものです。
これには glutInitDisplayMode() 関数を使います。
void glutInitDisplayMode(unsigned int mode);
mode にはディスプレイモードを示すフラグの組み合わせを指定します。ここには、次のいずれかの定数を組み合わせて指定することができます。

定数解説
GLUT_RGBARGBA モード
GLUT_RGBAも GLUT_INDEX も記述されないときのデフォルト
GLUT_RGBGLUT_RGBA と同じ
GLUT_INDEXカラーインデックス モード
GLUT_RGBAも記述された場合,上書きする
GLUT_SINGLEシングルバッファ モード
GLUT_DOUBLE もGLUT_SINGLEも記述されていない場合の デフォルトである
GLUT_DOUBLEダブルバッファ モード
GLUT_SINGLE も記述されていた場合,上書きする
GLUT_ACCUMアキュムレーション バッファ
GLUT_ALPHAカラーバッファにアルファ成分を加えること
GLUT_DEPTHデプス(Z)バッファを加えること
GLUT_STENCILステンシル・バッファを加えること
GLUT_MULTISAMPLEマルチサンプリングのサポート
マルチサンプリングが利用できない場合は無視される
※マルチサンプリングを可能とするためには,OpenGL の クライアントとサーバーの両方が
GLX_SAMPLE_SGIS エクステンションをサポートしていなければならない
GLUT_STEREOステレオ・ウィンドウビットマスク

この場では、デフォルトの GLUT_RGBA | GLUT_SINGLE を用いるため、glutInitDisplayMode() は必須ではありません。
ウィンドウを作るには glutCreateWindow() 関数を呼び出します。この関数は、トップレベル ウィンドウを生成します。
int glutCreateWindow(char *name); name には、ウィンドウの名前を示す ASCII 文字列を指定します。
関数は、他の関数で利用することができるウィンドウの識別子を表す数値を返します。ただし、この関数を呼び出した時点では、まだウィンドウは表示されません。

プログラムは最後に glutMainLoop() 関数を呼び出します。この関数は、GLUT がイベント処理ループに入ることを表します。
この関数を呼び出せば、トップレベル ウィンドウが破棄されるまで処理は戻ってきません。しかし、これだけではウィンドウが処理するべき基本的な処理を実現することができません。 ウィンドウ領域が再描画されようとした時 GLUT は何をすればよいのかわからないでしょう。
Windows プログラムで言うならば、ウィンドウ プロシージャが必要なのです。

そこで、GLUT はコールバック関数を登録することでイベントを処理します。最低限、作らなければならないコールバック関数は、ディスプレイ コールバックです。
この関数は、ウィンドウの再描画が必要であると判断された時に呼び出されます。ディスプレイ コールバックの登録は glutDisplayFunc() 関数を使います。
void glutDisplayFunc(void (*func)(void));
func には、登録するディスプレイ コールバック関数へのポインタを指定します。ディスプレイ コールバックは表示されるどんなウィンドウにも登録されなければなりません。
ディスプレイ コールバックを持たないウィンドウを表示しようとすれば、エラーが発生します。また、glutDisplayFunc() 関数に NULL を渡すことはできません。
これは、いかなる方法でもディスプレイ コールバック関数を解除する方法がないことを表します。

ディスプレイ コールバック関数では、少なくともウィンドウをクリアする必要があります。
ここではじめて OpenGL コマンドを利用することができます。
OpenGL では、すべてのピクセルの情報を保存する領域をバッファと呼び、ピクセルに関する1ビットの情報を保存するバッファをビットプレーンと呼びます。
ビットプレーンには、RGBA か、カラー参照テーブルを参照する値が保存されます。
多くの場合は、加法混色にアルファ値を加えた RGBA が用いられます。

バッファのクリアには glClear() 関数を呼び出します。この関数は、指定したバッファを特定の色で消去してくれます。
void glClear(GLbitfield mask);
mask には、現在の消去値でクリアする対象バッファを指定します。
ここには、次の定数の組み合わせを指定することができます。

定数解説
GL_COLOR_BUFFER_BITカラー バッファ
GL_DEPTH_BUFFER_BITデプス バッファ
GL_ACCUM_BUFFER_BITアキュムレーション バッファ
GL_STENCIL_BUFFER_BITステンシル バッファ

この場では glClear() 関数を用いれば、バッファを初期化できることを知ってください。
バッファを初期化するカラー情報は glClearColor()< 関数で指定できます。

void glClearColor(
	GLclampf red , GLclampf green ,
	GLclampf blue , GLclampf alpha
);

red には赤、green は緑、blue は青、alpha はアルファ値そ指定します。
これらは 0% 〜 100% の状態を float 型の 0.0 〜 1.0 で表します。
すべてが 0 であれば黒であり、1 であれば白い状態になります。
この場ではアルファ値を有効にしていないため、混合処理以外では alpha には 0 を指定しましょう。

さて、これでバッファを初期化できるようになりました。
デフォルトのディスプレイモーでは、単一のカラーバッファが用いられています。
そこで、glClear() 関数には GL_COLOR_BUFFER_BIT 定数を指定します。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp(void) {
	glClearColor(1 , 1 , 1 , 0);
	glClear(GL_COLOR_BUFFER_BIT);
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(640 , 480);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutMainLoop();
	return 0;
}

上記のプログラムを実行した結果の画像です。
Kitty on your lap

まず、3次元の座標において、2次元と大きく異なるのは視点です。コンピュータの2次元座標は、左上、または左下が (0 , 0) となりました。
しかし、3次元の世界では視点そのものが移動できてしまいます。たとえ被写体が動かなくても、カメラを移動させれば移るものが変化するということです。
そのため、OpenGL の座標は柔軟であり、コマンドによって変化させることができます。従来の2次元座標のように座標系を設定するようなことも可能です。

極めて重要なことですが、OpenGL は内部で4つの座標情報から位置を割り出します。4つの情報とは、X 座標、Y 座標、Z 座標、そして W からなる (x, y, z, w) で構成されます。
これを 同次座標 と呼びます。 OpenGL は3次元射影による幾何学の同次座標内で動作しているのです。 線分やポリゴンは、複数の頂点/Vertexからなり、頂点は同次座標で位置を表します。 3次元のユークリッド空間(x, y, z)T は、同次頂点(x, y, z, 1.0)Tになり、2次元のユークリッド空間(x, y)T は、(x, y, 0.0 , 1.0)Tとなります。
w が 0 以外であれば、同次座標は3次元の点 (x/w, y/w, z/w)T に対応します。例えば、同次座標(0.5, 1, 0, 0.1)はユークリッド点(5 , 10)に対応しています。

つまり、3次元の頂点では w を 1 に、2次元では z を 0 に、w を 1 に指定します。OpenGL では、2次元でも3次元でも、内部的にはすべての頂点が3次元であると仮定します。
OpenGL における描画は、幾何学的プリミティブの頂点郡として行われます。プリミティブとは、点、線、ポリゴン、ビットマップ、画像のいずれかを指します。幾何学的プリミティブは、幾何学的オブジェクトと考えても間違いではないでしょう。OpenGL で描画を行うには、まず、このプリミティブを定義しなければなりません。
ただし、プリミティブは頂点情報にすぎないということも意識してください。プリミティブの定義が描画とイコールするわけではありません。

OpenGL は最終的にプリミティブを2次元の画像に変換しなければなりません。頂点情報は3次元ですが、コンピュータがこれを表示するデバイスは2次元だからです。
この、プリミティブを画像に変換する処理をレンダリングと呼んでいます。 幾何学的プリミティブを記述するためには、何はともあれ頂点を指定しなければなりません。
頂点データを指定するには、最初に glBegin() 関数を呼び出します。
void glBegin(GLenum mode); mode には、頂点で構成されるプリミティブの形式を指定します。これは、次のいずれかの定数を指定しなければなりません。

定数解説
GL_POINTS各頂点を単独の点として扱う
頂点 n は、点 n を意味し n この点が描画される
GL_LINES2つの頂点をペアとし、それぞれのペアを独立した線分として扱う
GL_LINE_STRIP最初の頂点から最後の頂点まで、線分を連結して描画する
GL_LINE_LOOPすべての頂点を線分で連結する
GL_TRIANGLES3つの頂点をペアとし、それぞれ独立した三角形として扱う
GL_TRIANGLE_STRIP連結した三角形のグループを描画する
GL_TRIANGLE_FAN最初の頂点を軸に、連結した三角形のグループを描画する
GL_QUADS4つの頂点をペアとし、それぞれ独立した四角形として扱う
GL_QUAD_STRIP連結した四角形のグループを描画する
GL_POLYGON単独の凸ポリゴンを描画する

glBegin() 関数を使った後、頂点を指定するいくつかの関数を使うことができます。
頂点を記述する処理が終了すれば glEnd() 関数を使って終了します。
void glEnd(void); これらの処理はワンセットなので、覚えておきましょう。頂点を記述するには glVertex() 関数を使います。

void glVertex2d(GLdouble x , GLdouble y);
void glVertex2f(GLfloat x , GLfloat y);
void glVertex2i(GLint x , GLint y);
void glVertex2s(GLshort x , GLshort y);
void glVertex3d(GLdouble x , GLdouble y , GLdouble z);
void glVertex3f(GLfloat x , GLfloat y , GLfloat z);
void glVertex3i(GLint x , GLint y , GLint z);
void glVertex3s(GLshort x, GLshort y, GLshort z);

void glVertex4d(
	GLdouble x, GLdouble y,
	GLdouble z, GLdouble w
);
void glVertex4f(
	GLfloat x, GLfloat y, 
	GLfloat z, GLfloat w
);
void glVertex4i(
	GLint x, GLint y,
	GLint z, GLint w 
);
void glVertex4s(
	GLshort x, GLshort y, 
	GLshort z, GLshort w
);

void glVertex2dv(const GLdouble *v);
void glVertex2fv(const GLfloat *v);
void glVertex2iv(const GLint *v);
void glVertex2sv(const GLshort *v);
void glVertex3dv(const GLdouble *v);
void glVertex3fv(const GLfloat *v);
void glVertex3iv(const GLint *v);
void glVertex3sv(const GLshort *v);
void glVertex4dv(const GLdouble *v);
void glVertex4fv(const GLfloat *v);
void glVertex4iv(const GLint *v);
void glVertex4sv(const GLshort *v);

x、y、z、w には、それぞれ頂点の位置を表す座標を指定します。z を省略した場合は 0.0 が、w を省略した場合は 1.0 が設定されます。
v には、頂点の位置をあらわす配列へのポインタを指定します。例えば glVertex2iv() であれば x と y 座標の2つの要素を保有する int 型へのポインタです。

さて、ここで OpenGL のコマンドに対する命名規則を解説しましょう。上記のように、glVertex() は引数型によって様々にオーバーロードされています。
C 言語では同名の関数を宣言することができないため OpenGL では関数名に接尾子を設けています。 glVertex の直後の数字は引数の数を表し、その後の文字は引数型を表しています。引数型は次のような関係になっています。

接尾子対応する型
bGLbyte
ubGLubyte, GLboolean
sGLshort
usGLushort
iGLint, GLsizei
uiGLuint, GLenum, GLbitfield
fGLfloat, GLclampf
dGLdouble, GLclampd

glVertex() 関数を使って頂点を設定する場合、必ず glBegin() 関数の後、glEnd() 関数の前に glVertex() 関数を呼び出さなければなりません。
それ以外の位置で glVertex() 関数を呼び出しても、動作は保障されません。
座標は、画面中央を (0 , 0) とするワールド座標系となっています。これは、カメラのレンズから見える世界を想像するとわかりやすいでしょう。
また、何らかの頂点を原点にする座標系を、ワールド座標に対してモデル座標と呼びます。

視点や座標変換については、これから少しずつ解説していきます。デフォルトでは、カメラは原点に配置され、負の z 軸方向を指しています。
2次元グラフィックスと異なり、どんなに大きなオブジェクトを描画しても、カメラを引いて遠くから見れば小さくなることを忘れないで下さい。
逆に、どんなに小さなものでも近くで見れば大きく見えます。最初の視点では、X、Y座標共に ±1 で画面から外に出てしまいます。
つまり、左上隅は(-1, 1, 0, 1)、右下隅は (1, -1, 0, 1) となります。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp(void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glBegin(GL_TRIANGLES);
		glVertex2f(0 , 0);
		glVertex2f(-1 , 0.9);
		glVertex2f(1 , 0.9);

		glVertex2f(0 , 0);
		glVertex2f(-1 , -0.9);
		glVertex2f(1 , -0.9);
	glEnd();
	
	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(500 , 500);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutMainLoop();
	return 0;
}


Kitty on your lap2

このプログラムでは、glBegin() 関数から glEnd() 関数までの間に、glVertex() 関数を使って2次元の三角形を2つ表示しています。
glBegin() 関数では GL_TRIANGLES を指定しため、プリミティブは三角形となります。3つの頂点を1組に、現在設定されている色で頂点が保存されます。
glClear() 関数でカラーバッファを削除していますが、glClearColor() は使っていません。デフォルトでは、glClear() 関数は黒色でバッファを塗りつぶします。
逆に、頂点のデフォルトカラーは白です。そのため、白い三角形が黒い背景に描画されているのです。

このプログラムでは、3次元のワールドをカメラから除いていると思ってください。ウィンドウのサイズが変更されても、見えるものが変わることはありません。
頂点の色は glColor() 関数で設定することが可能です。この関数も,多くの型を受け取ることができるように設計されています。

void glColor3b(GLbyte red, GLbyte green, GLbyte blue);
void glColor3d(GLdouble red, GLdouble green, GLdouble blue);
void glColor3f(GLfloat red, GLfloat green, GLfloat blue);
void glColor3i(GLint red, GLint green, GLint blue);
void glColor3s(GLshort red, GLshort green, GLshort blue);
void glColor3ub(GLubyte red, GLubyte green, GLubyte blue);
void glColor3ui(GLuint red, GLuint green, GLuint blue);
void glColor3us(GLushort red, GLushort green, GLushort blue);

void glColor4b(
	GLbyte red, GLbyte green ,
	GLbyte blue , GLbyte alpha 
);
void glColor4d(
	GLdouble red, GLdouble green,
	GLdouble blue, GLdouble alpha 
);
void glColor4f(
	GLfloat red, GLfloat green,
	GLfloat blue, GLfloat alpha 
);
void glColor4i(
	GLint red, GLint green,
	GLint blue, GLint alpha 
);
void glColor4s(
	GLshort red, GLshort green,
	GLshort blue, GLshort alpha 
);
void glColor4ub(
	GLubyte red, GLubyte green,
	GLubyte blue, GLubyte alpha 
);
void glColor4ui(
	GLuint red, GLuint green,
	GLuint blue, GLuint alpha 
);
void glColor4us(
	GLushort red, GLushort green,
	GLushort blue, GLushort alpha 
);

void glColor3bv(const GLbyte *v);
void glColor3dv(const GLdouble *v);
void glColor3fv(const GLfloat *v);
void glColor3iv(const GLint *v);
void glColor3sv(const GLshort *v);
void glColor3ubv(const GLubyte *v);
void glColor3uiv(const GLuint *v);
void glColor3usv(const GLushort *v);
void glColor4bv(const GLbyte *v);
void glColor4dv(const GLdouble *v);
void glColor4fv(const GLfloat *v);
void glColor4iv(const GLint *v);
void glColor4sv(const GLshort *v);
void glColor4ubv(const GLubyte *v);
void glColor4uiv(const GLuint *v);
void glColor4usv(const GLushort *v);

red は赤、green は緑、blue は青要素を表す RGB の色値を指定します。alpha は、現在の色に対する新規のアルファ値を指定します。
v は、それぞれの色の値を含む配列へのポインタを指定します。
色を表すには様々な方を使えますが、浮動少数なら 0 〜 1 までのパーセントで色の強さを表し、整数型であれば、その整数型の最大値が最も色が強い状態を表しています。
色が設定されれば、その後に記述された頂点の色は現在設定されている色となります。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glBegin(GL_TRIANGLES);
		glColor3ub(0xFF , 0 , 0);
		glVertex2f(0 , 0);
		glColor3f(0 , 0 , 1);
		glVertex2f(-1 , 0.9);
		glVertex2f(1 , 0.9);

		glColor3i(2147483647 , 0 , 0);
		glVertex2f(0 , 0);
		glColor3b(0 , 127 , 0);
		glVertex2f(-1 , -0.9);
		glVertex2f(1 , -0.9);
	glEnd();
	
	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(500 , 500);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutMainLoop();
	return 0;
}


Kitty on your lap3

このプログラムでは、頂点に様々な色を設定しています。例題として、glColor() 関数の引数型も、様々なものを利用しています。
多くの場合、float 型の浮動少数か、unsigned char の整数型を使うべきでしょう。3D プログラミングでは、色も従来の2次元とは性質が異なってきます。
3次元において、照明やオブジェクトの材質によって反射する色が変化するためです。

因みに、方形は使用頻度が高いため glRect() 関数としてまとめられています。
GL_POLYGON や GL_QUADS を使えば、同様の処理が実現できますが、これらをまとめて glRect() 関数が行ってくれます。

void glRectd(
	GLdouble x1, GLdouble y1,
	GLdouble x2, GLdouble y2 
);
void glRectf(
	GLfloat x1, GLfloat y1,
	GLfloat x2, GLfloat y2 
);
void glRecti(
	GLint x1, GLint y1,
	GLint x2, GLint y2 
);
void glRects(
	GLshort x1, GLshort y1,
	GLshort x2, GLshort y2 
);

void glRectdv(const GLdouble *v1, const GLdouble *v2);
void glRectfv(const GLfloat *v1, const GLfloat *v2);
void glRectiv(const GLint *v1, const GLint *v2);
void glRectsv(const GLshort *v1, const GLshort *v2);

x1 と y1 には、方形の頂点のひとつとなる X 座標と Y 座標を、x2 と y2 には、方形の対角の頂点を指定します。
v1 には頂点のひとつの X と Y 座標を保持する配列へのポインタを、v2 には、方形の対角の頂点へのポインタをそれぞれ指定します。

glRect(x1 , y1 , x2 , y2) は、次のコマンド群と等しいと考えることができます。

glBegin(GL_POLYGON);
	glVertex2(x1 , y1);
	glVertex2(x2 , y1);
	glVertex2(x2 , y2);
	glVertex2(x1 , y2);
glEnd();

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	glClearColor(1 , 1 , 1 , 0);
	glClear(GL_COLOR_BUFFER_BIT);

	glColor3f(0 , 0 , 1);
	glRectf(-0.8 , 0.8 , 0.8 , -0.8);
	
	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);
	glutMainLoop();
	return 0;
}


Kitty on your lap4

このプログラムでは glRect() 関数を使って方形を描画しています。
これは glVertex() 関数でも実現できますが、このほうがスマートです。

OpenGL は描画に関する様々な情報を状態変数として内部に保持しています。残念ながら、そのすべてをここで紹介し、意味を説明することは不可能です
しかし、それらを制御する方法については、比較的簡単に説明することができます。
OpenGL では、状態変数を表す定数が定義されています。私たちは、この定数と値を組み合わせて状態変数を制御することができるのです。
状態変数を得るには、次の関数のいずれかを使います
void glGetBooleanv(GLenum pname , GLboolean * params);
void glGetDoublev(GLenum pname , GLdouble * params);
void glGetFloatv(GLenum pname , GLfloat * params);
void glGetIntegerv(GLenum pname , GLint * params);
void glGetPointerv(GLenum pname, GLvoid ** params);

pname には取得する状態変数を示す定数を、params には取得するデータが格納される、指定した型の配列へのポインタです。
一般には、これらの関数をまとめて glGet() 関数と呼ぶことがあります。
pname に指定する定数は、数が多いためこの場ですべてを説明することはできません。
必要に応じて、リファレンスを参照してください。

例えば、現在の色は GL_CURRENT_COLOR で取得することができますし、カラーバッファの消去色は GL_COLOR_CLEAR_VALUE で得ることができます。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

GLfloat fore[4] , back[4];

void disp( void ) {
	glClearColor(back[0] , back[1] , back[2] , back[3]);
	glClear(GL_COLOR_BUFFER_BIT);

	glColor3fv(fore);
	glBegin(GL_TRIANGLES);
		glVertex2f(0 , -0.9);
		glVertex2f(-0.9 , 0.9);
		glVertex2f(0.9 , 0.9);
	glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glGetFloatv(GL_CURRENT_COLOR , back);
	glGetFloatv(GL_COLOR_CLEAR_VALUE , fore);

	glutMainLoop();
	return 0;
}

Kitty on your lap4a

このプログラムは glutMainLoop() を呼び出す前にデフォルトのカラーバッファの消去色と前景色を取得し、これを float 型配列に格納しています。
ディスプレイ コールバックでは、これらの値を逆転させて描画を行っています。そのため、背景は白、頂点は黒で描画されます。

また、何らかの状態の ON/OFF を切り替えるようなこともしばしばあります。
このようなブーリアン型の情報は glEnable()glDisable() 関数を使います。
void glEnable(GLenum cap);
void glDisable(GLenum cap);

cap には、特定機能を示すシンボル定数を指定します。cap に指定できる定数は、リファレンスを参照してください。
glEnable() 関数はその機能を有効にし、glDisable() 関数は無効にします。有効状態は GL_TRUE で表され、無効状態は GL_FALSE で表されます。
OpenGL では、GL_DITHER 以外はすべて無効状態 GL_FALSE になっていると定義されています。
なぜならば、多くの機能は有効状態にすることによって負荷が大きくなるためです。機能が有効か無効かを知るためには glIsEnabled() 関数を使います。
GLboolean glIsEnabled(GLenum cap); 関数は cap で指定した機能が有効ならば GL_TRUE を、そうでなければ GL_FALSE を返します。


 点や線の大きさ

GL_POINTS によって作られた頂点は、それぞれが点として描画されます。しかし、デフォルトの点では限りなく小さいため、ほとんど見えないでしょう。
デフォルトでは 1.0 が線の太さとして設定されています。これは、点を 1 × 1 ピクセルの正方形として描画することを表しています。
点の太さは glPointSize() 関数を使って設定することができます。また、太さを取得したいのであれば glGet() に GL_POINT_SIZE を要求するとよいでしょう。
void glPointSize(GLfloat size); size には点の直径を指定します。(10程度を指定しないと、はっきり見えない)

#include <windows.h>
#include <stdio.h>
#include <GL/gl.h>
#include <GL/glut.h>

int pointSize = 10;

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glPointSize(pointSize);
	glBegin(GL_POINTS);
		glVertex2f(0 , -0.9);
		glVertex2f(-0.9 , 0.9);
		glVertex2f(0.9 , 0.9);
	glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	if (argc >= 2) sscanf(argv[1] , "%d" , &pointSize);

	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}


Kitty on your lap5

このプログラムは、コマンドライン引数として点のサイズを入力して起動します。すると、引数文字列を数値に変換し、glPointSize() 関数を使って点を描画します。
上の図は、引数に 10 を入力してプログラムを実行したものです。同様に、線の太さを設定することも可能です。
線の太さは glLineWidth() 関数を使って設定することができます。現在の線の太さを得るには glGet() 関数で GL_LINE_WIDTH を要求してください。
void glLineWidth(GLfloat width); width には線の太さをピクセル単位で指定します。この値は、デフォルトで 1.0 が設定されています。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	float width;
	glGetFloatv(GL_LINE_WIDTH , &width);

	glClear(GL_COLOR_BUFFER_BIT);

	glLineWidth(++width);
	glBegin(GL_LINE_LOOP);
		glVertex2f(0 , -0.9);
		glVertex2f(-0.9 , 0.9);
		glVertex2f(0.9 , 0.9);
	glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}

Kitty on your lap6

このプログラムは、ディスプレイ コールバックが呼ばれるたびに線が太くなります。glGet() 関数で現在の線の太さを取得し、これをインクリメントして新たに設定します。
ウィンドウを隠すなど、再描画処理を繰り返すと太くなるのが確認できるでしょう。

 破線

通常の GL_LINES 等で描画した線は単純な直線でした。しかし、設定を変えることで破線を扱えるようになり、線の表現力を強化できます。
デフォルトの設定では破線がオフ状態になっています。そこで、破線を使うには glEnable() 関数で GL_LINE_STIPPLE を有効化する必要があります。

次に、glLinStipple() 関数を使って破線パターンを設定します。この関数を使って、破線のパターンを 16 ビットの値で設定することができます。
void glLineStipple(GLint factor, GLushort pattern);
この関数は少しややこしいので、まずは pattern 引数から説明しましょう。ここには、2進数で破線を描画するピクセルを16ビットの数値で指定します。
与えられた2進数のうち 1 は描画するピクセルを表し、0 は描画しないピクセルを表します。

例えば、0xACF3 という16ビットを pattern に与えた場合、0xACF3 の2進数表現は 1010110011110011 となり、これが線として繰り返されます。
つまり - - -- ---- -- という形の破線になります。factor は、この破線を拡大するために設定します。
pattern に factor を乗算した値が実際の破線となります。(つまり、上記した例は factor=1)
先ほどの 0xACF3 という値に対して factor を 2 に指定した場合、11001100111100001111111100001111 となります。これは、単純に先ほどの破線を2倍にした形です。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glEnable(GL_LINE_STIPPLE);
	glLineStipple(1 , 0xF0F0);

	glBegin(GL_LINE_LOOP);
		glVertex2f(0 , -0.9);
		glVertex2f(-0.9 , 0.9);
		glVertex2f(0.9 , 0.9);
	glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}

Kitty on your lap7

このプログラムでは 0xF0F0 をパターンとして破線で三角形を描画しています。0xF0F0 は 1111000011110000 というパターンなので、これを繰り返すと上の図のようになります。 因みに、破線パターンは GL_LINE_STIPPLE_PATTERN 定数を用いて、反復回数は GL_LINE_STIPPLE_REPEAT 定数を用いて glGet() 関数から得ることができます。


 塗りつぶしアイコン

通常、ポリゴンの塗りつぶしは全体を固定して塗りつぶしますが、破線を描画したように、ビット配列をパターンとした点描処理で塗りつぶすことも可能です。
ビット配列は縦横 32 ビット、128 バイトの配列で構成されます。やはり、ビットが 1 であればピクセルが描画され、0 であれば描画されません。
この縦横32ビットのアイコンを繰り返してポリゴンが塗りつぶされます。ポリゴンの点描を有効化するには、GL_POLYGON_STIPPLE を有効化する必要があります。
パターン配列は glPolygonStipple() 関数で設定することができます。
void glPolygonStipple(const GLubyte *mask); mask には、点描パターンを表す128バイトの配列へのポインタを指定します。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

GLubyte mask[128];

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glEnable(GL_POLYGON_STIPPLE);
	glPolygonStipple(mask);

	glBegin(GL_POLYGON);
		glVertex2f(0 , -0.9);
		glVertex2f(-0.9 , 0.9);
		glVertex2f(0.9 , 0.9);
	glEnd();	

	glFlush();
}

int main(int argc , char ** argv) {
	int iCount;
	for(iCount = 0 ; iCount < 128 ; iCount++) mask[iCount] = 0xF0;

	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}

Kitty on your lap8

このプログラムでは、単純に 0xF0 を繰り返す配列をパターンとして選択しています。その結果、上の図のような縦じまの模様でポリゴンが塗りつぶされました。


 凸型と非凸型

実は、OpenGL のポリゴンは凸型のポリゴンしか表示することができません。GL_POLYGON で描画する時に、穴の開いたポリゴンや凹んだポリゴンは表示できないのです。
そのため、三角形や四角形などのポリゴンは有効ですが、中央に穴の開いたポリゴンや、アルファベットの M のような形は描画できないことになります。
このような複雑なポリゴンを作るには、複数の単純な凸型ポリゴンを組み合わせるのです。通常、複雑なポリゴンの描画は複数個の三角形で構成します。

しかし、複数の三角形で複雑なポリゴンを構成する場合、一部の辺が図の内部に埋もれます。これでは境界線がどこにあるのかわかりません。
そこで、非凸型ポリゴンを作るには glEdgeFlag() 関数を使って、頂点がポリゴンの境界辺の開始点となるかどうかを指定することができます
void glEdgeFlag(GLboolean flag);
void glEdgeFlagv(const GLboolean * flag);
flag が GL_TRUE を表すなら、その頂点は境界辺の開始点であることを表します。デフォルトではすべての頂点が境界辺の開始点となります。

この関数に GL_TRUE を渡した後の頂点は、境界辺の開始点であると判断され、GL_FALSE を渡した後の頂点は、境界辺の開始点ではないと判断されます。
この状態値を エッジフラグと呼んでいます。glEdgeFlag() 関数は、glBegin() と glEnd() の間でも呼び出すことができます。
それ以外の場所でも呼び出すことができるため、事実上、常にエッジフラグを変更できます。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glBegin(GL_POLYGON);
		glEdgeFlag(GL_TRUE); glVertex2f(-0.9 , -0.5);
		glEdgeFlag(GL_TRUE); glVertex2f(-0.85 , 0.9);
		glEdgeFlag(GL_FALSE);glVertex2f(0 , 0.3);

		glEdgeFlag(GL_TRUE); glVertex2f(-0.9 , -0.5);
		glEdgeFlag(GL_FALSE);glVertex2f(0.9 , -0.5);
		glEdgeFlag(GL_TRUE); glVertex2f(0 , 0.3);

		glEdgeFlag(GL_TRUE); glVertex2f(0.85 , 0.9);
		glEdgeFlag(GL_FALSE);glVertex2f(0.9 , -0.5);		
		glEdgeFlag(GL_FALSE);glVertex2f(0 , 0.3);
	glEnd();	

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}

Kitty on your lap9

上の図は、エッジフラグの設定を行うことで実現した非凸型のポリゴンです。この図形には、3 つ三角形が隠されていることに気づいてください。
頂点に色をつけるなどをすれば、3 つの三角形がはっきりと見えるでしょう。


 頂点を配列として保存する

これまでは、glBegin() と glEnd() の間で頂点を指定するとき、glVertex() 関数を用いて、一つひとつの頂点を直接指定しました。
しかし、定数を直接ハードコーディングするのは、保守性や整合性を維持するために、できる限り避けるべきであるという大原則があります。
とくに、OpenGL の晴れ舞台であるゲームプログラミングでは、定数を描画や制御に関係するコードに書き込まないのは基本中の基本です。

そこで、頂点を配列として扱うことができれば、かなりまとまった処理を実現できます。そうすれば、ディスプレイ コールバックでは配列から頂点を選択するだけとなるでしょう。
また、複雑なポリゴンでは頂点を共有することも珍しくありません。このような場合にも、配列化された頂点は大いに役に立つはずです。
頂点配列を使うためには、配列をセットする必要がありますが、その前に、まずは配列を有効化する必要があります。
配列を有効にするには glEnableClientState() 関数を呼び出します。また glDisableClientState() 関数で無効化することも可能です。
void glEnableClientState(GLenum array);
void glDisableClientState(GLenum array);
array には、有効化する配列を表す定数を指定します。
ここには、次のいずれかの定数を指定することができます。

定数対象配列と関連関数
GL_COLOR_ARRAYカラー配列。glColorPointer() を参照
GL_EDGE_FLAG_ARRAYエッジフラグ配列。glEdgeFlagPointer() を参照
GL_INDEX_ARRAY指標配列。glIndexPointer() を参照
GL_NORMAL_ARRAY法線配列。glNormalPointer() を参照
GL_TEXTURE_COORD_ARRAYテクスチャ座標配列。glTexCoordPointer() を参照
GL_VERTEX_ARRAY頂点配列。glVertexPointer() を参照

頂点配列を有効化するには、GL_VERTEX_ARRAY を glEnableClientState() に指定します。
なぜ、この定数が glEnable() 関数で有効化することができないかというと、このような配列情報はサーバーではなく、クライアントに保存されるためです。

次に、配列データを作成して保存する必要があります。ここで言う「配列」はプログラミング言語の配列ではなく OpenGL における配列です。
頂点配列を定義するには glVertexPointer() 関数を使います。

void glVertexPointer(
	GLint size , GLenum type , GLsizei stride ,
	GLsizei count, const GLvoid *pointer   
);

size には、頂点データのサイズを指定します。ここで言うサイズは、頂点データが保有する座標情報の数です。
2次元であれば 2 を、Z 座標を含めるなら 3 を、w を含めるなら 4 を指定します。
type には配列の型を指定します。ここには GL_SHORT、GL_INT、GL_FLOAT、GL_DOUBLE のいずれかを指定してください。
これまでのように float 型で頂点データを扱うならば GL_FLOAT を指定します。

stride には、連続する頂点間でのバイト・オフセットを指定します。
ここに 0 を指定すれば、頂点の情報が配列ないに隙間なく収まっているということを表します。pointer には、座標情報を含む配列へのポインタを指定します。
これで、頂点配列を定義することができました。あとは、glBegin() と glEnd() の間で、有効な配列から頂点データを取得するだけです。
頂点データの書き込みは glArrayElement() 関数で行うことができます。
void glArrayElement(GLint index); index には参照する頂点配列のインデックスを指定します。
これで、頂点をインデックス指定で制御することができるようになりました。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat vertex[] = {
	-0.9 , 0.9 , 0.9 , 0.9 , 0 , -0.9
};

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(2 , GL_FLOAT , 0 , vertex);

	glBegin(GL_POLYGON); {
		int i;
		for(i = 0 ; i < 3 ; i++) glArrayElement(i);
	} glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}

Kitty on your lap10

このプログラムは、これまでのように白抜きの三角形を描画するだけですが、重要なのは glBegin() と glEnd() の間です。
頂点の指定を glVertex() ではなく glArrayElement() 関数で行っています。頂点配列が定義されていれば、プリミティブのレンダリングも一括することができます。
glArrayElement() 関数は、頂点配列からインデックスを指定しましたが、頂点配列のインデックスを指定する指標を使ってプリミティブを指定できないかと考えるでしょう。
これを実現するのが glDrawElements() 関数です。

void glDrawElements(
	GLenum mode , GLsizei count ,
	GLenum type , const GLvoid *indices
);

mode には glBegin() 関数の引数で指定する、プリミティブの種類を指定します。
ここに指定することができる定数は glBegin() 関数を参照してください。
count にはレンダリング要素の数を指定します。

type には indices の型を指定します。
ここには GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT のいずれかを指定できます。
例えば GLubyte 型であれば GL_UNSIGNED_BYTE を指定してください。

indices にはインデックス指標を保存する配列へのポインタを指定します。
関数はこの指標と定義されている配列に基づいて、プリミティブを定義します。
glDrawElements() を使えば、glBegin()〜glEnd() を一行にまとめられます。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat vertex[] = {
	-0.9 , 0.9 , 0.9 , 0.9 , 0 , -0.9
};

void disp( void ) {
	GLubyte indices[] = { 0 , 1 , 2 };
	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(2 , GL_FLOAT , 0 , vertex);

	glDrawElements(GL_LINE_LOOP , 3 , GL_UNSIGNED_BYTE , indices);
	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}

Kitty on your lap11

このプログラムは、glBegin()〜glEnd() の代わりに glDrawElemnts() を使っています。
glDrawElements() にはインデックス指標となる indices 配列を渡しています。
関数はこの指標を元に、定義されている配列を参照してプリミティブを定義します。
指標となる indices は、先頭から順に 0、1、2 という値を格納しています。
これは、定義されている頂点配列のインデックスを表しています。

指標配列を用意することが冗長であるならば glDrawArrays() を使えます。
上のプログラムのインデックス指標は単純増加しているだけなので、このような場合は、glDrawArrays() 関数を使って特定範囲の頂点配列を指定することができます。
void glDrawArrays(GLenum mode , GLint first , GLsizei count);
mode にはプリミティブの種類を指定します。これもやはり glBegin() 関数で指定する定数のいずれかです。
first には、有効な配列内の開始指標を、count にはレンダリングする指標の数を指定します。
つまり first から first+count-1 までの配列要素でプリミティブを構築します。

#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat vertex[] = {
	-0.9 , 0.9 , 0.9 , 0.9 , 0 , -0.9
};

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(2 , GL_FLOAT , 0 , vertex);

	glDrawArrays(GL_POLYGON , 0 , 3);
	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glutMainLoop();
	return 0;
}

Kitty on your lap12

このプログラムもまた、単純な三角形を描画するだけですが、頂点配列と glDrawArrays() を使ってレンダリングしているところに注目してください。

[Home]