今週の進み具合 #19 - 趣味のゲームエンジン開発 2015 初夏

前回の日記から半年以上経ってしまいました。 前回の進捗日記から今の今まで一体何をしていたのかコミットログをさかのぼりながら振り返ってみます。

進み具合 (2014年9月23日 - 5月19日)

大きく分けて3つのことをやりました。

  • いろいろ動かしてみたり、試してみたりしました
  • Windows (DirectX, OpenGL) でも動かせるようにしました
  • Travis CI, AppVeyor, Gitter, Trello などの外部サービスを利用し始めました

SSAO を実装しました

詳しい話を書くと長くなるので、いまはスクリーンショットを載せておきます。 図はそれぞれ、SSAO 適用前 (図1) と SSAO 適用後 (図2) です。

(図 1) SSAO なし

(図 1) SSAO 適用前

(図 2) SSAO 適用後

(図 2) SSAO 適用後

SSAO については、いずれまとまったお話を書きたいところです。

MagicaVoxel に対応しました

去年の 10 月頃にサポートしたきり、日記にこの話を書いていなかったのを思い出しました。 MagicaVoxel が提供している VOX フォーマットに対応しました。 モデルの読み込みやレンダリング、VOX ファイルへの書き出しができるようになりました。 レンダリングについては、ハードウェアインスタンシングを使って、ボクセル1つ1つをリアルタイムに描画しています。 先ほど載せた SSAO のスクリーンショット (図1, 2 参照) でも使っています。

Clang のバージョンが 3.4 => 3.6 になりました

Xcode に含まれている Apple LLVM Compiler で採用されている Clang のバージョンが 3.6 ベースになりました。 前回の日記の時までは Clang 3.4 で C++1y オプションをつけてビルドしていましたが、現在は C++14 オプションをつけてビルドしています。

Windows と Visual Studio への対応について

実は Windows での実装はだいぶ早い時期に完了していました。昨年の 10 月頃には動いていたと思います。 いま作っているエンジンは Mac と Xcode を使って実装し始めましたが、元々 Win32 と DirectX でゲームを作っていたこともあって、過去の遺産がここで役に立っています。

Windows に対応する上で問題となったのは C++ コンパイラです。 Visual Studio はまだ完全に C++11/C++14 へ対応していません。 また警告やエラーの細かな振る舞いが Clang とは異なっています。 そのため Xcode 上で動いていたコードが Visual Studio ではコンパイルが通らないということが多々ありました。

Visual Studio については 14 CTP, 2015 Technical Preview, 2015 RC と使ってきて、少しずつ C++11/C++14 の機能が利用できることを確認しています。 ゲームエンジンのコアにあたるソースコードについては、(2015 年 5 月現在)最新の Visual Studio 2015 RC でビルドできるようになりました。 ゲームプログラムを書く分には、C++11/C++14 の対応状況はだいぶ良くなってきた印象を受けます。

DirectInput と RawInputDevice

これまで Win32 でキーボードとマウスまたはゲームパッドの入力を取得するために DirectInput を利用していました。 DirectInput はだいぶ前から DirectInput 8 を最後に更新が止まっており、代わりに XInput が登場しました。 XInput ではキーボードやマウスの入力は取得できず、ゲームパッドの入力のみとなっています。 DirectX 9 の時代にアナウンスがあったようで、「DirectInput を使用するのはゲームパッドやジョイスティックの入力を取るときのみにしてください」とのことです。 Win32 でキーボードやマウスの入力を取得するには代わりに RawInputDevice という API を利用します。

そういう経緯もあって、DirectInput で実装していたものをつい先日、WM_INPUT と RawInputDevice に置き換えました。

Direct3D 11 と HLSL の対応

Direct3D 11 で動くようになりました。 Direct3D 11 に対応するにあたって、HLSL によるカスタムシェーダもサポートしています。 コンパイル済みのバイナリと、HLSL で書かれたソースコードの文字列のどちらも利用できます。 もちろん HLSL の #include にも対応してます。 HLSL をコンパイルして、バイナリ (shader blob) を C++ のソースコードに埋め込めるツールも Python で用意しました。

DirectX 12 を試しました

ちょっと不安定ですが、なんとなく動いてます。 DirectX 12 の API 自体がころころ変わっているので、master ブランチにはマージしていません。

BlendState, DepthStencilState, RasterizerState がなくなり、新たに PipelineState が加わりました

BlendState, DepthStencilState, RasterizerState といった DirectX 11 世代ではおなじみだったレンダリングステートを廃止しました。 DirectX 12 の API に沿うように、新たに PipelineState を追加しました。 PipelineState は上記の BlendState, DepthStencilState, RasterizerState の他に、各ステージごとのプログラマブルシェーダ(頂点シェーダやピクセルシェーダ、ジオメトリシェーダなど)や入力レイアウトなどを統合したものになります。 Pomdog はこれまで Direct3D 11 に沿ったグラフィックス API を提供してきましたが、今後は Direct3D 12 に沿うようになります。 もちろん今まで通り、Direct3D 11 や OpenGL 環境でもエミュレートして動くので、安心してください。

GLEW を使いました

GLEW を使って、Windows 環境でも OpenGL が利用できるようになりました。 よって、Windows 環境では今のところ OpenGL 3.3/4.0 と Direct3D 11 の両方が利用できます。 コンパイル時ではなくランタイム時に、どちらをレンダリングシステムとして使うか選択することができます。 もちろん、ビルドするときに OpenGL と Direct3D のどちらかをビルドから外すこともできます。

また、X11 環境で OpenGL を使う場合も GLEW を使うことになります。

X11 で動かしてみました

現在、Windows では Win32, Mac OS X では Cocoa というウィンドウシステムまたはプラットフォームを利用しています。 Linux デスクトップ環境で動かす場合は、X11 を(おそらくほとんどの場合)利用することになります。 X11 の API 自体は Mac でも利用することが可能で XQuartz という実装が用意されています。

この X11 を動かせるかどうか試してみました。 こちらは、OpenGL 3 のコンテキストさえ作ることができれば、問題なく動かすことができました。 X11 自体は Win32 によく似ています。OpenGL コンテキストの作成も非常に既視感がありました。 問題が出てくるとしたら、動作環境を作るのが少し大変なところでしょうか。 OS X の XQuartz では OpenGL 2 までのコンテキストしか利用できません。 また、VirtualBox などを使って作った仮想の Ubuntu 環境でも OpenGL のドライバーが 3 以降に対応していません。

X11 と OpenGL 3.3/4.0 を使ってゲームを作るには、適当なラップトップかデスクトップに Linux をインストールする必要がありそうです。 Intel の Linux 向けグラフィックスドライバーは現在のところ OpenGL 3.3 にしか対応していないので、4.0 の機能をゲームで使うのはちょっと難しそうです。 ちなみに NVIDIA と AMD による Linux 向けのドライバーは 4 以降に対応しています。

X11 向けの実装は master ブランチにはまだマージしていないので、GitHub に公開しているリポジトリには含まれていません。 気が向いたら、Ubuntu でもゲームが動くようにしてみたいと思います。

OpenAL と XAudio2 で実装しました

対応しました。Windows でも Mac でも音が鳴ります。 一部オーディオリスナーやエミッターといった 3D オーディオの機能にも対応しています。

CoreAudio を試しました

OpenAL は非常に使い勝手のいい API で実装も大変楽でしたが、その分制約も厳しく、使えるフォーマットやチャンネル数などが限られていました。 そこで Mac の CoreAudio に含まれる AudioUnit.framwork を使ってみることにしました。 オーディオグラフを作成することができ、OpenAL に比べてより詳細な操作ができます。 ただし、実装しなければいけないことがちょっと多いです。これについては、少しずつ実装中です。

Cocoa と OpenGL のスレッド間の処理が安定するようになりました

Mac でゲームを動かすことになると、Cocoa の NSView が動いているメインスレッドとは別に、60 fps で描画するために毎フレーム、ゲームの処理を呼び出すスレッド(描画スレッド)を使うことになります。 この描画スレッドは Cocoa が提供している NSThread や C++11 の std::thread を使って while と sleep を使って実装してもかまいません。 60 fps 以上で動かしたい場合は、これらの方法が適していると思います。 今回は 60 fps または 30 fps で動かし、オーバードローを起こさないように Cocoa の CVDisplayLink を使ってディスプレイの更新に同期するようにしました。

OpenGL コンテキストを lock したり、コンテキストに描画先となる NSView を指定したり、リソースに mutex をつけたり、ついつい忘れがちになる処理が多くて安定するまで骨が折れました。 この NSView と OpenGL のスレッド間の話についてはまたどこかでお話したいところです。

NOTE
Mac OS X と Cocoa を使ってゲームを開発する場合に、方法によってはメインスレッドだけで完結することができます。 エントリーポイントとなる main 関数で NSApplicationMain を呼ばずにプログラム側で NSWindow や NSView を作り、自力でメインループを呼び出すことで描画スレッドをたてずに済みます。 例として SDL2 や SFML, GLFW がそのような実装になっています。 こうすると XIB ファイルを用意する必要がないので、ライブラリやフレームワークを使い始めやすい利点があります。 ただし、Interface Builder との連携が難しくなる短所もあります。

NSFont によるフォントレンダリング

フォントのレンダリングをいろいろ試していました。 日本語のように使用する文字の数が多く、漢字のように文字の形が複雑だと、レンダリングは、それはもう大変です。 Windows だと DirectWrite を使って高品質なフォントのレンダリングができますが、Mac では NSFont と Core Graphics の機能を利用することで実現できます。 あるいは、freetype2 を使うのもいいかもしれません。

iOS と OpenGL ES 3.0 対応

iOS で動かせるかどうか試してみました。なんとなく動きました。 ただ OpenGL ES 3.0 でジオメトリインスタンシングやサンプラーステートに対応したとはいえ、 どこまでいってもモバイルをターゲットにするというのは制約が強く、デスクトップ向けのゲームに比べて少し大変そうでした。 今は Windows と Mac OS X で安定した API と実装を提供することがひとまずの目標なので、モバイル対応についてはひとまず置いておきます。

Metal 始めてみました

これまたなんとなくゲームエンジン上で動かせそうでした。 OpenGL ES 3.0 よりは Metal のほうが DirectX 12 が提供している機能と似ているところがあって、実装しやすかったです。 ただ、signing しないとビルドすらできないということがわかったので、そっと Xcode を閉じました。 エミュレータでは動作せず、iPhone 5S や iPhone 6 以上の実機がないと動かせないこともわかったので「これにておさらば」といったところです。

UTF-8 BOM をソースコードから消しました

日本語コメントに対応するためにソースコードの先頭に UTF-8 BOM をつけていました。 そうすることで、Visual Studio では UTF-8 で書かれたソースコードだと正しく認識してくれます。 BOM をつけないと日本語環境の Visual Studio では Shift-JIS で読み込まれることもあり、読み込み時や保存時に大変なことになります。 特に Xcode やそのほかのエディタで読み込むと、頻繁に文字化けを起こします。 BOM をつけていて大変だったのは、Xcode が保存時に余計なおせっかいで BOM を削除することでした。 そこで BOM のない UTF-8 のソースコードに対して BOM を自動的につける Python スクリプトを書いて動かしていましたが、これらをすっぱりやめ C++ のソースコードについて BOM をつけないようにしました。 BOM を外したかわりに、日本語(英語以外)によるコメントアウトをすべて削除しました。

日本語のコメントアウトは主に、Doxygen 用のドキュメンテーションコメントでした。 Doxygen では @~English@~Japanese でドキュメントを多言語に対応することができます。 英語や日本語以外にドキュメントで扱う言語が増えていく予定は今のところありませんが、もし今後、中国語やロシア語、アラビア語などに対応していくなら、と考えた時に Doxygen が提供するこのドキュメンテーションコメントの記述の仕方はあまり現実的ではないと思いました。 ゲームエンジンのソースコード自体の更新頻度とドキュメントのローカライズの更新頻度も一致しないので、C++ のソースコードに多言語によるコメントを含めるべきではないと至りました。

ハードタブからソフトタブにしました

Linux Kernel も Go 言語もインデントはハードタブですが、調べてみたところ C++ のプロジェクトの大半がインデントにスペース(ソフトタブ)を用いているようです。 長いものに巻かれる思いで、ハードタブから 4 スペースにすべて置換しました。 こういった変更はコミットログが非常に大きくなるので、早い段階でインデントやコーディングスタイルについては取り決めをしておいたほうがよさそうです。

Clang Format 始めました

Clang Format を使うことにしました。 Clang Format はその名の通りフォーマッターです。C++ だけでなく Java や JavaScript にも対応しています。 リポジトリのルートディレクトリに .clang-format ファイルとして、コーディングスタイルを記述しておくことで、そのスタイルに沿ってソースコードをフォーマットしてくれます。

C++ の良いところであり悪いところは、コーディングスタイルが言語仕様(あるいはそれに相当するコミュニティ内)で決まってないところです。 関数名は Upper Camel Case (UCC) か LCC にするのか、メンバー変数名はアンダースコアをつけるべきか、UCC にするべきか、プリフィクスに m_ をつけるべきか、といった些細なことや、 インデントをハードタブにするかスペースにするかといったことや、const は型の右につけるのか左につけるのかといったことなど細かいところを挙げればきりがありません。

C++ で開発しているオープンソースリポジトリについてあちらこちら覗いてみても、スタイルに関しては各プロジェクトごと多種多様です。 ポインタやリファレンスの位置は右に寄せるか左に寄せるかといった本当に些細なスタイルを文書化するのはとても大変です。 .clang-format ファイルでは、そういった細かなスタイルについて記述することができるので、ドキュメントとして役立ちます。 フォーマットも機械的に行ってくれるので、些細なコーディングスタイルについて争うことがほとんどなくなります。

Travis CI と AppVeyor を使い始めました

GitHub で公開しているリポジトリに push するたびに Windows と Mac 向けのビルドと単体テストを実行するようにしました。 特に Visual Studio と Xcode の両方でビルドが通るかどうかをコミット毎に確認するのは非常に大変です。 そこで、Windows 向けのビルドに AppVeyor を、Mac 向けのビルドに Travis を利用することにしました。 どちらもオープンソースプロジェクトだと無料で利用できます。 AppVeyor は Visual Studio の最新版を含めた各バージョンや PowerShell が使えるので重宝しております。 Travis は Xcode を利用することができ、またタスクが実行されるのが速く、すぐに結果を通知してくれるので大変ありがたいです。

Gitter のチャットルームを作りました

GitHub の Issues や Pull Requests の機能以外で、質問とか雑談とかできる場所が欲しかったので Gitter を使ってみることにしました。 freenode で IRC チャンネルを作るか、どこかにフォーラムを作るか迷いましたが Gitter で落ち着きました。 GitHub のアカウントがあればどなたでも参加できます。ぜひ書き込んでみてください。

Trello のボードを公開しました

今どんな作業をしているのか、あるいは今後どんな機能を追加する予定があるのかといった情報を目に見える形にするため、Trello のボード としてロードマップを公開しました。 優先的に追加してほしい機能について誰でも投票ができるようになっています。よかったら投票してみてください。

今週の予定

だいぶ駆け足になりました。遅筆なのでこの日記を書くのも少し苦労しました。 本来はやっている最中やその直後にノートとして日記をとるのが理想ですが、なかなかうまくいってないというのが本当のところです。 こまめに日記をつけるように(できるだけ)努めていきたいです。

今週は、ゲームエンジンをコンパイルする時に Visual Studio で出ている警告をすべて取り除く予定です。 もっぱら、先週末は、Qt を使ってエンジンとは関係のない趣味のデスクトップアプリを作っていたので、今度は Qt の話を書けたらいいなあ、と。

Leave a Reply