LoginSignup
8
5

More than 1 year has passed since last update.

DirectXでエッジ検出

Last updated at Posted at 2020-01-13

edge合成.png

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

概要

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

1パス(通常レンダリング)
main2.png

2パス(法線マップ)
法線.png

3パス(深度値マップ)
深度値.png

法線マップと深度値マップを使用して、エッジ抽出を行います。
edge.png

エッジ抽出したものを1パス目に乗算合成します。
edge合成.png
乗算合成にはDirectX11のブレンドステートを使用しました。

	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方向)というものを使用しました。
例えばピクセルが

\left(
\begin{matrix}
1 & 0 & 0 \\
1 & 1 & 0 \\
1 & 1 & 1
\end{matrix}
\right)

とあったとします。中央がエッジかどうか判定したいピクセルです。各ピクセルに

\left(
\begin{matrix}
1 & 1 & 1 \\
1 & -8 & 1 \\
1 & 1 & 1
\end{matrix}
\right)

を掛けて足します。その結果の絶対値が一定数以上だとエッジとみなします。
この場合だと、

(1 * 1) * 5 + (0 * 1) * 3 + (1 * (-8))=  -3

となります。2より大きければエッジとしていた場合、このピクセルはエッジということになります。

シェーダー

/*!
  *@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;
}
8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5