2D描画#

MGLは2D描画機能として、線、矩形、スプライト(画像)の描画機能を備えています。本項ではこれらの基本的な使用方法について解説します。

文字の描画機能についてはフォントの利用方法を参照してください。

2Dレンダラ#

2D描画には2DレンダラクラスMGL::Render::Renderer2Dを利用します。このクラスはアプリケーション中の任意の場所で宣言することで利用可能となります。

MGL::Render::Renderer2D renderer;

このクラスは描画処理のバックエンドであるMGL::Render::Renderer2DDelegateをアプリケーション側から呼び出すためのラッパークラスです。MGLでは基本的にこのクラスを経由して描画を行います。

画面のクリア#

MGLは画面のクリアを自動で行ないません。もし画面内に何も描画しない領域が発生する場合は、フレームの描画開始時にクリアしておく必要があります。

画面をクリアするにはMGL::Render::Renderer2D::Clearを利用します。

画面をクリアする例
MGL::Render::Renderer2D renderer;

// 画面を黒でクリア
renderer.Clear(MGL::kColorBlack);
実行結果(640x480)
../_images/2d_graphics_screen_clear.png

引数にはMGL::Colorでクリアする色を指定します。ここで指定しているMGL::kColorBlackは黒を表す定数です。予め用意されている定数の一覧は定義済みの定数一覧を参照してください。

画面のクリアは描画命令の中でも処理コストの高い命令です。もし背景の描画などによって画面全体に何らかの描画が行われることが明確であれば、この処理を省略することで描画負荷の軽減を図ることが可能となります。なお、そのフレームで何も描画されなかった領域の色は未定義となるためご注意ください。

線と矩形の描画#

線と矩形の描画にはMGL::Render::Renderer2D::DrawLineMGL::Render::Renderer2D::DrawRectangleを利用します。これらの利用方法の例を次に示します。

線と矩形の描画の例
MGL::Render::Renderer2D renderer;

// 画面を黒でクリア
renderer.Clear(MGL::kColorBlack);

// (50, 50) の位置からサイズ 300x300 の青い矩形を表示
renderer.DrawRectangle(
         MGL::Rectangle(50.0f, 50.0f, 300.0f, 300.0f),   // 矩形の領域
         MGL::kColorBlue);                               // 色

// (100, 100) の位置から (200, 200)の位置まで白い線を表示
renderer.DrawLine(
         MGL::Vector2(100.0f, 100.0f),                   // 開始点
         MGL::Vector2(200.0f, 200.0f),                   // 終了点
         MGL::kColorRed);                                // 色
実行結果(640x480)
../_images/2d_graphics_line_rectangle.png

MGLでは2D座標の表現にMGL::Vector2を、矩形の表現にMGL::Rectangleを使用します。線の描画では引数に2つの座標と色を、矩形の描画では矩形と色を指定して表示します。

これらの機能は主にデバッグ用に利用することを想定しています。ゲーム中に利用することも可能ですが、機能面だけでなく速度面においても後述のスプライト描画の利用を推奨します。

注釈

バックエンドのレンダラの仕様にもよりますが、線と矩形、そして後述のスプライト描画はそれぞれ異なる命令で実行するため、頻繁に切り替えるとGPUの処理コストが増加する要因となります。

テクスチャの読み込みとスプライト描画#

スプライト描画は簡単に述べると読み込んだ画像を描画する機能です。2Dゲームプログラミングの描画においては、このスプライト描画をメインに利用することになります。

注釈

ラインバッファ方式以外はスプライトとして認められないという主張を作者は尊重していますが、MGLでは広義の「小さな画像を高速かつ大量に表示する機能」としてスプライトという呼称を用いています。

スプライト描画を利用するためには、先に画像をテクスチャとして読み込まなければなりません。MGLにおけるテクスチャはMGL::Render::Textureクラスによって表現されます。

テクスチャには2種類の管理方法があります。自身で管理する方法と、MGLが持つテクスチャストレージに管理してもらう方法です。MGLに管理させる方法はテクスチャストレージにて後述するため、ここでは自身で管理する方法の例について解説します。

次の例は、リソースディレクトリにあるimage.pngという画像を読み込む例です。

テクスチャ読み込みの例
constexpr const char *kTexturePath = "$resource/image.png"; // テクスチャファイルのパス

MGL::Render::Texture texture;

// テクスチャを読み込み
if (!texture.Load(kTexturePath))
{
    // 読み込みに失敗した場合はここに到達
}

テクスチャの読み込みに成功した場合はMGL::Render::Texture::Loadtrueを返し、利用可能な状態となります。

また、テクスチャは非同期読み込みにも対応しています。非同期読み込みにはMGL::Render::Texture::LoadAsyncを利用します。

テクスチャの非同期読み込みの例
constexpr const char *kTexturePath = "$resource/image.png";   // テクスチャファイルのパス

MGL::Render::Texture texture;

// テクスチャの読み込み開始
if (!texture.LoadAsync(kTexturePath))
{
    // 読み込み要求に失敗した場合にここに到達
}
// IsLoading()で読み込み中かどうかをチェックできる
if (texture.IsLoading())
{
    // 読み込み中ならここに到達
}
// 読み込み処理が行われておらず、テクスチャ自身が有効でない場合は読み込みに失敗している
if (!texture.IsLoading() && !texture.IsValid())
{
    // ここに到達する場合は読み込み失敗
}

テクスチャの読み込み状態を知るにはMGL::Render::Texture::IsLoadingを呼び出し、trueが返る場合は読み込み処理が実行中となります。読み込み処理が完了した後にMGL::Render::Texture::IsValidで有効状態をチェックし、その結果がfalseである場合は読み込みに失敗しています。

ヒント

MGLのAPIは読み込み中のテクスチャを用いて描画を行おうとした場合、何もせずに処理を返すようになっています。そのため、非同期読み込み要求後のテクスチャを単に描画命令に渡すだけでも「読み込み完了次第描画を開始する」といった処理を実現可能です。

これらの方法で有効化したテクスチャは、そのオブジェクトの寿命が切れるまで、すなわちデストラクタが呼び出されるまで有効です。途中でテクスチャをコピーした場合は、それら全てのデストラクタが呼び出されるまで利用可能となります。

有効化されたテクスチャの内容を画面に描画する場合は、MGL::Render::Renderer2D::DrawSpriteを利用します。スプライト描画の例を次に示します。

スプライト描画の例
// ここでは _texture は有効な MGL::Render::Texture クラスとする

MGL::Render::Renderer2D renderer;

// 画面を黒でクリア
renderer.Clear(MGL::kColorBlack);

// テクスチャの描画領域(この例はテクスチャ全体)
MGL::Rectangle textureRectangle(
    MGL::Vector2(0.0f, 0.0f),    // テクスチャの左上の座標
    _texture.GetSize());         // 描画するサイズ

// テクスチャの描画(左側)
renderer.DrawSprite(MGL::Vector2(100.0f, 100.0f), _texture, textureRectangle);

// テクスチャの描画領域を半分にして表示(右側)
textureRectangle.SetSize(_texture.GetSize() / 2.0f);
renderer.DrawSprite(MGL::Vector2(300.0f, 100.0f), _texture, textureRectangle);
実行結果(640x480)
../_images/2d_graphics_draw_sprite.png
テクスチャに利用した画像(128x128)
../_images/mgl1.png

MGL::Render::Renderer2D::DrawSpriteの第1引数には表示する座標を、第2引数には元となるテクスチャを、第3引数にはテクスチャの描画領域の矩形を指定します。

テクスチャの描画領域の矩形は、そのテクスチャのどの部分を切り取って表示するかの指定です。矩形には位置にテクスチャ上の左上の座標を、サイズに切り取り範囲を指定します。

左側に表示しているものは、位置に(0, 0)で左上を、サイズにMGL::Render::Texture::GetSizeで取得したサイズをそのまま指定し、画像全体を表示するようにしています。右側に表示しているものは、切り取り範囲の幅と高さを半分にして画像の左上の4分の1の領域を表示しています。

描画オプション#

2Dレンダラクラスは描画オプションを保持しており、これを変更することによって描画結果を加工できます。

描画オプションはMGL::Render::DrawOption2Dクラスで表現され、2Dレンダラが保持している描画オプションはMGL::Render::Renderer2D::GetDrawOptionにて取得可能です。この内容を変更することで、そのレンダラが実行する描画命令に適用されるようになります。

注釈

描画オプションは各々の2Dレンダラクラスが保持しています。したがって、ある箇所で加えた変更が別の箇所に影響を与えることはありません。

描画オプションの利用例を次に示します。

描画オプションの利用例
MGL::Render::Renderer2D renderer;

// 画面を黒でクリア
renderer.Clear(MGL::kColorBlack);

// テクスチャの描画領域(この例はテクスチャ全体)
MGL::Rectangle textureRectangle(
    MGL::Vector2(0.0f, 0.0f),    // テクスチャの左上の座標
    _texture.GetSize());         // 描画するサイズ

// まずは普通に描画(左)
renderer.DrawSprite(MGL::Vector2(50.0f, 100.0f), texture, textureRectangle);

// 描画オプションを取得
auto &option = renderer.GetDrawOption();

// 倍率を2倍に設定して描画(中央)
option.SetScale(MGL::Vector2(2.0f, 2.0f));
renderer.DrawSprite(MGL::Vector2(230.0f, 100.0f), texture, textureRectangle);

// 倍率を等倍に戻し、45°回転させて描画(右)
option.SetScale(MGL::Vector2(1.0f, 1.0f));
option.SetRotate(45.0f);
renderer.DrawSprite(MGL::Vector2(500.0f, 100.0f), texture, textureRectangle);
実行結果(640x480)
../_images/2d_graphics_draw_option.png

auto &option = renderer.GetDrawOption();により描画オプションを取得し、その内容を書き換えることによって描画結果に変更を加えています。このとき、&を付け忘れると参照ではなくコピーが作られてしまう点に注意してください。

描画オプションはレンダラ内部のオプションを使用せず、外部から指定することも可能です。MGL::Render::Renderer2D::DrawSpriteおよびMGL::Render::Renderer2D::DrawRectangleの引数の最後に描画オプションを指定することで、内部のオプションを使用せずそちらの参照するようになります。

例えば、上記の例の右側の描画を外部からの指定で行う場合の例は次のようになります。

描画オプションの外部指定の例
// 新規に生成した描画オプションを利用する例
MGL::Render::DrawOption2D extOption;
extOption.SetRotate(45.0f);
renderer.DrawSprite(MGL::Vector2(500.0f, 100.0f), texture, textureRectangle, extOption);
// レンダラ内部の描画オプションをコピーして利用する例
// この方法は内部オプションを引き継ぎつつ一時的に変更を加えたい場合に有効
// (ただし、ちょっとミスが怖い。いつもの癖で '&' を付けるとコピーではなく参照になってしまう)
auto copiedOption = renderer.GetDrawOption();
copiedOption.SetRotate(45.0f);
renderer.DrawSprite(MGL::Vector2(500.0f, 100.0f), texture, textureRectangle, copiedOption);

利用可能な描画オプションの一覧は、APIリファレンスのMGL::Render::DrawOption2Dを参照してください。

シザー矩形#

シザー矩形は描画範囲を一部の領域に制限する機能です、この矩形を設定すると、その範囲外への描画は行われなくなります。

シザー矩形を設定するにはMGL::Render::Renderer2D::SetScissorを利用します。この関数の利用例を次に示します。

シザー矩形の利用例
MGL::Render::Renderer2D renderer;

// 画面を黒でクリア
renderer.Clear(MGL::kColorBlack);

// 画面上の (50, 50) からサイズ (100, 100) の領域にシザー矩形を設定
MGL::Rectangle scissorRectangle(50.0f, 50.0f, 100.0f, 100.0f);
renderer.SetScissor(true, scissorRectangle);

// (0, 0) からサイズ (200, 200) の領域を緑で塗り潰す
renderer.DrawRectangle(MGL::Rectangle(0.0f, 0.0f, 200.0f, 200.0f), MGL::kColorGreen);

// 画面左上にテクスチャを表示
// _texture は初期化済みのテクスチャとする
MGL::Rectangle textureRectangle(MGL::Vector2(0.0f, 0.0f), _texture.GetSize());
renderer.DrawSprite(MGL::Vector2(0.0f, 0.0f), texture, _textureRectangle);

// シザー矩形を解除
renderer.SetScissor(false);
実行結果(640x480)
../_images/2d_graphics_scissor_enable.png
上記の例からシザー矩形を用いなかった場合の実行結果(640x480)
../_images/2d_graphics_scissor_disable.png

MGL::Render::Renderer2D::SetScissorの第1引数にはシザー矩形の有効フラグを、第2引数にはその範囲を設定します。有効フラグにfalseを指定した場合、範囲矩形の指定は不要です。

シザー矩形は描画オプションとは異なり、その後の各描画命令に影響を与えます。したがって、有効化したシザー矩形は適切なタイミングで解除する必要があります。

なお、MGL::Render::Renderer2D::Clearに対してはシザー矩形の影響は及びません。

レンダーターゲットの生成と変更#

テクスチャとして利用できるリソースは、画像データとしてあらかじめ用意されているもののみではありません。テクスチャはフレームバッファを内包しており、レンダーターゲットとして生成することで描画先に指定可能となります。また、その描画結果をテクスチャとして扱う事も可能です。

レンダーターゲットを生成するにはMGL::Render::Texture::CreateRenderTargetを利用します。この関数を利用例を次に示します。

レンダーターゲットの生成例
MGL::Render::Texture renderTarget;

// 320x224のレンダーターゲットを生成
if (!renderTarget.CreateRenderTarget(320, 224))
{
    // 生成に失敗した場合はここに到達する
}

引数には生成するフレームバッファの幅と高さを指定します。指定可能なサイズは環境によって異なる場合があるためご注意ください。生成に失敗した場合はfalseが返ります。

レンダーターゲットの変更はMGL::Render::Renderer2D::SetRenderTargetで行います。この関数の引数に有効なレンダーターゲットを指定する事で、以降はそのレンダーターゲットに対して描画を行います。

レンダーターゲットの変更例
MGL::Render::Renderer2D renderer;

// レンダーターゲットの設定(_renderTargetは有効なレンダーターゲットとする)
if (!renderer.SetRenderTarget(_renderTarget))
{
    // _renderTarget が有効でない場合はここに到達
}

レンダーターゲットの変更は描画オプションとは異なり、以降の全ての描画命令に影響を与えます。したがって、そのレンダーターゲットへの描画が完了した後に、MGL::Render::Renderer2D::ResetRenderTargetを用いて描画先を元に戻す必要があります。

レンダーターゲットのリセット
MGL::Render::Renderer2D renderer;

// レンダーターゲットの設定(_renderTargetは有効なレンダーターゲットとする)
if (!renderer.SetRenderTarget(_renderTarget))
{
    // _renderTarget が有効でない場合はここに到達
}

... // 各描画命令を実行

// レンダーターゲットをリセット
renderer.ResetRenderTarget();

レンダーターゲットは描画先へ指定して書き込みが可能な点を除けば、扱い方は通常のテクスチャと違いはありません。MGL::Render::Renderer2D::DrawSpriteの第2引数に指定することで、通常のテクスチャと同じようにその内容をスプライトとして描画可能です。

実際にレンダーターゲットを利用した描画の例を次に示します。次の例は、320x224のレンダーターゲットに描画を行い、それを画面の2箇所に表示する例です。

レンダーターゲットの利用例
MGL::Render::Renderer2D renderer;

// レンダーターゲットを変更して緑でクリア
// ここでは _renderTarget は 320x224 サイズの初期化済みレンダーターゲットとする
renderer.SetRenderTarget(_renderTarget);
renderer.Clear(MGL::kColorGreen);

// テクスチャの描画領域を準備(画像全体)
// _texture も画像データから読み込んだ初期化済みのテクスチャとする
MGL::Rectangle textureRectangle(MGL::Vector2(0.0f, 0.0f), _texture.GetSize());

// テクスチャを描画
renderer.DrawSprite(MGL::Vector2(20.0f, 20.0f), _texture, textureRectangle);
renderer.DrawSprite(MGL::Vector2(120.0f, 100.0f), _texture, textureRectangle);

// レンダーターゲットを元に戻す
renderer.ResetRenderTarget();

// 画面を黒でクリア
renderer.Clear(MGL::kColorBlack);

// レンダーターゲットの描画領域全体を表す矩形を準備
MGL::Rectangle renderTargetRectangle(MGL::Vector2(0.0f, 0.0f), _renderTarget.GetSize());

// レンダーターゲットをスプライトとして描画(左側)
renderer.DrawSprite(MGL::Vector2(20.0f, 20.0f), _renderTarget, renderTargetRectangle);

// レンダーターゲットを90°回転させて描画(右側)
auto &option = renderer.GetDrawOption();
option.SetRotate(90.0f);
renderer.DrawSprite(MGL::Vector2(320.0f, 100.0f), _renderTarget, renderTargetRectangle);
実行結果(640x480)
../_images/2d_graphics_rendertarget.png

レンダーターゲットのクリア命令の必要性も通常の画面と同等です。ここでは緑色でクリアしています。

テクスチャストレージ#

MGLはテクスチャリソースを管理するためのテクスチャストレージを備えています。テクスチャストレージはテクスチャに対応したテクスチャキーを登録し、そのキーを経由することでリソースを管理するための機能です。

テクスチャストレージを利用するためには、まずMGL::Render::TextureKey型のテクスチャキーを定義します。MGLには文字列からテクスチャキーを生成するヘルパー関数MGL::Render::MakeTextureKeyが用意されています。この関数を利用したテクスチャキーの生成例を次に示します。

テクスチャキーの生成例
// 画像ファイルのパスからテクスチャキーを生成する例
constexpr auto kTextureKey = MGL::Render::MakeTextureKey("path/to/imagefile");

ここでは解説のためにソースコードに直接定義していますが、実際にはテクスチャキーを生成するスクリプトを用意するなど、人の手によらない生成方法を推奨します。

MGL::Render::MakeTextureKeyは文字列からハッシュ値を生成し、それをテクスチャキーとして返します。第2引数にはハッシュ生成のシード値を指定可能で、衝突する場合はこの値を変更することで解消を図ることができます。

値の生成ルールは利用者に委ねられており、重複さえ発生させなければ任意のルールを用いることができます。テクスチャストレージが要求する唯一の要件は、そのアプリケーションが利用する範囲内において意図しない重複が起こらないことのみです。

ヒント

ルールに迷った場合、画像ファイルのパスから生成することをお勧めします。ファイルのパスは他と重複することはなく、ハッシュ値が重複した場合はシード値を変更することで解消を図ることが可能です。ただし、レンダーターゲットのようにプログラム側から生成するテクスチャの存在も忘れないようにしましょう。

テクスチャとテクスチャキーの関連付けは、読み込み時に引数でキーを指定することにより行います。

テクスチャキーを指定したテクスチャの読み込みの例
// 画像ファイルのパス
constexpr const char *kImagePath = "$resource/image.png";

// パスからキーを生成
constexpr auto kTextureKey = MGL::Render::MakeTextureKey(kImagePath);

// テクスチャキーを指定してテクスチャを読み込み
MGL::Render::Texture texture;
texture.Load(kTextureKey, kImagePath);

キーを指定した読み込みを行うことにより、そのリソースはストレージへと登録されます。ここでは同期読み込みでの例を示していますが、非同期読み込みのMGL::Render::Texture::LoadAsyncや、レンダーターゲットを生成するMGL::Render::Texture::CreateRenderTargetも同様にキーを指定することでテクスチャストレージへと登録されます。

もし既にそのキーで指定されたテクスチャが存在している場合、読み込みは行わずに既存のリソースを返します。これにより、異なるタスクが同一のリソースを利用する場合などにおいて、多重に読み込んでしまうことを避けられます。一方で、もしキーの重複を許してしまった場合、ストレージは意図しないリソースを返却することになります。この理由により、テクスチャキーの重複は必ず避ける必要があります。

登録済みのテクスチャは、MGL::Render::Textureのコンストラクタにそのキーを指定することで取得可能となっています。

テクスチャキーによるテクスチャの取得の例
// キーを指定してテクスチャを取得
MGL::Render::Texture texture(kTextureKey);

if (!texture)
{
    // キーに対応したテクスチャが存在しない場合はここに到達
}

そのテクスチャが不要になった場合はMGL::Render::Texture::Destroyを呼び出します。

登録したテクスチャの破棄
// キーでテクスチャを取得して、そのテクスチャを破棄
MGL::Render::Texture texture(kTextureKey);
texture.Destroy();
// staticメンバ関数も利用可能
MGL::Render::Texture::Destroy(kTextureKey);

実際のリソースの破棄は、破棄命令の直後ではなく、そのテクスチャの全ての参照が失われたタイミングで行われます。したがって、あるタスクがテクスチャを破棄し、他のタスクがそのテクスチャを利用中だった場合、直ちに利用不可能となる事はありません。ただし、破棄命令を行った時点でキーによる新たな取得は不可能となります。

注釈

MGLのテクスチャリソースは参照カウント方式の共有リソースです。テクスチャストレージが保持している限りは参照カウンタが0になることはなく、破棄命令はストレージに対する所有権の放棄となっています。