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

카테고리

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

Physically Based Rendering/Shading은 무었인가?

PBR은 단순하게 말해 물리 현상을 기반으로 그리는 기법이다.

물리현상을 기반으로 한다는 것은 어떤 말일까?

물체가 우리눈에 비춰지기 위해서는 첫번쨰로 빛(Light)이 필요하고 이 물체가 빛을 받을 때

어떻게 보이는지는 물체의 재질에 의해 결정된다.

결국 PBR이란 빛과 재질을 기존 보다 좀 더 물리적으로 현상에 맞게 구현한 것이라고 보면 된다.


기존 방식의 렌더링은 어떤식이었는가?

적당한 물리법칙에 적당한 느낌을 내기 위한 적당한 보정 (적당한 수식).

기본적으로 3D 물체처럼 보이기 위해 필요한 것은 음영이며 이 음영을 결정짖기 위해 필요한 것이

빛(라이팅) 정보인데 빛을 적당히 시뮬레이션 하기 위해서 분류를 하자면 

간접광(ambient light), 난반사(diffuse reflection), 정반사(specular reflection)으로 분류할 수 있다.


Diffuse Model (난반사광) : 반사하지 않은 빛이 물질내로 투과되었다가 방출되는 것(굴절)

=> 분자구조에 의해 특정 파장이 흡수된다.

     색이 붙는다

=> 반사(방사)에 지향성이 (기본적으로) 없다.

     분자배열등으로 부터의 기하학적 영향은 있다.

     엄밀히는 에너지 보존법칙등의 영향을 받아 지향성을 가진다.

- Lambert : 물체의 표면이 모든 각도에서 볼 때 같은 복사량(Radiance)를 가진다는 전제.

  빛의 입사벡터 L과 표면의 법선벡터 N이 이루는 각도에 비례한다. 코사인 함수 사용.

  Output.mDiffuse = dot(-lightDir, worldNormal);


- Half Lambert : Lambert의 계산으로는 90도가 넘어가는 cos값은 음수이므로 너무 어둡기에 

  적당한 수치로 보정한다.

  Output.mDiffuse = pow(dot(-lightDir, worldNormal) * 0.5 + 0.5, 4);


- wrapped diffuse : 부드러운 반사를 표현하기 위해 빛이 비치는 반대 부분을 감싸준다.

  loat magicNmuber = 0.5; 

  Output.mDiffuse = (dot(-lightDir, worldNormal) + magicNmuber) / (magicNmuber+1);


Oren Nayar : 표면의 거친 정도를 고려한 난반사광, 모든 표면은 거칠다.

float inten = rho_pi * cosTheta_i * (A + B * max(0, cosAzimuth) * max(sinTheta_r, sinTheta_i) * min(tanTheta_i, tanTheta_r)); 



- energy conserving wrapped diffuse

  float magicV = 0.5;  

  float NdotL = dot(-lightDir,worldNormal);

  Output.mDiffuse = saturate( (NdotL+magicV ) / ((magicV +1)*(magicV +1))  ); 




Specular Model (정반사광) : 물질의 표면에서 반사되는 빛(반사)

=> 반사 색은 광원의 색(정확하게 따지면 아니지만 적당히)

=> 금속 등에서 Specular 반사광에 색이 붙는것이 있다.(선택반사)

     실제로는 Specular에서 단순히 특정 파장의 빛이 흡수되지는 않는다.

=> 미세 레벨의 분자형상(배열)을 반영해서 반사된 빛이 확산한다(광택, glossy)

     반사의 지향성

     빛이 특정 방향으로 집중해서 반사한다.


- Phong : 하이라이트(강한 빛이 반사된 부분)을 표현하기 위한 정반사광

  Lambert에 반사광만 추가한 것. 

  R : -E + 2(N·E)N


- Blinn Phong : Phong 모델의 반사벡터 R을 적당한 연산으로 해서 하프벡터 H로 대체.


- Cook Torrance : 표면의 거친 정도를 표현하기 위한 정반사광


Ambient Light (환경광,주변광,간접광) : 다른 물체에 반사되어 도달하는 간접광

환경광, 주변광, 간접광 등으로 불리지만 그냥 간접광으로 통일.

계산하기도 애매하고 diffuse나 specular에 비해 영향이 적어 적당히 상수로 많이 사용했었다.

하지만 diffuse가 적은 경우에는 ambient에 의한 영향이 무척 크다.

그래서 이 ambient를 상수가 아니라 그럴듯하게 만들기 위해 몇가지 시도가 있었다.

Irradiance Volume(valve), Hemisphere Lighting(메탈기어4), Spherical Harmonics Lighting(IBL SH, Point Clouds SH)...

이에 대한 부분은 내공이 부족해 다음 기회에 정리하기로 ㄷㄷ.


기존의 반사 모델을 어떻게 바꿀 것인가?

빛의 반사를 표현하기 위해 BRDF라는 개념을 사용한다.

간단하게 말하자면 양방향 반사 분포 함수의 뜻으로 반사율 특성을 묘사하는 것이다.

기존에 사용하던 BRDF모델을 좀 더 물리적으로 맞게 바꾸어야 한다.

이를 물리적으로 올바르게 하기 위해 두가지 성질을 갖추도록 해야 하는데 "에너지 보존"과 "상반원리"가 그것이다.

에너지 보존 법칙은 "어떤 한 방향으로 부터 입사한 빛의 에너지는 그 빛이 반사해서 출사한 빛의 총량 보다 크거나 같다"를

나타내며 Helmholtz의 상반성(Reciprocity)은 "BRDF 함수는 입사방향과 출사방향을 바꿔서 입력해도 값이 같다."를 나타낸다.

BRDF에서 추가로 고려해야 할 것이 프레넬 이펙트이며 표면에서 반사된 빛의 양이 시야의 각도에 

영향을 받는것을 고려한 것이다.

거기에 BRDF의 미세표면 이론은 물리기반 셰이더에서 중요한 부분을 차지한다.

물체의 재질을 물리적으로 잘 표현하기 위해 Metallic(물체 특성)을 고려한다.

금속특성이 높을 수록 반사율이 높고 굴절된 빛의 흡수률이 높다.

알루미늄이나 은의 경우 금속에 조사된 백색광은 거의 동일한 파장과 진동수를 방출하지만

금과 구리의 경우 흡수된 빛을 동일한 파장으로 방출하지 않고 특정 파장의 빛만 방출하고

나머지는 흡수하면서 금은 노랑색, 구리는 오렌지색의 반사빛을 띈다.

비금속은 굴절된 빛이 확산되거나 흡수되면서 금속보다 더 작은 빛을 반사시키고 반사색(Albedo)를 가진다.

빛을 계산할 때 올바른 연산 결과를 위해 Linear Space 또한 고려해야 한다.

이에 대한 내용은 "감마보정" 글을 참고하면 된다.

사실 PBR이란 정확하게 정해진 수식이 있지는 않다. 각 엔진에서, 또는 각 렌더링 단에서 적용하는 수식이 조금씩 다르다.

하지만 위의 내용들은 PBR을 구현하기 위해 기본적으로 통용되는 개념이니 이를 기준으로 넓혀나가면 될 것 같다.



그래서 PBR을 왜 써야 하는가?

텍스쳐 리소스를 중복으로 사용할 수 있어 재질 표현에 있어 리소스를 줄일 수 있고,

아티스트의 프로그래머 의존도(또는 게임 내 정해진 규칙?)가 적으며 결과물을 적당히 예측할 수 있고

다양한 조명 환경에서도 수정할 부분이 줄어든다.


그러면 NPR(Non Photorealistic Rendering, 비실사)은 어떻게 할까? 

NPR에 PBR을 효율적으로 썻다는 게임은 아직 못봤다. 불가능 하지 않을까.

NPR이란 말 그대로 비실사, 즉 물리적인 법칙에 맞지 않는 방식이니..


PBR의 구현


Reference Link

- wrapped diffuse

- model tool - Lighting

- 기본적인 반사 벡터

- Not All Materials Are The Same (Oren Nayer)

- BRDF

- GGX

- 감마보정(gamma correction)

model tool - Lighting

- 왜 물은 물처럼 보이는 걸까?

- 기본적인 반사 벡터


- 유니티의 라이팅이 안 이쁘다구요? (A to Z of Lighting)

- Game Development Forever, Oren-Nayar Reflectance Lighting Model

- Game Development Forever, Wrapped Diffuse

- 포프 TV, 물리기반 렌더링

- wikipedia, Diffuse reflection

- Diffuse reflection, Ambient light, Specular Reflection

- Oren Narya shading

- 물리 기반 셰이더의 이해

- The Comprehensive PBR Guide by Allegorithmic

- 렌더링 기법 고찰과 PBR 물리기반 렌더링

- Ambient Light, Diffuse Light, Specular Reflection

- Specular BRDF Reference

- SIGGRAPH 2013 Course : Physically Based Shading in Theory and Practice

- 모바일에서의 물리 기반 셰이딩

- Physically Based Rendering 기초

- Physically Based Shading at Disney

- The Technical Art of Uncharted 4

- NDC2015, 물리기반렌더링 지난 1년간의 경험

- 물질의 광학적 특성

- The Comprehensive PBR Guide by Allegorithmic vol-1, vol-2

- BRDF 기반 사전정의 스킨 셰이더

- UDK로 물리 기반 셰이더 만들기

- 구세대 엔진 신데렐라 만들기 PPT, 영상

- SH & PRT (Kasastudy)

- 물리기반셰이더의 허와 실

- Physically Based Real-time Rendering for Artists

- 언리얼 엔진 4 렌더러 활용하기

-

Posted by 붕대마음
links
- 피부는 왜 피부처럼 보이는가? 1 : http://mgun.tistory.com/1545
- 피부는 왜 피부처럼 보이는가? 2 : 
http://mgun.tistory.com/1557 

- 피부는 왜 피부처럼 보이는가? 3 : http://mgun.tistory.com/1558

이전 글에서 RDP에 따른 블러를 실시해야 한다고 말했었다.
이 블러는 어떻게 해야 하는가?
- 3d 표면을 펼친 텍스처 좌표 사용
- 블러를 수행하기 위해 이 형태를 사용
- 텍스처 매핑을 한 이후에 다시 사상
- 이를 텍스처 공간 확산이라고 부른다.

GPU gems 1.
Chapter 16. Real-Time Approximations to Subsurface Scattering
link : 
http://http.developer.nvidia.com/GPUGems/gpugems_ch16.html

이를 실제로 구현해서 테스트 해 본 결과는 아래에 있다.
link : 
http://mgun.tistory.com/1600

흡사하지만 RDP에 따라 블러 해 주는 게 아니라 일정 오프셋값(대략적인 값)으로 
블러를 해 준 결과는 아래와 같다.




적당히 쓸만 한 것 같다.
구현에 대한 세부적인 내용은 링크를 참조하길 바란다.

이렇게 블러한 텍스처를 매핑해 주면 대략적인 피하산란 라이팅은 완성이 된다.
다만 블러에 좀 더 고민 해 볼 필요가 있는데
RDP를 보면 산란하는 방식이 빛의
RGB에 따라 다르기 때문
이다.


위 nvidia의 방식을 보면 가로, 세로 이렇게 두번 블러를 해 주는데,
블러시에는 RGB마다 가중치를 붙여 블러의 반경을 바꾸어 사용한다.

하지만 이렇게 이차원에서 텍스처를 일률적으로 블러 할 경우 왜곡 문제가 발생한다.
- 텍스처 공간에서 획일화된 블러 피하기.
   3d 공간 상에서 내부쪽은 넓고 강하게 블러되어 버린다.
   즉, 귀와같이 안쪽에 있는 것을 코와같이 앞쪽에 있는 것과 같은 지름으로 블러를 하면
   지나치게 강하게 블러가 발생해서 지나친 피하산란이 일어난다.



   
- 텍스처 공간에서의 한 픽셀이 실제월드에서의 거리상수값이 아니다.
     즉, uv좌표계와 실제 3D월드공간과의 어긋남이 왜곡을 만든다.
     이를 보정해 주기 위해서는 어긋난 정도를 사전에 계산해서 텍스처 데이터(보정맵)으로
     가지고 있다가 블러시에 이를(블러 반경 스케일값) 기준으로 해서 블러하면 된다.
 


관련공식등은 아래 참조링크에서 GDC 문서를 참조하면 된다.

- 텍스처 좌표계에서 가우스 블러를 하면 생겨나는 왜곡중 하나가 Seam(연결선)이다.
이는 텍스처의 경계부분에 텍스처 메모리상의 초기화색(검은색)이 스며들어 텍스처의 경계선이
연결선으로 보이는 문제이다.


위와같이 연결선이 생겨버린다.
이 부분의 해결을 위해서 여러가지 방법이 있지만 가장 저렴한 방법은 텍스처의 기본색을
알맞게 초기화 시켜주는 것이다.
위 텍스처의 경우에는 검은색이 아닌 황색으로 하면 충분히 효과적이다.

- 텍스처 좌표계에서 블러를 하면 나타나는 왜곡현상중 마지막은
바로 디테일이 없어진다는 것
이다.
범프매핑을 적용하고 텍스처 평면에 렌더링 해서 이를 블러처리하면 범프매핑의
음용이 약해져 버리게 된다.

그래서 반대로 범프매핑을 뒤로 넣어주면 주름이 너무 강하게 남아 라이팅과의 
매칭이 잘 맞지 않아 부자연 스러워 보인다.

이에 대한 중간적인 방법으로 피하산란 블러의 전후에 감쇠텍스처를 사용하는
방법이 있다.


Reference Links :
- allosha님의 표면하 사란에 의한 스킨 셰이더
- GPG Gems1. Chapter16. RealTime Approximations to Subsurface Scattering.
- GPG Gems3. Chapter14. Advanced Techniques for Realistic Real Time Skin Rendering.
- NVIDIA GDC 2007. Demo Teamp Secrets: Advanced Skin Rendering
-

----------------------------------------------------------------------------------------
ps.

틀린 부분이 있거나 이상한 부분이 있다면 언제든지 문의나 덧글 부탁 드립니다.
 

Posted by 붕대마음
links
- 피부는 왜 피부처럼 보이는가? 1 : http://mgun.tistory.com/1545
- 피부는 왜 피부처럼 보이는가? 2 : 
http://mgun.tistory.com/1557 


복잡해 지는 과정으로 넘어가기 전에 여태까지온 길을 되짚어 보자.

피부를 표현하기 위한 노력. 
1. 단순히 피부색의 텍스처를 붙여 확산반사 라이팅을 한다.
   (값싼 방법이지만 플라스틱처럼 보인다.)


2. Half Lambert Lighting
   (물리적으로는 맞지 않지만 부드러운 음영이 나온다.)

평준화.

pow 연산.
     
3. Half Lambert Lighting + Phong Specular Lighting. 
   (하프라이프2 에피소드1에서 사용. N과 L 이외에 E방향도 고려.
    피부에 한결같이 specular을 적용하면 금속처럼 보이므로 어느 위치에 어느만큼의
    specular을 적용할 지를 정하기 위해 Specular Exponent, Specular Mask 사용)


    4. KS BRDF (지방질층의 반사)
       면의 거친정도, 굴절률등을 고려한 반사공식.

면의 거친정도, 굴절률등을 고려.

5. KS BRDF + 프레넬 + 확산반사 * 화상텍스처. 

약간 건조한 느낌이 난다.
하프라이프2의 스킨셰이더와 거의 흡사하다.


6. 다중 레이어.
피부의 광산란은 단층이 아닌 복수층.
단일 레이어 스킨 모델은 밀랍처럼 보인다.
표피는 좁은 산란, 아래쪽 레이어는 넓은 산란 대부분 붉은 계통.

 
흐음..대략 이러한 수순을 밟는다.

이제 다시 진도를 나가보자.
이 다중 레이어를 만들기 위해서는 어떻게 해야하는가?
- 입사광으로 시작
- 표면의 블러
- 블러 추가
- 선형 조합
 
평평하고 높은 산란 표면의 어느 점에 좁게 집중되어 입사한 빛을 생각 해 보자.

 
음영처리를 하는 것은 해당 폴리곤 상의 픽셀이며, 어느점에 입사 한 빛이,
그 주변에 대해 어떤 확산광이 되어 돌아오는지를 알아야 한다.
아래 그림을 보면 입사광이 내부에서 산란을 거친 후 다시 확산이 일어나는것을 볼 수 있다.
여기에 집중되어 들어오는 빛은 거의 수직으로 들어오는 빛이라고 생각해도 될듯...
일단 여기서는 표면의 미세면 거친정도는 고려하지 않는다.

 

결국 필요한 정보는 광선이 피부를 조사할 때 그 조사점으로부터의 반경 r에서는
산란을 거친 후 어떤 색의 빛이 어느 정도의 밝기가 되는지 이다.

 
우리는 이러한 정보(피부에 비춘 한개의 광선이 피부 밑에서 어떻게 산란해서 나오는지)를
계산할 수 있다.

위 그림은 실제로 계측하는 모습이다.
방향을 고려하지 않고 전방위로 같은 결과를 얻을 수 있다고 가정하기 때문에
계측기는 일차원 모양의 센서로 되어있다.


위 그림은 확산 프로파일(RDP:Reflectance Diffusion Profile)을 그래프화 한 것이다.
세로축이 반사율, 가로축이 빛이 조사한 위치로부터의 거리이다.
즉, 멀리가면 갈수록 빨간색이 강하게 남는것을 알 수 있다.
여기에서는 특히 RGB에서의 반경과 반사율의 관계는 완전히 다르다.

위 이미지는 반사율 확산 프로파일을 이차원 평면상에 가시화 한 것이다.

다시 정리를....



산란에 대한 핵심 아이디어로는
- 모든 표면 점에서 입사광(color 값)을 모으고 확산을 근사화(방향을 무시한 합),
   이웃 표면지점 내에서의 산란.
- 2d 평평한 표면의 각 지점에서
  초기의 라이트를 모아서 이웃 픽셀들 내에서 산란,  
  r로 분리된 것에 의존.

  이는 블러와 같다.


즉, 표피상의 모든 픽셀이 이 ROP에 따라 피하산란을 하고 돌아온다고 가정하면
복잡한 피하산란을 이것으로 간이적으로 구현할 수 있지 않을까?

이 조건으로한다면 피하산란은 모든 표피픽셀에 대해 ROP를 참고해 블러하면된다.

사실 표피면에 수직으로 들어가는 빛이라는 것은 엄밀하게 구할수는 없지만 대부분은
지방질층을 빠져나온 확산광이라고 할 수 있다.

이것은 결국 확산반사의 음영처리를 해서 일반적인 화상텍스처 매핑을 해준 결과라고
가정 할 수 있는 것이다.

이번글은 여기에서 마무리 하고 다음 글에서는 실제로 블러하는 것에 대해 이야기 
하도록 한다.

Reference Links :
allosha님의 표면하 사란에 의한 스킨 셰이더
GPG Gems1. Chapter16. RealTime Approximations to Subsurface Scattering.
GPG Gems3. Chapter14. Advanced Techniques for Realistic Real Time Skin Rendering.
NVIDIA GDC 2007. Demo Teamp Secrets: Advanced Skin Rendering
----------------------------------------------------------------------------------------
ps.

틀린 부분이 있거나 이상한 부분이 있다면 언제든지 문의나 덧글 부탁 드립니다.

Posted by 붕대마음
TAG Skin, 피부

links
- 피부는 왜 피부처럼 보이는가? 1 : http://mgun.tistory.com/1545

이전글의 내용은 기존 렘버트 만으로는 피부 질감을 내기 어려워서
어두운 부분의 영역을 들어 올려 부드럽고 전체적으로 밝아지는
Half Lambert를 만들었고 여기에 원하는 피부의 부분에 원하는 만큼의
광택을 주기 위해 Specular Exponent와 Specular Mask를 추가해서 
하이라이트 반사를 표현 한다는 내용이었다. 

그런데 사실 이러한 내용들은 가짜 피부를 어떻게 하면 그럴 듯 하게 보일까 하는
일종의 꼼수이다.
즉, 피부의 특성을 고려하지 않고 대략적인 값으로 그럴듯 하게 보이도록 한 것이다.

그렇다면 실제 피부는 어떤 특성을 가지는가??


피부란 위와 같은 모습을 하고 있다.
의학 서적에서나 나올 것 같은 그림 인데 여기서 봐야 할 것은 피부가 결코 하나의
층으로 만들어 져 있는게 아니라는 것이다. 

즉, 인간의 피부 음영은, 표피에 닿은 빛의 단순한 반사나 확산만으로 결정되지 않는다.
추가적으로 피부 밑으로 침투해 피부 밑에서 난반사해서 다시 표피로부터
튀어 나오는 것도 고려
해 줘야 한다.

이렇게 피부 밑에서 산란하는 현상피하산란(Subskin Scattering)이라고 하며
반투명 물체에 빛이 들어와 내부에서 산란하고 나서 다시 튀어 나오는 일반적인 현상
표면하 산란(Subsurface Scattering)이라고 한다.

이에 대한 설명은 아래 링크에 좀 더 자세히 나와있다.
http://mgun.tistory.com/1294

표면하 산란을 정식으로 구현하면 피부 음영처리가 무척이나 복잡해 진다.
그래서 위와 같은 개념을 근사하게 하나씩 재현 해 나가면 비슷하게 만들 수 있을 거라는 생각을 
가지고 NVIDIA가 GeForce 8800시리즈 데모로 Adrianne의 스킨 셰이더를 만들었다.


link : http://www.nvidia.co.kr/coolstuff/demos#!/human-head
오~ 그럴듯하게 보이는걸...ㄷㄷ.

피부를 피부답게 보이게 하는 SSS.


피부뿐 아니라 아래와 같은 부분을 표현할 때에도 사용한다.


SSS는 결국 빛이 오브젝트의 표면의 한 지점으로 투과되어 내부로 퍼져나간후
다른 위치로 빠져나가는 현상을 말하는데 이때 표면의 종류와 두께에 따라서
보이는 정도 또한 다르다
. 위 사진의 오른쪾 아래, 얼음 사진을 보면 두께에 따라 
투과되는 정도가 다르다. 사람 몸의 경우 귀처럼 얇은 부분이 특히 그렇다.
크라이 엔진에서는 초목, 피부, 아이스 셰이더에 자주 사용되는 편이다.

언리얼에서의 SSS는 위와 같다.
link : https://udn.unrealengine.com/docs/ue3/KOR/Engine/Subsystems/Rendering/DirectX11/ScreenSpaceSSS/index.html


그림으로 요약해 보자면 아래와 같다.

일반적인 고체의 표면, 즉 불투명한 물체는 빛을 받으면 빛의 대부분을 그대로 반사시킨다.

 

Subsurface Scattering은 빛이 오브젝트의 표면의 한 지점으로
투과되어 내부로 퍼져나간 후 다른 위치로 빠져나가는 현상. 

 

피부는 대략적을 생각하면 아래와 같이 지방질, 표피, 진피라는 층들로 나뉜다.

인간의 피부 단면도

왼쪽 위 부터 얇은 지방질층, 표피, 진피
우측 위 부터 지방질, 표피, 지극히 작은 사이즈.

측정에 의하면 입사한 빛의 6%가 지방질층까지에서 반사가 되고 나머지 94%가 피하의
영향을 받는다고 한다.
대략적으로 피부의  최표층에서의 반사와 피부 밑에서의 산란, 이 두개의 처리로 나누어 보면 된다.

피하 산란의 개념도

표피에서의 반사는 위와 같고 이제 지방질층에서의 반사에 대해 생각 해 보자.

지방질층에서의 반사는 일반적으로 퐁이나 블린퐁을 사용하지만 이는 피부를 표현하는 데 있어
적합하지 않기 때문에 NVIDIA에서는 KS BRDF를 선택했다.


정면샷은 대략 비슷하지만 측면샷의 끝 부분을 보면 하이라이트가
더 리얼하게 맺히는 것을 볼 수 있다.

즉, 음영계산에서 기본 N,V,L 이외에 경면반사 강도와 면의 거친정도, 굴절률을 
고려해 준다는 의미이다.
경면반사강도는 하이라이트의 강도로 이해하면 되고 면의 거친 정도는
굴절률은 프레넬반사라고 생각 해 주면 된다.
면의 거친 정도는 말 그대로 면의 거친정도에 대한 값이다.
아래 링크를 참고하면 프레넬과 면의 거친정도에 대한 설명이 되어있다.
http://mgun.tistory.com/1518
http://mgun.tistory.com/1304

자!! 그럼 이제 얼굴의 어느부분에 빛을 어떻게 표현해야 할까? 를 고민해 봐야 한다.
하지만 친절하게도 이미 측정해서 SIGGRAPH에 발표되어 있다.

위 사진을 보면 안면의 10군데에서의 경면반사 강도와 면의 거친정도를 나타내고 있다.

위 값을 사용하기 위한 분포텍스처

사실 이 값들을 실제로 고려해 주면 좋지만 구현상 많은 부하를 차지하며
식이 복잡해 진다.

이 사진을 보면 오른쪽이 실제로 경면반사 강도 분포 텍스처와 면의 거칠기 분포 텍스처를 사용하여
만든 결과물 이고 왼쪽이 대략적인 평균값 0.3을 사용해서 만든 값이다.
그다지 큰 차이가 없다.
그래서 NVIDIA도 그냥 평균값을 사용했다고 한다.

표피와 지방질층에서의 반사를 알아보았다.
표피에서의 6%반사, 지방질층을 위한 KS BRDF, 그리고 피하에서의 빛의산란.
그렇다면 피하에서의 빛의산란(나머지 96%)는 어떻게 표현해야 하는가?

피하산란 계산은 간단하지 않다.
빛의 이동경로나 빛이 산란되어 나온 빛의 포함여부나 빛의 흡수 및 감쇄를 고려해야 한다.
이러한 내용들을 간략 근사화 해서 접근해 보자.

위에서 언급했던 피하산란 관련 그림을 다시 보자.


우선 알아야 하는 기본 현상을 적어보자면
- 빛이 다른곳에서 나온다.
   (즉, 빛이 입사한 위치에서 반사되는게 아니라 다른위치에서 반사되어 나온다.)
- 빛이 어디로 갔는지에 따라 착색이 된다.
   (즉, 빛이 어디까지 갔는지, 어디를 통과했는지에 따라 색이 변한다.)
- 외견상 불가능해 보이는 작업들이 있다.
   - 무한적으로 가능한 경로들.
   - 어떤 방향에 대한 영향인가?
- 물리학에서 모델의 산란
   - 산란 이벤트와의 거리가 얼마나 먼가?
   - 흡수 이벤트와의 거리가 얼마나 먼가?
- 많은 산란 이벤트가 light diffuse를 만든다.
   - 이러한 일들이 일어나기까지 얼마나 먼가?

좀 축약해 보자면...
빛이 산란해 다시 나올 때까지의 평균 거리는 어느 정도인가? 
빛이 흡수되어 버리는 평균 빈도는 어느정도인가?

입사광의 약 10%는 최초의 지방질층을 돌파하지만 이 시점에서 빛의 지향성을
잃고 확상광
이 되어 버리고, 나머지 빛들은 KS BRDF와 프레넬 반사를 고려한 반사광이었거나
지방질을 지나 표피에 도달한 시점까지에서 흡수된다.
결국, 이 지방질을 빠져나온 10%의 확산광에 대해서만 피하 산란을 생각해 주면 된다.
그렇다면 단순하게 입사광에 대해 확산 반사의 음영 처리를 실시해서
화상의 피부 텍스처와 곱하고, 앞에서의 KS BRDF + 프레넬 반사의 결과와 
합성하면 되지 않을까?
이 생각으로 구현한 결과물이 아래와 같다.


KS BRDF + 프레넬 + 확산반사 X 화상 텍스처.
하프라이프2의 스킨 셰이더와 거의 같다.
약간 건조한 느낌으로 보인다.

- cook (ks brdf) 관련 : http://mgun.tistory.com/1337
- fresnel 관련 : http://mgun.tistory.com/1518



왜이럴까?!!
- 한 지점으로 입사해서 다른 지점으로 나가는 라이트를 고려하지 않았다.
   (확산빛은 그 픽셀에 입사한 빛만 확산한 것이 아니라 인접하는 다른 픽셀에
    입사 한 빛이 피부내에서 산란해서 나온 확산빛도 포함되어진다.)
- 지방질층의 거친 표면은 불균형한 투과률을 가진다.

그래서! 다른방식으로 접근하자.
- 다중 레이어 재질의 몬테 카를로 렌더링.
- 2005 SIGGRAPHA Craig Donner and Henrik Wann Jensen.
   ( 피부의 광산란은 단층이 아닌 복수층)


오른쪽 단층 레이어 결과물은 납인형 같아 보인다.


Reference Links :
allosha님의 표면하 사란에 의한 스킨 셰이더
GPG Gems1. Chapter16. RealTime Approximations to Subsurface Scattering.
GPG Gems3. Chapter14. Advanced Techniques for Realistic Real Time Skin Rendering.
NVIDIA GDC 2007. Demo Teamp Secrets: Advanced Skin Rendering
----------------------------------------------------------------------------------------
ps.

틀린 부분이 있거나 이상한 부분이 있다면 언제든지 문의나 덧글 부탁 드립니다.

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

피부는 왜 피부처럼 보이는가? 4  (0) 2014.01.05
피부는 왜 피부처럼 보이는가? 3  (0) 2013.12.18
피부는 왜 피부처럼 보이는가? 2  (0) 2013.12.13
피부는 왜 피부처럼 보이는가? 1  (0) 2013.12.02
Depth Of Field 2 (Advanced DOF)  (0) 2013.04.13
행렬  (0) 2013.03.21
Posted by 붕대마음
TAG Skin, 피부

물, 메탈, 그림자 이후 피부 시리즈이다.

물 관련 포스트는 아래 포스트를 참조하면 된다.
앞의 글 참조 
1. http://mgun.tistory.com/1282
2. http://mgun.tistory.com/1294
3. http://mgun.tistory.com/1304
4. http://mgun.tistory.com/1306
5. http://mgun.tistory.com/1351
6. http://mgun.tistory.com/1368  (비공개)

꽤 오래동안 쓴 물 관련 시리즈물인데 6번째 글이 비공개인 이유는 아직 쓰고 있기 때문이다.
마지막 글은 계속해서 추가되는 공부와 자료들로 늘 계속해서 이어질 수 있도록 비공개로 둔다.

메탈, 그림자 관련 강좌 시리즈도 아직 비공개인 이유는 아직 정리가 덜 되서..ㅎㅎ

그럼에도 피부시리즈를 시작하는 이유는 누군가의 요청 때문이다.
앞으로 쓰여질 이 글이 도움이 되길 바라며...
-------------------------------------------------------------------------------------

피부...
게임상에서 피부를 실제 피부처럼 만들기란 쉽지 않다.
diffuse 하나 그럴듯하게 그린다고 해서 피부처럼 보이지는 않는다.
피부를 피부처럼 보이게 하기 위해서는 Lighting을 살펴봐야 할 필요가 있다.

우선 가장 접근하기 쉬운 unreal의 예를 들어보자.

unreal에서는 기본적으로 퐁 반영반사를 쓰는데 custom으로 설정해서 하나씩 만들어 보자.


전형적인 N dot L 공식의 Lighting 이다.
날카로운 빛으로 각져 보인다.



Half Light로 Lighting을 수정했다.
훨씬 부드러워 보인다.

공식에 관한 내용은 아래 링크를 참고 하면 된다.
http://mgun.tistory.com/1306

후에 여기에 정반사를 위해 퐁 반사를 적용해 주는데 그냥 을 넣어주면 피부 전체에 적용되어
피부가 마치 금속처럼 보일 수 있다. 




brdf 모델에 따라 느낌이 많이 다르다.

 
피부에 전부 퐁 반사를 사용하면 금속처럼 보이는 문제를 해결하기 위해 어떻게 해야 할까?
원하는 곳에 원하는 만큼의 퐁 반사를 적용하면 되지 않을까?
어느 정도 강도의 하이라이트(광택)을 줄 지에 대한 분포 정보를 담는 텍스처를
Specular Exponent 라고 하고, 어느 곳에 어느 정도 강도의 반사를 줄 것인지의
분포 정보를 담는 텍스처를 Specular Mask라고 한다.

그리고 Specular Exponent Map은 광택을 조정하기에 Gloss Map 이라고도 불린다.

link : http://gamebanana.com/tuts/11134


 link : http://udn.epicgames.com/Three/MobileMaterialReferenceKR.html

여기까지가 unreal에서의 기본 반영반사를 설명한 글이다.
다음 글 부터는 피부 셰이더에 대한 기본 개념을 설명하고
어떻게 해야 사실같은 피부셰이더를 만들 수 있는지를 설명 한 후
게임에서 사용할 수 있는 정도의 피부 셰이더를 제시 할 예정이다. 

----------------------------------------------------------------------------------------
ps.

틀린 부분이 있거나 이상한 부분이 있다면 언제든지 문의나 덧글 부탁 드립니다.
 

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

피부는 왜 피부처럼 보이는가? 3  (0) 2013.12.18
피부는 왜 피부처럼 보이는가? 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
Posted by 붕대마음
TAG Skin, 피부
이전 글 참고 : 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 붕대마음

행렬

Study/M's Lecture / 2013.03.21 11:57
  
행렬이란 무었인가?
벡터를 움직이는 것.

행렬을 사용하면 벡터의 방향이나 크기를 바꿀 수 있다.

EX) x축 방향을 향하고 있는 벡터가 아래의 행렬을 통해 방향을 바꾸는 모습을 볼 수 있다.
[0, -1, 0]        [1]        [0*1 + -1*0 + 0*0]    [0]
[1,  0, 0]   *    [0]   =    [1*1 +  0*0 + 0*0] =  [1]
[0,  0, 1]         [0]        [0*1 +  0*0 + 1*0]    [0]
x축을 향하던 벡터가 행렬과 연산된 후 y축을 향하는 것을 볼 수 있다.

행렬의 내용은 대략 위의 사진과 같고 행과 열을 같을 경우 정방행렬이라고 한다.
정방행렬로 벡터를 변환하면 벡터의 차원이 변함없기 때문에 컴퓨터 그래픽에서 정방행렬을 많이 사용한다.

행렬을 사용하여 벡터를 움직이는 것은 행렬의 각 열을 벡터로 간주했을 경우 벡터의 내적처럼 계산된다.

위에서 언급했듯이 행렬은 벡터의 방향이나 크기를 바꿀 수 있는데 벡터를 움직이지 않는 행렬도 있다.
이 행렬을 단위행렬 이라고 하며 대각선상의 요소가 모두 1이고 나머지 요소는 0인 행렬을 말한다.
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
3x3 단위 행렬은 위와 같다.

[1, 0, 0, 0]
[0, 1, 0, 0]
[0, 0, 1, 0]
[0, 0, 0, 1]
4x4 단위 행렬은 위와 같다.




행렬을 나타내는 두가지 방법.

1. 열로 나열하는 3개의 좌표계축을 가지고(오른쪽, 위, 전방) 마지막 열은 각 축의 평행이동 구성요소를 가지는 것처럼 출력.
Right_x   Up_x    Forwaed_x    T_x
Right_y   Up_y    Forwaed_y    T_y
Right_z   Up_z    Forwaed_z    T_z
0            0          0                  1
이 포맷을 사용하면 벡터를 열처럼 취급할 수 있다.
이를 열 우선 포맷이라고 한다.
OpenGL에서도 이 방식을 사용한다.


2. 행으로 나열하는 3개의 좌표계축을 가진다.
Right_x         Right_y        Right_z       0
Up_x            Up_y           Up_z           0
Forwaed_x   Forwaed_y   Forwaed_z  0
T_x             T_y             T_z             1
이 포맷에서는 벡터를 행으로 취급한다.
그래서 행 우선 포맷이라고 한다.
dx에서도 이 방식을 사용한다.

world matrix는 object의 local 좌표계의 방향과 world 좌표계의 원점으로 부터의 위치를 담고 있다.


view matrix는 camera의 local 좌표계를 invert한 vector들로 기술되고
4번째 행에 있는 vector는 camera 위치 base로 한 inverse translation으로 되어 있다.
이 translation은 world의 원점을 공유하며 camera의 위치를 반영하는 위치로 vertex들을 이동시킬 것이다.

matrix의 첫번째 column에 있는 "Right Vector"는 camera local좌표계 상의 x축 방향을 기술한다.
두번째 column에 저장된 "Up Vector"는 Y축을, 세번쨰 column의 "Look Vector"는 Z축 방향을 각각 기술한다.

dx 행렬이나 opengl 행렬이나 결론은 하나다.
행렬은 벡터를 나열해서 만든 것이라는 것.


행렬 연산.

1.
덧셈


2.
뺄셈


3.
곱셈

왼쪽 행렬의 행과 오른쪽 열의 곱셈은 사실 내적과 같다.
 = a.i1*b.1i + a.i2*b.2i + a.i3*b.3i; 이 값은 사실 dot(a,b)와 같다.

행렬의 곱셈에는 사실 숨겨진 의미가 있는데 그것은 "벡터를 연속해서 움직인다"라는 것이다.
EX)
x축을  향하는 벡터 1,0,0을 y축을 향하는 벡터 0,1,0으로 움직이는 행렬 Rz
[0, -1, 0]   [1]    [0*1 + -1*0 + 0*0]  [0]
[1,  0, 0] * [0] = [1*1 +   0*0 + 0*0] = [1]
[0,  0, 1]    [0]   [0*1 +   0*0 + 1*0]   [0]

y축을 향하는 벡터 0,1,0을 z축을 향하는 벡터 0,0,1으로 움직이는 행렬   Rx
[1, 0, 0]     [0]   [0*0 + 0*1 +   0*0]   [0]
[0, 0, -1] * [1] = [0*0 + 0*1 + -1*0] = [0]
[0, 1, 0]     [0]   [0*0 + 1*1 +   0*0]   [1]

x축을 향하는 벡터를 y축으로 향하게 하는 행렬과 y축을 향하는 벡터를 z축으로 향하게 하는 행렬을
곱셈연산으로 하나로 만들면 이 행렬은 x축을 향하는 벡터를 z축으로 향하게 하는 행렬이 된다.
Rx * Rz = Rxz
[1, 0, 0]     [0, -1, 0]      [0, -1, 0]
[0, 0, -1] * [1,   0, 0]  =  [0, 0, -1]
[0, 1, 0]     [0,   0, 1]      [1, 0,  0]

이렇게 두번의 행렬을 곱할필요 없이 행렬끼리 미리 곱해서 벡터에 적용하면 한번에
원하는 결과를 얻을 수 있다.
Rx * Rz * Vtox = Rxz * V = Vtoz
[0, -1, 0]   [1]    [0*1 + -1*0 +  0*0]    [0]
[0, 0, -1] * [0] = [0*1 +  0*0 + -1*0] =  [0]
[1, 0,  0]    [0]   [1*1 +   0*0 +  0*0]    [1]

4. 스칼라 곱셈

 

스칼라 곱셈은 단순히 벡터의 길이를 조절할 수 있다.

회전행렬
회전행렬이란 벡터의 크기를 바꾸지 않고 방향만 바꾸는 행렬을 뜻한다.
위의 예에서 x축을 향하는 벡터를 y축이나 z축을 향하는 벡터로 바꾼것이 회전행렬이다.


x축을 향하는 벡터를 y축을 향하는 벡터로 만든 행렬을 생각해 보자.
[0, -1, 0]
[1, 0, 0]
[0, 0, 1]
이는 벡터를 z축을 중심으로 90도 만큼 돌린 회전행렬이며 위 사진의 Rz에 해당한다.
cos90도는 0, sin90도는 1을 뜻하므로 Rz에 값을 대입하면 위의 행렬이 나온다.


간단하게 cos값과 sin값을 정리해 보자.
cos0 = 1
cos60 = 0.5
cos90 = 0

sin값은 90-cos각도다.
cos0 = sin90 = 1
cos60 = sin30 = 0.5
cos90 = sin0 = 0

자세한 각도는 위의 그림을 보면 되지만 사실 0도와 90도 정도만 알아줘도 상관없다.

http://terms.naver.com/imageDetail.nhn?cid=200000000&docId=1107832&imageUrl=http%3A%2F%2Fdicimg.naver.com%2F100%2F800%2F1%2F8101.jpg&categoryId=200000451
위의 곡선을 보면 sin과 cos은 1~-1사이를 오가는데 sin은 90도일 때 1의 값을, cos은 0도일 때 1의 값을 같는 것을 알 수 있다.

그렇다면 z축으로 90도 회전해서 x축을 향하는 벡터를 y축을 향하는 벡터로 만들었으니
다시 y축을 향하는 벡터를 x축을 향하는 벡터로 만들려면 어떻게 해야 하는가?
z축으로 90도 회전한걸 다시 z축으로 -90도 회전하면 되는데 벡터는 행렬에 의해 움직일 수 있다고 했으니
다시 되돌리는 것도 행렬을 통해 되돌리 수 있다.
처음 회전한 것을 다시 되돌리는 행렬을 처음 회전한 행렬의 역행렬이라고 한다.

z축 중심회전 행렬인 Rz의 역행렬이 아래와 같을 때
| cosB  -sinB   0 |
| sinB   cosB   0 |
|      0        0    1 |

이 행렬의 역행렬은 각도값만 - 해주면 되는데
| cos(-B)  -sin(-B)  0 |
| sin(-B)   cos(-B)  0 |
|         0             0   1 |
그런데 위의 cos 곡선을 보면 0을 기준으로 좌우가 대칭이다.
즉, cosB와 cos(-B)와 값이 같다는 것을 알 수 있다.
sin곡선은 어떠한가? sin곡선은 0을 기준으로 위아래가 뒤집힌 모양이다.
즉, sinB값이 1이라면 sin(-B)는 -1이 되는 형태가 되며 결론적으로 sinB = - sin(B)가 되는 형태를 띈다.
그래서 이 특성을 이용하여 위의 역행렬을 고쳐주면 아래와 같다.
|  cos(B) sin(B) 0 |
| -sin(B) cos(B) 0 |
|         0         0  1 |

기존행렬 Rz와 이 행렬의 역행렬인 Rz-1과 다른점은 sin값의 부호 뿐이다.
이는 단순히 기존행렬에서 행과 열을 바꿔주는작업을 하는 것과 같다.
이렇게 행렬의 행과 열을 바꿔준 행렬을 전치행렬 이라고 한다.


그렇다면 역행렬의 다른말은 전치행렬이란 말인가?
그렇다면 참 좋겠지만 늘 그렇지는 않다.
전치행렬이 역행렬이 되는 것은 "회전행렬 특유"의 성질이다.

회전행렬의 역행렬은 그 행렬이 회전한 값의 반대값으로 다시 회전하면 된다고 하였다.
그렇다면 행렬의 확대 축소는 어떠한가?
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
벡터를 움직이지 않는 위와 같은 단위행렬에 특정한 수를 스칼라 곱으로 곱해준다고 생각해 보면
[2, 0, 0]
[0, 2, 0]
[0, 0, 2]
위와 같은데 이제 이 행렬은 더이상 단위행렬이 아니라 벡터의 길이를 두배로 늘려주는 확대행렬이 된다.
이 행렬의 역행렬은 회전행렬의 역행렬과 같이 개념은 "변화 준 것을 되돌린다"로 같지만 전치행렬로 표현할 순 없다.
전치행렬이 역행렬이 되는 것은 회전행렬의 특유의 성질이기 때문이다.
확대행렬의 역행렬은 축소행렬, 즉 확대한 만큼 축소하면 된다.
[1/2, 0, 0]
[0, 1/2, 0]
[0, 0, 1/2]
즉 1/확대률 해주는 것이 확대행렬의 역행렬 이다.

다음으로 살펴봐야 할 것은 평행 이동 행렬이다.
발상은 3차원 벡터를 확장해서 (x,y,z,1)처럼 생각하자~!! 이다.
위와같이 3차원을 4차원으로 확장해서 사용하는 이유는 이동,크기,회전을 모두
담을 수 있는 행렬이 4x4이기 때문이다.
그런데 행렬은 사실 벡터로 이루어 져 있으므로 벡터가 4차원으로 확장되야 한다.

          동차항
[1, 0, 0, 0] - x축
[0, 1, 0, 0] - y축
[0, 0, 1, 0] - z축
[0, 0, 0, 1] - 이동관련

4x4 행렬에서 보면 x,y,z, 그리고 이동에 관한 축이 있다.
동차항을 자세히 보면 각 방향을 나타내는 축들은 0의 동차항을 가지고
이동에 관한 값은 1의 동차항을 가진다.
즉, 0의 동차항을 가진다는 말은 방향을 의미하는 벡터, 1의 동차항을 가지는 벡터는
위치를 의미하는 벡터이다.
동차좌표는 투영행렬과의 연산에서 특별한 의미를 가지는데 이에관한 설명은 아래의
글들을 통해 쉽게 이해할 수 있다.
참고 : http://blog.naver.com/nt0083/20078644391
         http://scosco.com.ne.kr/ES20Html/basic_homogeneous.htm
         http://carstart.tistory.com/180
         http://geometricmind.wordpress.com/author/geometricmind/page/9/

행우선과 열우선.
http://www.gpgstudy.com/forum/viewtopic.php?t=995&view=next&sid=2e0cb9576fd820c9d3b9e9686c6aa6f8
manilee 말씀대로 직접 증명해보는 게 도움이 될 것이구요. 
용어 관련해서요.. 행우선, 열우선은 사실 행렬의 성분들을 색인으로 참조할 때
(수식 안에서 또는 배열 저장에서) 외에는 수학적으로 별 의미가 없습니다. 
행렬 자체는 그냥 격자 형태로 존재할 뿐이구요. 
또한 행렬이라는 말에서 알 수 있듯이 기본적으로는 행을 먼저 치는 것이 일반적입니다. 
m x n 은 m 행, n 열의 행렬이다.. 등등. 
차이가 나는 것은 행렬에 열벡터를 곱하느냐 행벡터에 행렬을 곱하느냐 문제이구요. 
제가 본 수학책이나 프로그래밍 문서에서, 한 점을 행렬로 변환한다고 할 때 
대부분은 행렬이 왼쪽에 오고 오른쪽에 열벡터가 오는 형태입니다. 
다만 프로그래밍의 편의 상 행벡터 곱하기 행렬이 쓰이기도 할 뿐... 
오른손 왼손 역시 행우선, 열우선과 별 관련이 없을 듯... 
위의 예에서 오른손과 왼손의 차이는 Nx, Ny, Nz의 부호의 차이일 뿐이니까요..
 


행우선과 열우선, 왼손과 오른손좌표 이야기






참고 :

책 : C++와 DirectX9을 이용한 실시간 3D 지형엔진
DirectX 9 셰이더 프로그래밍
벡터 : http://mgun.tistory.com/1143 

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

피부는 왜 피부처럼 보이는가? 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
5. 왜 물은 물처럼 보이는 걸까?  (0) 2013.02.20
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. FX Composer
https://developer.nvidia.com/fx-composer

사실 2.5까지 버전업이 되었는지도 몰랐다.
이전에 1.x 버전을 쓸 때는 버그가 꽤나 많아서 몇번 쓰다가
패스해 버린 기억이 난다.

2. RenderMonkey
http://developer.amd.com/wordpress/media/files/RenderMonkey.2008-12-17-v1.82.322.msi
상당히 조아라 하는 툴이다.
이유는 단지 가벼워서!!
그냥 적당히 필요한 기능은 다 있는 것 같다.
아쉬운건 한 패스에 물체 하나밖에 그리질 못한 다는 거...

3. UDK Material Editor.
Unreal에서 제공하는 비쥬얼 머티리얼 제작 편집 툴.
코딩할 필요도 없고(물론 custom로 코딩할 수도 있긴 하다) 간편하며
즉시 결과물을 볼 수 있기 때문에 파워풀한 녀석이다.
그렇지만 너무 무겁다.

4. Shader FX
max의 플러그인 형식으로 지원

개인적으로는 RenderMonkey를 즐겨 사용한다.
익숙하기도 하고 가볍기도 하고 무었보다 실제로 내가 코드를 짤 수 있다는 점에서
프로그래머한테 추천 해 주고 싶은 툴이다.
아티스트분들에게는 조금 다루기 까다로운 툴 일 수도 있다.
실제 문법을 다 알아야 하기 때문이다.

두번째로 추천하는 것은 UDK Material Editor이다. 
무겁기는 하지만 파워풀하고 강력한 기능이 있어서 사용하기 편하다.
아티스트분들이 사용하기에는 편할 듯..

Shader FX는 사용해 보질 않아서 잘 모르겠음.
FX Composer은 오래전에 사용해 봤는데 당시에는 무겁기도 하고 
버그도 꽤 있고 해서 잘 사용하지 않았지만 기능은 많이 있었던듯...

5. Unity Shader

셰이더를 작업해서 테스트 하기 좋은 범용 엔진 툴이며 셰이더 포지같은 비쥬얼 노드도 있다.
cg나 서페이스 셰이더를 사용하면 되지만 개인적으론 cg로 구현하는걸 추천.


그냥 뭐 그러함...개인취향입니다. ㅎㅎㅎ
 



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

행렬  (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
3. 왜 물은 물처럼 보이는 걸까?  (0) 2013.01.17
Posted by 붕대마음

앞의 글 참조
1. http://mgun.tistory.com/1282
2. http://mgun.tistory.com/1294
3. http://mgun.tistory.com/1304
4. http://mgun.tistory.com/1306

5번째 글이 늦은 이유는.....
글 적다가 한번 날려서 멘붕이 왔다...
글을 적을 때 한번에 쫘악~ 다 적을 능력이 안 되서
하나의 글을 몇일동안 조금씩 적는데 그걸 날려먹으니
멘탈이 쫀득쫀득해져서 한동안 다시 쓸 엄두가 안났다는...ㅜㅜ...

무튼~!
기본적인 라이팅도 했고 반사도 했으니 이젠 굴절(Refraction)이라는 녀석을 살펴 볼 차례다.
굴절이란 무었인가??

그림 1.
출처 : http://mathforum.org/mathimages/index.php/Snell's_Law

위 사진을 보면 물이 담긴 유리잔에 숟가락이 들어 있는데
물 표면을 경계로 위쪽과 아래쪽이 매칭이 안 된다.
마치 어긋나 있는 것 처럼 보인다.


그림 2.
출처 : http://terms.naver.com/entry.nhn?cid=200000000&docId=1067924&mobile&categoryId=200000457

위 설명에서 그림이 워낙 직관적이라서 글은 안읽어도 될 듯.
사실 위 그림을 확대한 듯한 그림을 예전에 한번 올렸었다.
BRDF를 설명할 때 였었는데 2번째 글에서 언급했었다.
http://mgun.tistory.com/1294
부연 설명을 하자면 물과 공기라는 다른 성질을 가지고 있는 각 층을 나누어 주는 것이 바로 물 표면(surface)다.
그래서 빛이 공기에서 물로 들어갈 때 물 표면을 기점으로 꺽이게 되고, 그래서 우리가 물에 담긴
연필이나 스푼을 보면 마치 휘어지거나 어긋나 있는 것 처럼 보인다는 것이다.

그리고 여기 water 예제로 유명한 "Pond Water"(연못) 을 보자.

그림 3.
 원본 PDF 파일 :



한글 번역 : http://blog.naver.com/swoosungi/90086817771
나름 상당한 퀄리티를 자랑한다.

기본적인 내용은 아래와 같다.
1. 물 표면을 제외한 현재 화면을 텍스처에 렌더링 한다. (이를 굴절맵이라고 부른다.)
2. 물 표면을 제외하고 물표면에 대해 반사되는 장면을 텍스처에 렌더링 한다. (이를 반사맵이라고 부른다.)
3. 하늘과 다른 메시들을 그리고 반사맵과 굴절맵을 사용하여 물 표면을 그린다.

여기서는 반사맵도, 굴절맵도 동적으로 그렸다.
반사맵 같은 경우는 정적으로도 많이 그리지만 당연히 동적으로 매번 그려주는게 퀄리티는 훨씬 좋다.

실제 코드를 보면 아래와 같다.
1. 굴절맵에 하늘과 프랍들(메시들)을 그린다.
refractMap->beginScene();
mSky->draw(0);
drawSceneMesh(0);
refractMap->endScene();

2. 반사맵에 하늘과 프랍들을 그린다.
reflectMap->beginScene();
mSky->draw(&waterPlaneW);       // 반사된 하늘을 반사맵에 그리기 위한 인자. 카메라를 수면 아래로 한다.
drawSceneMesh(&waterPlaneW);
reflectMap->endScene();

중복 사진이지만 대략 이렇게...



3. 하늘과 프랍과 물을 그린다.
mSky->draw(0);
drawSceneMesh(0);
mWater->draw();

2번에서 반사맵에 하늘과 프랍들을 그릴 때 인자로 넣어준 waterPlaneW는 dx에서 제공하는 평면 구조체이다.
이 값은 a,b,c,d 값을 가지는 데 a=normal.x  b=normal.y  c=normal.z  d= dot(-normal,임의의 한점p)로 나타낸다.


// 지역공간에서의 반사평면
D3DXPLANE waterPlaneL(0.0f, -1.0f, 0.0f, 0.0f);


// 월드 공간에서의 반사평면
D3DXMATRIX WInvTrans;
D3DXMatrixInverse(&WInvTrans, 0, &(mWaterWorld));

// 전치행렬로 전환
D3DXMatrixTranspose(&WInvTrans, &WInvTrans);
D3DXPLANE waterPlaneW;
D3DXPlaneTransform(&waterPlaneW, &waterPlaneL, &WInvTrans);

// 동차 클립공간 (wvp)에서의 반사평면
D3DXMATRIX WVPInvTrans;
D3DXMatrixInverse(&WVPInvTrans, 0, &(mWaterWorld*gCamera->viewProj()));
D3DXMatrixTranspose(&WVPInvTrans, &WVPInvTrans);
D3DXPLANE waterPlaneH;
D3DXPlaneTransform(&waterPlaneH, &waterPlaneL, &WVPInvTrans);

// 수면에 걸친 오브젝트를 제외하고 장면을 그리기 위해 clip plane을 이용.
// 인자로 들어가는 평면은 projection까지 끝난 scene 좌표.

float f[4] = {waterPlaneH.a, waterPlaneH.b, waterPlaneH.c, waterPlaneH.d};
HR(gd3dDevice->SetClipPlane(0, (float*)f));
HR(gd3dDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, 1));
참고 : http://kesegyu.com/tt/480


법선벡터의 변환은 신경을 좀 써 줘야 한다.
리얼 타임 렌더링 56P를 보면 "법선 벡터 변환(Normal Transform)"이라는 항목이 있다.
 "법선 벡터들은 기하 구조 변환에 사용된 특정 행렬의 역행렬의 전치행렬에 의해서 변환되어야만 한다."

이동의 경우는 변할것이 없지만 회전일 경우 똑같이 회전해 줘야 하고
크기조정일 경우는 균등크기 조정이라면 그 값으로 나누어 주면 되지만 비 균등 이라면
역행렬을 새로 구해줘야 한다.

참고 글 : http://scosco.com.ne.kr/ES20Html/basic_normal.htm
             http://tails.tistory.com/archive/20111121

참고로 타카시 책의 279p에도 설명이 되어 있다. 
float3 N = normal(mul(Normal, (float3x3)mWIT)); // 월드 좌표계에서의 법선.
법선 벡터 N은 mWIT라는 행렬로 변환하는데 이는 로컬좌표를 월드좌표로 변환하는 행렬의 역전치행렬이다.
법선 벡터는 특별한 벡터로써 물체를 평행이동 하면 변하지 않으며 물체를 회전할 경우에는 
동일한 방향으로 방향이 바뀐다
. 또한, 물체를 크기 변환할 때도 방향을 바꾼다.
즉, 단순하게 월드행렬로 변환되는 것이 아니다.


참고 글 : http://www.gpgstudy.com/forum/viewtopic.php?t=1337&highlight=
 

그림 4.

그럼 이런 멋진 굴절들을 어떻게 만들 수 있을까?

우선 udk를 통해서 한번 살펴보자.
 

그림 5.

위 사진은 UDK material editor에서 만든 왜곡(Distortion)이다.
이 머티리얼을 static mesh를 하나 띄워서 그 mesh에 넣어 보면 차이는 더욱 명확해 진다.

1. 왜곡없는 머티리얼.
그림 6
.
2. 왜곡있는 머티리얼.

그림 7.
왜곡이 추가된 모습

수면을 기준으로 어긋나 보이는 것을 알 수 있다.
위 결과물은 가장 처음 사진인 유리잔에 스푼이 담겨있는 것과 꼭 닮았다.


UDK Material Editor에는 기본적으로 Distortion(왜곡)이라는 항목이 존재한다.

그림 8.

아잉, UDK 넘흐 편해유~! 사랑해유~!

그림 9.

우선 뒤에있는 와이어좀 보이게 투명좀 먹였다.
그리고 Distortion 항목에 적당한 값을 넣어주었다.
첫번째 값 5는 좌우 어긋남을, 두번째 값 5는 상하 어긋남을 표현 해 준다.

자동완성 코드에는 아래와 같이 되어 있다.
float2 GetMaterialDistortion(FMaterialPixelParameters Parameters)
{
 float3 Local10 = (UniformPixelVector_0.rgb * GetPerInstanceSelectionMask(Parameters));
 return float3(5.00000000,5.00000000,0.00000000).rg;
}
오잉? 내가 생각했던 것 만큼 자세히 나오지 않는다.
distortion 계산은 오프셋값만 노출되어있고 실제 계산은 실제 shader 파일에서 한다.

이제 이 Distortion값을 내 시야와 연계해서 작동되게 만들어 줘야 한다.
시야와 노말을 기반으로 감쇠를 해주는 녀석이 있었는데 바로 "프레넬"이다.
이녀석은 표면의 노멀이 카메라를 향하면 0, 표면의 노멀이 카메라에 수직인 경우 1의 값을 가진다.
프레넬에 대한 설명은 http://mgun.tistory.com/1304 을 참고하면 된다.

즉 끝부분일 왜곡이 심하고 현재 내가바라보는 부분일 수록 왜곡이 없다는 것이다.
한번 테스트 해 보자.

그림 10.

끝 부분(시야벡터와 직각에 가까운 부분)쪽의 그리드가 왜곡되어 있는게 보인다.

어차피 프레넬의 결과값은 0~1사이다.
이 0~1사이의 비율을 좀 넓혀서 사용해 보자.

그림 11.

단순히 5,5 벡터를 곱해 0~1사이의값을 0~5사이의 값으로 뻥튀기 해주었다.
그런데 내가 원하는 왜곡은 사실 끝에서 왜곡되는 것이 아니라 가장자리에서 왜곡 되는 것이다.
즉, 물 표면에서 끊어지는 것 처럼 왜곡되는 것이 아니라 물 내에서 늘어지는 것처럼 왜곡 되는 것이다.

그렇다면 프레넬의 성질을 반대로 이용하면 된다.
프레넬의 값은 0~1사이니까 1에 가까운 값일수록 0을, 0에 가까운 값일수록 1의 값을 가지도록
1 - value 를 해 주면 된다.

그림 12.

확실히 결과물이 다르다.
결과물을 좀 더 확실히 알 수 있도록 5,5의 값을 좀 변경하자.

그림 13.
결과물이 확실히 다르다.

지금 물은 그냥 투명하다. 전혀 물 처럼 보이지 않는다.
물에 색깔을 좀 입혀보자.

그림 14.

Diffuse로 사용하기 위해 환경맵(여기서는 큐브맵)을 사용하는데 이 큐브맵의 UV좌표로 Transform을 통과시킨
Reflection이 사용된다.
Transform은 좌표시스템을 변환해 주는 표현식이다.
좌표 시스템의 변환 대한 이해가 필요하다면 아래 글을 보면 간단하게 적혀 있다.
http://mgun.tistory.com/1342

기본적으로 머티리얼의 모든 셰이더 계산은 탄젠트 공간에서 수행되는데
벡터상수, 카메라 벡터, 라이트 벡터등은 모두 머티리얼에 사용되기 전에 탄젠트 공간으로 변형된다.
이러한 벡터를 탄젠트 공간이나 월드공간, 로컬공간, 뷰공간 좌표시스템으로 변환시켜 주는 것이 Transform이다.

그림 15.

여기서는 Reflection을 Tangent공간에서 World 공간으로 변환했다.


표면 노말에 걸쳐 반사된 카메라 방향을 나타내는 3채널 벡터값인 ReflectionVector을 Transform에 통과시켜
환경맵이 할당된 TextureSample표현식에 대한 UV텍스처 좌표로 사용해야 큐브맵을 메시의 표면에 매핑시킬때
올바르게 나온다.

여기에 프레넬을 곱해서 표면이 카메라에서 멀어질 수록 반사가 뚜렵해 지도록 만들어 준다.

그림 16.

이렇게 하면 카메라가 바라보는 부분은 반사가 거의 일어나지 않고 카메라가 멀어질 수록, 즉 바깥부분은 더 반사가
확실하게 일어나도록 표현된다.
 

그림 17.

Opacity(투명)값에도 프레넬을 적용해서 Diffuse와 같은 느낌을 살려준다.
이렇게 해 줘야 현재 카메라벡터와 같은 노말(즉, 마주보는)을 가진 표면에서는 투명하고
노멀이 직각에 가까울 수록(멀리있을수록) 반사가 뚜렷하게 일어나면서 불투명 해 진다.

추가적으로 specular과 specular power을 주면 좀 더 그럴듯하게 보일 수 있다.
이 값은 적당히 원하는 const 수치를 주면 된다.

여기까지가 UDK에서 기본적으로 제공하는 Distortion을 사용해서 Cubemap을 왜곡시킨 결과물 이다.

내부적으로 이 distortion이 어떻게 만들어 지는지 좀 더 살펴보자...다음글에서.ㅇㅇ

Posted by 붕대마음

최근에 달린 댓글

최근에 받은 트랙백

글 보관함