サイトのトップへ戻る

libGDX ドキュメント 日本語訳

ModelBatch

ModelBatch (コード) は描写呼び出しを管理するために使用されるクラスです。 通常はモデルのインスタンスを描写するのに使われますが、モデルに限定されません。 ModelBatch クラスは全ての描写コードを抽象化し、その上にレイヤーを用意して、あなたがゲーム固有のロジックにより集中できるようにします。 ModelBatch の機能の全ての部分は、設計によってカスタマイズ可能です。

注意: ModelBatch は描写呼び出しを管理してその結果レンダリングコンテキストも管理しているので、 ModelBatch.begin()呼び出しとModelBatch.end()呼び出しの間であなたが手動でレンダリングコンテキストを編集すべきではありません(例えば、シェーダーやテクスチャやメッシュの紐付け、glで始める機能の呼び出しなど)。 これは正常に機能せず、予期せぬ動作を引き起こす可能性があります。そうではなく、ModelBatch が提供するカスタマイズオプションを使用してください。



よくある誤解

  • ModelBatch はしばしば SpriteBatchと比較されます。 APIを見れば理解できるかもしれませんが、両者を隔てる非常に大きな違いがいくつかあります。 主な違いは、SpriteBatch は複数のspriteを1つの描写呼び出しにまとめますが、ModelBatchは描写呼び出しを1つにまとめないということです。 これはパフォーマンスに影響を与えるので、描写呼び出しを1つにまとめてからそれらをModelBatchへ送るよう注意してください。
  • ModelBatch は(視錐台)カリングを実行しません。 最適なパフォーマンスの方法を使ってカリングを行うための十分な情報を、ModelBatchが持っていないだけです。 既定では、 ModelBatch.render()を呼び出す度に、少なくとも一回の描写呼び出しが発生します。 ModelBatch では、これをカスタマイズして、実際の描写を行う前に視錐台カリングを実行するようにできます。 しかし一般的には、ModelBatch.render()を呼び出す前に(視錐台)カリングを実行すべきです。


概要

では、 ModelBatch は実際には何をしているのでしょう?

  1. 描写呼び出しを集めます
  2. 各描写呼出しごとのシェーダーを集めます
  3. 描写呼び出しを並び替えます
  4. レンダリングコンテキストを管理します
  5. 描写呼び出しを実行します

これで終わりです。それ以上でも以下でもありません。そしてこれの全ての部分はカスタマイズ可能です。 この設計思想のため、同一の基本的なタスクを実行する方法が複数あるかもしれないので注意してください。



ModelBatchを使用する

シェーダーを作成する可能性があるため、ModelBatchは比較的重いオブジェクトです。 可能であれば再利用をするよう心掛けましょう。 通常はcreate() メソッド内でModelBatchを作成します。 ModelBatchにはネイティブリソース(自身が使用するシェーダーのような)が含まれているので、不要になった時は dispose()メソッドを呼び出す必要があります。

描写処理は毎フレームごとに行う必要があり、通常はrender()メソッド内で行います。 描写を開始するには、modelBatch.begin(camera);を呼び出す必要があります。 次にmodelBatch.render(...);メソッドを使用して、1つ以上の描写呼び出しを追加します。 描写呼び出しの追加が完了したら、指定した呼び出しを実際に描写するために modelBatch.end();を呼び出す必要があります。

    ModelBatch modelBatch;
    ...

    @Override
    public void create () {
        modelBatch = new ModelBatch();
        ...
    }

    @Override
    public void render () {
        ... // call glClear etc.
        modelBatch.begin(camera);
        modelBatch.render(...);
        ... // add other render calls
        modelBatch.end();
        ...
    }

    @Override
    public void dispose () {
        modelBatch.dispose();
        ...
    }

modelBatch.render(...)の呼び出しは、modelBatch.begin(camera)modelBatch.end()の間でのみ有効です。 実際の描写はend();を呼び出した時に実行されます。 modelBatch.begin(camera)modelBatch.end()の間で描写を強制実行したい場合は、modelBatch.flush();メソッドを使用できます。

引数として渡す Camera は参照によって保持されます。 つまり、 begin 呼び出しと end呼び出しの間でカメラを変更してはいけません。 begin 呼び出しと end呼び出しの間でカメラを切り替える必要がある場合は、modelBatch.setCamera(camera);を呼び出すことができます。 必要であればbatchのflush()を実行します。



描写呼び出しを集める



描写呼び出しとは何か?

ModelBatchの目的は描写呼び出しを管理することです。では描写呼び出しとは正確には何のことで、それらはどうやって設定するのでしょうか?

"描写呼び出し" (しばしば "ドローコール"とも言われます)は 、基本的にはGPUへ何かを描写(表示)するよう指示する命令です。 簡単にいれば、各 "描写呼び出し" ではいくつかのプロパティ(例えば、位置や画像や色など)が付いた形状が表示されます。 もしくはもっと正確に言うと、"描写呼び出し"はGPUに対して、渡されたコンテキストの渡されたシェーダーを使って、渡されたメッシュのパーツを描写するよう指示します。 これについては後で詳しく見ていくことにします。

基本的な使い方の場合は、描写呼び出しの詳細についてまで知る必要はありません。 ModelBatchがそれらを抽象化しているからです。 しかし、描写呼び出しはパフォーマンスは影響を与えます。 通常、各描写呼び出しはGPU上で実行されます。つまり、CPUのコードと並行して実行されるのです。 新たに描写呼び出しが実行される度に、GPUとCPUを同期させる必要があるかもしれないのです。 言い換えれば、小さな描写呼び出しが大量にあるのより、大きな描写呼び出しが少しだけある方が、通常はパフォーマンスがよくなるのです。

描写呼び出しを設定するために、libGDXにはRenderable (コード) クラスが実装されています。 これには、一回の描写呼び出しを実行するのに必要なもの(cameraを除く)がほぼ全て含まれています。 基本的には、どのコンテキストで(environment とmaterial)、どのように(シェーダー)、どこに(変換情報)、形状(メッシュパーツ)を描写するのか、といった情報が含まれています。

ModelBatchrender(...) メソッドは、たくさんのシグネチャ (メソッド引数の異なるバリエーション)を持っています。 そのうちの1つが ModelBatch.render(Renderable);です。 これを使うと、ModelBatchに追加したい描写呼び出しを直接指定することができます。 他の render(...) メソッドでは、RenderableProviderを使って一つ以上の描写呼び出しを指定できます。 これらのメソッドでは、他の引数を使って描写呼び出しの既定値を渡すことができます。 例えば ModelBatch#render(RenderableProvider, Environment, Shader) メソッドを使用する時は、RenderableProviderが提供する全ての Renderableenvironmentメンバと shaderメンバを設定(上書き)します。



RenderableProvider

RenderableProvider (コード)は、1つ以上のRenderableインスタンスを提供するための実装をすることができるインタフェースです。 おそらく、この印てフェースの実装で最も一般的なものは ModelInstance クラスです。 このクラスは全てのnodes (parts) にまたがり、それらをRenderable インスタンスに変換します。しかし、必要に応じてRenderableProvider を使用することができます。 例えばボクセルエンジンを作成している時、各塊ごとに Renderableを作成できます。 またはエンティティコンポーネントシステムを使用している時、コンポーネントとして RenderableProvider を使用できます。

RenderableProviderはメソッドを1つだけ持っています:

public void getRenderables (Array<Renderable> renderables, Pool<Renderable> pool);

これは非常にオープンな API ですが(例えば、これまで追加した全てのRenderableArray にアクセスできます)、配列に要素を追加するだけの使い方にとどめておいた方が良いでしょう。 必要に応じて、割り当てを避けるためにプールを使用できます。 プールを無視するのも、必要に応じて動的 Renderable にプールを使用するのもあなたの自由です。 obtain()を使ってRenderableProviderから取得したRenderableModelBatchによって自動的に解放されるので、あなたが解放について気にする必要はありません。



シェーダーを集める



シェーダーとは何か?

Shader (コード) は、描写呼び出しを実際に実行するための実装を抽象化するインタフェースです。 通常この実装ではa ShaderProgram を使用します。ShaderProgramとは描写呼び出しを実行するために必要な GPU プログラム(例えば the vertex shaderfragment shader)です。 Shader の実装にも、このShaderProgramを使うのに必要なもの(uniform値の設定のような)が全て含まれています。



ShaderProvider

実際にどのような描写を行っているかに関わらず、ModelBatch ではRenderableごとに1つのShaderが必要です。 このため、ModelBatchは ShaderProvider (コード) インタフェースを使用します。 batch に追加された全てのRenderableごとに(例えシェーダーが含まれていたとしても)、ShaderProvidergetShader が呼び出されてシェーダーを取得してRenderableを描写します。

public Shader getShader (Renderable renderable);

既定では、DefaultShaderProvider (code)が使用されます。 これにより、既に作成したシェーダーが再利用できない場合は常にDefaultShaderが作成されます。 しかし、独自のShaderProvider を渡すかDefaultShaderProviderを拡張することでこれをカスタマイズできます。

ModelBatchShaderの管理を ShaderProvidersへ委譲します。 Shader は通常 ShaderProgramを使用しているので、不要になったら破棄をする必要があります。 modelBatch.dispose(); が呼び出された時、ModelBatchShaderProviderdispose()メソッドを呼び出します。

シェーダーの管理と再利用を手助けするため、libGDX には抽象クラス BaseShaderProvider (コード)が用意されています。 このクラスは作成された全てのシェーダーを追跡し、可能であればそれらを再利用し、不要になったらそれらを破棄します。 このクラスを継承すると、再利用できるシェーダーがない場合にはcreateShader(Renderable)メソッドが呼び出されます。 Shader を再利用できるかどうかはshader.canRender(Renderable)を呼び出すことで分かります。

一般的な使用例は、 DefaultShaderProvider ( BaseShaderProviderを継承している)を継承し、必要に応じてカスタムシェーダーを渡し、カスタムシェーダーが使用できない場合はDefaultShader へ切り戻すというものです。

public static class MyShaderProvider extends DefaultShaderProvider {
	@Override
	protected Shader createShader (Renderable renderable) {
		if (renderable.material.has(CustomColorTypes.AlbedoColor))
			return new MyShader(renderable);
		else
			return super.createShader(renderable);
	}
}

上記コードでは Material を使ってカスタムシェーダーを使用するかどうか決めています。 これが推奨される最も簡単な方法です。 しかし、渡されたrenderableでのshader.canRender(renderable)メソッドがtrue を返すのであれば、汎用のrenderable.userDataを含む任意の値を使って、どのシェーダーを使用するか決めることができます。



Default shader

カスタムShaderProviderを指定しない時、 ModelBatchDefaultShaderProviderを使用します。 DefaultShaderProviderは必要な場合は新規にDefaultShaderのインスタンスを作成します。

DefaultShader クラスでは標準的なmaterial と environment の属性(lightingと normal mapsと reflection cubemapsなどを含む)のほとんどのデフォルト実装が用意されています。 つまり:属性値と対応するuniform値を紐付けます。 ユニフォーム変数名の一覧はここで見ることができます

メモ: 既定では、シェーダープログラム ( glsl ファイル) は頂点ごとの照明 (グーローシェーディング)を使用します。 Normal mappingや reflection などは既定では適用されません。

このクラスの動作は、DefaultShader.Config インスタンスを DefaultShaderProviderに渡すことで設定できます。

DefaultShader.Config config = new DefaultShader.Config();
config.numDirectionalLights = 1;
config.numPointLights = 0;
config.numBones = 16;
modelBatch = new ModelBatch(new DefaultShaderProvider(config));

メモ: 既定の設定が各ユースケースにとって最適になっていることはめったにありません。 例えば、あなたが point lightを1つとdirectional lightを1つだけ使用している場合でさえ、既定の設定では point lightを5つとdirectional lightを2つを使用するのです。 DefaultShader をあなた固有のユースケースに合わせて調整し、最大限活用してください。 スキン処理を使用している場合、ボーンの数はモデルと一緒に作成されたボーンの数と一致しなければなりません。

使用するGPU シェーダー (頂点シェダーとフラグメントシェーダー)もこのconfigを使って設定できます。 このシェーダーは属性の様々な組み合わせに使用できるので、通常は ubershaderと呼ばれています。 これは、プリプロセッサマクロを使って、Renderableに基づいてパーツを有効や無効にする、シェーダーglsl コードです。 例:

#ifdef blendedFlag
	gl_FragColor.a = diffuse.a * v_opacity;
	#ifdef alphaTestFlag
		if (gl_FragColor.a <= v_alphaTest)
			discard;
	#endif
#else
	gl_FragColor.a = 1.0;
#endif

上記では、シェーダーで実際に使用されるコードはblendedFlagalphaTestFlagが定義されているかどうかによって変わります。 DefaultShader クラスは Renderableの値に基づいてこれらを定義します。

カスタム ubershaderを指定しない場合、既定の ubershader が使用されます (vertex shaderfragment shaderのソースを参照してください)。 このシェーダーはほとんどの基本的な属性をサポートしていますが (スキン処理やディヒューズや頂点ごとの鏡面照明などのような)、 非常に汎用的すぎて、考えられる全ての属性の組み合わせをサポートすることはできません。

フラグメントごとの照明や法線マッピングやリフレクションを使用したい場合は、この "非公式" シェーダーを使用できます。 しかしこのシェーダーは、directional lightが1つに制限されるといったことなどと引き換えにこの機能を追加しているので、注意してください。

既定のubershaderのソースを見るとおそらく、そのコード量は膨大でコードを管理するどころか読むことすらほぼ不可能であることに気づくでしょう。 幸いなことに考えられる全ての属性の組み合わせをサポートする必要はなく、前回の段落で見たようにDefaultShaderProvider を継承して複数のシェーダーに分割し、管理を簡単にすることができます。例えば:

public static class MyShaderProvider extends DefaultShaderProvider {
	DefaultShader.Config albedoConfig;

	public MyShaderProvder(DefaultShader.Config defaultConfig) {
		super(defaultConfig);
		albedoConfig = new DefaultShader.Config();
		albedoConfig.vertexShader = Gdx.files.internal("data/albedo.vertex.glsl").readString();
		albedoConfig.fragmentShader = Gdx.files.internal("data/albedo.fragment.glsl").readString();
	}
	@Override
	protected Shader createShader (Renderable renderable) {
		if (renderable.material.has(CustomColorTypes.AlbedoColor))
			return new DefaultShader(renderable, albedoConfig);
		else
			return super.createShader(renderable);
	}
}


描写呼び出しを並び替える

ランダムな順番で描写呼び出しが実行された場合、描写がおかしくなる上にパフォーマンスが低下します。 例えば、透明なオブジェクトがその後ろにあるオブジェクトよりも先に描写された場合、後ろにあるオブジェクトが見えなくなります。 これは、深度バッファによってオブジェクトの描写が妨げられるためです。 描写呼び出しを並び替えることでこれを解決できます。

既定では、ModelBatchDefaultRenderableSorter (コード) を使って描写呼び出しを並び替えます。 この実装ではまず不透明なオブジェクトが前から後ろにある順に描写され、その後に透明のオブジェクトが後ろから前にある順番で描写されます。 オブジェクトが透明かどうかを判断するため、既定の実装では BlendingAttribute#blended 値を確認します。

並び替えをカスタマイズするとパフォーマンスが向上します。 例えば、シェーダーやメッシュが使用するテクスチャに基づいて並び替えをすると、シェーダーやメッシュやテクスチャの切り替えを減らすのに貢献します。 この種の最適化は、非常にアプリケーション固有のものになります。 ModelBatchのインスタンスを作成する時に独自のRenderableSorter (コード) 実装を指定することで並び替えをカスタマイズできます。このインタフェースはメソッドを1つだけ持っています:

public void sort (Camera camera, Array<Renderable> renderables);

このメソッドは、ModelBatchが実際に描写する直前に持っている全ての情報を引数として渡します。 これも非常にオープンな APIで、必要に応じて配列を編集することができます。 このインタフェースを使って、ラスト数分のアクション(視錐台カリングのような、並び替えとは関係ないものでさえ)を実行することが可能です。 このメソッドが完了した後の renderables の順番が、実際に描写呼び出しが実行される順番になります。



レンダリングコンテキストを管理する

ModelBatchを使うことで、複数のShader 実装にまたがるテクスチャの紐付けといった、冗長なOpenGL呼び出しを避けることができます。 例えば Shaderで背面カリング処理が必要でなおかつ前回のシェーダーで背面カリング処理が有効になっている場合は、 glEnableglCullFaceの冗長な呼び出しを避けることができます。



RenderContext

RenderContext クラスはこうした不要な呼び出しを避けようとします。 このクラスはOpenGL ES上の薄いレイヤーとして動作して以前まで呼び出しを追跡し続け、冗長な呼び出しが発生するのを防ぎます。 GL呼び出しの小さなサブセットのみ実装されていますが、それを継承して追加の呼び出しを追加できます。 コンストラクタに引数として設定されなかった時は、 ModelBatchがあなたに代わってRenderContext を作成して管理します。

注意: 一目瞭然ですが、これはShader の実装がRenderContextを使っている場合のみ動作します。 GL呼び出しを直接呼び出している場合は動作しません。 可能であれば、対応するGL呼び出しを直接実行するのではなく、常に RenderContext を使用してください。

例: RenderContextを使って深度テストを有効にした場合、RenderContextがあなたに代わって深度テストを有効にします。 次に、例えばSpriteBatchを使用する場合、SpriteBatchは深度テストを無効にしてもRenderContextの更新まではしません。 これにより予期せぬ結果が引き起こされます。 これを避けるために、既定では (あなたが自分で RenderContext を指定しなかった場合は)ModelBatchは begin()メソッドとend()メソッドの両方で、RenderContextの同名のメソッドを呼び出すことでRenderContextをリセットします。 これは、ModelBatch外のコンテキストの切り替えが描写を妨げないようにするためです。

しかし、独自のRenderContext (その実装をカスタムする必要はありません) を指定した場合は、 あなたが責任を持ってcontext.begin()メソッドと context.end()メソッドを呼び出す必要があります。 これにより複数のModelBatch インスタンスで同じコンテキストを使用することができ、コンテキストを全て一緒にリセットする必要さえなくなります。

インスタンス作成時にRenderContext を指定した場合、あなたがRenderContext を保持しているので必要に応じてあなたがリセット (begin() メソッドとend() メソッドの呼び出し) を行うことが求められます。 modelBatch.ownsRenderContext メソッドを呼び出した、RenderContextの保持と管理をModelBatch が行っているかどうかを確認できます。



TextureBinder

現在紐付けされているテクスチャを追跡し続けるため、 RenderContext は textureBinder メンバを持っています。 TextureBinder (コード) は、 テクスチャの紐付けとテクスチャコンテキスト (例えば minification/magnification フィルタ)を追跡し続けるのに使用されるインタフェースです。 既定ではDefaultTextureBinder (コード)が使用されます。 独自の実装を指定することもできますが、そのようなことはめったに必要ありません。

DefaultTextureBinder は全ての使用可能な(指定した範囲内の)テクスチャユニットを使って不要なテクスチャの紐付けを防ぎます。 一般的な最新の携帯用GPU では、だいたい16個か32個のテクスチャユニットにテクスチャを紐付けることができます。 OpenGL ES ではユニット数は 32個に制限されます。 offset引数と count 引数を使って、DefaultTextureBinderのインスタンス作成時に使用する範囲を指定できます。 offsetを指定しない場合、0を指定したと見なされます。 count を指定しない場合、使用可能な残り全てのユニットが使用されます。 既定では、ModelBatch は0番のテクスチャユニットを範囲から除外します。0番はGUIでよく使用されるためです。 そのためGPU がサポートするテクスチャユニットが少ない場合を除き、 既定では 1番から 31番までのテクスチャユニットが 使用されます。

DefaultTextureBinder は二つのメソッドをサポートしています:

  • ROUNDROBIN: テクスチャが既に紐付けされている時、それは再利用されます。 そうでない場合、最初のテクスチャは使用可能な最初のテクスチャユニットに紐付けられ、次のテクスチャは次に使用可能なテクスチャユニットに紐付けられるといった流れになります。 使用可能なテクスチャユニットが全て使用されると、紐付けは最初に使用可能なテクスチャユニットから再スタートして、前回紐付けられたテクスチャを上書きします。
  • WEIGHTED: テクスチャの使用回数をカウントすることでテクスチャに重み付けを行います。頻繁に再利用されるテクスチャは上書きされる可能性が低く、再利用が少ないテクスチャは上書きされる可能性が高くなります。

既定では、 ModelBatch は WEIGHTED のやり方を使用します。

bind(...) メソッドを呼び出すことで、 TextureBinder を使ってテクスチャを紐付けることができます。 このメソッドはテクスチャが紐付けられたユニットを戻り値として返します。 そのため実際には、以下のようにしてShaderのテクスチャとuniformを紐付けることができます:

program.setUniformi(uniformLocation, context.textureBinder.bind(texture));


TextureDescriptor

テクスチャを指定する時、この api ではしばしば TextureDescriptor を使用します。 これは、テクスチャを使用したいが特定のコンテクストプロパティが必要、といった場合があるためです。 こうしたプロパティには、現時点ではminification filter とmagnification filter と horizontal wrapping と vertical wrapping があります。 そのため、 TextureDescriptor は例えばTextureAttributeCubemapAttributeでも使用されます。 利便性のために、TextureBinderではTextureDescriptorを直接指定することができます:

program.setUniformi(uniformLocation, context.textureBinder.bind(
    (TextureAttribute)(renderable.material.get(TextureAttribute.Diffuse)))
        .textureDescription));