DirectXでエッジ検出


![edge合成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368850/7db3c2d8-9cb9-d452-115e-12761635d2ad.png)


法線マップや深度値を使用して、輪郭を強調する方法を紹介します。

# 概要
MRTで1パス目で普通にレンダリングして、2パス目で法線マップ、3パス目でカメラからの深度値マップを作製します。

1パス(通常レンダリング)
![main2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368850/29ecc9fa-d166-d5c2-d429-d5e9e53544f3.png)

2パス(法線マップ)
![法線.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368850/5571b739-729e-bdbf-2027-ab153db75811.png)

3パス(深度値マップ)
![深度値.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368850/ee4b4fec-2986-4473-62c3-6a70120fcc30.png)


法線マップと深度値マップを使用して、エッジ抽出を行います。
![edge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368850/76d5f9c4-38be-b6d4-28b0-2acd971afd2c.png)

エッジ抽出したものを1パス目に乗算合成します。
![edge合成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368850/b022ae92-5e66-8d85-34a6-cc2d6e68bf64.png)
乗算合成にはDirectX11のブレンドステートを使用しました。

```C++
	ID3D11BlendState* m_finalBlendState;		//乗算合成用のブレンディングステート。
    ID3D11Device* device;   //d3d11デバイス
    ID3D11DeviceContext* deviceContext   //d3d11デバイスコンテキスト
    

//設定
{
	CD3D11_DEFAULT defaultSettings;
	//デフォルトセッティングで初期化する。
	CD3D11_BLEND_DESC blendDesc(defaultSettings);
    //合成用のブレンドステートを作成する。
	//乗算合成。
	blendDesc.RenderTarget[0].BlendEnable = true;
	blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO;
	blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_COLOR;
	device->CreateBlendState(&blendDesc, &m_finalBlendState);
}

//ドロー時
{
    //乗算合成用のブレンディングステートを設定する。
	float blendFactor[] = { 0.0f, 0.0f, 0.0f, 0.0f };
	deviceContext->OMSetBlendState(m_finalBlendState, blendFactor, 0xffffffff);
}

```

# エッジ検出
エッジ検出ですが、法線マップ、深度値マップ共に、ラプラシアンフィルタ(8方向)というものを使用しました。
例えばピクセルが

```math
\left(
\begin{matrix}
1 & 0 & 0 \\
1 & 1 & 0 \\
1 & 1 & 1
\end{matrix}
\right)
```
とあったとします。中央がエッジかどうか判定したいピクセルです。各ピクセルに

```math
\left(
\begin{matrix}
1 & 1 & 1 \\
1 & -8 & 1 \\
1 & 1 & 1
\end{matrix}
\right)
```
を掛けて足します。その結果の絶対値が一定数以上だとエッジとみなします。
この場合だと、

```math
(1 * 1) * 5 + (0 * 1) * 3 + (1 * (-8))=  -3
``` 
となります。2より大きければエッジとしていた場合、このピクセルはエッジということになります。

# シェーダー


```hlsl
/*!
  *@brief	頂点シェーダーの入力。
  */
struct VSInput {
	float4 pos : SV_Position;
	float2 uv  : TEXCOORD0;
};
/*!
 *@brief	ピクセルシェーダーへの入力。
 */
struct PS_EdgeInput {
	float4 pos : SV_Position;
	float2 tex0	: TEXCOORD0;
	float4 tex1 : TEXCOORD1;
	float4 tex2 : TEXCOORD2;
	float4 tex3 : TEXCOORD3;
	float4 tex4 : TEXCOORD4;
	float4 tex5 : TEXCOORD5;
	float4 tex6 : TEXCOORD6;
	float4 tex7 : TEXCOORD7;
	float4 tex8 : TEXCOORD8;
};

Texture2D<float4> normalTexture : register(t0);	//シーンテクスチャ。
Texture2D<float4> depthValueTexture : register(t1); //深度値テクスチャ

sampler Sampler : register(s0);		//サンプラー

PS_EdgeInput VSXEdge(VSInput In)
{
	float2 texSize;
	float level;
	//テクスチャーのサイズを取得する
	normalTexture.GetDimensions(0, texSize.x, texSize.y, level);

	PS_EdgeInput Out;
	Out.pos = In.pos;
	float2 tex = In.uv;

	float offset = 0.2f;
	//法線
	{
		//真ん中のピクセル
		Out.tex0 = tex;

		//右上のピクセル
		Out.tex1.xy = tex + float2(offset / texSize.x, -offset / texSize.y);

		//上のピクセル
		Out.tex2.xy = tex + float2(0.0f, -offset / texSize.y);

		//左上のピクセル
		Out.tex3.xy = tex + float2(-offset / texSize.x, -offset / texSize.y);
		
		//右のピクセル
		Out.tex4.xy = tex + float2(offset / texSize.x, 0.0f);

		//左のピクセル
		Out.tex5.xy = tex + float2(-offset / texSize.x, 0.0f);

		//右下のピクセル
		Out.tex6.xy = tex + float2(offset / texSize.x, offset / texSize.y);

		//下のピクセル
		Out.tex7.xy = tex + float2(0.0f, offset / texSize.y);

		//左下のピクセル
		Out.tex8.xy = tex + float2(-offset / texSize.x, offset / texSize.y);
	}

    //深度値
	{
		//深度値を取り出すときに使うUV座標
		offset = 1.0f;
		//右上のピクセル
		Out.tex1.zw = tex + float2(offset / texSize.x, -offset / texSize.y);

		//上のピクセル
		Out.tex2.zw = tex + float2(0.0f, -offset / texSize.y);

		//左上のピクセル
		Out.tex3.zw = tex + float2(-offset / texSize.x, -offset / texSize.y);

		//右のピクセル
		Out.tex4.zw = tex + float2(offset / texSize.x, 0.0f);

		//左のピクセル
		Out.tex5.zw = tex + float2(-offset / texSize.x, 0.0f);

		//右下のピクセル
		Out.tex6.zw = tex + float2(offset / texSize.x, offset / texSize.y);

		//下のピクセル
		Out.tex7.zw = tex + float2(0.0f, offset / texSize.y);

		//左下のピクセル
		Out.tex8.zw = tex + float2(-offset / texSize.x, offset / texSize.y);

	}
	return Out;
}

float4 PSEdge(PS_EdgeInput In) : SV_Target0
{
	//周囲のピクセルの法線の値の平均を計算する。
	float3 Normal;
	Normal = normalTexture.Sample(Sampler, In.tex0).xyz * -8.0f;
	Normal += normalTexture.Sample(Sampler, In.tex1.xy).xyz;
	Normal += normalTexture.Sample(Sampler, In.tex2.xy).xyz;
	Normal += normalTexture.Sample(Sampler, In.tex3.xy).xyz;
	Normal += normalTexture.Sample(Sampler, In.tex4.xy).xyz;
	Normal += normalTexture.Sample(Sampler, In.tex5.xy).xyz;
	Normal += normalTexture.Sample(Sampler, In.tex6.xy).xyz;
	Normal += normalTexture.Sample(Sampler, In.tex7.xy).xyz;
	Normal += normalTexture.Sample(Sampler, In.tex8.xy).xyz;

	//周囲のピクセルの深度値の平均を計算する。
	float depth2 = depthValueTexture.Sample(Sampler, In.tex1).x;
	depth2 += depthValueTexture.Sample(Sampler, In.tex2.zw).x;
	depth2 += depthValueTexture.Sample(Sampler, In.tex3.zw).x;
	depth2 += depthValueTexture.Sample(Sampler, In.tex4.zw).x;
	depth2 += depthValueTexture.Sample(Sampler, In.tex5.zw).x;
	depth2 += depthValueTexture.Sample(Sampler, In.tex6.zw).x;
	depth2 += depthValueTexture.Sample(Sampler, In.tex7.zw).x;
	depth2 += depthValueTexture.Sample(Sampler, In.tex8.zw).x;
	depth2 /= 8.0f;

	float4 Color;
	//法線の計算結果、あるいは深度値の計算結果が一定以上ならエッジとみなす。
	if (length(Normal) >= 0.2f || abs(depth2-depth) > 0.001f ) {
		Color = float4(0.0f, 0.0f, 0.0f, 1.0f);
	}
	else {
		Color = float4(1.0f, 1.0f, 1.0f, 1.0f);
	}

	return Color;
}
```