DXLibで川瀬式MGFを実装

この記事は、[DxLib Advent Calendar 2018](https://qiita.com/advent-calendar/2018/dxlib) の10日目の記事です。
# はじめに
[エンドレスシラフ](http://endless-shirafu.com/)というサークルで同人ゲーム作ってるNicolaiです。
最近Unityの方にお熱なのでアレですが、折角カレンダーにお誘い頂いたので書きました。

内容としてはDXLib初心者向けの記事となります。
正直縮小バッファとGraphFilterの組み合わせ方について記述しているに過ぎないので、
そんなの今更説明されんでもとっくに知っとるわという感じの人は回れ右でお願いします。

# 川瀬式MGFとは
いわゆるグローとかグレアフィルタとかの、加算合成系ポストエフェクト実装の一種です。
Wikipediaにも載ってます。
[川瀬のブルームフィルター](https://ja.wikipedia.org/wiki/%E5%B7%9D%E7%80%AC%E3%81%AE%E3%83%96%E3%83%AB%E3%83%BC%E3%83%A0%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%BC)
川瀬さんが考案したことからこう呼ばれているようです。

まあぶっちゃけ2018年12月現在からすると15年ぐらい前の技術なんですが、ポストエフェクト載っていない素の状態よりは
表現の選択肢は広がるので検討されてみてはいかがでしょうか。
AAとグロー表現載せるだけで、素ポリを出すよりは見た目はマシになる気がします。
あと川瀬式MGFに限らず、縮小バッファとGraphFilterの組み合わせは色々使い出があると思います。

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

## ソースコード(川瀬式MGF無し)
```cpp: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();        // ＤＸライブラリ使用の終了処理
	return 0;        // ソフトの終了
}

```
## 結果
![test_01.gif](https://qiita-image-store.s3.amazonaws.com/0/31206/79a8f50b-069b-5b90-f267-4c5449b378fc.gif)
何の変哲もない結果になりました。

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

## ソースコード(川瀬式MGF有り)
```cpp: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();        // ＤＸライブラリ使用の終了処理
	return 0;        // ソフトの終了
}

```
## 結果
![test_02.gif](https://qiita-image-store.s3.amazonaws.com/0/31206/710609c9-98af-23a9-caef-74b2a9b91836.gif)
何が変わったのか超絶分かりづらいので静止画で「test」のあたりを拡大表示して比較します。
赤枠の方が川瀬式MGF有りです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31206/a4e5d84a-ac8d-8b6d-cdee-bec28f20e841.png)
川瀬式MGF有りについて、若干グローがかかっているのがお分かりいただけるかと思います。

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

```cpp
// 川瀬式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関数のリファレンス](http://dxlib.o.oo7.jp/function/dxfunc_graph1.html#R3N26S1)に記載されている使い方まんまですね。

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

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

```cpp: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();        // ＤＸライブラリ使用の終了処理
	return 0;        // ソフトの終了
}

```

やっていること自体は1回だけ掛ける版と変わりありません。
変更点としては縮小バッファを1/2, 1/4, 1/8, .. と作成し、最終段の加算合成時に合成率を縮小バッファごとに変更しているだけです。

## 比較
上から順に、川瀬式MGFなし、川瀬式MGF1段、2段、3段、4段となっています。
光芒が広がっているのがお分かりいただけるかと思います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31206/fd82b6ac-a758-c9ef-55cb-84beadcb44ba.png)

# 宣伝
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31206/11b547f6-b5e6-f182-c239-b821b5c4818a.png)
弊サークルの方で以前発行した[「DXライブラリで学ぶ2Dゲームエフェクト入門」](http://ameroad.net/l/PLQ)という本がありまして、
そちらの方では初心者の方にも分かりやすいように、DXライブラリを利用した様々なエフェクトテクニックを解説しております。
電子書籍版を[ameroadの方で販売](http://ameroad.net/l/PLQ)しておりますので、ご興味ある方は是非ご覧頂ければ幸いです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31206/6fe56003-24d9-bee0-fe77-531339396195.png)


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