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

카테고리

전체목록 (663)
참고사이트 (8)
Goal (4)
Travel (10)
My Life (102)
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)
Total338,297
Today6
Yesterday63

'RenderMonkey'에 해당되는 글 2건

  1. 2013.02.02 기본적인 반사 벡터
  2. 2013.01.31 lambert (3)


반사벡터에 대한 이론과 언리얼 에디터에서 반사벡터 만들기는 요 링크를 참조하면 된다.
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 붕대마음

최근에 달린 댓글

최근에 받은 트랙백

글 보관함