DirectX에서 에지 감지



노멀 맵과 깊이 값을 사용하여 윤곽을 강조하는 방법을 소개합니다.

개요



MRT로 1패스로 보통 렌더링하고, 2패스로 법선 맵, 3패스에서 카메라로부터의 깊이값 맵을 제작합니다.

1패스(일반 렌더링)


2패스(법선 맵)


3패스(심도값 맵)


노멀 맵과 깊이 값 맵을 사용하여 가장자리 추출을 수행합니다.


엣지 추출한 것을 1 패스째에 곱셈 합성합니다.

곱셈 합성에는 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;
}

좋은 웹페이지 즐겨찾기