FPSを作ってみる@wiki
06)
最終更新:
slice
-
view
(2013/06/26)
シリアライズ
引き続き2D物理エンジンに取り組みつつ、将来を見据えてゲームシーンのシリアライズやリソースハンドルについて考える。
シリアライズというのはメモリの上に乗っているクラスや配列なんかの情報をバイト列として書きだす事で、
早い話がゲームのセーブやロードってどうしたもんかねと。
シリアライズというのはメモリの上に乗っているクラスや配列なんかの情報をバイト列として書きだす事で、
早い話がゲームのセーブやロードってどうしたもんかねと。
いや、周りを見渡せば今でさえインディーのアクションゲームなんかは普通に
途中セーブ(チェックポイント制やクイックセーブ問わず)が無くてチャプター毎だったりするから
必須という訳ではないのだが。
ま、でも折角ゲーム作るからには実現したいよね。
途中セーブ(チェックポイント制やクイックセーブ問わず)が無くてチャプター毎だったりするから
必須という訳ではないのだが。
ま、でも折角ゲーム作るからには実現したいよね。
という訳でboost::serializationについて調べていた。こいつは自分のクラスに
決められた読み書き用のメソッドを用意して、そこで渡されたオブジェクトに対して & や << や >> で
std::ostreamみたいに変数を突っ込んでいけばライブラリが良きに計らってくれるという物。
読み書き用オブジェクトと一緒にバージョン番号も渡されるから古いデータとの互換もバッチリ。
もしメンバ変数が他のオブジェクトへのポインタやスマートポインタでもちゃんと追跡してくれるし、復元の際も内部でnewを呼んでくれるようで至れりつくせりである。
決められた読み書き用のメソッドを用意して、そこで渡されたオブジェクトに対して & や << や >> で
std::ostreamみたいに変数を突っ込んでいけばライブラリが良きに計らってくれるという物。
読み書き用オブジェクトと一緒にバージョン番号も渡されるから古いデータとの互換もバッチリ。
もしメンバ変数が他のオブジェクトへのポインタやスマートポインタでもちゃんと追跡してくれるし、復元の際も内部でnewを呼んでくれるようで至れりつくせりである。
次の問題は「実際に何を保存すべきか」なのだが・・
これについてはまた後ほど。
これについてはまた後ほど。
(2013/06/24)
更新が滞った
「作業内容なんてgithubに定期的にコミットしてんだし、いいじゃん」等と思ったり思わなかったり。
ソースあればプログラムの説明要らないだろ的なアレ。
しかし実際かなり前に書いた自分のソースコードは今読んでも確実に何がなんだか分からんのでこの考え方は宜しくない訳で、
従って覚えてる範囲で作業内容を記すべきなのだ。
ソースあればプログラムの説明要らないだろ的なアレ。
しかし実際かなり前に書いた自分のソースコードは今読んでも確実に何がなんだか分からんのでこの考え方は宜しくない訳で、
従って覚えてる範囲で作業内容を記すべきなのだ。
spinnerについて
一応数学ライブラリのつもりなのだが、動作確認なんてあまりしてないし実質自分専用。
前回までで行列やベクトル、クォータニオンなんかの基本クラスは実装が終わっていたんで
細々とした補助ルーチンやバグフィックスが中心。
増えたといえばstd::atomicを勉強したついでのスピンロックなmutexや
一般にAssocVectorと呼ばれる、中身が常にソートされた配列(2分探索ができる)クラスを用意してみたりとか
そのくらい。
前回までで行列やベクトル、クォータニオンなんかの基本クラスは実装が終わっていたんで
細々とした補助ルーチンやバグフィックスが中心。
増えたといえばstd::atomicを勉強したついでのスピンロックなmutexや
一般にAssocVectorと呼ばれる、中身が常にソートされた配列(2分探索ができる)クラスを用意してみたりとか
そのくらい。
boomstickについて
boomstickってなんぞ?
spinnerは数学なライブラリ。当たり判定とかカリングは別にした方が良かろうという事で分けようかと。
で、どうせライブラリを作る・使うなら自分の気に入った名前を付ければ若干でもモチベーションが保てそう・・
との事で名前を考えてはみたものの、これという物が思いつかなくて悶々としていた所
ふと某ゲームでソードオフなシャッガンを手にした時の台詞「ブーンスティックタァーーイム!」が浮かんでしまったので。
意味は諸説あるが、ここでは前述そのまま。
で、どうせライブラリを作る・使うなら自分の気に入った名前を付ければ若干でもモチベーションが保てそう・・
との事で名前を考えてはみたものの、これという物が思いつかなくて悶々としていた所
ふと某ゲームでソードオフなシャッガンを手にした時の台詞「ブーンスティックタァーーイム!」が浮かんでしまったので。
意味は諸説あるが、ここでは前述そのまま。
前回記事からの作業は殆どこのboomstickに関する物で、すなわち当たり判定がメイン。
形状クラスのレイアウトや相互にどうやって判定ルーチンを呼び分けるか等で割と悩んだ。
形状クラスのレイアウトや相互にどうやって判定ルーチンを呼び分けるか等で割と悩んだ。
というのも先々代のresonant4では仮想関数の呼び出しコストを嫌ってタイプリストで当たり判定構造
(最初バウンディング球で判定して、ヒットしたら次はパーツ単位のバウンディング球で・・とか)
を定義し、テンプレートやポインタ操作を駆使して展開&判定を試みていた。
要素が固定数なら線形2分木みたくメモリを詰めてみたり。
現行のresonant5でもC++とluaの両方で構造定義できる等、更に柔軟性を持たせてあるものの基本は一緒だ。
(最初バウンディング球で判定して、ヒットしたら次はパーツ単位のバウンディング球で・・とか)
を定義し、テンプレートやポインタ操作を駆使して展開&判定を試みていた。
要素が固定数なら線形2分木みたくメモリを詰めてみたり。
現行のresonant5でもC++とluaの両方で構造定義できる等、更に柔軟性を持たせてあるものの基本は一緒だ。
この方式は判定自体はともかくバイナリサイズの肥大化を招くしなによりコンパイル速度が激遅過ぎて実用性には疑問だし
それより「使いにくい」「判定までの手順やお約束が多い」
どの位って、1〜2週間も触らなければすっかり忘れて自分で使いたくなくなるレベル。
(ゲーム完成しないのってコレが主な原因じゃないかと)
それより「使いにくい」「判定までの手順やお約束が多い」
どの位って、1〜2週間も触らなければすっかり忘れて自分で使いたくなくなるレベル。
(ゲーム完成しないのってコレが主な原因じゃないかと)
だから今度は当たり判定処理を使い回さず使いやすさ優先で設計したかった。
仮想関数は遠慮なく使うし、変に固定配列で誤魔化したりも無し。
当たり判定の階層構造も事前にタイプリストで表現して、それを使うのではなく
シンプルにstd::vectorだかに突っ込んで、個々の判定処理は仮想関数でIDを特定するなりしてしまえば良いだろう。
std::vectorはメモリが動的確保だからどうというご時世ではない。
仮想関数は遠慮なく使うし、変に固定配列で誤魔化したりも無し。
当たり判定の階層構造も事前にタイプリストで表現して、それを使うのではなく
シンプルにstd::vectorだかに突っ込んで、個々の判定処理は仮想関数でIDを特定するなりしてしまえば良いだろう。
std::vectorはメモリが動的確保だからどうというご時世ではない。
で、結局どうしたの
形状クラスのレイアウトはソースを見てもらえば分かると思うが
たかが円(12byte)のクラスに仮想関数テーブルのポインタ(4byte)は流石にどうなの?と思ったので
仮想関数無しの基本バージョンとそれを継承し、かつ仮想関数を定義したバージョンで分けた。
更に形状によってはバウンディング円を計算するのにコストが掛かるケースを想定し
そういった変数を内部にキャッシュする機構を備えるバージョンも加え、合計3パターンとなった。
たかが円(12byte)のクラスに仮想関数テーブルのポインタ(4byte)は流石にどうなの?と思ったので
仮想関数無しの基本バージョンとそれを継承し、かつ仮想関数を定義したバージョンで分けた。
更に形状によってはバウンディング円を計算するのにコストが掛かるケースを想定し
そういった変数を内部にキャッシュする機構を備えるバージョンも加え、合計3パターンとなった。
他は剛体シミュに必須のGJK、EPAを実装した。
ただしこれら全て2Dのみ。
ただしこれら全て2Dのみ。
個々の形状に対する判定ルーチンを呼び分ける所はまだ書いてない。
さっさと簡単な2D剛体シミュを動かしたいので、最低限GJKがあればいい訳で。
3Dにするのはその後。まずは2Dをちゃんとやろうかなと。
さっさと簡単な2D剛体シミュを動かしたいので、最低限GJKがあればいい訳で。
3Dにするのはその後。まずは2Dをちゃんとやろうかなと。
あれれ?前回からあまり進んでないように見える・・気のせいだろうか
(2013/06/08)
移植の筈が
spinnerにいくつか関数など追加。といってもシングルトンや簡単なビット操作、タイプリストとかだし
前に書いてたソースのイケてない所を省いたり修正したりが主でわりかし楽な作業だった。
前に書いてたソースのイケてない所を省いたり修正したりが主でわりかし楽な作業だった。
ついでにベクトルの4要素から1.0を255として8bitずつにパックした32bit数値を出力する、要するに色ベクトルからRGBAに変換するコードなんかもSSEを使って実装。
SSEの整数値計算やfloat変換というのは2になって本格的に対応し出したので
SSE1だけの命令じゃどうにも効率が悪く、SSE2の命令に頼る形となった。
頭の中に入ってるSSE命令と言えば1の物しかなかったのでどんな命令があるか調べたり、試したりしてる内に時間が鬼のように過ぎる。
SSEの整数値計算やfloat変換というのは2になって本格的に対応し出したので
SSE1だけの命令じゃどうにも効率が悪く、SSE2の命令に頼る形となった。
頭の中に入ってるSSE命令と言えば1の物しかなかったのでどんな命令があるか調べたり、試したりしてる内に時間が鬼のように過ぎる。
こうなると前提とするSSEバージョンはどの辺にするかが悩みどころ。
とはいえ整数が絡んでくる箇所なんてここぐらいなので現時点でSSE1からの対応というのは変わらず。
SSE3になれば水平演算系の命令が入ってきてfloatでも有り難みが出てくるが・・・まだ時期尚早な気はする。
とはいえ整数が絡んでくる箇所なんてここぐらいなので現時点でSSE1からの対応というのは変わらず。
SSE3になれば水平演算系の命令が入ってきてfloatでも有り難みが出てくるが・・・まだ時期尚早な気はする。
同人でゲーム出す時って結構なロースペックも考慮したりするんだろうか?
std::atomic
前のソースではマルチスレッドにおける同期でWindowsで言うInterlockedCompareExchangeとかの関数(lock prefix付き命令)を使っていたのだけど
これでは当然Windows依存になってしまうし、まぁgccにはgccの似たようなintrinsic関数も用意されてるようだが
なるべくそういう環境依存のアレコレは避けたい訳で。なんとかならんか・・
これでは当然Windows依存になってしまうし、まぁgccにはgccの似たようなintrinsic関数も用意されてるようだが
なるべくそういう環境依存のアレコレは避けたい訳で。なんとかならんか・・
とかグズってたらふとC++11の標準ライブラリにそんなのがあったのを思い出し、std::atomicを触る。
まだメモリバリアがわかったようなわからんようなってレベルで引き続き勉強中。
まだメモリバリアがわかったようなわからんようなってレベルで引き続き勉強中。
そんな感じの、まったり進行。
(2013/06/07)
更新再開
まずは・・コメント貰えると嬉しいと書いたら本当にコメント貰えたので嬉しい。我ながらシンプル。
更新休止といっても結局何時もの間隔と変わらない気もするが、それは置いておきつつ・・
色々あった訳だが感情的な文章やプライベートな事はココに書かないと決めてあるし、
全然作業してなかった訳でもないのでそれを少し書く。
色々あった訳だが感情的な文章やプライベートな事はココに書かないと決めてあるし、
全然作業してなかった訳でもないのでそれを少し書く。
spinner
まずはSSEを使った計算ライブラリの続き。姿勢を管理するPoseクラスの追加や諸処の2D図形クラス(これはまだ途中)など。
当たり判定ルーチンをSpinnerに含めるか、それとも別リポジトリで作るかで迷っていて
分けるとしたらgitのsubmodule機能を使うかなーとちょっと勉強してみたり。
現状SSE命令を使う関係でベクトルと行列クラスを16byteアラインメントしたバージョンとそうでない物を別個に定義しているのだが
なんか・・関数の引数や戻り値として使う時にごっちゃになって面倒だな?
一応、AlignedなクラスからUnAlignedなクラスへは暗黙的に変換できるようにはしてあるが・・
当たり判定ルーチンをSpinnerに含めるか、それとも別リポジトリで作るかで迷っていて
分けるとしたらgitのsubmodule機能を使うかなーとちょっと勉強してみたり。
現状SSE命令を使う関係でベクトルと行列クラスを16byteアラインメントしたバージョンとそうでない物を別個に定義しているのだが
なんか・・関数の引数や戻り値として使う時にごっちゃになって面倒だな?
一応、AlignedなクラスからUnAlignedなクラスへは暗黙的に変換できるようにはしてあるが・・
OpenGLのエフェクトファイル
テクスチャのミップマップ対応含め自前のエフェクトファイルを使って描画までは行けたかな?という感じで、Hello OpenGLみたいな事をしてみた。
しかし流石にXWindowの勉強まではする気にならなかったのでQtにて。
ちなみにチェッカー模様のテクスチャは内部で生成している。分割数や色は自由に変えられる。
しかし流石にXWindowの勉強まではする気にならなかったのでQtにて。
ちなみにチェッカー模様のテクスチャは内部で生成している。分割数や色は自由に変えられる。
使用したエフェクトファイル
uniform TheBaseUnif {
mat4 mTrans;
}
uniform TheUnif : TheBaseUnif {
sampler2D tDiffuse;
}
attribute TheBaseAttr {
highp vec3 atPos : POSITION;
}
attribute TheAttr : TheBaseAttr {
highp vec4 atTex : TEXCOORD0;
}
varying TheVary {
highp vec4 vrTex;
}
vertexshader TheVS() {
vec3 tmp = atPos;
tmp.z += 1.0;
vec4 v = vec4(tmp,1);
gl_Position = v * mTrans;
vrTex = atTex;
}
pixelshader ThePS(vec4 val) {
gl_FragColor = texture2D(tDiffuse, vrTex.xy);
}
technique TheTech {
CullFace = false; // glDisable(GL_CULL_FACE);
DepthTest = true; // glEnable(GL_DEPTH_TEST);
DepthRange = 0 1; // glDepthRangef(0,1);
// 変数ブロック指定
Attribute = TheAttr;
Varying = TheVary;
Uniform = TheUnif, TheBaseUnif; // 複数指定可能(ブロック要素がダブってもOK)
pass P0 {
VertexShader = TheVS();
PixelShader = ThePS([1000 1 2 3]);
}
}
動作チェックのために意味も無くブロックを分けたりPixelShaderに引数渡したりしてるが、そこは無視してほしい。
Pixel ShaderはOpenGLではFragment Shaderと呼ぶらしいが、自分が慣れ親しんだ名称を使った。
OpenGL ES2.0用に設計しているのでGeometry Shaderの事は考えてない。今の所は。
Pixel ShaderはOpenGLではFragment Shaderと呼ぶらしいが、自分が慣れ親しんだ名称を使った。
OpenGL ES2.0用に設計しているのでGeometry Shaderの事は考えてない。今の所は。
全体的にDirectX9時代のD3DXEffectのフォーマットと似せたが(当社比)
変数の定義ブロックは自分が「継承できたらなー」と思うことが度々あったので、継承出来るようにした。
変数ブロックをTechにセットする際にも
Attribute = Block; とすれば上書きになり、Attribute += Block; なら現在指定されているAttribute変数に加える形になる。
あとTechのブロックで記述したセッティングはPassが上書きしなければ暗黙的に継承されるとか、色々・・
この辺は完成度が上がってきてから詳しく解説したい。
変数の定義ブロックは自分が「継承できたらなー」と思うことが度々あったので、継承出来るようにした。
変数ブロックをTechにセットする際にも
Attribute = Block; とすれば上書きになり、Attribute += Block; なら現在指定されているAttribute変数に加える形になる。
あとTechのブロックで記述したセッティングはPassが上書きしなければ暗黙的に継承されるとか、色々・・
この辺は完成度が上がってきてから詳しく解説したい。
座標系について
ところでOpenGLについてwebで調べていた時にやれ右手座標系だのテクスチャの原点は左下だの吹きこまれ、未だにそれが抜けないのだけど
固定機能を一切使わない前提でいくつかプログラムを試してみたら、OpenGLが右手座標系と言われる所以は
「OpenGLの補助関数がそうなっているだけ」
かなという結論に。
テクスチャ生成のglTexImage2D()に渡す画素データのポインタは、ラインが上からを想定しているのかあるいは逆なのか分からんが
とりあえず上からを前提にデータを生成して、ポリゴンに貼り付ける時もDirectXのように左上原点でセットしてみたら反転されずちゃんと描画された。
スクリーン座標系も奥がZのマイナス方向なんて事は全くなく、普通にZ値の手前が0で奥が1だった。
単にgluLookAtで生成されるビュー行列が右手座標系なだけ。
(Z値の範囲はglDepthRangef()でユーザーが変更できる)
固定機能を一切使わない前提でいくつかプログラムを試してみたら、OpenGLが右手座標系と言われる所以は
「OpenGLの補助関数がそうなっているだけ」
かなという結論に。
テクスチャ生成のglTexImage2D()に渡す画素データのポインタは、ラインが上からを想定しているのかあるいは逆なのか分からんが
とりあえず上からを前提にデータを生成して、ポリゴンに貼り付ける時もDirectXのように左上原点でセットしてみたら反転されずちゃんと描画された。
スクリーン座標系も奥がZのマイナス方向なんて事は全くなく、普通にZ値の手前が0で奥が1だった。
単にgluLookAtで生成されるビュー行列が右手座標系なだけ。
(Z値の範囲はglDepthRangef()でユーザーが変更できる)
シェーダーに関してもベクトルと行列を掛ける時に右からベクトルを掛けなければいけない決まりはなく、どちらでも計算できる。
もちろん右からベクトルを掛ける前提で変換行列を用意するか、逆にするかで事前にシェーダーに渡す行列が違ってくる。
シェーダー内では行列をColumn-Majorで持っているようだがこれはメモリにどう置くか、アクセスや演算効率の話なので行列の成分には関係ない。
もちろん右からベクトルを掛ける前提で変換行列を用意するか、逆にするかで事前にシェーダーに渡す行列が違ってくる。
シェーダー内では行列をColumn-Majorで持っているようだがこれはメモリにどう置くか、アクセスや演算効率の話なので行列の成分には関係ない。
つまるところ
OpenGLに移行する訳で。
予定としては
- DirectXで言うところのRenderTargetに当たるFrameBufferに対応
ES2.0じゃMRT使えないけどPC用
- 剛体シミュレータの仕切り直し
spinnerを使いつつ。Qtベースで。やはりまずは2Dからでしょうな
こんなとこで。
それらが終わったらいよいよ「グラフィック強化期間 with OpenGL」。
去年やってた海の描画が個人的に「イマイチ。PS2レベルかよ!」と納得行かないので、それのクオリティアップも図れれば万々歳だが
欲張ると終わらないので
CascadedShadowMapsとLisPSMあたりが出来れば良し。
こんなとこで。
それらが終わったらいよいよ「グラフィック強化期間 with OpenGL」。
去年やってた海の描画が個人的に「イマイチ。PS2レベルかよ!」と納得行かないので、それのクオリティアップも図れれば万々歳だが
欲張ると終わらないので
CascadedShadowMapsとLisPSMあたりが出来れば良し。
添付ファイル