Posted at
DxLibDay 10

DXLibで川瀬式MGFを実装

この記事は、DxLib Advent Calendar 2018 の10日目の記事です。


はじめに

エンドレスシラフというサークルで同人ゲーム作ってるNicolaiです。

最近Unityの方にお熱なのでアレですが、折角カレンダーにお誘い頂いたので書きました。

内容としてはDXLib初心者向けの記事となります。

正直縮小バッファとGraphFilterの組み合わせ方について記述しているに過ぎないので、

そんなの今更説明されんでもとっくに知っとるわという感じの人は回れ右でお願いします。


川瀬式MGFとは

いわゆるグローとかグレアフィルタとかの、加算合成系ポストエフェクト実装の一種です。

Wikipediaにも載ってます。

川瀬のブルームフィルター

川瀬さんが考案したことからこう呼ばれているようです。

まあぶっちゃけ2018年12月現在からすると15年ぐらい前の技術なんですが、ポストエフェクト載っていない素の状態よりは

表現の選択肢は広がるので検討されてみてはいかがでしょうか。

AAとグロー表現載せるだけで、素ポリを出すよりは見た目はマシになる気がします。

あと川瀬式MGFに限らず、縮小バッファとGraphFilterの組み合わせは色々使い出があると思います。


比較

まずは川瀬式MGFを使わない普通の描画です。


ソースコード(川瀬式MGF無し)


original

#include "DxLib.h"

#include <math.h>

const int SCREEN_SIZE_X = 640;
const int SCREEN_SIZE_Y = 480;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ChangeWindowMode(TRUE); // ウィンドウモード
SetMainWindowText("Advent Calendar 2018");
SetGraphMode(SCREEN_SIZE_X, SCREEN_SIZE_Y, 32);

if (DxLib_Init() == -1 || SetDrawScreen(DX_SCREEN_BACK) != 0)
{
return -1; //初期化と裏画面化
}
if (GetValidShaderVersion() < 200)
{
// 有効なシェーダバージョンではなかった
DxLib_End();
return -1;
}

SetCreateDrawValidGraphMultiSample(4, 2); // 描画クオリティ
SetDrawMode(DX_DRAWMODE_BILINEAR); // バイリニアで描画

int TestGraphic = LoadGraph("test.png");
int ScreenBuf = MakeScreen(SCREEN_SIZE_X, SCREEN_SIZE_Y, TRUE); // 描画スクリーンバッファ
int MGFBuf = MakeScreen(SCREEN_SIZE_X/2, SCREEN_SIZE_Y/2, TRUE); // 縮小バッファ

int Count = 0;

while (ProcessMessage() == 0 && ClearDrawScreen() == 0)
{
SetDrawScreen(ScreenBuf);
ClearDrawScreen();

// 適当になんか描画
DrawRotaGraph(SCREEN_SIZE_X / 2 + 100*sin(Count/180.0f * 3.14), SCREEN_SIZE_Y / 2, 1, 0, TestGraphic, 1);

SetDrawScreen(DX_SCREEN_BACK); // バックバッファへの描画
ClearDrawScreen();
DrawGraph(0, 0, ScreenBuf, FALSE); // 描画スクリーンバッファの反映
ScreenFlip(); // 実画面への反映

Count++;
}

DxLib_End(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}



結果

test_01.gif

何の変哲もない結果になりました。

次に川瀬式MGFありのソースコードと結果を載せます。


ソースコード(川瀬式MGF有り)


kawase_mgf

#include "DxLib.h"

#include <math.h>

const int SCREEN_SIZE_X = 640;
const int SCREEN_SIZE_Y = 480;

// 川瀬式MGF。ScreenBufを書き換える
void KawaseMGF(int ScreenBuf, int MGFBuf)
{
SetDrawScreen(MGFBuf);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 255);
// 1/2サイズに縮小
DrawExtendGraph(0, 0, SCREEN_SIZE_X/2, SCREEN_SIZE_Y/2, ScreenBuf, FALSE);

// 一度描画バッファをGraphFilterの対象バッファ以外に切り替えなければGraphFilterが正常に働かないため注意!
SetDrawScreen(ScreenBuf);
// 1/2サイズに縮小したバッファに対してガウシアンフィルタを掛けたのち、DrawExtendGraphでバイリニア拡大して表示
GraphFilter(MGFBuf, DX_GRAPH_FILTER_GAUSS, 8, 100);
SetDrawBlendMode(DX_BLENDMODE_ADD, 255);
DrawExtendGraph(0, 0, SCREEN_SIZE_X, SCREEN_SIZE_Y, MGFBuf, FALSE);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 255);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ChangeWindowMode(TRUE); // ウィンドウモード
SetMainWindowText("Advent Calendar 2018");
SetGraphMode(SCREEN_SIZE_X, SCREEN_SIZE_Y, 32);

if (DxLib_Init() == -1 || SetDrawScreen(DX_SCREEN_BACK) != 0)
{
return -1; //初期化と裏画面化
}
if (GetValidShaderVersion() < 200)
{
// 有効なシェーダバージョンではなかった
DxLib_End();
return -1;
}

SetCreateDrawValidGraphMultiSample(4, 2); // 描画クオリティ
SetDrawMode(DX_DRAWMODE_BILINEAR); // バイリニアで描画

int TestGraphic = LoadGraph("test.png");
int ScreenBuf = MakeScreen(SCREEN_SIZE_X, SCREEN_SIZE_Y, TRUE); // 描画スクリーンバッファ
int MGFBuf = MakeScreen(SCREEN_SIZE_X/2, SCREEN_SIZE_Y/2, TRUE); // 縮小バッファ

int Count = 0;

while (ProcessMessage() == 0 && ClearDrawScreen() == 0)
{
SetDrawScreen(ScreenBuf);
ClearDrawScreen();

// 適当になんか描画
DrawRotaGraph(SCREEN_SIZE_X / 2 + 100*sin(Count/180.0f * 3.14), SCREEN_SIZE_Y / 2, 1, 0, TestGraphic, 1);

KawaseMGF(ScreenBuf, MGFBuf); // 川瀬式MGFでグロー

SetDrawScreen(DX_SCREEN_BACK); // バックバッファへの描画
ClearDrawScreen();
DrawGraph(0, 0, ScreenBuf, FALSE); // 描画スクリーンバッファの反映
ScreenFlip(); // 実画面への反映

Count++;
}

DxLib_End(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}



結果

test_02.gif

何が変わったのか超絶分かりづらいので静止画で「test」のあたりを拡大表示して比較します。

赤枠の方が川瀬式MGF有りです。

image.png

川瀬式MGF有りについて、若干グローがかかっているのがお分かりいただけるかと思います。


やっていること

KawaseMGF 関数のコメントに書いてあることがすべてですが、解説します。

// 川瀬式MGF。ScreenBufを書き換える

void KawaseMGF(int ScreenBuf, int MGFBuf)
{
SetDrawScreen(MGFBuf);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 255);
// 1/2サイズに縮小
DrawExtendGraph(0, 0, SCREEN_SIZE_X/2, SCREEN_SIZE_Y/2, ScreenBuf, FALSE);

// 一度描画バッファをGraphFilterの対象バッファ以外に切り替えなければGraphFilterが正常に働かないため注意!
SetDrawScreen(ScreenBuf);
// 1/2サイズに縮小したバッファに対してガウシアンフィルタを掛けたのち、DrawExtendGraphでバイリニア拡大して表示
GraphFilter(MGFBuf, DX_GRAPH_FILTER_GAUSS, 8, 100);
SetDrawBlendMode(DX_BLENDMODE_ADD, 255);
DrawExtendGraph(0, 0, SCREEN_SIZE_X, SCREEN_SIZE_Y, MGFBuf, FALSE);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 255);
}

前提として、直接DX_SCREEN_BACKに描画するのではなく、MakeScreenで作った描画バッファに一度噛ませてからDX_SCREEN_BACKに描画します。

この描画バッファをScreenBufとしています。

SetDrawScreen で縮小バッファ(MGFBuf)に描画対象スクリーンを切り替えます。

このMGFBufは元のスクリーンバッファの1/2の解像度を持ちます。

縮小バッファ(MGFBuf)にDrawExtendGraphを1/2サイズで描画します。

WinMain で設定した SetDrawMode(DX_DRAWMODE_BILINEAR); の効果により、バイリニア縮小されてMGFBufへ描画されます。

その後、もう一度描画バッファをSetDrawScreen でScreenBufに切り替えます。

コメントにもありますが、一度描画バッファをGraphFilterの対象バッファ以外に切り替えなければGraphFilterが正常に働かないようです。

GraphFilter関数でガウシアンフィルタを適用します。

この縮小したバッファに対してガウシアンフィルタを掛けるという使い方は GraphFilter関数のリファレンスに記載されている使い方まんまですね。

あとはSetDrawBlendMode関数で加算合成モードに切り替えたのち、縮小バッファをバイリニア拡大して描画するだけです。


多重版

多重に川瀬式MGFを掛けてみます。


multiple_kawase_mgf

#include "DxLib.h"

#include <math.h>
#include <array>

const int SCREEN_SIZE_X = 640;
const int SCREEN_SIZE_Y = 480;
const int MGF_BUF_SIZE = 4;

// 川瀬式MGF(多重)。ScreenBufを書き換える
void KawaseMGFMultiple(int ScreenBuf, std::array<int, MGF_BUF_SIZE>& MGFBufs)
{
for (int i = 0; i < MGF_BUF_SIZE; i++)
{
SetDrawScreen(MGFBufs[i]);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 255);
// 1/2, 1/4, ... に縮小
DrawExtendGraph(0, 0, SCREEN_SIZE_X / powf(2, (i+1)), SCREEN_SIZE_Y / powf(2, (i + 1)), ScreenBuf, FALSE);

// 一度描画バッファをGraphFilterの対象バッファ以外に切り替えなければGraphFilterが正常に働かないため注意!
SetDrawScreen(ScreenBuf);
// 1/2, 1/4, ...サイズに縮小したバッファに対してガウシアンフィルタを掛けたのち、DrawExtendGraphでバイリニア拡大して表示
GraphFilter(MGFBufs[i], DX_GRAPH_FILTER_GAUSS, 8, 100);
SetDrawBlendMode(DX_BLENDMODE_ADD, 255 / powf(2, i+1));
DrawExtendGraph(0, 0, SCREEN_SIZE_X, SCREEN_SIZE_Y, MGFBufs[i], FALSE);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 255);
}
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ChangeWindowMode(TRUE); // ウィンドウモード
SetMainWindowText("Advent Calendar 2018");
SetGraphMode(SCREEN_SIZE_X, SCREEN_SIZE_Y, 32);

if (DxLib_Init() == -1 || SetDrawScreen(DX_SCREEN_BACK) != 0)
{
return -1; //初期化と裏画面化
}
if (GetValidShaderVersion() < 200)
{
// 有効なシェーダバージョンではなかった
DxLib_End();
return -1;
}

SetCreateDrawValidGraphMultiSample(4, 2); // 描画クオリティ
SetDrawMode(DX_DRAWMODE_BILINEAR); // バイリニアで描画

int TestGraphic = LoadGraph("test.png");
int ScreenBuf = MakeScreen(SCREEN_SIZE_X, SCREEN_SIZE_Y, TRUE); // 描画スクリーンバッファ

std::array<int, MGF_BUF_SIZE> MGFBufs;
for (int i = 0 ; i < MGF_BUF_SIZE; i++)
{
MGFBufs[i] = MakeScreen(SCREEN_SIZE_X / powf(2, i + 1), SCREEN_SIZE_Y / powf(2, i + 1), TRUE); // 縮小バッファ
}
int Count = 0;

while (ProcessMessage() == 0 && ClearDrawScreen() == 0)
{
SetDrawScreen(ScreenBuf);
ClearDrawScreen();

// 適当になんか描画
DrawRotaGraph(SCREEN_SIZE_X / 2 + 100*sin(Count/180.0f * 3.14), SCREEN_SIZE_Y / 2, 1, 0, TestGraphic, 1);

KawaseMGFMultiple(ScreenBuf, MGFBufs); // 川瀬式MGF(多重)でグロー

SetDrawScreen(DX_SCREEN_BACK); // バックバッファへの描画
ClearDrawScreen();
DrawGraph(0, 0, ScreenBuf, FALSE); // 描画スクリーンバッファの反映
ScreenFlip(); // 実画面への反映

Count++;
}

DxLib_End(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}


やっていること自体は1回だけ掛ける版と変わりありません。

変更点としては縮小バッファを1/2, 1/4, 1/8, .. と作成し、最終段の加算合成時に合成率を縮小バッファごとに変更しているだけです。


比較

上から順に、川瀬式MGFなし、川瀬式MGF1段、2段、3段、4段となっています。

光芒が広がっているのがお分かりいただけるかと思います。

image.png


宣伝

image.png

弊サークルの方で以前発行した「DXライブラリで学ぶ2Dゲームエフェクト入門」という本がありまして、

そちらの方では初心者の方にも分かりやすいように、DXライブラリを利用した様々なエフェクトテクニックを解説しております。

電子書籍版をameroadの方で販売しておりますので、ご興味ある方は是非ご覧頂ければ幸いです。

image.png


さいご

時間があればDXライブラリでシェーダの話も書きたい。時間があれば…。