블로그 이미지
자신의 단점을 메꾸는 것을 단(鍛)이라 하고 자신의 강점을 갈고 닦는 것을 련(鍊)이라 하여, 두가지를 합친 것을 단련이라고 부른다. 붕대마음

카테고리

전체목록 (667)
참고사이트 (8)
Goal (4)
Travel (10)
My Life (105)
Game (35)
Game Review (7)
Game Plan (0)
Books (5)
English (1)
Optimizing (12)
Study (218)
유용한 것들_etc (44)
유용한 것들_func (20)
Unity (48)
Unreal (87)
작업장 (54)
RenderMonkey (6)
정리요망 (1)
따라잡기 시리즈 (0)
링크용 (0)
Total344,974
Today26
Yesterday53

'DOF'에 해당되는 글 3건

  1. 2016.10.22 Efficient Gaussian blur with linear sampling
  2. 2013.04.13 Depth Of Field 2 (Advanced DOF)
  3. 2013.03.18 Depth Of Field 1 (Simple DOF) (1)

원문 : Efficient Gaussian blur with linear sampling

이 글은 필요에 의해 모자란 번역실력으로 번역한 글이다.

좀 더 올바른 정보 습득을 위해서는 원문을 추천한다.


선형 샘플링과 효율적인 가우시안 블러

가우시안 블러는 원본 이미지를 부드럽고 흐리게 만드는데 사용하는 이미지 공간 효과다.

그리고 나서 이 이미지는 bloom이나 depth of field, heat haze or fuzzy glass 같은

좀 더 세련된 알고리즘을 만드는데 사용된다.

이 글에서는 효율적인 가우시안 필터를 만들기 위한 다양한 속성을 활용하는 방법과

텍스처 조회texture fetch) 수를 줄이기 위해 선형 텍스처 필터링을 활용하여

기본적인 가우시안 블러 필터 구현 성능을 크게 향상시킬수 있는 기술을 제시한다.

글 자체는 가우시안 블러 필터에 초점을 맞추고 있지만,

제시된 원리는 대부분은 실시간그래픽에서 사용되는 

convolution 필터들에서도 유효하다.


가우시안 블러는 컴퓨터 그래픽 분야에서 폭넓게 사용되고 있고

많은 렌더링 테크닉에서 그럴듯한 photorealistic(실사) 효과를 만들기 위해 사용하지만,

그에 상관없이 오프라인 렌더러나 게임엔진에 대해서 이야기해 보자.

텍스처 결합기를 통해 fragment 처리과정을 설정할수 있게 되면서

가우스 블러 또는 다른 흐림필터를 사용하는 거의 모든 렌더링 엔진에서는

fragment shader를 사용해야 한다.


기본적인 convolution 필터 알고리즘은 다소 비싼 반면,

그 계산 비용을 획기적으로 줄여서 꽤 오래된 하드웨어에서도

실시간 렌더링으로 사용할 수 있게 하는 산뜻한 기술들이 많이 있다.


이 문서는 대부분 현재 추천하는 사용가능한 최적화 기술들을 

제공하기위한 튜토리얼 형식이 될 거다.


이 기술들중 일부가 이미 대부분의 사람들에게 익숙하겠지만

선형 샘플링은 몇몇 사람들에게는 놀라운 방식일 거다.

하지만 바로 선형 샘플링에 대해 이야기 하기보다 기본적인 개념부터 알아보자.


전문용어

글을 읽는데 있어 혼란을 줄이기 위해 이 글의 도입부에 이 글에서 사용하는

몇가지 용어와 개념을 소개한다.

Convolution filter - 픽셀 그룹의 색상값을 조합하는 알고리즘

(추가 설명 : digital image processing에서 모든 채널을 포함한 각 pixel의

brightness값이 주위에 분포된 pixel 값들을 기준으로 다시 계산되는

방식을 convolution 이라고 한다.

이 convolution이 적용되는 룰이 바로 matrix에 의해 결정되기 때문에

통상 matrix를 사용하여 여러가지 효과를 얻어내는 image processing을

convolution이라 하고 convolution을 이용하는 filter을 보통 custom filter라 한다.)

NxN-tap filter - 측면의 길이가 N 픽셀인 정사각형 모양의 필터

N-tap filter - N 픽셀을 사용하는 필터. N-tap 필터가 반드시 N 텍셀을

                  사용하는걸 의미하지는 않으며 N-tap 필터는 N 이하의

                  texel을 사용하여(fetch) 구현할 수 있다.

Filter kernel - 필터의 픽셀을 결합하는데 사용되는 상대좌표와 가중치의 모음.

Discrete sampling - 정확히 한 텍셀을 데이터를 가져올때 쓰는 샘플링 방법

                  GL_NEAREST라고도 한다)

Linear sampling - 최종 색상값을 위해 2x2텍셀을 얻어 이중 선형보간 필터를

                  적용시키는 샘플링 방법.(GL_LINEAR 라고도 한다)


Gaussian filter

이미지 공간 가우시안 필터는 가우시안 함수를 기반으로 한

가중치 픽셀 형식의 NxN-tap convolution filter다. 



이 필터의 픽셀들은 블러효과를 위해 가우시안 함수로부터 

가중치가 적용된 값을 얻어 사용한다.

가우시안 필터의 공간적 표현은 때때로 "bell surface(종 표면)"라고 하며,

아래 그림은 개별 픽셀이 최종 픽셀 색상에 얼마나 기여하는지를 보여준다.


2차원 가우시안 함수의 그래픽적 표현.


이를 기반으로 몇몇은 벌써 "아하, 그래서 NxN texture fetch들을 할때

가중치가 필요하구나" 라고 할지 모른다.

이는 맞는말이지만 그다지 효율적으로 보이진 않는다.

1024 x 1024 이미지의 경우, 전체이미지에서 블러 효과를 주기 위해서는

33x33 tap 가우시안 필터를 기반으로 접근하면 1024 x 1024 x 33 x33 = 11억 4천

이라는 거대한 수의 texture fetche들을 필요로 한다.


보다 효율적인 알고리즘을 위해 가우시안 함수의 좋은 특성 중에 일부를

분석해 봐야 한다.



● 2차 가우시안 함수는 1차 가우시안 함수 두개를 곱함으로써 계산할수 있다.


● 2σ(시그마)의 가우스 함수 분포와 두개의 σ가우스 함수 분포의 결과물은 같다.


이러한 두 가우스 함수의 속성이 우리에게 많은 최적화의 여지를 준다.



첫번쨰 가우시안 함수의 특성을 기반으로, 2차 가우시안 함수를 두개의

1차 가우시안 함수로 나눌 수 있는데, fragment shader의 경우로 보면

가로 블러 필터와 세로블러 필로 가우시아 블러를 분리할 수 있고,

렌더링 후에도 여전히 정확한 결과를 얻을 수 있다.

두개의 N-tap 필터 결과에 추가로 두번째 필터를 위해 필요한 렌더링 패스가 있다.

예제로 돌아가서 1024 x 1024 이미지에 33-tap 가우시안 필터를 두 필터로 사용할 경우

1024 x 1024 x 33 x 2 = 6900백만 texture fetche들이 필요한걸 알 수 있다.


두번째 속성은 단일 패스에서 제한적인 texture fetches를 지원하는 플랫폼에서

하드웨어 제한을 우회하는데 사용될 수 있다.

(추가 설명

33x33 필터를 3개의 9x9 필터로 분해한다.

9+8 = 17 이며 17+16 = 33 이다.

이렇게 한다면 1024 x 1024 x 9 x 9 x 3 = 2억 5천번으로 줄어든다.

위 두가지 속성을 조합한다면 이렇게 생각할 수 있다.

33-tap 필터를 3개의 9-tap 필터로 변형하고 이를 수평과 수직으로 분할한다.

이렇게 할 경우 1024 x 1024 x 9 x 3 x 2 = 5600만개로 최적화 할 수 있다.)


가우시안 커널 가중치

우리는 적어도 이론적으로 우리의 응용프로그램에서 효율적인 

가우시안 블러 필터가 어떻게 만들어 지는지 살펴보았지만,

적절한 결과를 얻기 위해 필터를 이용해서 어떻게 각 픽셀의 가중치를

계산하여 결합할 건지에 대해서는 아직 이야기 하지 않았다.


커널 가중치를 결정하는 가장 간단한 방법은 단순히 가우시안 함수의

분포값을 계산하여 그걸 좌표로 사용하는 것이다.

이게 가장 일반적인 해결방법인데, 이항계수를 사용하여 가중치를

얻을 수 있는 간단한 방법이 있다.

왜 해야 할까?

가우시안 함수는 실제로 정규분포의 분포함수이며,

정규분포의 이산 수치는 샘플의 가중치를 얻기위해 이항계수를

사용하는 이항분포이다.


커널 가중치를 계산하기 위해 사용될수 있는 이항계수를 보여주는 파스칼 삼각형

(각 후속행의 합은 상위 행의 합이다.)


수평과 수직으로 9-tap 가우시안 필터를 구현하기 위해서는 파스칼 삼각형 그림에서

마지막 행의 가중치 값을 사용해야 한다.

아마 8번째 행이 9개의 값을 가지는데 왜 그걸 사용하지 않느냐고 물을지도 모른다.

이는 정당한 질문이지만 대답하기는 쉽다.

전형적인 32비트 컬러 버퍼에서 가장 바깥쪽 계수는 마지막 이미지에 

아무 영향을 주지 않고 두번째 바깥쪽 계수도 거의 영향이 없거나 아예 없기 때문이다.

우리의 9-tap필터는 가능한한 높은 퀄리티의 블러여야만 하지만 texture fetch는

가능한한 최소화 해야 한다.

확실히, 매우 높은 정밀도의 결과는 반드시 더 높은 컬러버퍼가 가능해야 하므로

부동 소수점(floating point) 하나가 8번째 인덱스 행을 사용하는 것이 더 좋다.

하지만 원래의 아이디어에 충실하게 마지막 행을 사용하자...


필요한 계수를 얻음으로써, 픽셀의 선형보간을 위해 사용되어지는 가중치값을

계산하기가 아주 쉬워졌다.

이 경우에는 계수들의 합인 4096으로 계수를 나누어 주기만 하면 된다.

물론 네개의 바깥쪽 계수를 제거해서 4070으로 값을 감소시킬수 있는데

이럴경우 블러를 여러번 하면 이미지가 어두워 질 것이다.


이제, fragment shader에서 가중치 값을 얻는것은 매우 쉬워졌다.

어떻게 GLSL에서 세로(수직)를 계산하는 shader 파일을 보자.


01uniform sampler2D image;
02 
03out vec4 FragmentColor;
04 
05uniform float offset[5] = float[]( 0.0, 1.0, 2.0, 3.0, 4.0 );
06uniform float weight[5] = float[]( 0.2270270270, 0.1945945946, 0.1216216216,
07                                   0.0540540541, 0.0162162162 );
08 
09void main(void)
10{
11    FragmentColor = texture2D( image, vec2(gl_FragCoord)/1024.0 ) * weight[0];
12    for (int i=1; i<5; i++) {
13        FragmentColor +=
14            texture2D( image, ( vec2(gl_FragCoord)+vec2(0.0, offset[i]) )/1024.0 )
15                * weight[i];
16        FragmentColor +=
17            texture2D( image, ( vec2(gl_FragCoord)-vec2(0.0, offset[i]) )/1024.0 )
18                * weight[i];
19    }
20}


확실히 가로 필터도 다르지 않는데 fragment shader에서 단순히 x에 

오프셋값을 설정하지 않고 y에 오프셋값을 설정해 주면 된다.

여기서는 1024 크기의 이미지일 경우를 고려해서 이미지 공간 좌표를

나누는 코드를 하드코딩했다.

실제로 구현할 때는 uniform 값으로 대체하거나 단순하게

정규화되어지는 텍스쳐 좌표를 사용하지 않는 텍스쳐의 사각형을 사용한다.


만약 더 강한 블러 효과를 얻기 위해 필터를 몇번 더 적용해야 한다면,

이전 스텝의 결과물을 셰이더에 적용시키면서 두 프레임 버퍼(렌더타겟)을

핑퐁해주기만 하면 된다.


1024 x 1024 이미지에 9-tap 가우시안 블러 필터가 적용된 모습.

필터 미적용(왼쪽),  한번적용(중간), 9번 적용(오른쪽).

클릭해서 전체이미로 보면 다른부분이 더 명확하게 보인다.


선형 샘플링

지금까지, 우리는 9-tap 가우시안 블러를 얻기위해 두 렌더링 패스를 사용하여

분리된 가우시안 필터를 구현하는 방법을 보았다.

또한 5600만 texture fetche들을 사용하여 33-tap 가우시안 블러를 얻기 위해

이 필터를 1024x1024 크기의 이미지로 세번 실행하였다.

이는 꽤나 효율적이긴 하지만 CPU에서 거의 수정되지 않고 잘 동작하는 

알고리즘 형식처럼 GPU의 가능성을 나타내지는 않는다.


이제, GPU의 하드웨어에서 가능한 고정함수의 이점으로

 texture fetch 횟수를 더 줄일수 있는지 알아볼 것이다.

이 최적화를 위해서 이 글을 쓸때 만들었던 가정에 대해서 논의하자.


지금까지, 우리는 하나의 단일 픽셀 정보를 얻기 위해서는 

하나의 texture fetch를 만들어야 한다고 가정했는데,

이는 9개의 픽셀정보를 얻기 위해서는 9번의 texture fetch가 필요하다는 것이다.

CPU에서 구현한다면 이는 맞는 말 이지만, GPU에서 구현한다면

이렇게 할 필요가 없다.

GPU는 이중 선형 필터링을 특별한 비용 없이 처리하기 때문이다.

이는 텍스처의 중간텍셀은 fetch하지 않으면, 여러개의 

픽셀들의 정보를 얻을 수 있다는 것을 의미한다.

실제로 가우시안 함수의 분리적 속성을 사용하여 두개의 1D로

작업하는 하기에 이중 우리에게 선형 필터는 두 픽셀의 정보를 제공할 것이다.


적당한 텍스처의 오프셋을 적용함으로써 하나의 texture fetch로

두개의 텍셀이나 픽셀들의 정보를 정확하게 얻을 수 있다.

이는 9-tap 가로/세로 가우시안 필터를 구현하기 위해 단순히

5번의 texture fetch만 하면 된다는 것을 의미한다.

일반적으로, N-tap 필터는 [N/2] 횟수 만큼의 texture fetch를 필요로 한다.


이전에 사용되어진 가우시안 필터의 이산 샘플의 가중치 값은 어떤 의미인가?

두 텍셀에 관한 정보를 얻기 위해 하나의 texture fetch를 사용하는 경우

두 텍셀에 상응하는 가중치의 합으로 색상값을 구해 가중치를 줘야한다는 의미이다.

이제우리는 가중치가 무었인지 알고 있으므로 적절한 텍스처 오프셋을 계산해야 한다.


텍스처 좌표에서, 단순하게 두 텍셀의 중심 사이의 중간값을 사용할수 있다.

이는 좋은 접근방법이이며 우리가 이산샘플링을 사용했었던 때와 정확히

같은 결과이지만 더 나은 좌표를 계산할수 있도록 하지는 않는다.


두 텍셀을 결합하는 경우 텍셀1의 중심으로부터 정해진 좌표의 거리가

두 가중치의 합으로 나누어준 텍셀2의 가중치와 같게 되도록

좌표를 조정해야 한다.

(texel2 center + offset) == texel2 weight / (texel1 weight + texel2 weight)


결과로, 아래와 같이 선형으로 샘플된 가우시안 블러 필터의 가중치와 오프셋을

정하는 공식을 얻을 수 있다.


이 공식을 이용하려면 아래의 셰이더에서 단지 uniform 상수를 교체하고 

수직필터 반복 횟수를 줄이기만 하면 된다.


01uniform sampler2D image;
02 
03out vec4 FragmentColor;
04 
05uniform float offset[3] = float[]( 0.0, 1.3846153846, 3.2307692308 );
06uniform float weight[3] = float[]( 0.2270270270, 0.3162162162, 0.0702702703 );
07 
08void main(void)
09{
10    FragmentColor = texture2D( image, vec2(gl_FragCoord)/1024.0 ) * weight[0];
11    for (int i=1; i<3; i++) {
12        FragmentColor +=
13            texture2D( image, ( vec2(gl_FragCoord)+vec2(0.0, offset[i]) )/1024.0 )
14                * weight[i];
15        FragmentColor +=
16            texture2D( image, ( vec2(gl_FragCoord)-vec2(0.0, offset[i]) )/1024.0 )
17                * weight[i];
18    }
19}


이 알고리즘의 단순화는 수학적으로 올바르고 만약 우리가 

이중 선형 필터의 하드웨어 구현으로부터의 결과가 발생 가능한 

반올림 에러를 고려하지 않는다면 이산 샘플링 한 경우처럼

선형 샘플링 셰이더와 정확히같은 결과를 얻을 수 있다.

 


9번 9-tap 가우시안 블러가 이산샘플링으로 적용된 왼쪽과 선형샘플링으로 적용된 오른쪽.

전체해상도로보려면 이미지를 클릭하면된다.

심지어 패스를 몇번 더 돌려도시각적으로 두 이미지간 차이가 없다.


선형샘플링의 구현은 매우 간단하지만 가우스 블러 필터 성능에 상당히

가시적 효과가 있다.

9-tap 필터를 구현하기 위해 9개의 texture fetch 대신에 

단지 5개의 texture fetch를 사용하는 것을 고려하면, 예제로 돌아가서

1024 x 1024 이미지를 33-tap 필터로 블러링 하는데 5600만번 대신

1024 x 1024 x 5 x 3 x2 = 3100만번의 

texture fetch의 이산 샘플링을 요구한다

이는 꽤나 타당한 차이이며, 두 기술간의 차이를측정하기 위해

몇가지 실험을 수행했다.


결과는 명백하다 : 

Radeon HD5770에서 9-tap 가우시안 필터의 이산샘플링과 선형샘플링의 성능비교.

세로축이 초당 프레임률(높을수록 좋다)이며 가로축은

블러의 횟수를 나타낸다.(높을수록 더 흐리다).


위에서 볼 수 있듯이, 선형샘플링으로 구현한 가우시안 필터가

이산 샘플링으로 보다 이미지에 블러 단계를 적용시키는게 

60프로 정도 더 빠르다.

이는 선형필터링을 사용하여 절약되어지는 texture fetch의 수에 얼추 비례한다.


결론

효율적인 가우시안 필터에 대해 알아보았는데 구현이 꽤나 쉽고

특히 선형샘플링을 사용하면 결과가 실시간 알고리즘으로 매우 빨라서

고급 렌더링 기술의 기반으로 사용될수 있다.


이 문서에서는 가우시안 블러에만 집중했음에도 불구하고,

대부분의 convolution 필터 타입들에 적용하는 원리에 대해

많이 논의하였다.

또한 일반적으로 블룸효과에서 필요로 하는 사이즈를 줄인 

블러 처리된 이미지가(다운샘플링 관련) 필요한 경우 선형 샘플링을

포함해서 대부분의 이론이 적용된다.

크기를 줄인 블러된 이미의 경우 실제로 다른것은

pixel의 중심이 "두개의 픽셀"이라는 것이다.

이는 우리가 파스칼 삼각형에서 행을 선형샘플하는 중간 텍셀들도 

짝수의 계수를 가지는 행을 사용해야 한다는 것을 의미한다.


또한 우리는 다양한 기술들의 복잡성 계산과 

어떻게 GPU에서 필터가 효율적으로 구현되어질 수 있는가에 대해

간단한 통찰을 가졌다.


데모 프로그램은 이산 샘플링과 선형샘플링 방법에 대한

효율성을 비교하기 위해 만들어졌고 아래 링크에서 다운로드 받을 수 있다.


Binary release

Platform: Windows
Dependency: OpenGL 3.3 capable graphics driver
Download link: gaussian_win32.zip (2.96MB)


Source code

Language: C++
Platform: cross-platform
Dependency: GLEW, SFML, GLM
Download link: gaussian_src.zip (5.37KB)


Reference Link

- convolution filters

- GL_NEAREST vs GL_LINEAR

- binormal coefficient (이항계수)

- binomial distribution (이항분포)

-

'Study > Graphics' 카테고리의 다른 글

Texture types  (0) 2017.02.05
rgbm  (0) 2016.12.29
Efficient Gaussian blur with linear sampling  (0) 2016.10.22
Blur 1  (0) 2016.10.12
UV Texture Coordinates and Texture Mapping - OpenGL / DirectX  (0) 2016.08.14
normal map compression  (0) 2016.06.13
Posted by 붕대마음

댓글을 달아 주세요

이전 글 참고 : 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
Depth Of Field 2 (Advanced DOF)  (0) 2013.04.13
행렬  (0) 2013.03.21
Depth Of Field 1 (Simple DOF)  (1) 2013.03.18
Shader 개발을 위한 툴  (0) 2013.03.12
Posted by 붕대마음

댓글을 달아 주세요

Depth Of Field
- 한국어로 피사계 심도(한국어가 더 어려운...)라고 한다.
   원래 사진술 관련 용어로서 한장의 사진에서 초점이 맞은것으로 인식되는 범위.
   즉, 실제 사진에서 초점면을 중심으로 서서히 흐려지는 현상이 발생하는데
   이때 충분히 초점이 맞은것으로 인식되는 범위의 한계를 피사계 심도라고 한다.


  그렇다면 이 DOF(피사계심도, Depth Of Field)에 영향을 주는 것은 어떤것이 있는가?

  1. 조리개 값

      조리개는 간단히 말해 빛의 양을 조절한다.
      렌즈를 통해 카메라로 빛이 유입되는 통로의 폭을 결정하는데 당연히 그 폭이 넓을 수록 보다 많은
      빛이 유입되고, 빛이 많이 유입될 수록 DOF에 미치는 영향력도 커진다.

               조리개값이 작을 수록 DOF가 얇아진다. 위 사진은 작은 조리개값을 사용하여 DOF를 얕게 만들어
               화면 중간 부분만 또렷하게 나오도록 하였다.

      각 조리개값에 따른 DOF의 변화를 보기 원한다면 Wiki를 참고하면 된다.
      http://ko.wikipedia.org/wiki/%ED%94%BC%EC%82%AC%EA%B3%84_%EC%8B%AC%EB%8F%84

      렌즈의 F값은 렌즈의 조리개의 개방정도를 나타내는데 렌즈의 구경/조래개의 개방정도로 표현된다.
      위의 사진을 보면 조리개 값이 작을 수록 DOF가 얇아져서 화면이 많이 뿌옇게 보이는 걸 알 수 있다.

  2. 초점 거리
      초점거리가 길수록 망원에 가까워지는데 망원이라는 것이 곧 무었인가를 확대 한 다는 개념이다.
      따라서 초점이 맞는 부분과 맞지 않는 부분과의 차이도 확대되게 된다.
      망원렌즈 역시 같은 맥락으로 멀리있는 물체 따위를 크고 정확하게 볼 수 있도록 초점 거리를 비교적 길게 만든 렌즈다.    

  3. 카메라와 피사체의 거리
      카메라가 피사체에 가까울 수록 피사체 전면과 후면의 상대적인 거리가 멀어진다.
      이 상대적인 거리가 멀어질 수록 DOF가 얕아지게 된다.

위 사진을 보면 카메라를 책에 가까이 가져가면 뷰 파인더 내에서 볼 때 책 상단의 글씨와
책 하단의 글씨사이의 거리가 상대적으로 멀어지게 된다. 따라서 심도가 얕은, 즉 아웃포커싱이 많이 일어나게 된다.

OK !!
여기까지 정리 해 보자면 Depth Of Field(피사계 심도, DOF)는 렌즈로 어떤 특정한 물체를 볼 때
그 앞/뒤로 초점이 맞는 범위를 이르는 말이다.



그렇다면 초점에서 떨어져 있는 물체는?
이 물체들에게는 초점과의 거리에 따라서 물체가 특정한 무늬 모양으로 흐려지는 현상이 나타난다.
이를 착란원(Circle Of Confusion)이라고 한다.
COC의 크기는 물체의 윤곽선의 해상도에도 영향을 주는데 이 때 COC가 너무 커서 물체의 윤곽을
뚜렷하게 인식할 수 없어지는 범위까지가 DOF이다.

위의 그림을 보면 두번째 그림이 초점이 맞게 나온 것이다.
첫번째 그림은 찍고자 하는 물체보다 초점이 앞에 있어 COC가 생긴다.
세번째 또한 내가 찍고자 하는 물체보다 뒤에 초점이 있어 COC가 생겨 흐릿한 이미지가 나온다.

COC(Circle Of Confusion에 대한 추가설명....


"렌즈에 의해 결상이 되어지는 물체의 한점은 상면에서 점으로 맺혀지지만,
상면의 전 후로 멀어질수록 점이 커져서 원으로 보이게 됩니다.
이것을 착란원이라고 하고 그 크기를 원의 지름으로 표시하는 것입니다.
이 착락원의 크기는 피사계 심도, 초점심도, 과초점거리를 계산하는 기준으로 사용하는 것입니다.
허용 착란원 permissible circle of confusion 또는 허용 분원 circle of confusion 허용 흐림 circle of confusion 이라고도 합니다.
착란원의 지름이 작으면 점으로 인식이 되지만 점이 커지면 점이 원으로 되어 초점이 흐려지게 되는 것입니다.
점으로 인식되는 최대 허용 한계를 허용착란원이라고 하는 것인데, 허용 착란원의 지름은 화면의 대각선이
10inch가 되는 6x8inch 사진을 10inch(약 25.4cm)거리에서 볼 때에 1/100inch로 규정되는 것이 일반적이기는 합니다.
조도나 개인 시력, 콘트라스트, 형태의 지각 능력 등의 요소에서 영향을 받는 편이지만,
이를 무시하고 피사계 심도, 초점심도, 초점거리 등을 계산 할 때에 사용되고 있습니다.
35mm 네거티브의 경우에 네거티브의 크기가 6x8inch 에 비해 길이의 비로 1/6 정도이므로1/100 x1/6 = 1/600inch(약0.04mm)
정도의 길이가 네거티브 상의 허용 착란원의 지름이 되는 것입니다.
일반적으로 화면 대각선의 길이의 1/1000~1/1500 정도로 계산을 하는 편이는 합니다.
사진에서 초점이 맞지 않았을 때에 착란원이 커지게 되면 점이 원으로 나타나게 되는 것입니다."
link : http://k.daum.net/qna/view.html?qid=3kJ12&aid=3kPxv   

핀홀 카메라에 대한 깔끔 설명 http://carstart.tistory.com/179


DOF를 구현해 보자!!


간단하게 말하자면 초점이 맞는 곳은 깔끔하게 나오고 초점이 맞지 않는 곳은 뭉개져서 나온다는 개념이다.
이를 구현하기 위해서 어떻게 해야 할까?
화면의 특정 부위가 뭉개져야 하는지 깔끔하게 나와야 하는지의 기준이 초점이고, 그 초점을 구분 할 수
있는 것은 카메라로부터의 거리값, 즉 깊이값이다.
1. 일반화면 하나를 준비한다.
2. 뭉갠화면 하나를 준비한다.
3. 초점을 기준으로 일반화면과 뭉갠화면을 합성한다.

pass0 - 원본 화면

vs shader
크게 특별한 건 없다.
Out.Position = mul(Input.Position, view_proj_matrix);    position을 투영공간으로 옮겨준다.
Out.TexCoord = Input.TexCoord;

Out.Normal = mul(Input.Normal, view_matrix); normal은 view공간으로 옮긴다.

거리 값을 view공간에서의 position값을 곱한다. ps에서 하는것 보다는 vs에서 하는게 퍼포먼스적으로 낫다.
Out.ViewVec = -distanceScale * mul(Input.Position, view_matrix);

ps shader
float3 base = tex2D(Base, Input.TexCoord);    텍스쳐 픽셀값
float diffuse = dot(lightDir, Input.Normal);         렘버트 diffuse
float specular = pow(saturate(dot(reflect(-normalize(Input.ViewVec),Input.Normal),lightDir)), 16); specular값

float3 light = Kd * diffuse * base + Ks * specular;   최종 light 값

float dist = length(Input.ViewVec);   viewvec의 길이를 구해 거리값으로 사용한다.
return float4(light, dist);  

위의 사진은 그냥 결과화면을 보기 위해 뿌린거고 원래는 렌더타겟에 뿌려줘야 한다.


pass 1 blur

두번째 패스는 블러(뭉개기)다.
이 패스에서는 DOF 이외의 영역에서 흐려지는 부분을 만들어 주기 위해 블러를 사용했다.

vs shader
Pos.xy = sign(Pos.xy);  -1~1사이의 값으로 만들어서 부정확한 부분의 값을 없앤다.


Out.Pos = float4(Pos.xy, 0, 1);    어차피 이 pos값은 텍스처 uv로 쓸거임..

-1~1 사이의 값을 uv에서 허용하는 0~1사이의 값으로 변환시켜준다.
Out.texCoord.x = 0.5 * (1 + Pos.x);
Out.texCoord.y = 0.5 * (1 - Pos.y);

ps shader
이곳에서 하는 일은 단순하게 이전 패스에서 넘겨준 텍스쳐를 위치를 조금씩 바꿔서 여러번 얻어와서
뭉개주는 것 밖에 없다.

float4 sum = tex2D(RT, texCoord);
   for (int i = 0; i < 12; i++)
   {
      sum += tex2D(RT, texCoord + sampleDist0 * samples[i]);
   }
   return sum / 13;

pass 2 blur
- 위 패스의 blur와 완전히 같다.

pass 3 combine

드디어 내가 원했던 결과화면이 나왔다.
DOF 영역은 깔끔하고 그 외 부분은 블러가 먹는 화면....
난 안경을 빼면 눈이 무척 나빠서 늘 DOF 외곽지역....ㅡㅡ;;;;..

vs shader
할게없다. 그냥 ps에게 위 blur의 vs 처럼 uv 좌표만 넘겨줄뿐...

ps shader
원본이미지로 blur한 rendertarget texture와 원본이미지를 적당히 섞어줘야 한다.
sampler Blur1;
sampler RT;

float4 sharp = tex2D(RT, TexCoord);
float4 blur = tex2D(Blur1, TexCoord);

두 이미지를 섞어주는 데 첫 패스에서 alpha에 저장한 깊이값을 사용한다.
return lerp(sharp, blur, saturate(range* abs(focus-sharp.a)));  


blur패스를 한번만 한 결과는 아래와 같다.

뭉개진 부드러움정도를 자세히 보면 확실히 차이가 난다.
하지만.. 알겠지만 blur 자체가 꽤나 많은 부하를 먹는다는걸 잊지말자.

부분 요소별로 살짝 살펴보자.

이 값은 원본 이미지에서 구한 깊이값만을 찍은 것이다.
return float4(sharp.a, sharp.a, sharp.a, 1.0);

이 값은 깊이값에 focus값을 적용한 결과값이다.
return float4(abs(focus-sharp.a), abs(focus-sharp.a), abs(focus-sharp.a), 1.0);
focus는 어차피 상수값이고 sharp.a는 거리값이다.
이 두 값은 0~1사이의 값이므로 focus를 적당히 중간값으로 설정해 주면 된다.

이 값은 range까지 적용한 값이다.
float resultVal = saturate(range* abs(focus-sharp.a)); 
return float4(resultVal, resultVal, resultVal, 1.0);
range값을 적당히 줘서 값을 부풀렸다.

range, focus, rendertarget.depth의 값을 적용하여 lerp의 rate를 지정한다.

DOF.rfx


여기까지가 DOF의 기본 개념에 대한 설명이다.
그런데!!! DOF도 계속해서 발전해서 같은개념의 다른 방식을 사용하여 퀄리티를 끌어올렸다.
그래서 지금까지 설명한 것을 Simple Depth Of Fiel라고 하자.


GDC 2004에서 소개된 Advance DOF가 있다.

Scheuermann_DepthOfField.pdf


컴퓨터 그래픽스 에서는 핀홀(바늘 구멍 카메라) 카메라 모델을 사용한다는걸 우선적으로 알아줘야 한다.


핀홀카메라를 사용하면 깊은 심도 때문에 또렷한 이미지를 얻을 수 있다.
하지만 실제 카메라는 렌즈를 사용하고, 그렇기 때문에 한정된 수치를 가진다.
(바로 이것이 Depth Of Field의 발생원인.)

핀홀 카메라에 대한 링크
http://dica.dcinside.com/study_listN.php?id=3030&code1=20&code2=10&s_mode=&s_que=&page=3&



References

wiki - Depth Of Field
http://ko.wikipedia.org/wiki/%ED%94%BC%EC%82%AC%EA%B3%84_%EC%8B%AC%EB%8F%84

심도란 무었인가?
http://blog.daum.net/usstory/5264427

심도와 조리개의 관계, 착란원
http://blog.naver.com/PostView.nhn?blogId=isue79&logNo=80171033118

심도와 착란원
http://blog.naver.com/hohwang1/10033991880






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

Depth Of Field 2 (Advanced DOF)  (0) 2013.04.13
행렬  (0) 2013.03.21
Depth Of Field 1 (Simple DOF)  (1) 2013.03.18
Shader 개발을 위한 툴  (0) 2013.03.12
5. 왜 물은 물처럼 보이는 걸까?  (0) 2013.02.20
4. 왜 물은 물처럼 보이는 걸까?  (2) 2013.01.28
Posted by 붕대마음

댓글을 달아 주세요

  1. 2013.09.16 17:06 신고 카이제곱  댓글주소  수정/삭제  댓글쓰기

    잘보고 갑니다~

최근에 달린 댓글

최근에 받은 트랙백

글 보관함