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

카테고리

전체목록 (666)
참고사이트 (8)
Goal (4)
Travel (10)
My Life (105)
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)
Total343,756
Today5
Yesterday73

'RenderMonkey'에 해당되는 글 6건

  1. 2014.01.11 Shadow Mapping
  2. 2013.10.22 fresnel
  3. 2013.02.07 normal mapping (법선 매핑) (2)
  4. 2013.02.02 기본적인 반사 벡터
  5. 2013.01.31 lambert (3)
  6. 2013.01.29 흐음...렌더몽키라..

Shadow Mapping

RenderMonkey / 2014.01.11 12:31

Depth Shadow(깊이 그림자), Shadow Maps(그림자 맵),
Depth Bufffer Shadow(깊이버퍼 그림자) 등의 여러가지 이름으로 불린다.

이 기법의 핵심은 광원으로부터 물체까지의 거리정보를 저장해서 사용한다는 것이다.

1. depth 찍기
// 조명위치로 시선 이동
float3 vLook = float3(0,0,0);
float3 vEye = lightPos.xyz;
float3 vUp = float3(0,0,1);

// 조명의 시야벡터들 만들고 뷰 공간으로 변환
float4x4 lightMat = GenViewMatrix(vLook, vEye, vUp);
Pos -= lightPos; 
Out.Pos = mul(Pos, lightMat);

// 조명의 투영변환행렬.
Out.Pos = mul(Out.Pos, proj_matrix);

대충 요렇게 하면 우선 깊이값은 얻어올 수 있다.


2. 모델의 diffuse, specular 적용해서 찍기


3. 미리 만들어둔 그림자맵과 렌더링하려는 픽셀의
   광원까지의 거리를 비교한다.

   렌더링하려는 픽셀의 광원까지의 거리값이
   그림자맵보다 크다면 그 부분은 그림자가 된다.

float shadow = (depth < shadowMap + shadowBias);


float shadow = (depth < shadowMap + shadowBias);

4. 그림자를 바닥에 붙여준다.



보면 알겠지만 재기스 현상이 좀 심하다.
이는 그냥 그림자맵 텍스처 해상도를 올려 줄 수 밖에...



참고 링크들...
행렬 정리
- http://mgun.tistory.com/1363

Allosha님의 블로그,  깊이버퍼 그림자 기법, 문제점
- http://allosha.tistory.com/24

opengl tutorial
- http://www.paulsprojects.net/tutorials/smt/smt.html



  - 언렬 머티리얼 에디터 쓰다가 렌더몽키 처음 쓸때는 많이 불편했는데
    그래도 난 렌더몽키가 더 재미있는듯....
    

'RenderMonkey' 카테고리의 다른 글

Shadow Mapping  (0) 2014.01.11
fresnel  (0) 2013.10.22
normal mapping (법선 매핑)  (2) 2013.02.07
기본적인 반사 벡터  (0) 2013.02.02
lambert  (3) 2013.01.31
흐음...렌더몽키라..  (0) 2013.01.29
Posted by 붕대마음

댓글을 달아 주세요

fresnel

RenderMonkey / 2013.10.22 12:50
참고.

1. http://mgun.tistory.com/1282
2. http://mgun.tistory.com/1294
3. http://mgun.tistory.com/1304


그냥 프레넬.


프레넬을 최적화 해서 림라이트처럼 쓰거나 할 거면 그다지 노말은 필요없지만
그게 아니라면 노말은 꼭 넣어주는 센스.

위의 모습은 렌더몽키로 만든 모습이다.

'RenderMonkey' 카테고리의 다른 글

Shadow Mapping  (0) 2014.01.11
fresnel  (0) 2013.10.22
normal mapping (법선 매핑)  (2) 2013.02.07
기본적인 반사 벡터  (0) 2013.02.02
lambert  (3) 2013.01.31
흐음...렌더몽키라..  (0) 2013.01.29
Posted by 붕대마음

댓글을 달아 주세요


specular map : 각 텍셀에 정반사광을 보여줄 정도를 정의
normal map : 법선벡터의 방향을 나타낸다.
                    높이맵(height map)에서 기복을 계산하고, 그 높이의 기복으로부터 법선벡터를
                    계산하여 텍스처에 기록.
                    이렇게 각 픽셀에 사용할 법선 정보를 담고 있는 텍스처를
                    법선맵(normal map)이라고 하고 법선맵을 이용해서
                    조명을 계산하는 기법을 법선매핑(normal mapping)이라고 한다.                   
height map : 법선방향의 기복 크기를 기록한 텍스처, 높은곳이면 하얗게,
                      낮은곳이면 검게 그려진다.

법선벡터의 중요성 : 
확산광에서는 법선과 광원벡터만 사용해서 조명의 강도를 계산.
   ex : Lambert( N · L )
반영반사광에서도 법선벡터가 중요.
   ex : Phong, Blinn Phong ( N · L )

문제점 : 
일반적으로 법선은 정점마다 있어서 픽셀단위 조명에서 이러한 정점에 정의된
법선 벡터를 보간해서 사용
.
하지만 이 방법은 솟아 오른곳이나 울퉁불퉁한 곳에는 폴리곤 수를 늘려
하나하나의 정점과 법선을 
모델러가 조절해 줘야 한다는 단점이 있다.
결론은, 아무리 하이폴리곤 오브젝트를 사용하더라도 정점과 정점 사이는 선형으로 보간되기 떄문에
굴곡의 표현이 어려운데 이 부분을 픽셀셰이더에서 매핑을 이용해 해결.
변경된 법선을 이용하여 정점과 정점 사이에 주름과 돌기같은 음영표현을 한다. 

대안 : 
텍스쳐를 이용해서 간편하게 폴리곤의 삼각형 크기보다 상세한 기복을 표현할 수 있도록 한다. 

즉, 법선맵핑이란 거친질감을 표현하기 위해 특수한 텍스쳐를 사용해 법선벡터를 섭동시켜 픽셀마다 조명계산을 
하게 하여 기복이 있는 것처럼 보이게 한다. 

만들어 보기.
텍스처는 R,G,B채널이 있으므로 각 X,Y,Z값을 대입하여 저장한다.
하지만 텍스처의 각 채널에 가질 수 있는 값의 범위는 0~1 이다.
이곳에 정규화한 단위벡터를 넣어주면 된다.
단위벡터는 크기값이 1인 벡터인데 크기값이 1이라는 것은 (1,0,0)일 수도 있지만
(-1,0,0)이 될 수도 있다는 말이 된다.
즉, 정규화된 법선벡터의 범위가 0~1이 아니라 -1~1이 되어 버리는 것이다.

그렇다면 0~1의 범위만을 포용하는 텍스처에 -1~1의 값을 가지는 벡터값을 어떻게 넣을 것인가?
담을 바구니를 크게 만들지 못한다면 넣을 물건을 작게 만들면 되지 않을까?
즉, -1~1을 0~1의 값으로 변환시키면 된다.
법선맵 RGB = 법선벡터 XYZ * 0.5 + 0.5
이렇게 하면 -1은 0에 맞추어지고 1은 1에 맞추어 진다.

반대로 법선맵 RGB로부터 법선벡터 XYZ를 구하고자 한다면?
위의 식 중 *0.5 + 0.5를 넘겨주면 된다.
법선맵 RGB - 0.5 = 법선벡터 XYZ * 0.5
2*(법선맵 RGB - 0.5) = 2*(법선벡터 XYZ * 0.5)
2*법선맵 RGB - 1.0 = 법선벡터 XYZ * 1.0 = 법선벡터 XYZ
법선맵 RGB * 2 - 1.0 = 법선벡터 XYZ

Toon Shading에서는--------------------------------------------------------
Toon Shading에도 이와 비슷한 방법을 사용했었는데 결과 diffuse값이 0~1인 값을
0, 0.2, 0.4, 0.6, 0.8, 1.0 과 같이 0.2 단위의 값만을 가지도록 만들기 위해
diffuse*5를 곱해서 0~5의 값을 가지게 만들고 ceil()을 사용하여
딱 맞게 0,1,2,3,4,5 의 값중 하나만 가지도록 올림연산 한다.
그리고 이 값들을 5로 나누어 주면 0, 0.2, 0.4, 0.6, 0.8, 1.0의 값으로 만들 수 있다.
----------------------------------------------------------------------------

이렇게 법선 벡터를 텍스처에 저장하는데 이 법선벡터가 가지는 x,y,z의 의미를 생각해 보자.
법선벡터가 (-1,0,0)의 값을 가진다면 이 벡터는 어딜 향하고 있는 것일까?
x,y,z의 값이 의미가 있으려면 이 값을 표현해 줄 수 있는 공간이 있어야 한다.


위 그림은 법선매핑에서 사용되는 공간의 xyz 좌표계다.
표면에 수직인 방향이 z이며 오른쪽이 x고 y는 이 두 방향에 직각인 방향이다.
그런데 여태까지 해오던 로컬공간, 월드공간, 뷰공간, 프로젝션 공간같은 것중 이 공간은 무슨 공간일까?


육면체의 왼쪽과 오른쪽 면에 동일한 법선맵을 사용한다고 가정한다면 왼쪽면의 z방향(0,0,1)은 왼쪽을 향하고
오른쪽면의 z방향(0,0,1)은 오른쪽을 향한다.
동일한 벡터이지만 가리키는 방향이 다르다?
현재까지 사용한 공간이랑 개념이 조금 다른데 이전 공간들은 물체마다 정의된 것이라면 이 공간은
표면마다 정의
되어 있는 것이다.
"표면의 바깥쪽 방향을 법선의 +z로 한다"는 명제를 사용하여 법선맵을 만들기 때문에
표면마다 다른 공간이 존재하는데 이 공간을 접선공간(tangent space) 또는 표면공간이라고 한다.

이제 이 법선맵에서 법선벡터를 가져와 라이팅을 계산하면된다.
그런데 문제가 하나 있다.
이전 툰셰이딩에서도 말했듯이 같은 공간의 것들끼리 연산을 해주어야 하는데
법선정보가 접선공간에 존재한다는 것이다.
즉, 법선정보를 사용하고 한다면 공간변환을 해 줘야 한다는 것이다.
그렇다면 공간변환을 위해 필요한 행렬연산은 어떻게 만들어줘야 하는가?
접선공간을 구성하는 행렬은 좌표축 3개만 있다면 쉽게 만들 수 있는데
우선 정점의 법선인 Z, 표면위를 달리는 축인 X, 그리고 X와 Z에 직각인 Y를 구해야 한다.
Z는 법선자체 이므로 그대로 사용하면 되고 X는 표면위의 정보에서 구해올 수 있는데
UV좌표가 표면위에 정의되어 있기 때문에 U와 V중 하나를 가져와 그것을 X로 사용하면 된다.
그래서 이 X를 접선(tangent)라고 한다.
X와 Z에 동시에 직각인 Y는 외적을 통해 쉽게 구할 수 있는데 이 축을 종법선(binormal)이라고 한다.
접선 T = (Tx, Ty, Tz)
종법선 B = (Bx, By, Bz)
법선 N = (Nx, Ny, Nz)
로 표시할 수 있는데 접선공간변환에 사용하는 행기준 행렬은 (d3d)
| Tx Ty Tz |
| Bx By Bz |
| Nx Ny Nz |
이렇게 표시 할 수 있고 열기준 행렬이라면 (open gl)
| Tx Bx Nx |
| Ty By Ny |
| Tz Bz Nz |
요렇게 표현할 수 있다.

이제 말로만 계속 언급했던 normal map(법선맵)을 보도록 하자.

법선맵은 왜 항상 푸른계열의 색상을 유지할까?
RGB채널중 B 채널은 법선벡터의 Z와 매칭이 되는데 법선벡터의 Z는 항상 표면의 바깥쪽을 가리키는 방향이기 때문에
벡터의 범위인 -1~1의 범위가 아니라 0~1의 범위를 가진다.
그런데 이전에도 말했듯이 벡터범위인 -1~1를 텍스처 범위인 0~1로 우겨넣기 위해 *0.5, +0.5를 시행하므로
Z값은 범위가 0.5~1.0이 되어 항상 Z값은 최소한 절반이상 들어가 있게 된다.
그래서 B값이 유독 크기 때문에 파란색을 띄게 된다.


돌판의 오른쪽을 보면 붉은색의 하이라이트가 있고 왼쪽은 어두워 진다.
실제 이 돌판은 분명 튀어나와 있는데 오른쪽 붉은색 하이라이트 부분의 경우는 오른쪽을 향하는
법선을 가지고 있을 텐데 이 값이 (1,0,0)이라서 변환 후 (1, 0.5, 0.5)가 되서 붉은색을 띄고
왼쪽은 당연히 왼쪽을 향하고 있을 것이며 왼쪽은 (-1,0,0)이 되고 이는 변환시 (0, 0.5, 0.5)가 되는 것이다.

위 아래도 마찬가지로 위쪽은 당연히 위쪽으로 법선이 향할것이고 아래는 아래쪽 방향으로 향할 텐데
아래쪽 법선의 값은(0,1,0)이라서 변환하면 (0.5, 1.0, 0.5)가 되어 녹색을 띄고 위쪽은 (0,-1,-0)이라서
변환하면 (0.5, 0, 0.5)가 되어 녹색이 사라지면서 어둡게 된다.

이제 렌더몽키로 실제 구현을 해 보자.
중요한건 이전 diffuse를 구하던 것을 vertex shader가 아닌 pixel shader에서 해야 한다는 것이다.
diffuse를 구한 다는 것은 light dir과 normal을 내적한다는 말인데 여기에서는 normal을
texture에서 구해서 사용하기 때문에 texture의 텍셀값을 얻어올 수 있는 pixel에서 diffuse를 구해야 한다.
즉, 법선매핑을 사용할 때 정점에서 가져오는 법선은 조명에 사용하지 않는다.
그리고 Reflection값도 http://mgun.tistory.com/1337 요기 링크를 보면 알겠지만 E와 N의 내적으로 구해진다.
N을 정점의 노말로 사용하지 않고 텍스처에서 사용하므로 Reflection값도 픽셀에서 구할 수 밖에 없다.

그리고 접선공간을 변환하기 위해 지정한 세 데이터(법선, 접선, 종법선)도 월드 공간에 있어야 한다.
조명계산에 필요한 것은 결국 Light Dir(입사광 벡터), View(카메라 벡터), Normal(법선벡터)다.
그런데 올바른 법선이 텍스처에서 읽어와야 하는데 텍스처는 접선공간에 존재하므로
이를 월드공간으로 변환해 줘야 조명계산하는데 필요한 모든 요소들을 한 공간에 묶을 수 있다.
그러려면 픽셀 셰이더에서 법선/접선/종법선 벡터를 사용해서 접선공간에서 월드공간, 또는
월드공간에서 접선공간으로 변환하는 행렬을 만들 수 있다는 말이 된다.

struct VS_OUTPUT
{
   float4 mPosition : POSITION;
   float2 mUV: TEXCOORD0;
   float3 mLightDir : TEXCOORD1;  
   float3 mViewDir: TEXCOORD2;
   float3 T: TEXCOORD3;
   float3 B: TEXCOORD4;
   float3 N: TEXCOORD5;

};

픽셀셰이더에서 법선텍스처에서 법선을 읽는다.
float3 tangentNormal = tex2D(NormalSampler, Input.mUV).xyz;

텍스처에서 읽어온 값은 0~1이다.
하지만 실제 법선벡터의 범위는 -1~1이다.
0~1의 범위의 값을 -1~1로 다시 바꿔주기 위해서는 *2를 하여 0~2로 만든 후 -1을 하여 -1~1로 설정한다.
tangentNormal = normalize(tangentNormal*2-1);

접선공간 법선을 구했는데 이 녀석은 말 그대로 접선공간에서의 법선값이다.
이를 월드공간에서의 값으로 변환 해 줘야 라이팅 계산시 사용할 수 있다.
그래서 T,B,N벡터로 행렬을 하나 만든다.
float3x3 TBN = float3x3(normalize(Input.T), normalize(Input.B), normalize(Input.N));

그런데 이 TBN 행렬은 월드공간을 접선 공간으로 변환하는 행렬이다.
지금 필요한건 접선공간을 월드공간으로 변환시켜줄 행렬이다.
TBN의 역행렬을 사용해야 하는데 직교행렬의 역행렬(Inverse)은 전치행렬(Transpose)과 같다.
그렇다면 직교행렬이란 무었인가?


정리해 보면 직교행렬이란..
1. 행렬의 각 행벡터가 서로 직교해야 한다.
2. 각 행벡터의 크기가1이어야 한다.
3. 두 행벡터의 내적은 항상 0이어야 한다.

우선 T,B,N은 모두 normalize했으므로 벡터의 길이는 1인것은 당연하고
세 벡터가 서로 직교하므로  내적은 항상 cos90도, 즉 0이 나오므로 TBN은 직교행렬이다.

그렇다면 전치행렬은 무었인가?
전치행렬은 단순하게 행렬의 행과 열을 바꾸는 것을 말한다.

TBN은 직교행렬이고, 월드공간을 접선공간으로 변환시켜준다.
이를 접선공간에서 월드공간으로 바꿔주는 행렬로 바꿔주기 위해
TBN의 역행렬을 구해줘야하는데 TBN은 직교행렬이므로 역행렬과 전치행렬은 같다.
HLSL에서 전치행렬을 구하는 함수 transpose()가 미리 준비되어 있다.
TBN = transpose(TBN);

이제 접선에서 월드공간으로 변환시킬 수 있는 행렬을 구했으니 접선공간에서 구한 normal값을
월드공간으로 변환시켜 주자.
float3 worldNormal = mul(TBN, tangentNormal);

오잉? 먼가 좀 이상한 부분이 있다.
여태까지는 mul(벡터, 행렬)의 방식으로 곱했었다.
ex) Output.mPosition = mul( Input.mPosition, gWorldViewProjectionMatrix );
그런데 여기에서는 왜 mul(TBN(행렬) , tangentNormal(벡터)) 인가?
이유는 TBN을 만들 때 float3x3 생성자를 사용했기 때문인데, 이를 사용하면
행기준 행렬이 나온다.
그와 반대로 SetMatrix()함수를 통해 CPU로부터 건네받은 행렬은 열기준 이다.
행기준 행렬은 mul(행렬, 벡터)의 순서로, 열기준 행렬은 mul(벡터, 행렬)의 순서로 곱한다.

이젠 월드상의 노말값도 구했겠다 나머지는 이전에 하던 것과 같다.
float4 albedo = tex2D(DiffuseSampler, Input.mUV);
float3 lightDir = normalize(Input.mLightDir);
float3 diffuse = saturate(dot(worldNormal, -lightDir));
diffuse = gLightColor * albedo.rgb * diffuse;
diffuse 텍스처로부터 diffuse값을 구하고 normal과 light를 내적하여 조명도 계산하고
거기에 추가적으로 light의 color도 고려해서 최종 diffuse값을 구해주면 된다.

반사값은 미리 제공해 주는 reflect()함수를 사용해서 구하면 되고
float3 reflection = reflect(lightDir, worldNormal);

specular은 반사R과 camera라 V를 내적해서 하이라이트를 구하고
specular = saturate(dot(reflection, -viewDir));
specular = pow(specular, 20.0f);

specular 텍스처를 통해 specular의 강한정도와 light의 color을 고려해서 최종 specular값을 구한다.
float4 specularIntensity = tex2D(SpecularSampler, Input.mUV);
specular *= specularIntensity.rgb * gLightColor; 


Normal.rfx


Normal Mapping : 법선맵 사용, 법선맵이 입혀진 측면에서 보면 입체감이 없다.
Parallax Mapping : 법선 외에 높이맵(height map)을 사용하여 문제점 해결
Parallax Occlusion Mapping : 인접픽셀과의 높이차를 구한 뒤 그에 따라 그림자를 입히는 기법.


위의 사진은 view와 light를 tangent 공간으로 바꾼 후 계산한 diffuse + normal + specular의
결과물이다.

Default.rfx




참고 : 
- Displacement Mapping과 Parallax Mapping의 차이
  http://mgun.tistory.com/122
- Surface Texture Mapping
  http://mgun.tistory.com/140
- mapping
  http://mgun.tistory.com/280
- 사용 리소스
  http://www.bencloward.com/shaders_offset.shtml
- kw x-port
  http://www.kwxport.org/
-탄젠트공간
  http://mgun.tistory.com/1289
-법선 매핑
http://blog.naver.com/sorkelf/40157218010

ps. 문제가 되는 이미지나 링크는 알려주시면 자삭하겠습니다.


'RenderMonkey' 카테고리의 다른 글

Shadow Mapping  (0) 2014.01.11
fresnel  (0) 2013.10.22
normal mapping (법선 매핑)  (2) 2013.02.07
기본적인 반사 벡터  (0) 2013.02.02
lambert  (3) 2013.01.31
흐음...렌더몽키라..  (0) 2013.01.29
Posted by 붕대마음

댓글을 달아 주세요

  1. 2017.11.09 15:28 student  댓글주소  수정/삭제  댓글쓰기

    잘보고 가요!


반사벡터에 대한 이론과 언리얼 에디터에서 반사벡터 만들기는 요 링크를 참조하면 된다.
http://mgun.tistory.com/1306

반사벡터 공식은 R = -E + 2*dot(N,E)*N 라고 했다.
언리얼 머티리얼 세이더에서는 이미 반사벡터가 노드로 제공되어 지는데
shader 함수로는 이미 reflect라는 함수가 제공되어 진다.

Output.Reflect = reflect(lightDir, worldNormal);
이렇게 간단하게 reflection 값을 구할 수 있다.

reflect함수가 없더라도 큰 걱정은 없다. 이미 reflect를 구할 수 있는 공싟을 알고 있으니까.
Output.Reflect  = lightColor * dot(-lightDir, worldNormal);
내적시 밑동을 맞춰준다라고 생각한다면 부호도 금방 생각난다.

이제 반사벡터도 생겼겠다, 반사모델계의 Hello World인 Phong를 만들어 보자.
http://mgun.tistory.com/1099
상당히 오래전에 쓴 글이긴 하지만 대략적인 phong model에 대한 설명은 이곳에 있으니 패스.
공식은 diffuse + ambient + specular인데 diffuse는 dot(L, N)을, specular에는 dot(l,R)에 승수해 준것을 곱해준다.
즉, diffuse는  float3 lambertResult = dot(-lightDir, worldNormal);
ambient는 그냥 적당히 float3 ambient = float3(0.1f, 0.1f, 0.1f);
specular은 specular = saturate(dot(reflection, -viewDir));
                specular = pow(specular, 20.0f); 이 된다.
이 세개의 조명값들을 다 더하면 원하는 결과값이 나온다.
specular의 값을 구하기 위해 dot 연산시 viewDIr의 방향이 눈에서 정점으로 향하는 벡터라 반사벡터와
밑동을 맞춰주기 위해 -을 곱해준 것을 눈여겨 보자.

그리고 아직 좀 의문스로운 부분 중 하나가 specular을 계산할 때 light vector를 사용하지 않고 view vector을 사용한다는 점이다.
내가 아는 한도 내에서는 light vector을 사용했었는데 말이다.

내가 사랑하는 타카시 아저씨 책에는 분명 specular은 dot(L,R) 인데....L을 쓰는 것과 V를 쓰는것의 차이는 뭘까?
언리얼에서도 dot(V,R)을 쓰고 있던데 말야...

이에 대한 대답은 아래 두 링크에 나와 있다.
http://kblog.popekim.com/2012/01/04-part-3.html
http://www.renderman.or.kr/RenderMan/shader_05.html



그리고 추가적으로 per pixel specular 에서 부하를 먹는 주 요인인 pow를 좀 더 빠르게 처리하기 위해 언렬에서 쓰는 방법.

영어라 항상 큰맘먹고 읽어봐야 하는 GAMASUTRA, 하지만 좋은글이 정말 많다. 그리고 제목보고 낚여서 읽다가 원하는
내용이 아닌경우 대략난감..읽은부분이 아까워서라도 다 읽어버리게 한다.ㅜㅜ.
http://www.gamasutra.com/view/feature/2972/a_noninteger_power_function_on_.php

늘 좋은 정보로 가득한 GDF : http://gamedevforever.tistory.com/36

일반적인 specular reflection 함수에 대한 error들에 대한 글.
(목록 ui가 좀 불편하긴 하지만 재미난 글들이 많은 블로그.)
http://chihiroblog.blogspot.kr/2010/02/specular-reflection-function.html

blin phong은 그냥 H vector 구해서 쓰면 된다. R vector 구하기 귀찮을 때 쓰기도 하지만 더 빠른 속도를 위해서
쓰기도 한다. 결과물 차이는 좀 나기는 하는데 그다지 신경쓸 정도는 아닌듯...


cook은 공식이 좀 복잡한데 타카시 아저씨 책 공식을 그대로 옮겨주면 위와 같은 결과물이 나온다.
결과 값은 return diffuse + ks*max(0,F*D*G/NV); 
반사값은 D x G x F / dot(N,V)로 구해주고 이 값을 diffuse에 더해준다.

추가적으로 "Programming Vertex Geometry and Pixel Shaders" 에 있는 "The Cook Torrance Equation"도 좀 보자.
우선 geometric term을 구하는 방법은 똑같다.
float G = min(1, min(2*NH*NV/VH, 2*NH*NL/VH) );

그런데 기하감쇠계수인 D 값을 구할 때 분모에 있는 4배수값이 없다.
타카시 책을 보고 만든거랑 비교해 보자.
float D = exp(-(1-NH2) / (NH2*roughness)) / (4*roughness*NH2*NH2);
공식이 약간 틀리다. 하지만 이 D라는 항은 그냥 면의 거친정도를 나타내 주는 항이다.
저 4를 나눠주던 곱해주던 그냥 매직넘버 같은 것일 뿐, 큰 의미는 없다.

가장 중요한 프레넬 F는 어떻게 되어 있는가?

책에서는 dotV,H)가 아니라 dot(L,H)라고 쓴다.
이것도 그닥 문제가 될 부분은 없다.

이제 specular을 보자.


타카시 책에서는 대략적인 값을 넣어줬었는데 대략 이렇게 되어 있다.
이 공식에서 양쪽에 dot(N,L)을 곱해주자.
Rs * dot(N,L) = (F * D * G) / dot(N,V)
많이 보던 모양이다.
타카시 책의 cook torrance 의 반영반사광 값 I = kct * (D*G*F)/dot(N,V) 였다.
kct는 물체의 색인데 요것도 추가로 곱해줘서 모양을 맞춰보자.
Rs * dot(N,L) * kct = kct * (F*D*G) / dot(N,V) 이 된다.

float4 kct = float4(2.0*0.486, 2.0*0.433, 2.0*0.185, 1.0);
kct 값은 대충 요렇게 임의의 값을 넣어주기로 하고 계속 테스트를 해보자.

결과값에서 타카시 책에는 이렇게 결론을 낸다.
return diffuse + ks*max(0,(F*D*G)/(NV));

지금 비교하고 있는 문서 파일에는 이렇게 결론을 낸다.
float3 final = max(0.0f, NdotL) * (cSpecular * Rs + Input.mColor);

우선 diffuse 값을 Input.mColor로 생각하자.
float3 final = max(0.0f, NdotL) * (cSpecular * Rs + diffuse );

그리고 NdotL이나 NdotV나 같으니 보기 편하게 하기 위해 NdotV로 고치자.
float3 final = max(0.0f, NV) * (cSpecular * Rs + diffuse);

그리고 specular 값인 cSpecular은 우선 ks와 동일하다고 취급해 주자.
float3 final = max(0.0f, NV) * (ks* Rs + diffuse);

순서좀 바꿔주고.
float3 final = (ks* Rs + diffuse) * max(0.0f, NV);

Rs 값도 위에서 구했던 것 처럼 대입해 주자.
Rs = (F*D*G) / dot(N,V)*dot(N,L)

float3 final = (ks* Rs + diffuse) * max(0.0f, NV);
float3 final = (ks* (F*D*G) / dot(N,V)*dot(N,L) + diffuse) * max(0.0f, NV);
               = (diffuse + ks*(F*D*G) / (dot(N,V)*dot(N,L) ) * max(0.0f, NV);

타카시 책 공식에서
return diffuse + ks*max(0,(F*D*G)/(NV)); 의 max를 빼 보자.
return diffuse + ks*(F*D*G)/(NV);

여태까지 계산했던 공식에서 max를 빼 보자
float3 final = (diffuse + ks*(F*D*G) / (dot(N,V)*dot(N,L) ) *  NV;
dot(N,V) 끼리 없애주면 diffuse + ks * (F*D*G) / (N,L) 이 된다.

결론은 똑같은 공식이다. 다만 결과가 조금 차이가 나는데 그건 계산 순서 때문이다.
즉, dot(N,L)을 구하여 반대편 측(0 이하값)을 먼저 제외해 준 후 계산을 할 건지
반영반사값을 다 계산 하고 그 값이 반대편 값이면 제외시킬 건지의 문제다.



ps - 혹시 잘 못된 부분이 있으면 따끔하게 한마디 해주세요.ㅎㅎ
render monkey 소스가 좀 지저분 할 수 있습니다. 이리저리 테스트 하느라 그런거니
적당히 정리하셔서 사용하시면 됩니다.

'RenderMonkey' 카테고리의 다른 글

Shadow Mapping  (0) 2014.01.11
fresnel  (0) 2013.10.22
normal mapping (법선 매핑)  (2) 2013.02.07
기본적인 반사 벡터  (0) 2013.02.02
lambert  (3) 2013.01.31
흐음...렌더몽키라..  (0) 2013.01.29
Posted by 붕대마음

댓글을 달아 주세요

lambert

RenderMonkey / 2013.01.31 16:11

렘버트 또는 람베르트의 기본 공식은 dot(L,N).
http://mgun.tistory.com/1306
위 링크에서 엄청나게 마니 언급되었던 기본 조명모델이다.
이걸 렌더몽키에서 표현하는것도 무척 쉽다.

공식대로 계산을 하기 위해 Light Vector가 우선 필요하다.





사진으로 보니 더욱 명확하다.
램버트에 대한 내용이나 코사인 각도가 무었을 의미하는지 등은 언급한 링크에서 읽어보면 될 것 같고..
내가 말하고자 하는 것은 벡터의 방향이다.

이 부분에 대한 링크 : http://kblog.popekim.com/2011/01/blog-post_4153.html

첫번째 사진이나 두번째 사진이나 결국은 같은 거라고 생각한다.
내적의 특성상 두번째 사진의 개념은 부호를 한번 뒤집어서 벡터의 방향을 바꿔야 하긴 하지만
개인적인 생각으로는 두번째 사진이 "내가 보기에는" 이해하기가 쉽다.
옆 노멀벡터는 점이 속한 평면에서 수직인 벡터인데 결국 점을 기준으로 방향을 잡기 때문에
광원벡터 역시 광원에서 빛을 쏘는 방향이라고 일관성 있게 이해하고 싶다.


벡터에 대한 정리는 이 링크를 참고하세요. http://mgun.tistory.com/1143

무튼 라이트 위치를 추가해 주자


대략 오른쪽 위에, 모니터의 반대방향쪽에 위치시켰다.


오옷~! 빛의 위치는 바로 저기~!.. 2d 이미지라서 z위치는 식별하기 어렵지만..대략 저기임....

자~ 이제 노말은 어쩔거임? 노말은 이미 시멘틱이 존재한다.
그래서 걍 코딩으로 추가해 주면 된다.
struct VS_INPUT
{
   float4 mPosition : POSITION0;
   float3 mNormal : NORMAL;
};

이제 빛의 위치로 부터 빛의 방향벡터도 가져와야 겠지.
float3 lightDir = Output.mPosition.xyz - gWorldLightPosition;
lightDir = normalize(lightDir);
대부분 알고있겠지만 Position - LightPosition과 LightPosition - Position의 차이는 명백하다.
사실 무었을 하든 상관없다. 이 감산연산의 결과만 제대로 이해하고 있다면.
첫번째 식인 정점의 위치 P에서 광원의 위치 LP를 빼면 LP 에서 P로 향하는 벡터가 만들어 진다.
반대로 LP - P라면 정점 P에서 광원 LP로 향하는 벡터가 만들어 진다.
벡터는 크기와 방향을 가지기 때문에 이 방향을 제대로 아는 것이 상당히 중요하다.

float3 lightDir = Output.mPosition.xyz - gWorldLightPosition;
lightDir = normalize(lightDir);
이 식대로 계산하면 LP에서 P로 향하는 Light Vector을 만들 수 있다.
L(빛의방향 벡터)와 N을 내적해야 하는데 그러기 위해서는 두 벡터의 밑동이 서로 만나야 한다.
하지만 현재 L은 방향이 반대다. 즉 밑동이 광원의 위치이고 머리가 정점의 위치다.

Output.mDiffuse = dot(-lightDir, worldNormal);
그래서 이렇게 내적시 -을 곱해주어 방향을 바꾼다.

그리고 가끔 빼먹는 저 normalize !!.
욘석은 은근히 많은 사람들이 "그냥 올바른 값을 만들기 위한 초기화"정도로 이해하고 있는사람이 많지만
사실 내적의 공식을 들여다보면 반드시 필요한 "정리"다.
내적공식인 dot(A,B) = Cos각도 * A의 길이 * B의 길이로 정리할 수 있는데 두 벡터인 A와 B의 길이를 1로 만들어 주면
dot(A,B) = Cos 각도가 되서 단지 A벡터와 B의 벡터의 내적만으로 간단히 cos의 각도를 구할수 있다는 것이 핵심!
저 normalize가 바로 벡터의 길이를 1로 만들어 주는 정규화를 해 주는 녀석이다.
벡터의 길이가 1인 벡터를 단위벡터라고 말하는데 단위벡터를 직접 구하는건 그다지 어렵지 않다.
단위벡터 = 벡터 / 벡터의 길이.
즉 (1,2,3)의 벡터가 있다면 이 벡터의 길이는 sqrt(1*1 + 2*2 + 3*3) 가 되고
단위벡터는 (1,2,3) / sqrt(14) 가 된다.
사실 여기까진 몰라도 된다. 다만 내가 내가 무의식적으로 적어주는, 또는 빼 먹는 저 normalize가
결코 가볍지 않은 일을 한다는 것을 말하고 싶었다.

추가적으로...
 

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


wrapped diffuse

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

energy conserving wrapped diffuse
float magicNmuber = 0.5;  
float NdotN = dot(-lightDir,worldNormal);
Diffuse = saturate( (NdotN+magicNmuber) / ((magicNmuber+1)*(magicNmuber+1))); 


렌더몽키 파일 : 


 

'RenderMonkey' 카테고리의 다른 글

Shadow Mapping  (0) 2014.01.11
fresnel  (0) 2013.10.22
normal mapping (법선 매핑)  (2) 2013.02.07
기본적인 반사 벡터  (0) 2013.02.02
lambert  (3) 2013.01.31
흐음...렌더몽키라..  (0) 2013.01.29
Posted by 붕대마음

댓글을 달아 주세요

  1. 2013.02.13 11:49 김성준  댓글주소  수정/삭제  댓글쓰기

    자세한 설명 감사합니다~^^ 한가지 어리석은 질문을 드리겠습니다. 포프님의 책에서도 LightVector를 구하기위해 정점의위치(VP) - 라이트위치(LP) 로 해주고, 나중에 내적을 구할때 -Lightvector 이런식으로 "-"를 붙이는데, 이런식으로 하지않고, LP - VP 로하고 dot(LightVector, worldNormal) 이런식으로 하면 안되는것일까요?

    • 2013.02.13 16:19 신고 붕대마음  댓글주소  수정/삭제

      상관없습니다. 나중에 내적연산할 때만 제대로 설정해 주면 됩니다.
      벡터는 점A - 점B 라고 하면 이 벡터의 방향은 점B에서 점A로 향하는 벡터가 됩니다. 내적연산할 때 인자로 넣어주는 벡터가 어느방향인지만 알고 제대로 넣어준다면 상관없습니다. 벡터 내적시 벡터의 방향이
      왜 중요한지는 내적공식을 보면 알 수 있습니다.
      참고로 렌더몽키나 UDK가 깔려있으시다면 한번 테스트 해 보세요.
      렌더몽키 파일은 올려두었습니다.

  2. 2013.02.13 17:40 김성준  댓글주소  수정/삭제  댓글쓰기

    아~ 그렇군요 감사합니다~^^

2008년도 이후로 업데이트가 없는 렌더몽키....
분명 잘 만든 녀석인데 계속 서비스를 안해줘서 안타깝다.
몇번 살짝씩 사용해 본 게 다 이긴 한데 요즘 다시 욘석을 꺼낸건
안드로이드에 게임 쿠킹 탐이 너무 길어서....ㅜㅜ.
udk는 너무 마니 잡아먹고 간단하게 테스트만 해보는 것으로 욘석 만한게 없는듯 하다.

http://developer.amd.com/wordpress/media/files/RenderMonkey.2008-12-17-v1.82.322.msi
요기에서 다운 받을 수 있다.

'RenderMonkey' 카테고리의 다른 글

Shadow Mapping  (0) 2014.01.11
fresnel  (0) 2013.10.22
normal mapping (법선 매핑)  (2) 2013.02.07
기본적인 반사 벡터  (0) 2013.02.02
lambert  (3) 2013.01.31
흐음...렌더몽키라..  (0) 2013.01.29
Posted by 붕대마음

댓글을 달아 주세요

최근에 달린 댓글

최근에 받은 트랙백

글 보관함