본문 바로가기

개발/DirectX3D

DirectX SSAO(Screen Space Ambient Occlusion) Shader 코드 구현

안녕하세요 넬다이입니다.

오늘은 SSAO에 대해서 공부를 해볼 생각입니다.

 

SSAO (Screen Space Ambient Occlusion)은 사실적인 광원 표현 기술이라고 생각하면 좋습니다.

화면의 특정 부분에 기하학 기반으로 밝고 어두운 부분을 계산하여 게임의 그래픽에 적용하는 기술로써

어두워야 하는 부분은 어둡게 표현을 게임화면에 표현해줌으로써 사실적인 보다 현실적인 조명효과와 입체감을 표현해 주는 기술입니다.

 

앰비언트 오클루전은 여러기술이 존재하며

SSAO , HBAO , HBAO+등과 같은 기법들이 존재하며 우리가 다룰 내용은 SSAO입니다.

 

가장 기본적이고 프레임 저하가 적은 SSAO는 모니터에 렌더링 되는 부분만 영역으로 인식하여 물체나 표면에 앰비언트 오클루전을 적용함으로써 일정 수준의 기본적이지만 최적화된 렌더링을 제공하게 됩니다.

 

01

 

 

자 이제 코드를 살펴볼 차례인데요 제 풀코드는 이렇습니다

float g_fRadius = 0.001f;
float g_fFar = 10000.f;
float g_fFalloff = 0.000002f;
float g_fStrength = 0.0007f;
float g_fTotStrength = 1.38f;
float g_fInvSamples = 1.f / 16.f;

texture g_DepthTex;
sampler g_DepthSam = sampler_state
{
	Texture = g_DepthTex;
};

texture g_NormalTex;
sampler g_NormalSam = sampler_state
{
    Texture = g_NormalTex;
};

float3 g_vRandom[16] =
{ 
    float3(0.2024537f, 0.841204f, -0.9060141f),
    float3(-0.2200423f, 0.6282339f, -0.8275437f),
    float3(0.3677659f, 0.1086345f, -0.4466777f),
    float3(0.8775856f, 0.4617546f, -0.6427765f),
    float3(0.7867433f, -0.141479f, -0.1567597f),
    float3(0.4839356f, -0.8253108f, -0.1563844f),
    float3(0.4401554f, -0.4228428f, -0.3300118f),
    float3(0.0019193f, -0.8048455f, 0.0726584f),
    float3(-0.7578573f, -0.5583301f, 0.2347527f),
    float3(-0.4540417f, -0.252365f, 0.0694318f),
    float3(-0.0483353f, -0.2527294f, 0.5924745f),
    float3(-0.4192392f, 0.2084218f, -0.3672943f),
    float3(-0.8433938f, 0.1451271f, 0.2202872f),
    float3(-0.4037157f, -0.8263387f, 0.4698132f),
    float3(-0.6657394f, 0.6298575f, 0.6342437f),
    float3(-0.0001783f, 0.2834622f, 0.8343929f),
};


struct tagSSAO_In
{
    float2 vUV;
    float fDepth;
    float fViewZ;
    half3 vNormal;
};

struct tagSSAO_Out
{
    float4 vAmbient;
};

float3 randomNormal(float2 tex)
{
    float noiseX = (frac(sin(dot(tex, float2(15.8989f, 76.132f) * 1.0f)) * 46336.23745f));
    float noiseY = (frac(sin(dot(tex, float2(11.9899f, 62.223f) * 2.0f)) * 34748.34744f));
    float noiseZ = (frac(sin(dot(tex, float2(13.3238f, 63.122f) * 3.0f)) * 59998.47362f));
    return normalize(float3(noiseX, noiseY, noiseZ));
}

tagSSAO_Out Get_SSAO(tagSSAO_In In)
{
    tagSSAO_Out Out = (tagSSAO_Out) 0.f;

	 half3 vRay;
	 half3 vReflect;
	 half2 vRandomUV;
	 float fOccNorm;
 
	 int iColor = 0;
 
     for (int i = 0; i < 16; ++i)
     {
         vRay = reflect(randomNormal(In.vUV) ,  g_vRandom[i]);
         vReflect = normalize(reflect(vRay, In.vNormal)) * g_fRadius;
         vReflect.x *= -1.f;
         vRandomUV = In.vUV + vReflect.xy;
         fOccNorm = tex2D(g_DepthSam, vRandomUV).g * g_fFar * In.fViewZ;
 
         if(fOccNorm <= In.fDepth + 0.0003f)
 			++iColor;
     }
 
 	Out.vAmbient = abs((iColor / 16.f) - 1);
     return Out;

}

float4 PS_MAIN(float2 vUV : TEXCOORD0) : COLOR0
{
    float4 vDepth = tex2D(g_DepthSam, vUV);
    half4 vNormal = tex2D(g_NormalSam, vUV);

    if(vNormal.a != 0.f)
        return float4(1.f, 1.f, 1.f, 1.f);

    vNormal = normalize(vNormal * 2.f - 1.f);


    tagSSAO_In SSAO_In = (tagSSAO_In) 0;
    SSAO_In.vUV = vUV;
    SSAO_In.vNormal = vNormal.rgb;
    SSAO_In.fViewZ = vDepth.r * g_fFar;
    SSAO_In.fDepth = vDepth.g * g_fFar * SSAO_In.fViewZ;

    tagSSAO_Out Out = Get_SSAO(SSAO_In);

    return (1.f - Out.vAmbient);
}

technique DefaultTech
{
    pass Pass0
    {
        ZWriteEnable = false;

        lighting = false;

        VertexShader = NULL;
        PixelShader = compile ps_3_0 PS_MAIN();
    }
}

자 이제 하나하나 뜯어가면서 설명을 드리겠습니다

 

함수 tagSSAO_Out Get_SSAO(tagSSAO_In In) 를 보시면은
for (int i = 0; i < 16; ++i) 이렇게 for문을 16번 돌고 있는것을 볼 수 있습니다.

저는 16번을 돌면서 주변 깊이를 체크를 했습니다.

16번도 가능하지만 퀄리티를 높일려면은 이 반복 횟수를 증가시켜주시면 됩니다.

 

이 구문은 랜덤 레이를 구하고 있습니다.

vRay = reflect(randomNormal(In.vUV) , g_vRandom[i]) 

 

랜덤 레이를 통해서 Normal을 통해서 반사각 구하고 길이를 Radius의 길이만큼 해준다.  

vReflect = normalize(reflect(vRay, In.vNormal)) * g_fRadius;

vReflect.x *= -1.f;

 

랜덤 위치의 uv를 구하기 위해서 vUV + Reflect.xy를 통해서 2차원 상에 랜덤 위치를 구한다.

vRandomUV = In.vUV + vReflect.xy;

 

랜덤 위치의 randomUV를 통해서 랜덤 위치에 깊이를 구하자.

fOccNorm = tex2D(g_DepthSam, vRandomUV).g * g_fFar * In.fViewZ;

 

정도로 정리할 수 있을 것 같습니다.

 

실제로 SSAO 코드를 통해서 화면을 그리면은 이런 모습이 나옵니다.

 

 

 

저는 이렇게 나온 화면은 blur처리를 해서 사용을 했습니다.

 

캐릭터는 게임 특성상 제외되야 되서 제외를 했고 배경에만 SSAO를 적용한 모습입니다.

저는 프레임때문에 blur의 해상도를 낮게 처리해서 그렇게 큰 차이는 없지만 이것 하나하나가 큰 차이라고 생각합니다.

 

자 이렇게 오늘은 SSAO에 대해서 공부해봣는데요

이 코드는 제가 공부하던시절 아무리 찾아도 코드나 없거나 찾아도 알아보기 힘들어서 직접 원리를 보고 직접 코드를 구현을 해본 것입니다. 또 한 프레임때문에 16번만 돌린것이기도 해서 그렇게 큰 퀄리티를 장담하지는 못하지만 이를통해서 SSAO를 게임속에 녹여낼 수 있었습니다.

 

그럼 오늘 공부내용은 끝마치도록 하겠습니다.

'개발 > DirectX3D' 카테고리의 다른 글

DirectX Shader LUT 필터 코드 구현  (0) 2020.07.19
DirectX Shader Bloom 코드 구현  (1) 2020.07.18
DirectX Blur Shader 코드 구현  (9) 2020.07.17
DirectX 3D 기법 및 Shader 시작하기.  (1) 2020.07.15