lumino trail

ゲームエンジン作ってます。Github:https://github.com/lriki/Lumino Twitter:https://twitter.com/lriki8

開発状況 2020 #6

今月は主にポストエフェクト周りを詰めていました。

機能として Fix するのはまだ先になりそうですが、次にリリース予定の Rendering モジュールがちゃんと動くことを検証するためにいろいろ試しています。

f:id:lriki:20200630233754g:plain

以下、動作確認済みのエフェクトです。

  • Bloom
  • DepthOfField
  • FXAA
  • Blur
  • SSAO
  • SSR
  • Tonemap
  • Transition
  • Vignette

Bloom と SSRユニットテストも組んだのでもうこれで Fix のつもり。

あと残り Godray を入れたら、作りたかったものは一通り動かせたという状態になる予定です。

開発状況 2020 #5

ここ1カ月くらいはまた RPGツクール 周りで活動してます。

こちらはペース落としてますが、リファクタリング中心にちょっと変更が入ってます。

Shader

Shader は結構古いモジュールになっていて DirectX9 と互換持たせた API をずっと引きずっていました。

特に、Uniform 個別に値を設定する仕組みになっていたため、UniformBuffer を使ってまとめてデータを GPU に送るスタイルとのミスマッチがひどく、処理速度を落としていました。 基本的にユーザープログラムから Shader に対して値を設定するには Material を通すので、Shader 自体の使いやすさはそれほど重要ではないだろう、ということで、全部 UniformBuffer を使うようにしました。

またリフレクションについても、Vulkan バックエンドはリフレクション情報をコンパイル済みシェーダと一緒に外部から渡すのに対して、OpenGL バックエンドは glGetProgramiv() などを使用して内部で解析してました。 それなのでバックエンドの種類によってデータの方向が異なる負担を全部 Shader クラスが受け持っていてかなり複雑度が上がっていました。ので、シェーダ関係の情報はすべて外部から渡すようにしました。

あと Lumino の Shader は、DirectX9 の Effect みたいな仕組みを持っていて、technique 構文でシェーダプログラムをグループ分けして管理できます。

Sprite.fx

technique Forward_Geometry_UnLighting
{
    pass Pass0
    {
        VertexShader = VS_ClusteredForward_Geometry;
        PixelShader = PS_Main;
    }
}
technique Forward_Geometry_UnLighting_Instancing
{
    pass Pass0
    {
        VertexShader = VSI_Main;
        PixelShader = PSI_Main;
    }
}

ただ今日日この構文をサポートしているシェーダコンパイラは無いので、technique 部分に限りコンパイラを自分で実装しています。 …ですが、やっぱりメンテの負担が大きいし、特にコンパイルエラーがすごく不親切なので微妙に使いづらい。

ということで YAML 方式で書けないか考えています。

Sprite.fx

@module
techniques:
    Forward_Geometry_UnLighting:
        passes:
        -   Pass0:
                vertexShader: VS_ClusteredForward_Geometry
                pixelShader: PS_Main

    Forward_Geometry_UnLighting_Instancing:
        passes:
        -   Pass0:
                vertexShader: VSI_Main
                pixelShader: PSI_Main
@end

なんかごちゃごちゃしてる気がするなぁ…。実装はしたけど、しばらく様子見。

Rendering

一応次のリリースで正式公開する予定のモジュールなので、今まで書き散らしてきたものを整理しています。

Lumino の中でもトップクラスに複雑なモジュールですが、ユニットテストと CI がちゃんと仕事してくれて、安定してリファクタリングできています。

Effect (Particle)

↑の整理に関連して Rendering モジュールの中のレガシーな機能に依存している人たちを修正して回っています。

で、パーティクル周りはちょっと寄り道して、ちょっと前に メッシュ描画の Instancing に対応したのでその仕組みを入れて高速化することにしました。

f:id:lriki:20200531131537g:plain

処理負荷は従来の 70% くらいになりました。座標変換を全部 GPU に持って行けた、ってところだとこんなものか…。メッシュパーティクルだともっと効果出そう。

あと、パーティクルの軌跡も作成中です。

f:id:lriki:20200531132013g:plain

UE4 のコードを参考に効率的なデータの使いまわしを実装した…つもりだけどなんか全然別物になっちゃった気がする。まぁ、VisualStudio のデバッグ実行中で 500FPS 出てるなら上出来じゃないかな…。

開発状況 2020 #4

4月は Lumino 使って作っているゲームのほうに力入れてました。

f:id:lriki:20200430201633g:plain

なのでこっちはあんまり更新無いです。

アセットファイルフォーマットの YAML

エディタ作ってもいいんだけどそれはそれで時間かかるので、ゲームデータを手打ちしやすいように JSON から YAML に変更しました。

f:id:lriki:20200430201702p:plain

C++ だとこんな感じに書けます。

class MapData : public ln::Object
{
public:
    int id;
    ln::String name;
    int width;
    int height;
    ln::List<ln::List<int>> data;

    void serialize(ln::Serializer& ar) override
    {
        ar & LN_NVP(id);
        ar & LN_NVP(name);
        ar & LN_NVP(width);
        ar & LN_NVP(height);
        ar & LN_NVP(data);
    }
};

開発状況 2020 #3

0.9.0 リリースしました

Lumino | Lumino website

Ruby 対応がメインです。

https://luminoengine.github.io/articles/documents/first-tutorial/img/object-1.png

↓こんな感じの雰囲気です。

require "lumino"

class App < Application
  def on_init
    Engine.render_view.guide_grid_enabled = true
    Engine.camera.set_position(5, 5, -5)
    Engine.camera.look_at(0, 0, 0)

    box1 = BoxMesh.new
    box1.set_position(1, 0, 0)

    box2 = BoxMesh.new
    box2.set_position(0, 2, 0)

    box3 = BoxMesh.new
    box3.set_position(0, 0, 3)
  end
end

App.new.run

また、チュートリアルを全体的に直してます。

まだまだ未実装の部分は多いですが、何となくゲームエンジンとして目指したい全体像が見えてきたかなというところです。

画面遷移実装中

エディタはあまり使わずプログラムがしがし書くときに、タイトル画面やゲームプレイ画面を効率よく開発するための仕組みです。

仕組み自体は Lumino 開発の初期からあったのですが、画面単位の名前を Scene にするか Level にするか、長い闘いの末、

  • Level: 画面単位
  • Scene: シーン描画全体にかかわるユーティリティ

としてみました。

Level はこんな感じ↓で実装します。よくあるやつですが。

// タイトル画面
class TitleLevel : public Level
{
    void onStart() override
    {
        // 初期化

        // 背景を灰色にする
        Scene::setBackgroundColor(Color::Gray);

        // 画面遷移時のマスク画像を設定する
        Scene::setTransitionEffectMaskTexture(Texture2D::load(u"Transition1.png"));
    }

    void onUpdate() override
    {
        // 更新
        
    if (Input::triggered(u"submit")) {
           // キーが押されたら別の画面へ移動
           Scene::gotoLevel(u"Game");
        }
    }
};

また画面遷移のエフェクトも標準搭載してます。

単純なフェードイン・フェードアウトのほか、以前のツクールシリーズにあった、マスク画像を用いたクロスフェードも使えます。

これは一般的な雲画像を使った遷移エフェクトです。

f:id:lriki:20200331202228g:plain

一応、画面単位ということで Level を説明してますが、実装は UE4 の Level や Unity の Scene と同じです。

Level 自体アセットファイルに保存してエディタでいじったり、普通にマップ作るのにも使います。 あとマルチレベルのロードや先行ロード、非同期ロード、Loading 画面とかいろいろ応用できます。の予定。

画面遷移くらいなら次のリリースに入れられるかな。

3D オートタイルを作成中

作成中のゲームでビジュアルを、全部 2D にするのか、背景 3D でキャラ 2D にするのか、いろいろ考え中です。 この 3D オートタイルは、そんな中で出来上がってきたコンポーネントです。

イメージ的には RPG ツクールシリーズの床・壁オートタイルがそのまま Voxel になる感じです。

Lumino の標準機能にするかは未定ですが、できれば入れたいです。 プロシージャルマップとか、コードファーストのテライン構築との相性は抜群だと思う。 あと3Dモデル用意しなくても、好きなタイルマップ素材もってくればすぐ3Dマップが作れたりします。

f:id:lriki:20200331203931p:plain

Instancing 対応中

何も考えずに 3D オートタイル実装してみたら描画がとんでもなく重くなったので、Instancing に挑戦しました。

↓のような感じで使います。

void onStart() {
    batch = InstancedMeshList::create(mesh, material);
}

void onRender(RenderingContext* context) {
    batch->reset();
    for (たくさん) {
        batch->drawMesh(transform);
    }
    context->draw(batch);
}

泥臭く自分で描画コード組む用の API なので、Unity みたいな、マテリアルで Instancing 指定するような使い方はまた別になります。

ちなみにオートタイルは、1タイルが↓のように4つのサブタイルから成っていると考えて、

f:id:lriki:20200331204555p:plain

これが4隅5パターンあります。

f:id:lriki:20200331204956p:plain

組み合わせは全部で 48 パターンです。

f:id:lriki:20200331205020p:plain

で、3D の Voxel で考えるので、ひとつの Voxel あたり 6 面描きます。

前セクションの3Dマップの例だとマップの大きさは 30×30 なので、全平面マップでも最低 900 ドローコール必要になったりします。

単純な Voxel なら各面を四角形スプライトと考えて Dynamic な頂点バッファに四角形を詰め込みまくって描けばいいのではってなるのですが、 やってみたいのは風来のシレン2・3 であったり世界樹と不思議のダンジョンであったりするので、やっぱりメッシュで書きたかったです。

まぁ結果的に描画速度が当社比100倍くらいになったのでヨシ。

Asset シリアライザを変更中

Lumino は Archive クラスがオブジェクトの保存・復元を担当していますが、これは cereal とかをすごく参考にして実装したもので、 ものすごく C++ オブジェクトのシリアライズに特化しています。

何がマズイのかというと、Ruby オブジェクトをシリアライズするのに使えないです。いや実は 0.9.0 時点でも使えてはいるのですが、めちゃくちゃ使いづらい。 なので、API はかなり原始的になるけど Ruby とか他の言語で定義されたオブジェクトも Asset に保存できるように見直してます。

あと、これまでは JSON で保存してましたが YAML にします。 多分十分な機能のアセットエディタなんて直近じゃ作れるはずもないので、ある程度は手打ちもやむなしかなぁ…とか思ってます。 そのため (JSON と比べて) 人間が読み書きしやすいフォーマットとして YAML を使うことにしました。

UIダークテーマ追加

とは言うものの、多少使いづらくてもマップエディタは無いと結構ツラいです。特に 3D だと。

エディタもどきは既にあるのですが、今Luminoがサポートしてる唯一のテーマは Material-UI を参考に作っているものです。 どちらかというとマウスよりも指でタップ操作する向けのものなので、ボタンとかでかいです。エディタには使いづらい。

なのでデスクトップで作業するエディタ向けのテーマを作ってます。ついでに目が疲れにくくなるらしいダークにしてます。

f:id:lriki:20200331211232p:plain

開発状況 2020 #2

0.9.0 リリースに向けてイロイロ作業中です。

特にチュートリアルを Lumino の雰囲気がつかめることを優先して作り直してます。(ゲーム一本作り上げるのは重いのでいったん置いて)

とはいえ全部の機能を紹介するには API がまだ安定してない部分が多いので、公開できそうなところから FIX → Test → 問題出たら修正、してます。

そんな感じで今月は不具合修正がメインだったので紹介できそうな機能は特にナシです。

C++ 版はほぼ作業完了なのであとは Ruby 版。3月中にはリリースしたいところ。

開発状況 2020 #1

ぼちぼちリリースしたい。いい感じに機能がそろってきた気がするよ。

ということで、0.9.0 リリースに向けて、チュートリアルを作り始めています。

f:id:lriki:20200131190330p:plain

他に今月何かやったかなぁ…

非同期ロード

Loading 画面とか作れるように、テクスチャやメッシュなどを非同期ロードできるようにしています。

基本の考えは JavaScript の fetch API や Promise。ネイティブマルチスレッドが苦手なスクリプト系言語でも問題なくできるよ!ということで、こんな感じで使います。

// C++
Assets::loadAssetAsync(u"asset.json")->thenWith([](Ref<Object> asset) {
    Debug::print(u"Loading completed.");
});
# Ruby
Assets.load_asset_async("asset.json").then_with do | asset |
  Debug.print("Loading completed.");
end

基礎部分は出来上がったので、実際のアセット読み込み部分を少しずつ対応していく予定です。

オートタイル

タイルマップのアレ。坂道対応。

f:id:lriki:20200131193243p:plain

絵文字

冒頭の Hello, Lumino! についてるこれ→ 🌱

テクスチャとしてロードできるようにしました。

// C++
auto texture = Texture2D::loadEmoji(u"🌱");
# Ruby
texture = Texture2D.load_emoji("🌱");

一説によるとバカゲーがはかどるらしいです。🔥🔥🔥

ポストエフェクト

↓色調シフト

f:id:lriki:20200131193022p:plain

↓ブルーム

f:id:lriki:20200131193012p:plain

Twitterでも言ったけど、ブルームは自分がシェーダの勉強始めるきっかけになったものだったりします。

なので長く試行錯誤を続けてきた今のレンダリングアーキテクチャの上で動かせたのは、個人的に大きなマイルストーンの達成だったりします。

0.4.0 で実装してた FXAA や SSAO も早く持ってきたいところ。

開発状況 2019 #12

月の前半は こんなの 書くのに時間使ってました。

ただそれにしてもあんまり作業進んでない気がしてます。

背景いじくり中

f:id:lriki:20191231191812p:plain

どうなんだろうなぁ… 2Dドットを3D空間にいい感じになじませる絵作りを目指したいところだけど。いろいろテスト中です。

Ruby 版 Lumino

基盤はとりあえず版完成です。

できたはいいけど、C++ クラス定義に こんなかんじの タグを振っていかないとならないのが予想外に面倒かったです。

ひとまずの公開クラスに振り終わったら次リリースかな。

glTF 対応

glTF フォーマットの 3D モデルを読み込めるようにしました。ただ、アニメーションは未対応です。

f:id:lriki:20191231191757p:plain

作成中のゲームで背景に 3D モデル使いたい案があったし、過去に途中まで実装してたので一気に進めました。

でもよく考えたら対応済みの OBJ フォーマットでも足りたような気もする…。

Box 要素のスタイル描画

CSS では background, border, shadow を駆使して色々な形状を表現することができますが、Lumino の UIElement も似たようなシステムを持っています。

しかし shadow の blur サイズが CSS のと微妙に違ってたりとか、サイズを小さくすると描画が崩れたりとか、いろいろ問題があったので刷新しました。

f:id:lriki:20191231192522p:plain

ただ描画速度を優先したかったので、ステンシルバッファとか使わないと実装無理じゃないかみたいな一部のパラメータは制限事項にすることにしました。

まー8割くらいはCSSと同じ感覚で使えるよ、みたいなところに落とせたからおおむね満足。BoxElement はこれで完成かな…。

Qrunch お試し中

アウトプットが雑すぎる自分にはこっちの方があってるかもしれない。

ylx1pj6121qlldxq.qrunch.io

年の瀬

よいお年を。