Depth Of Field 2 (Advanced DOF)

반응형
이전 글 참고 : http://mgun.tistory.com/1388

이전글에 이어서 Advanced Depth Of Field를 언급한다.

Depth of Field의 구현.
1. destination alpha(출력 알파) 채널에 per pixel depth와 blurriness 정보를 저장한다.
2. post processing을 위한 pixel shader .
   a. 이미지를 downsample해서 pre blur한다.
   b. COC 근사치를 위해 다양한 크기의 필터커널을 사용한다.
   c.
더 나은 이미지 품질을 위해 원본과 pre blurred한 이미지를 섞는다.
   d. 또렷한 전경값이 블러 원경으로 쓰이는 것을 측정해서 방지한다.


위의 내용이 핵심이다.
이 개념을 실제로 옮겨 보자.

1. destination alpha(출력 알파) 채널에 per pixel depth와 blurriness 정보를 저장한다.
post processing shader에서는 각 픽셀의 depth와 blurriness값을 필요로 하고,
scene shaders를 위해 세개의 면의 카메라 거리를 전달해야 한다.
세개의 각 면은 아래와 같은 의미를 지닌다.
   - Focal plane : 초점에 있는 이 평면은 초점이 맞다.
   - Near plane : 이 평면보다 가까운 것들은 전부 블러된다.
   - Far plane : far plane 너머에 있는 모든 것들은 전부 블러된다.
각  오브젝트의 픽셀 셰이더는 depth와 blurriness 정보를 destination alpha에 그린다.

그렇다면 destination alpha 값에 depth와 blurriness 정보를 어떻게 저장하는가?
destination alpha값은 당연히 float이고 하나의 값만을 저장할 수 있지 않은가?
그래서 아래와 같이 한다.

위 그림을 통해서 알수 있는 사실들은 아래와 같다.
  1. -1에서 1사이의 범위를 가지는 depth 값이 위의 그림에서 pink 그래프로 나타나 있다.
  2. blurriness를 얻기 위해서는 단지 절대값 처리만 해 주면 된다.
  3. depth값은 destination alpha에 쓰여지기 전에 0~1 사이의 범위를 가지도록 변환된다.


위의 내용대로 옮겨보면..

VS_OUTPUT main( VS_INPUT Input )
{
   VS_OUTPUT Out = (VS_OUTPUT) 0;

   float4 offsetPos = Input.Pos;

   // 이 모델은 배경으로 쓸거라서 위치 조정.
   offsetPos.xyz *= 600.0;
  
   // 투영 공간으로 변환
   Out.Pos = mul( view_proj_matrix, offsetPos );

   // 뷰 공간으로 변환
   float3 Pview = mul( view_matrix, offsetPos );
  
   Out.Tex = Input.Pos.xyz;

   // 깊이값 저장  
   Out.depth = Pview.z;

   return Out;
}

float4 main( PS_OUTPUT Output ) : COLOR
{
   // 배경 모델, 큐브매핑.
   float4 FinalColor = texCUBE(tCubeMap, Output.Tex);
   
   // 깊이값으로 blur값을 얻어낸다.
   FinalColor.a = ComputeDepthBlur (Output.depth);
  
   return FinalColor;
}

그냥 depth 정보를 저장해서 그 값을 기준으로 blur 값을 얻을 수 있다는 것만 알면된다.
ComputeDepthBlur의 내부 내용은 의외로 간단하다.

그림으로 직접 그려보면 좀 더 이해하기가 쉽다.
---------------------------------------------------------------------------------
if( 현재 깊이가 초점보다 가까우면 )
{
    // 현재 깊이가 초점보다 가깝다는 말은 현재 깊이가 near 평면과 초점 사이에 있다는 뜻.
    현재깊이와 초점사이의 거리 / 초점과 near 평면 사이의 거리
}
else (현재깊이가 초점위치이거나 초점보다 멀리 있는 경우라면)
{
    // 현재 깊이가 초점보다 멀리 있다는 말은 현재 깊이가 far 평면과 초점 사이에 있다는 뜻.
    현재깊이와 초점사이의 거리 / 초점과 far 평면 사이의 거리
}
마지막으로 -1~1사이의 값을 0~1사이의 범위로 축약해서 반환
---------------------------------------------------------------------------------
이렇게 FinalColor.a(destination alpha)에 blur값을 저장할 수 있다.

구현.
ComputeDepthBlur가 올바르게 작동 하는가?

ComputeDepthBlur()을 통해 얻은 깊이값을 물체의 색깔값으로 설정한다.
0~1의 값을 가질테고 가까이에 있을 수록(0~0.5) 어둡게,
멀리 있을 수록(0.5~1.0) 밝게 표현된다.



위의 사진은 세개의 teapot을 z값을 달리하여 (각 -10, 10, 30) 배치한 것이다.
멀리 있을 수록 밝아지고(0.5~1.0), 가까이 있을수록 어두워(0.0~0.5)지는 것을 알 수 있다

Alpha Blending 처리.

destination alpha를 blur 정보 저장용으로 사용하더라도 alpha blending은 할 수 있다.
- 1st pass에서 blending을 enable한 상태로 RGB만 렌더한다.
- 2rd pass에서 destination alpha만을 위한 ComputeDepthBlur()함수의 결과를 렌더한다.

2. post processing을 위한 pixel shader .

Post Processing 단계에서는 어떤 일을 하는가?

초점이 맞는 이미지(In-Focus image) -> 1/16 Size로 down sampling -> 3x3 Gaussain Blur.

How to fix the directx rasterisation rules
http://www.sjbrown.co.uk/2003/05/01/fix-directx-rasterisation/
이상적으로는 하나의 텍스처에서 다른 텍스처로의 처리를 위해 그래픽스하드웨어를 사용하려 할 때,
source 이미지와 destination 이미지가 얼마나 많은 텍셀들을 가지고 있는지 알필요는 없으며,
뷰포트 전체와 텍스쳐 좌표만 처리할 수 있으면 된다.

예를 들어, 하나의 텍스처를 다른 텍스처로 downsample 하기 위해 단일 뷰포트 공간 쿼드를 
아래의 다이어그램과 같이 렌더링 하고 싶다.
위의 좌표는 레이터라이저에서 사용할 수 있는 텍스쳐나 렌더타겟 해상도,
또는 어떠한 원하는 멀티 샘플링 모드들에서 독립적이다
그리고 위 그림에서 볼 수 있듯이 quad는 source 텍스처를 -1,0 그리고 1과 같이
간단한 숫자로 픽셀을 완벽하게 다시 샘플링 한다.

DirectX에서는 실제로 위와 같이 픽셀 중간을 그린다.
만약 어떠한 보정없이 디폴트로 연산을 수행한다면 위와 같이 될 것이다.
원본 이미지가 시각적으로 오른쪽 아래로 이동되어져 있다.
이러한 현상은 DirectX 게임이나 데모들에서 일반적으로 발견되어 진다.
이 부분을 어떻게 고칠 수 있을까?
각 버턱스 셰이더의 끝 부분에 뷰포트 텍셀의 절반을 조정해 줘야 한다.

렌더몽키 예제에서는 어떻게 처리했는지 보자.

우선 렌더타겟의 viewport ratio를 1/16 로 downsampling 하기 위해 위와 같이 1/4인 0.25로 설정한다.

화면 쿼드의 너비와 높이가 2(값의 범위가 -1~1)이므로 절반 픽셀 값은 아래와 같다.
float2 halfPixelSize = 4.0 / float2( fViewportWidth, fViewportHeight );

픽셀과 텍셀을 맞추기 위한 오프셋.
Out.Pos.xy += float2(-1, 1) * halfPixelSize;

텍셀 좌표로 변환(0~1)
Out.texCoord = 0.5 * Pos.xy + 0.5;
Out.texCoord.y = 1.0 - Out.texCoord.y;

위 관한 내용은 http://blog.daum.net/gamza-net/16 이곳에 자세히 설명 되어 있다.

a. 이미지를 downsample해서 pre blur한다.

downsampling 코드는 아래와 같다.
float2 pixelSize = 1.0 / float2( fViewportWidth, fViewportHeight );

texCoordSample.x = texCoord.x - pixelSize.x;
texCoordSample.y = texCoord.y + pixelSize.y;
cOut = tex2D(tScreenImage, texCoordSample);
   
texCoordSample.x = texCoord.x + pixelSize.x;
texCoordSample.y = texCoord.y + pixelSize.y;
cOut += tex2D(tScreenImage, texCoordSample);
   
texCoordSample.x = texCoord.x + pixelSize.x;
texCoordSample.y = texCoord.y - pixelSize.y;
cOut += tex2D(tScreenImage, texCoordSample);
   
texCoordSample.x = texCoord.x - pixelSize.x;
texCoordSample.y = texCoord.y - pixelSize.y;
cOut += tex2D(tScreenImage, texCoordSample);
      
return cOut * 0.25;
참고 : http://www.gamedev.net/topic/498662-help-downsampling-with-hlsl/

 

before down sampling
 

after down sampling


pre blur을 해 보자.
down sampling까지 했으니 다음은 blur를 적용해 줘야 할 차례이다.
여기서는 여러가지 filter중 gaussian filter을 사용하여 blur를 처리해 준다.

UDN, 가우시안 블러에 관한 내용
http://udn.epicgames.com/Three/BloomKR.html

가우시안 필터(Gaussian Filter)
http://scosco.com.ne.kr/Stereo3DHtml/vr_0003_gaussian.htm

16박스 필터 샘플링
http://mgun.tistory.com/1115

가우시안 필터
http://mgun.tistory.com/1131


 

y coord filtering

 
y and x coord filtering



b. COC 근사치를 위해 다양한 크기의 필터 커널을 사용한다.

Circle Of Confusion Filter Kernel
다운샘플링 하여 가우시안 블러를 적용해 흐리게 만들어준 이미지와 원본이미지를 합칠 차례이다.
이 때 두 이미지(원본 이미지와 다운샘플링하여 블러처리한 이미지)는 Possion disc filter kernel을 이용해서 블러링 해 준다.
1. 확률 샘플링 (http://pages.cpsc.ucalgary.ca/~mario/courses/591-691-W06/PR/3-ray-tracing/3-advanced/readings/Cook_Stochastic_Sampling_TOG86.pdf)
확률 샘플링 또는 통계적 샘플링 이라고 한다.
그냥 픽셀별로 다른 샘플링 패턴을 적용한다는 것 정도만 알아주면 된다.

2. 프와송 분포 (http://ko.wikipedia.org/wiki/%ED%91%B8%EC%95%84%EC%86%A1_%EB%B6%84%ED%8F%AC)
일정한 시간(또는 구간)내에서 특정한 사건이 발생할 확률의 분포를 푸아송 분포라고 한다.


내용은 그냥 원본 이미지에서 depth로 구한 coc를 원본이미지 샘플링에 사용하는데 이 때 원형 푸아송 분포를 사용한다는 것이다.
무튼 이렇게 coc를 사용해서 원본이미지와 다운샘플링된 이미지를 possion disc filter kernel을 사용하여 블렌딩 한다.

다만, 알아야 할 것은 kernel size가 "blurriness"값을 기준으로 한다는 것이다.
blurriness값이 낮은(Focus에 가까운) 텍셀은 disc의 반경이 작고, blurriness값이 큰 텍셀은 disc 영역이 크다.


구현부.
하나의 원에서의 프와송 분포
float2 poisson[8] =

  float2( 0.0,      0.0),
  float2( 0.527837,-0.085868),
  float2(-0.040088, 0.536087),
  float2(-0.670445,-0.179949),
  float2(-0.419418,-0.616039),
  float2( 0.440453,-0.639399),
  float2(-0.757088, 0.349334),
  float2( 0.574619, 0.685879)
};

c. e더 나은 이미지 품질을 위해 원본과 preblur한 이미지를 섞는다.

그런데 관습적으로 블러라는 포스트 프로세싱 기술은 또렷한 전경의 오브젝트들이 블러된 원경들로의 "leaking" 현상이 있다.
그래서 샘플들의 깊이값을 비교하여 원경에 "leaking"을 주는 하나를 버림으로써 "leaking"을 감소시킬 수 있다.

// 중심 tap.
cOut = tex2D(tSource, texCoord);

// 중심 tap의 깊이값.
centerDepth = cOut.a;

// 픽셀의 depth값을 blur 반지름값으로 변환한다.
// 중심 탭의 깊이값에 CoC의 최대 지름값을 곱한값에  CoC의 최대 반지름값을 뺀 값을 절대값 처리 한다.
discRadius = abs(cOut.a * vMaxCoC.y - vMaxCoC.x);

// 저사양 이미지에서 disc 반경을 계산한다.
discRadiusLow = discRadius * radiusScale;

for(tap의 갯수만큼)
{
    1. 저해상도와 고해상도의 탭 텍스쳐 좌표를 구한다.
    2. 구한 좌표값으로 탭값(tex2D)을 구한다.
    3. 탭의 블러정보를 기반으로 저해상도와 고해상도의 탭값을 섞는다.
    4. 중심 탭보다 앞에 있는 것은 블러 예외처리 해 준다.
}


결론적으로 DOF 기술은
- 썩 괜찮은 photorealistic 모습을 만들 수 있다.
- destination alpha를 depth와 blur 정보로 사용한다.
- 포스트 프로세싱은 성능상 무겁다.


 

 



References
Advanced Depth Of Field 원문
http://www.hardwareheaven.com/reviews/X800XTdhreview/advanceddepth.htm

ShaderX3 Advanced Rendering with DirectX and OpenGL
4.4 Improved Depth-Of-Field 내용정리
http://jeddli.tistory.com/110

ShaderX2 Tip & Trick p736
Real Time Depth of Field Simulation

참고 블로그들.
http://littles.egloos.com/846641
http://cagetu.egloos.com/4109651


ps.
잘못된 부분이나 애매한 부분이 있다면 댓글로 달아주세요.
3편으로 이어지긴 하지만 언제 쓸지는 모름...ㅡㅡ;

'Study > M's Lecture' 카테고리의 다른 글

피부는 왜 피부처럼 보이는가? 2  (0) 2013.12.13
피부는 왜 피부처럼 보이는가? 1  (0) 2013.12.02
행렬  (0) 2013.03.21
Depth Of Field 1 (Simple DOF)  (1) 2013.03.18
Shader 개발을 위한 툴  (0) 2013.03.12
TAGS.

Comments