Bashプログラミング
 Computer >> コンピューター >  >> プログラミング >> Bashプログラミング

BashでOpenGLを動かす――シェルスクリプトのためのOpenGLバインディング

Perl 5の設計と「グルー言語」としての適性について語った前回の記事の中で、私は以前Bash用のOpenGLバインディングを書いたことに触れました。証拠なしには信じがたい発言だったので、ハードディスクの埃をかぶった片隅から掘り起こし、コードを手直しし、フォントサポートを改善し、ドキュメントを整えて、自身のサイトとGitHubで公開しました。実際に体験するにはBashとOpenGLの両方をサポートしたシステムが必要ですが、デモ動画も用意しています。

さて、ここでひとつ告白をさせてください。前回の記事「My DeLorean runs Perl」で紹介したデロリアンのダッシュボードに表示されるPerlグラフィックスは、実はこのBash向けOpenGLプロジェクトと歴史を共有しています。面白い皮肉なことに、このプロジェクトを始めたきっかけは13年前、Frozen Bubbleを目撃したことでした。「誰かがPerlでリアルタイムのビデオゲームを書いた」という事実に、技術者としての感性が深く傷つけられたのです。当時、私の主力言語はC++で、ゲーム開発のためにOpenGLを学んでいる最中でした。友人たちに「これよりひどいのは、3Dで、しかもBashで書かれていた場合だけだ」と宣言しました。ところが、口に出したその言葉が頭から離れず、結局は「リアルタイムグラフィックスにPerlを使う」という「ひどさ」を上回るために、自ら試してみることにしたのです。

Bashの拡張方法

BashにOpenGLサポートを追加する最も直接的な方法は、ソースコードを改造して各OpenGL関数をシェルの組み込みコマンド(builtin)として追加することでした。しかし、それを体験できるのはカスタム版Bashのインストールを喜んで受け入れる人だけで、おそらく誰もそんなことをしないでしょう。何より、「Bash流」のやり方の精神にも沿わない気がしました。

次に思いついたのがクライアント・サーバー方式です。各OpenGL関数をパス上に配置し、クライアントがOpenGLサーバーへ接続して、関数の実行を依頼するメッセージを送る仕組みです。これは実に「ひどい」解決策として魅力的でしたが、どれほど最適化しても、当初の目標を達成したと言える速度には到底届きませんでした。

最終的に落ち着いたのは、「OpenGLインタープリター」と呼ぶプロセスを用意する設計です。このプロセスは標準入力からOpenGLコマンドを読み込み、ユーザーの入力イベントを標準出力へ書き出します。Bashスクリプトはこのインタープリターをパイプで接続して起動し、各OpenGLコマンドはパイプに書き込むBash関数として実装されます。せっかくなので、手持ちの効率化テクニックを総動員し、静的コンパイルしたハッシュテーブルと赤黒木を組み合わせた素のC言語でインタープリターを書き上げました。

OpenGLのイミディエイトモード

OpenGLの詳細は、このプロジェクトがデロリアンのダッシュボードプロジェクトと交差する部分です。まず一歩引いて、OpenGL APIをおさらいしましょう。

OpenGLとは、3D座標とテクスチャの観点からグラフィックスを設計するプログラムを書き、そのデータをグラフィックカードへ送り、2D画面にレンダリングさせる仕組みです。極端に単純化して言えば、「描画対象となる画面領域を記述する数学」と「その領域内の各ピクセルの色を記述する数学」の2つのセットで構成されています。

最も一般的な操作は、仮想3D空間内の三角形の3つの頂点を定義し、OpenGLに2D画面上のどこに投影されるかを計算させ、その領域に2D画像を貼り付けることです。別の画像との合成や、明るさの計算による加工が加わることもあります。プログラムのメインループは、バッファを消去し、可視のすべてのポリゴンを描画し、結果を画面へ送ることで1フレームを生成します。この一連の処理を16ミリ秒以内に完了できれば、毎秒60フレームを維持し、滑らかで流麗なグラフィックスが実現します。

OpenGL APIの起源について権威をもって語ることはできませんが、このAPIが「ストリーミング」という発想を軸に構築されていることは明らかです。X11ディスプレイプロトコルとの互換性を意識したためでもあり、単純にそれが優れたアイデアだったからでもあります。そのため、ほとんどのOpenGL関数は戻り値を持ちません。他の多くのAPIが、関数呼び出しごとに操作の成否を示すステータスを返すのとは対照的です。glVertex3f(1,2,3)のような関数を「パイプ越しに3つの数値を書き出すprint文」としてイメージすれば、内部の動作がおおよそ掴めるでしょう。実際、Bashバインディングでは、glVertex 1 2 3が実行されると、文字通りパイプへ「glVertex 1 2 3」というテキストを書き出します。Bashスクリプトは目隠し状態で走り、自分のグラフィックス命令が意図どおり機能しているかどうかを一切知りません。

OpenGL APIには長年にわたり幾度もの大きな改訂があり、「保持モード(retained mode)」対「即時モード(immediate mode)」について書籍で一章を丸ごと割くこともありますが、その本質は次の2つの概念に集約されます。

  • パイプ越しに毎フレームすべてのデータを再送するのは遅すぎる。向こう側でキャッシュしよう。
  • 組み込みの数学だけでは全員のニーズを満たせない。独自の数学を記述できる言語を提供しよう。

とはいえ、新しいOpenGL APIが提供するキャッシュ機構とカスタムシェーダーは格段に効率的で、トップクラスのビデオゲームには確実に必要です。しかし一方で、セットアップにも実装にも、そして学習にも多くの手間がかかり、ホビイストにとっては害の方が大きいかもしれません。高度なグラフィックスエフェクトや高精細なモデルを扱わない限り、必須ではないのです。

だからこそ、Bash OpenGLバインディングは「非推奨(deprecated)」扱いの旧APIのみを対象としていますが、教育や実験の目的であれば、今なお十分に価値があると考えています。

ディスプレイリスト

幸い、初期のOpenGL APIには扱いやすいキャッシュ機構が備わっています。おおまかには、次のような流れで動作します。

「ねえOpenGL、リモート側にオブジェクト37を作っておいてほしいんだ」

「これはオブジェクト37を記述するデータだよ」

「次のレンダリングステップで、オブジェクト37を使ってね」

さらに便利にするため、Bashバインディングでは数値の代わりに名前を使えるようにしました。

第1の主要なオブジェクトタイプはテクスチャです。2D画像をグラフィックカードへロードし、それを使ってポリゴンを「塗る」ものです。第2が「ディスプレイリスト」で、OpenGLコマンドのシーケンスを記録しておき、それをまるで単一のOpenGLコマンドであるかのように再生できる仕組みです。

ディスプレイリストは、場当たり的なプロトタイピングに絶大な威力を発揮します。シンプルな頂点コマンドで3D(または2D)モデルを構成する点群を記述し、そのシーケンスをディスプレイリストとして記録すれば、以降はたった1つのコマンドでモデル全体をレンダリングできるのです。

この威力の実例として、ExamplesディレクトリのRobot.shをご覧ください。起動時にロボットの体の各セグメントごとにディスプレイリストを1つずつ作成し、実行中はロボットを描画するために必要な出力が、わずかフレームあたり58行のテキストだけになります。Bashでも16ミリ秒以内に58行のテキストを生成するのは十分可能です(12年前の環境でもできました)。だからこそ、このデモは一般的なハードウェア上でフルスピードで動作できたのです。

ディスプレイリストこそが、デロリアンのダッシュボードソフトウェアで使っているPerlグラフィックスに持ち越した最大のトリックです。Perlの関数呼び出しはCよりもやや高価で、特にOpenGL 1.4のような関数中心のAPIではその傾向が顕著です。しかし、処理をディスプレイリストへまとめてしまえば、Perlが1フレームあたり担う仕事はごくわずかで済みます。Bashプロジェクトでこの手法を学んでいなければ、Perlプロジェクトはここまで成功しなかったはずです。

なぜ12年前に公開しなかったのか?

Bashで派手なアニメーションデモを作ることはできましたが、本当の目標は完全なゲームの制作でした。狙いは往年のフライトシューティングゲームTerminal Velocityのクローンで、立方体のフィールドを飛行できる宇宙船までは実装しました(ExamplesのFlight.shを参照)。しかし、次の壁は衝突判定でした。Bashで利用できるプリミティブは、変数名をキーとしたグローバルな「連想配列」と配列変数程度(Bash 4では連想配列変数が追加されました)で、しかもすべての演算は整数のみ。固定小数点整数演算による3D行列回転はなんとか実現できたものの、衝突判定の実装は乗り越えるには大きすぎる障害に見えました。加えて、自作のフォントAPIにも満足していませんでした。こうした問題の解決策を模索するうちに、プロジェクトはいつしか私の優先リストから消えていったのです。

そして私はそこで、記事に書いたとおり「Bashはひどいグルー言語だ」と結論づけました。このプロジェクトはジョーク(あるいは教育ツールと呼んでもいいかもしれません)として始まり、ジョークとして終わりました。ところが、同じスタイルのプログラムがPerlでは現実のアプリケーションに驚くほど役立つことが分かったのです。もし13年前にFrozen Bubbleをもっと真剣に評価していたら、今や私のお気に入りの言語となったPerlに、もう少し早く巡り会えたかもしれません。

バグ修正以外でこのプロジェクトを拡張する予定はありませんが、OpenGLの概念を学びたい方や、余った時間と強い意志を持って「M4でDoomクローンを書きたい」という方には、少なくとも役に立つのではないでしょうか。

ぜひ楽しんでください!そして、このリンクを周囲に広めてください。さもないと、誰もあなたの話を信じてくれませんから。

  1. 楽しく学べる!Bash習得に役立つコマンドラインゲーム3選

    学習は大変な作業であり、誰も「作業」そのものは好きではありません。つまり、Bashがどれだけ習得しやすい言語だとしても、やはり「勉強」という感覚を拭いきれないかもしれません。しかし、ゲームを通して学ぶなら話は別です。 ターミナルの使い方を教えてくれるゲームなど、そう多くは存在しないと思うでしょう。実際、その通りなのです。PCゲーマーならご存知の通り、FalloutシリーズにはVault内にターミナル式のコンピュータが登場し、テキストでコンピュータと対話するという概念に自然と親しめるようになっています。AlpineやEmacsに似たアプリケーションも登場しますが、残念ながらFalloutをプレイ

  2. 【2022年版】PCで使えるAndroid OSおすすめ12選|エミュレータの仕組みも徹底解説

    Googleが開発したAndroidは、スマートフォンやタブレット向けに設計された、世界で最も利用されているOSのひとつです。アプリの作成からダウンロード、インストールまで柔軟に行える利便性の高さから、「PCでもAndroidを使いたい」と考える人は少なくありません。実際、エミュレータソフトやAndroidベースのOSを活用すれば、WindowsパソコンやノートPCでもAndroidアプリやゲームを快適に動かせます。 本記事では、PCで使えるおすすめのAndroid OS・エミュレータを12個厳選して紹介します。まずは、これらのツールがどのような仕組みで動作しているのかを理解しておきましょう