지금까지 텍스쳐를 쓰면서, 정말 중요하지만 사용하지 않았던 기능이 있다.
바로 알파다... 게임에서나 그래픽적으로나 알파의 표현은 중요하다.. 일단 알파가 뭔가 하면, 투명도를 말하는 것이다. 우리가 유리를 표현하고 싶을 때, 그 유리가 진한 불투명인지 거의 있는 듯 없는 듯한 투명인지를 표현할 때 바로 알파를 사용하는 것이다..

 일단 알파텍스쳐를 사용하려면, 말 그대로 알파 정보가 있어야된다. 그 것을 알파채널이라고 하는데, tga, png, dds 정도가 게임에서 많이 쓰는 알파용 텍스쳐 파일들이다. 다른 bmp, jpg 등은 알파를 표현할 채널이 없기 때문에, 알파를 쓰려면 저 3가지 중에 한가지를 쓴다. 

 그렇다면, 알파는 어떻게 표현이 될까.. 쉽게 생각하면.. 일단 알파로 표현될 부분의 뒷부분 픽셀의 색과 섞으면된다.. 그 것이 바로 알파 블랜딩이다. 쉽게 생각해 보면 된다.. 결국 반투명이라는 것은, 우리가 앞의 가린 물체 뒤의 물체가 보이는 것이다. 그런데 투명도가 높을수록, 거의 제 색에 가깝게 보이지만, 투명도가 점점 낮아질수록, 뒷 색이 선명도가 떨어지면서, 만약 그 유리가 색이 있는 색 유리라면, 그 색도 절반정도 보이는 형태가 될 것이다. 그것이 단순한 (픽셀 + 픽셀 ) / 2 연산으로 가능하다는 것이다.. 그럼 소스를 보자..


위의 텍스쳐가 바로 알파가 들어간 텍스쳐이다. 저 격자무늬부분이 빈공간을 나타내는데, 텍스쳐 가운데에 하늘색부분이 격자무늬가 절반정도 보이는 것을 알 수 있다. 이 텍스쳐는 tga 파일로 만들어졌다.


일단 기본적인 버텍스 정보나, 텍스쳐 로드는 다 똑같다. 랜더링 내에서만 조금 틀리다. 먼저 RenderState에서 알파블렌딩Enable을 True로 바꿔준다. 그리고 아래 2개도 똑같이 만든다.


그런데.. 조건에 sort어쩌고.. 라는 부분이 있다. 이 것이 무엇인고하니.. 알파가 아까 말했다시피 뒷색과 앞색을 섞는 것이라면... 뒷부분이 그려져야.. 제대로 알파색이 나오게 된다. 하지만.. 우리가 하는 랜더링은 사실.. 앞에서 부터 그린다.. 그러면 이미 결정나 버린 곳은 다른색을 칠할 필요가 없으니 연산량이 줄어들기 때문이다. 하지만 알파만은 반대로 그려야 하는데, 그래서 나온것이.. 알파 부분은 2번 그리면 된다.. 뒷면 한번, 앞면 한번.. 이 뒷면 앞면이 무엇인가 하면.. 컬링의 문제다.. 컬링이 화면에 보이지 않는 뒷면을 제거하는 것이긴한데.. 엄밀히 말하면.. 뒷면을 판단을 어떻게 할 것인가를 선택하는 것 뿐이다.. 우리가 만드는 3d 모형의 기본은 폴리곤. 즉 삼각형이다. 삼각형은 3개의 점을 가진다.. 하지만.. 그 3개의 점은... 순서가 없다.. (index버퍼를 순서라고 생각할 지 모르지만.. 그 것은 그냥 점 3개를 찍는 것일 뿐 여기의 순서와는 의미가 다르다.) 그래서 컬링 모드에 따라서.. 오른쪽으로 돌지, 왼쪽으로 돌지를 판단하여, 여기가 앞인지 뒤인지 결정한다.
 오른쪽으로 도는 것과 왼쪽으로 돌지가 무슨 상관이냐고 생각하겠지만, 그 방향에 따라 연산을 하면, 노멀방향이 반대가 되어, 앞 뒤를 판단할 수 있다.

 


그럼 위를 보면, D3DCULL_CW가 있다. 이 것이 우리가 현재 생각하는, 안보이는 뒷면을 그려준다. 그리고 CCW가 우리가 보는 부분을 그리고 뒷면을 컬링 시켜준다.


그리고 알파를 쓰더라도.. 컬모드를 NONE을 주면.. 좀 이상한? 현상을 확인할 수 있다.. 그 것은 잠시뒤에 실제로 보자..


그리고 모든 조건을 끄고 그리면.. 그냥 불투명텍스쳐가 된다.


이 것이 실행한 모습이다. 현재 제대로 알파값이 들어가서 나온다.

 

그리고 이 것이 알파 블랜딩을 끈 모습이다.. 그냥 자기만의 불투명 텍스쳐가 나온다...


그리고 이 것이 바로 아까 말했던 이상한 현상이다.. 분명히 지금.. 알파는 먹혔다. 하지만.. 뒷면이.. 없다.. 그 것이 바로 컬링으로 제거된 부분이 제대로 연산에 반영되지 못했기 때문이다. 이제 큐브를 돌려보자..


이 번에는 어떤 면은 보이고 어떤 면은 안보인다.


이 것이 돌릴 때 마다.. 계속 바뀐다. 이렇듯 뒷면의 유무에 따라서 알파 적용이 제대로 안될 수도 있는 것이다.
Posted by 바람처럼..
|

 DirectX의 텍스쳐 맵핑에 관해서는 이전 포스팅으로 충분히 배웠다. 그렇다면, 만약, 한 폴리곤 내의 맵핑을 2~3개 만들 수는 없을까? 그 것이 바로. MultiTexture를 이용하면된다.
 개념도 간단하고, 바로 예제로 넘어가 보자.


먼저 버텍스 설정이다. 보면, 텍스쳐uv가 2개가 있다. 바로 텍스쳐를 2장 사용할 것이기 때문이다. 저 Flags를 보면 TEX2를 쓰는 것도 같은 이유다. 현재 8장까지는 지원가능하지만, 보통 그렇게 많이는 쓰지않고, 3~4장만 쓴다.


loadTexture에서는 역시 2가지 텍스쳐를 다 로드한다.


랜더의 스테이트 설정 부분이다. 각 스테이지 별로 설정을 따로 해준다. 그리고, 아래쪽의 STAGE2 부분에서는 Modulate를 설정하는 부분이 있는데, 색을 섞을 때, Modulate로 설정하면, 각 픽셀의 색을 곱하고, 그 외에 Add를 선택하면, 더하기 연산으로 픽셀의 색을 정한다.


스테이지 1과 2가 내용이 다른 것을 알 수 있다. 그리고 스테이지 0에는 처음 색은 TEXTURE를 쓰고, 스테이지 1에서는 CURRENT를 쓴 것이 다르다.


그리고, SetTexture에 0번에는 0번 텍스쳐, 1번에는 1번 텍스쳐를 설정하면된다.


그러면 2가지 텍스쳐가 합쳐져서 나오게 된다. 지금 이 것이 Modulate 된 것이고,

 


이 것이 Add된 것이다. 같은 텍스쳐라도 설정에 따라 다르게 표현되는 것이 재밌는 점이다..
Posted by 바람처럼..
|

 얼마전 포스팅에서 텍스쳐의 필터링 속성을 설정에 대해 적다가, 밉맵은 따로
설명하기로하고 건너뛰었는데, 그 이유가 바로 간단히 설명하기에는 너무 양이 많기 때문이였다.
 일단은 mipmap이 무엇인지에 대해서 부터 알아봐야 할 것이다. mipmap이란 이전에 texture가 실제 해상도에 비해 커지거나 작아졋을 때에 사용하는 필터링 옵션과 더불어 사용되면서, 역시나 확대 축소가 되었을 때 어떤식으로 동작하는지에 대해서 기본적으로 설정을 줄 수 있었다. 헷갈리지 않게 텍스쳐에서의 픽셀에 맵핑될 범위를 텍셀이라고 한다. 그렇다면, 텍셀의 크기가, 실제로 화면상의 픽셀과 맞아지면 상관없지만, 크기가 작거나 클수도있다. 텍셀보다 픽셀이 크면, 텍셀을 늘려서 픽셀을 채우면되고, 반대로 텍셀의 크기보다 픽셀이 작으면, 즉, 텍셀의 색 수보다 픽셀이 더 적어서 그 픽셀들에 맵핑되려하면 주변의 텍셀의 색들이 합쳐진 평균값으로 그 작아진 픽셀범위에 맵핑된다. 이것을 방지하기 위해서 사용되는 것이 바로 mipmap이다. 
 이렇게 설명만 들으면, 텍스쳐의 최소, 최대 시의 옵션과 무엇이 다르지.. 라고 생각 할 수 있다. 하지만 mipmap의 가장 큰 특징은 바로... 실제 텍스쳐크기가 512 라면 (가로x세로가 같다고 보면) 그것의 절반크기인 256, 128, 64 이런식으로 1x1이 될 때까지의 텍스쳐를 미리 가지고 있다가, 실제 텍셀보다 거리가 멀어져서 맵핑될 픽셀이 줄면, 더 해상도가 낮은 텍스쳐를, 그 보다 더 작아지면 더 작은 텍스쳐를 불러서 맵핑을 시켜준다..
 그럼 이렇게 생각할 수도 있다.. 그게 결국 최대, 최소 옵션과 뭐가 다르냐고... 머.. 그냥 보면 결과적으론 크게 다르지 않다. 하지만, 랜더링 시의 연산양으로 보면, 큰 차이가 있다. mipmap없이 확대 축소 옵션(물론 이렇게 텍스쳐를 가지지 않는 mipmap 옵션도 효과는 생긴다.) 으로 하는 것보다. 미리 줄여놓은 텍스쳐를 가지고 있으면, 실제 작은 크기에 맵핑을 할 때 아까 말했던, 평균을 구하는 연산을 생략할 수 있다.. 이 것이 바로 큰 연산의 이점을 가져오는 것이다.
 3d에서 그냥 기술만 있으면, 바로 구현하면 되지 머 라고 생각할 수 있지만, 사실 3d에서 가장 중요한 것은 부하를 줄이는 것이다. 오늘날에야 옛날에 비해서 하드웨어가 발전했기 때문에, 신경을 쓰지 않아도, 큰 차이가 없을지 모르지만, 지금도 무거운 연산량을 가진 연산을 2개 3개 합치면 30프레임 이하의 프레임이 나올 수도 있다. 이런 연산을 줄이기 위한 기법이 바로 mipmap인 것이다.

이제 mipmap이 무엇인지는 알았으니, 어떻게 사용되는지 알아봐야할 것이다..


이 것이 준비된 텍스쳐들이다..각자 크기별로 색이 다르다.


일단 예전에는 텍스쳐를 한장만 로드했지만, 현재는 보면, 256 부터 8까지 차례대로 6장을 로드한다. 물론 4, 2, 1 도 있어야하지만, 예제에서 없어서 생략이다. 그리고 베이스가 되는 256 싸이즈 텍스쳐를 로드하고,


텍스쳐의 levelCount를 얻어온다. 그러면 9라는 값이 리턴된다. 즉 현재 있는 6장 외에 아까있던, 4, 2, 1 의 장수를 다 얻어온것이다. 하지만 이번 예제에 테스트는 6장만 가지고 하기 때문에, 현재 얻어온 6장의 텍스쳐를 가지고, 각자 사이즈에 맞는 표면레벨에 8 까지, 텍스쳐 메모리를 복사한다.
 위의 색깔이 텍스쳐 크기이다.


이전에 확대 축소 옵션도 있고,

 


이것이 mipmap 옵션이다. mipmap 옵션에 따라 동작을 안할 수도, 눈에 띌수도, 부드럽게 연결 될 수도 있다..


랜더에서 들어오는 셋팅값을 변화 시켜줄수도 있다. 저기 mipmap용 변수값을 바꿔주면, 효과의 강도가 틀려진다. 어쨋든, 이상태로 컴파일하면, Linear 상태로..


이렇게 부드럽게 나온다.. 즉.. 거리에 따라서 각자 자기에 맞는 밉맵 레벨의 텍스쳐를 맵핑 한 것이다.

 

이 것은 None 상태로 mipmap이 적용이 안되었다.


이 것은 Point형으로 확실한 경계가 보이는 것을 알 수 있다..


카메라를 움직여서 가까이 가보면, 거리에 따라 색이 틀려지는 것을 볼 수 있다.


그리고 다시 뒤로 쭉 나오면, 거의 제일 작은 텍스쳐 색만이 나오는 것을 알 수 있다...

그리고 보통, D3d로 게임을 만들 때 쓰는 텍스쳐 포멧이 dds 이다. dds포멧이 편한 이유중에 하나가 이런 밉맵설정 부터, 아까 봤던 level을 쓸수있어서, 볼륨텍스쳐라는 것도 사용하기가 매우 용의하다. 그리고 메모리에 올라가는 속도상의 이득도 많다.

어쨋든 이번 예제는 mipmap이 어떻게 적용되는지 보기 위해서, 이렇게 색이 다른 텍스쳐를 썻지만, 실제는 쓸일이 없으니, 앞으로는 그냥 개념만 이해하고 사용하면 될 것이다..

 

 

Posted by 바람처럼..
|

 지난번 포스팅에서는 텍스쳐 필터링을 배웠다. 이 것은 화면과 텍스쳐 해상도 간의 차이가 생길 때 어떤식으로 표시할 것 인지에 대한 옵션이였다..
 그렇다면 어드레싱은 무엇일까에 대해 알아보면, 전에 텍스쳐에 관해 설명할 때 텍스쳐는 버텍스와 매칭이 되는 UV 좌표가 있다고 했었다.. UV의 좌표는 0~1.0 사이라고 말했는데, 꼭 그럴 필요는 없다.. - 값이 나올수도 있고, 2.0,3.0 처럼 계속 커질 수도 있다...
 이런 좌표는 어디서 쓰냐면, 타일맵이라던지, 넓은 지형에서 보통 uv 좌표를 딱 0~1.0에 고정시키지 않고, 늘려서 만드는 경우가 많다.
 텍스쳐 어드레싱은 바로 이렇게 UV좌표가 0과 1 사이가 아닌 다른 값이 되었을 때, 어떻게 표현될 것인 지를 결정하는 옵션이다.. 이 부분은 간단한 부분이니까, 설정하는 방법과, 효과만 확인하도록 하자..


먼저 제일 위를 보면 Border 색을 정한다. 현재는 보라색이다. 이 색은 나중에 Border옵션에서 사용된다.


그리고 버텍스 좌표 뒤에 UV 좌표에 3.0f 까지를 최대로 잡아놓았다. 즉, 사각형 버텍스들 안에 채울 텍스쳐를 0.0~3.0 까지 이기 때문에 1.0까지인 텍스쳐를 어떤 방식으로 채울지를 결정해주는게 바로 이번 포스팅에 핵심이다. 
 채우는 방식에는 WRAP,CLAMP,MIRROR,BORDER,MIRRORONCE가 있다.

 


그리고 함수를 보면 알겠지만, U와 V를 따로 설정해준다. 오른쪽으로는 u 아랫쪽으로는 v라고 보면된다..


D3D에서 정의된 옵션 내용은 하나가 더 있지만, 잘 안쓴다..

 


그리고 랜더 함수에서, 옵션이 바뀌면, 바뀐 함수를 호출하고, Border색을 설정하고, 그려주면 끝이다. 이제 각 장면이 어떻게 표현되는지 확인해보자.


이 것이 기본 그림이다. 기본 텍스쳐는 왼쪽상단의 5가 들어간 4가지 색 텍스쳐가 기본이지만, 현재 WRAP모드 이기 때문에 똑같은 텍스쳐를 다 채워놓았다. 이 옵션이 바로 타일맵이라고 보면된다. 흔히 던젼 같이 바닥의 텍스쳐가 같은 색으로 계속 채워지는 형태 말이다.


이 것은 현재 U 만 설정을 CLAMP로 바꾸었다. 그렇게 되면, 제일 끝 픽셀색으로 쭉 그려버린다.


이 것은 V 도 같이 바꾼 상태이다. 이제부터는 2가지를 모두 바꾸겠다.


MIRROR 옵션은 말그대로 거울 옵션이다. 즉 거울처럼 계속 반대로 찍고 반대로 찍는다.


그리고 위가 바로 Border옵션이다. 그냥 실제 텍스쳐 UV좌표 까지만 그리고 나머지는 그리질 않고, 지정해둔 색으로 그린다. 만약 아무색도 지정하지 않는다면, 검정색이 나온다.


그리고 MIRRORONCE 역시, 마지막 부분이 반대로 뒤집혀서 쭉 그려진다. 일단 다른텍스쳐로 테스트를 해보아야 하는지는 모르겠는데, CLAMP와 다른 부분이 확인이 안되었다.

이상의 옵션들로, UV 좌표를 벗어난 버텍스들에 대해, 어떻게 맵핑을 할 것인가에 대한 옵션을 살펴 보았다. 이 방식을 잘 활용하면, 적은 텍스쳐로도 효과적인 화면 표현을 할 수 있다는 장점이 있다.

 

 

Posted by 바람처럼..
|

 다이렉트X 든 opengl 이든.. 보통 3d 물체에 어떤 표면에는.. 텍스쳐를 입힌다. 물론 텍스쳐 없이 기본 빛 속성만 가지고도 색을 표현할 수도 있지만, 그 것은 간단한 물체일 때 가능한 이야기이고, 보통은 텍스쳐 맵핑을 한다.
 하지만 여기서 생각해 볼 수 있는 것이.. 텍스쳐는 멀리있으나, 가까이 있으나, 해상도가 같다... 미리 만들어 놓은 256x256이든, 512x512 이던 같다는 의미이다. 그렇다면.. 카메라의 위치에 따라 정말 가까이 있는 물체와 저 멀리 있는 물체의 실제 화면에 나타나는 면의 크기는 달라진다. ( 프로젝션 연산의 원근 비율로 인하여) 그렇다면, 같은 픽셀양의 해상도를 가진 텍스쳐가.. 넓게, 혹은 좁게 면안에 색을 채워야 하는데.. 그럴때 설정해 주는 것이 필터링이다.

 필터링에는 크게 4가지 종류가 있다. (물론 실제로는 6가지이지만, 보통 4가지만 쓴다. )



이렇게 4가지를 enum으로 정의해서, 현재 예제에서 값이 바뀔때 마다 넣어준다. 실제 D3D의 define 값은.. 6개 이다.


피라미드 형태와 가우시안 필터가 있지만, 보통은 잘 쓰지 않는다.
그럼 필터를 설정하는 부분을 보자..


loadTexture의 내부를 보면, 파일을 로드한 이후에 샘플러스테이트를 설정해준다. 거기서 3가지를 설정하는데, 현재는 모두 NONE 즉 아무 필터링 옵션 없이 라는 뜻이다. 저기서 MIN, MAG, MIP 필터가 있는데, MIN은 텍스쳐가 실제 해상도보다 축소되었을 때, MAG는 실제 해상도보다 확대 되었을 때, 그리고 MIP필터는, 밉맵인데, 이 필터는 나중에 따로 설명해야 하겠다.


그리고 랜더 함수에서 필터가 변했다면, 새로 옵션을을 설정하는 부분을 만들어뒀다. 그리고, 샘플러스테이트에서, 최대Anisotropy 값을 설정해 줄 수 있고, 밉맵의 수치도 설정해 줄 수 있다.


위의 사진은 각 부분 함수들의 모습이다..

그럼 실제로 화면을 보면서 어떻게 변하는지 보도록 하자...


이 것이 기본 실행 화면에, 아무 설정도 없는 상태이다.


위의 3장을 보면, MIN필터의 값을 계속 바꾸지만, 변화가 없다. 왜냐하면, 아직 해상도보다 줄어 들지 않았기 때문에, 바뀔 값이 없기 때문이다.


이제 mag 필터에서 point와 Linear로 설정해 보았다.

 


하지만 아직 까진 크게 바뀐거 같지 않다... 그래서...


확대를 해서 보았다...먼저 아무것도 없는 상태...


그리고 포인터..


그리고 Linear다. Anistropic 은 아직 수치 조절을 많이 하지 않으면, 거의 Linear와 흡사한 느낌이다. 먼가 Point와의 차이는.. 약간 뭉게진 듯한. .그런 느낌이 든다.. 그렇다.. 텍스쳐를 화면에 입히다 보면, 케릭터같이 디테일을 요하는 텍스쳐가 있는 반면에, 지면 같이 약간 뭉개진 흙바닥이 더 사실감있어보이는 경우가 있다.. 그럴 때 이런 필터 옵션을 주면 되는 것이다..


위의 3장은 Min필터의 None, Point, Linear 옵션이다. 사진상으로는 티가 잘 안나는데, 실제로 화면으로 보면, 역시나, Point는 각 픽셀이 선명하게, Linear는 뭉게져서 나온다..

머, 옵션이 별로 필요없어 보일 지도 모른다. 하지만 아까 들었던 예들처럼, 상황에 따라 어떤 필터링 옵션을 주고 안주고에 따라, 화면의 디테일이 더 살아난다는 점을 생각해야하겠다.
Posted by 바람처럼..
|

 이제 직접 띄운 3D 물체를 한번 공간 상에서 움직여 보자.. 초반 포스팅에 보면, 랜더링 파이프 라인에 대해 적었는데, 이제야 로컬 좌표에 기본 0,0,0에서의 물체만 보는 것이 아니라, 월드 뷰 행렬에 대입하여, 물체를 움직여 볼 수 있겠다...


먼저 이 예제는 태양, 지구, 달의 자전과 공전에 대한 예제이다. 그러니 메쉬가 3개 필요하다. 구체이기 때문에 CreateSphere 로 구를 만들 것이다. 그리고 특별히 텍스쳐는 로드 하지 않는다.


버텍스 포멧으로는, 포지션 값과, 디퓨즈 색 값을 받도록 설정한다..


그리고, 먼저 Temp 지구와 태양을 선언하여 그 변수를 이용해 구를 만드는데, 그 이유는 색을 적용시키기 위해서이다. 지금 CreateSphere로 구를 만들면, 색 정보가 없는 버텍스 버퍼로 구를 만들었을 뿐이다. 그렇기 때문에 색 정보를 주기 위해서 미리 템프로 만들고, 색 정보 FVF 포멧을 가진, 클론 메쉬를 만들 것이다. 텍스쳐 맵핑을 하려면, 이 단계를 생략하고, 바로 지구와 태양 메쉬를 구로 생성한 후에 로드한 텍스쳐를 맵핑하면 될 것이다.

 


이제, 생성된 템프지구 메쉬로 클론메쉬FVF 함수를 통해,  우리가 정의한 FVF 포멧을 가진 구 메쉬를 생성한다. 마지막에 짤린 변수는 초기에 선언해준 메쉬이다. 그 후에 지구 메쉬의 버텍스 버퍼를 없어온 후, 그 버텍스 버퍼에 락을 걸로, 디퓨즈 색을 파랑으로 설정하고 언락을 한다.


태양 메쉬도 같은 방법으로 하여, 노란색 디퓨즈 값을 넣어준다.


이 것 외에는 설정할 것이 없기 때문에 바로 랜더로 와서, 일단 뷰 행렬을 넣어준다. 현재는 고정이지만, 키보드 인풋이나 머 다른 요인으로 저기 제일 첫 Vector3의 값을 조정하면 위치가 바뀌고, 2번째 값은 바라보는 방향, 3번째는 업벡터이다.

카메라 값에 왜 포지션, 룩엣, 업 벡터가 필요한지는 이전에 말했으니 넘어가도록 하고, 그렇게 정해진 값을 SetTransform하여 D3DTS_VIEW에 넣어준다. 기본적으로 우리가 넣는 행렬값은 월드, 뷰, 투영 인데.. 사실 투영은 초기화 할 때 한번 넣어주면 잘 변하지 않는다. 뷰는 카메라 이동이 있을 때 넣어주기 때문에, 루프가 돌 때 마다 위치나 보는 방향을 갱신해서 넣어주면된다..


 이제 실제 메쉬에 행렬을 곱하는 부분이다. 태양을 Y축으로 회전 시키고, Scale을 크게 늘렸다. 그리고 곱하는 순서는 스케일 회전 순이다.. 3D의 행렬 연산에서는 곱하는 순서가 아주 중요하다. 왜냐하면 뒤의 연산에 앞의 연산이 영향을 받기 때문이다. 잘 감이 오지 않는다면, 그냥 기본적이던 모델링이 우리의도에 따라 어떻게 변하는지 생각하면된다. 보통 스케일, 로테이션, 트렌스레이션 순으로 크기, 회전, 이동 순으로 곱하게 되는데, 그렇게 되면 모델링은 로컬 좌표를 기준으로 크기가 커지고, 거기서 회전을 한 후 우리가 정한 월드 좌표로 이동을 한다.

 하지만 그 순서가 바뀌어서 회전을 한후, 스케일이 커지고, 이동을 하면.. 머 영향이 없을 수도 있다. 스케일은 보통 x,y,z 같은 크기로 키우기 떄문이다. 하지만 한 축이라도 같은 값이 아니라면, 뒤의 연산에 큰 영향을 끼친다. 그리고 사실 회전과, 이동 이 큰 영향을 받는다.. 순서가 중요한데, 이동하고 회전을 하느냐, 회전을 하고 이동하느냐 이 것에 따라 결과가 크게 차이난다..

 


그럼 먼저, 지구를 보자, 행렬을 Y 축 회전 행렬 2개, 위치 이동 행렬 1개, 이렇게 있다. 우리는 먼저 생각할 것이, 이 이동 행렬의 영향은 로컬 좌표계를 기준으로 이루어 진다는 점이다. 처음에 메쉬가 생성되게 되면 로컬 0,0,0을 기준으로 우리가 만든 버텍스들의 각 좌표가 월드상에 펼쳐져 있게 된다. 그 상태에서 위의 회전 부터 곱해진 순서대로 생각해보자...

 먼저 자전 회전을 하였다. 그렇게 되면, Y축을 기준으로 일정양만큼 회전하였다. 제자리에서 어느정도 각도록 돌았을 것이다. 그 다음에 위치 이동이다. 로컬 좌표를 기준으로 z축방향으로 12 만큼 갔을 것이다.. 여기서 예제를 돌리면서 깨달았는데, 나는 처음에 돌리는 Y축회전이 공전일 줄 알았다. 그래서 축 자체가 돌아갔기 때문에, 그 후에 내가 원하는 방향으로 한번만 이동한후, 또 한번 더 돌면 그 때 자전이 될 거라고 생각했었다.. 그런데 행렬을 하나씩 제거하면서 연산을 하자, 마지막 행렬이 빠지면, 제자리에서 자전만 되었다.  
 즉, 로컬축이 돌아가는 것이 아니라, 회전 행렬을 그냥 로컬 축의 기준점을 기준으로 회전 이동 의 영향만 준다는 사실이다. 항상 쓰면서도, 헷갈렸는데 ( 물론, 대부분 의도한 대로 잘 움직였다. s*r*t 순의 연산 순서만 지키면 말이다.. )
오늘 제대로 정리한 것 같다. 

 다시 돌아와서, 제자리에서 Y축 회전 후, z축 방향으로 12만큼 이동 후 다시 Y축회전이 되면 바로, 로컬의 기준 점을 기준으로 z축으로 12만큼 가있기 때문에, 제자리 회전이 아니라 콤퍼스의 끝 점 처럼 원을 그리면서, 쭉 돌게 된다. 그래서 태양 주변을 도는 공전 회전이 완성 되는 것이다.


그리고 위의 것은 달의 모습이다. 이 것은 한번의 연산이 더 있다. 바로, 지구의 행렬 값도 받은 상태에서 움직이는 것이다. 그래서 처음 3개의 행렬의 연산 만으로는 그냥 달이 태양 주변에서 공전을 했겠지만, 공전이 된 상태에서 다시 지구의 위치 좌표로 움직이고, 그 다음에, 지구가 돈 방향만큼 더 돌기 때문에, 달이 지구를 도는 속도는 지구가 태양을 도는 속도보다 빠르게 되는 것이다.

컴파일하면 다음과 같다...

 



계속 태양도 돌고, 지구도 돌고, 달도 돈다. 그리고 지구는 태양 주변을 돌고, 달은 지구 주변을 돈다...

물론 처음보면, 이 개념자체가 잘 이해가 안될 수도 있다.. 물론 나도 처음엔 그랬다. 하지만 자꾸 손으로 왼손 좌표계를 만들고 회전을 적용시켜보면 언젠가는 행렬의 연산이 쉬워지는 날이 올 것이다.
Posted by 바람처럼..
|

 이제 기본적인 vertex를 이용한 물체 생성을 알아 보았다.
 하지만 이전의 예제들을 보면, 한 가지 의문이 들 수도 있다.. 그 것은 바로...
 공통된 버텍스 좌표인데, 각자 다 따로 선언하여 사용했다는 것이다..

 그렇다 생각해보면, 정육면체 큐브를 만드는데에는 24개의 점이 필요없다. 이 것은 한 면은 4개의 버텍스라는 기본적인 생각에서 x6을 하면 24가 되는 것이지, 겹치는 점을 생각하면, 8개면 충분하다..

 바로 이런 겹치는 점을 없애기 위해서 나온 개념이 바로, 인덱스 버퍼 이다. index라 함은 말 그대로 순번이 메겨져 있다는 것이다.

 즉, 꼭 필요한 만큼의 버텍스를 선언해 놓고, 그 것에 0~n 개의 번호를 부여한다고 치고, 그 n 개 까지의 번호중 현재 필요한 3개의 점을 순서대로 잇도록 배열한 인덱스만 있으면, 똑같은 큐브를 8개의 점만 가지고, 그릴 수 있다..

 머 이렇게 큐브만 놓고 보자면, 사실.. 큰 필요가 없어 보일 지도 모르겠다. 하지만 많은 폴리곤들이 겹치는 하나의 모델링을, 모든 버텍스의 정보를 다 입력하는 것과, 사용되는 점들에 인덱스를 매겨서 가지고 있다가, 인덱스로 그리는 것은 엄청난 버퍼 용량의 이득을 가져온다..

 이 번 예제는 그냥 버텍스버퍼로 그리는 것과 인덱스 버퍼로 그리는 것에 결과가 같음을 보여주기 위한 예제이니, 인덱스 버퍼 위주로만 보겠다..


일단 버텍스를 선언하는데, 인덱스 용으로 따로 하나를 더 했다는 것을 알 수 있다. 그리고 인덱스 정보가 저장되어있는 인덱스버퍼를 선언했다. 그리고 이제는 멀티플 버텍스가 아니기 때문에, 그냥 구조체에 모든 정보를 넣었다.


이 것이 큐브를 그리기 위한 버텍스이다. 뒤의 주석을 보면 알겠지만, 사실 쓰는 꼭지점의 위치는 0~7까지.. 밖에 없다.. 하지만 계속 반복이다.. 아래의 5번 6번 면도 더 있다... 그 것을 인덱스 버퍼로 그리게 되면,


고유의 8개의 버텍스 정보와, 어떤 순서로 그릴지 하는 인덱스 정보만 남는다.

 


그리고 버텍스 8개만큼의 공간을 선언하고, 인덱스의 갯수는 24개 이니까 24개의 공간만큼 (WORD로 선언되었으니까 2byte 만 쓴 것이다. 실제로 저 구조체 자체를 24개를 썻다면, 현재 차이가 나는 16개에 곱하기 16바이트만 해도 엄청난 차이이다. ) 선언해준다.


해제도 물론 다 해줘야한다.


그리는 법은 인덱스 쪽만 보겠다. 먼저 스트림에 인덱스 버퍼의 버텍스 정보가 들어있는 버퍼를 넣어주고, Set인덱스에다가 인덱스 버퍼를 넣어준다. 그리고 FVF 정보도 넣어주면된다.
 그리고 나머지는 DrawIndexedPrimitive로 그려주면된다. 중간에 Indexed가 추가되었다.. 그렇게 하면,


 


인덱스 버퍼 만으로, 이런 큐브들이 만들어 진다..
Posted by 바람처럼..
|

이전 포스팅에는 하나의 버텍스 버퍼를 가지고, 모든 정보를 담아서 그 것으로 물체를 그리는 방법을 보았다.. 하지만 다이렉트x 에서는 여러가지 정보를 합성하는 방법이 있다. 이번에는 그 차이점만 확인해 보도록 하자...


 변수 선언 부분을 보면, 예전에는 define으로 버텍스 버퍼의 종류를 정했지만, 그런 부분이 없다.. 그리고 선언할 때 어떻게 다른지는 조금있다가 살펴보자.. 그리고 버텍스디클레이션 이라는 변수가 선어되었다. 이 것은 나중에 어떤식으로 버텍스 버퍼를 합칠 것인지 선언해주는 것이다. 
 그리고 아래에 보면 버텍스 정보를 담는 배열이 있다..


그리고 그 아래에는 색을 담는 버퍼,


그 다음은 텍스쳐로 각각 따로 배열을 선언한다.


그 다음 초기화 되는 부분으로 내려오면,


위와 같이 각 버텍스 버퍼를 각각 생성하는 것을 알 수 있다. 그리고 이전에는 3번에 인자에 define 해놓았던 FVF 상수를 넣어줬지만, 현재는 그냥 0이 들어간다..

 아 FVF에 대한 설명이 빠졌는데, FVF란 Flexible Vertex Format 의 약자로, 머 직역하면 유연한 버텍스 포멧 정도의 의미인데, 정확히는 자신이 의도하는 vertext 포멧을 정해서 쓴다는 정도의 의미이다.


 어쨋든 이렇게 컬러도, 텍스쳐 UV 버퍼도 모두 만들면...


버텍스 엘리멘트 배열에 값을 넣는다. 주석처럼, Stream번호 Offset, 그리고 타입 정보를 넣고, 버텍스디클레이션도 생성한다.


F12키로 따라가보면, 여러가지 설정이 있는 것을 확인할 수 있다.


그리고 꼭 해제는 잊지 말고 하기!


랜더에 와보면, 지난번과 달라진 점은 SetTexture이후에 버텍스디클레이션을 셋팅해주고, 지정된 스트림 순서대로 각 버퍼를 넣어준다. 그후에 큐브를 그리면..

 


이렇게 한 큐브에 같은 텍스쳐이지만, 색이 다른 큐브가 만들어진다...
사실 한 버텍스버퍼로도 다 표현되지만, 굳이 왜 이렇게 쓰냐고 의문이 들수도있다. 사실 DirectX는 랜더링용 엔진이라고 볼 수 있다..

 엔진이란 것이 무엇인가.. 바로 밑바닥의 api에서 그 것들을 좀 더 사용하기 좋게, 직관적이게 바꿔 사용하는 입장에서 더 쉽게 쓸 수 있도록 하는 것이다. 그래서 다이렉트x는 어떤 의미에선 실제 엔진들과 기본 api의 중간정도의 위치라고 볼 수 있다.

 간혹, 이런 경우를 생각해볼 수 있다. 랜더링 할 때마다, 색을 바꿔 적용해야하는 경우가 있다고 말이다.. 예를 들면, 리니지에서 독이 걸린상태, 이런 경우, 맵을 하나 더 쓰는 것보단, 그냥 강제로 전체에 한 색을 입혀버리면 더 리소스도 줄이면서 편하지 않을까?

 이렇듯 DirectX에는 이 것이 정답이라는 것이 없다. 그 것을 응용해서 사용하는 사용자의 능력에 달린 것일 뿐.....
Posted by 바람처럼..
|

 지난번 에도 말했지만, 3D에서 가장 중요한 것은 바로.. vertex.. 즉, 점이다.. 이 점이 바로 3D의 가장 기본적인 요소가 되는 것이다. 같은 위치의 vertex를 가지고 랜더링 하는 방식에 따라 어떻게 기록되는 지는 보았으니, 이제 vertex의 집합으로 하나의  큐브. (정육면체)를 띄우는 예제를 보겠다..

 먼저 선언에 관해서 보자..


먼저 Texture 변수를 선언한 것을 알 수 있다.. 이번 예제는 텍스쳐를 로드해서 버텍스에 적용시킨다. 텍스쳐란 바로, 겉을 덮는 그림이라고 생각하면된다.. 그렇다면, 과연 어떤 점에 텍스쳐의 어떤 부분을 넣을 지를 어떻게 정하는가가 중요하겠다... 그 것이 바로.. UV 좌표가 되겠다..

즉.. 현재의 버텍스와 그다음 버텍스 그다음 버텍스의 각 위치의 UV 좌표를 지정하면, 텍스쳐 상에 UV 좌표와 서로 매치를 시켜서, 텍스쳐의 그 면을 그 삼각형에 그려준다..  아래의 버텍스 배열을 보며 더 알아보자..


먼저 제일 위에 4개의 UV 좌표를 보면 먼저 (0,0) 부터 (1,0)(0,1)(1,1) 이렇게 모두 있다. 먼저 (0,0) 이라면 텍스쳐 상의 맨 왼쪽 상단의 픽셀이 된다. 그리고 1,0을 오른쪽 끝, 0,1은 왼쪽 최하단, 1,1은 오른쪽 최하단 이다.

 그럼 각 점의 위치로 4각형을 그리면, z는 현재 제외하고 생각하면...
1번이 -1,1 로 좌측 상단, 1,1로 우측 상단 -1,-1로 좌측 하단, 1,-1로 우측 하단 순이라는 것을 알 수 있다..

그렇기 떄문에 이 순서로 4각형을 그리면, 텍스쳐의 4면의 모든 부분에 딱 맞아져서 정확히 맵핑이 되는 것이다..

물론 실제로 각 UV의 좌표들은 맥스 상에서 모델러들이 모델링 작업을 하면서, 직접 좌표를 구해서 우리가 원하는 형태로 출력해서 주게 된다. 하지만 자기만의 포멧을 만들려면, 이런 부분도 알아야 가능한 것이다..


그 다음은 바로.. 적용시킬 텍스쳐를 로드하는 것이다. 다이렉트x에서는 함수를 지원하고 있으므로 바로 사용하면된다.. 첫번째에는 디바이스, 그다음은 로드할 텍스쳐의 파일명, 그리고 마지막에는 텍스쳐 변수다..

아래 샘플러스테이트에 설정해 준 것은 바로, 확대 축소시.. 어떤 형태로 할 것인가를 지정해 주는 것이다.. 예를들면, 텍스쳐의 픽셀 수는 정해져 있다. 그런데 3D상의 모델링은 카메라 뷰에 따라 화면좌표에 크게도, 작게도 나올 수 있다. 그렇게 되면 실제 맵 소스에 있는 픽셀량보다.. 많아질수도 적어질수도 있는데 그 때에 어떻게 늘릴지, 어떻게 줄일지를 결정하는 상태값이다. 보통 기본적으로 LINEAR로 해놓는다..


 그리고 초기화 부분에 가보면.. 이곳에서 텍스쳐를 로드 했다는 사실을 알 수 있다.. 그리고 CreateVertex버퍼를 보면 24를 Vertex라는 우리가 새로 정의한 구조체의 크기만큼 만드는 것을 알 수 있다.



이제 모든 초기화가 끝났으면 랜더로 가보자.. 위의 장면에 있는 것들은 큐브의 회전을 위해 있는 부분이므로, 별로 신경쓰지 않아도 된다.


이제 BeginScene 이후부터 살펴보면, SetTexture로 현재 그릴 텍스쳐를 설정하고, StreamSource에 버텍스 버퍼를 넣어준다. 그 후에 지정된 배열을 가지고 삼각형스트립 상태로 그려주면...

 


이렇게 우리가 의도한 텍스쳐가 입혀진, 사각형이 나타난다..
이 텍스쳐를 보면, 각 지점의 UV좌표가 opengl과 다이렉트x에서 어떻게 다른지 각 표시해 놓았다..
Posted by 바람처럼..
|


 지금까지의 예제들은 간단해서 다 옮겨 썻지만, 이제 각 부분의 역할들만 간단히 알아보자...

 이제 다이렉트X에 대해서 기본적인 셋팅에 대한 부분에 대해 알았다면, 이제 간단한 Geometry들을 그려보자...

 그러기 위해서는 과연 화면에 어떻게 어떤 물체가 뜨는지를 생각해 보자...

 3D 공간에서 가장 기본이 되는 단위는 점. 버텍스이다. 그 버텍스 3개가 모인 삼각형, 즉 폴리곤이 되고, 그 폴리곤들이 모이면.. 하나의 오브젝트가 된다고 전에도 말했다..

 그래서 이 예제는 그 점들이 어떻게 찍히느냐.. 각 속성에 따라 어떻게 보이는 것이 다르냐에 대해 알 수 있는 예제이다... 

 


예전 프로젝트에 설정은 그대로이므로, 소스만 복사해서 넣으면된다..
(물론, 윈도우 프로젝트로 설정되어 미리 컴파일된 헤더라던지 하는 좀 복잡한 문제가 있기 때문에.. 조금 신경써줄 곳이 있긴 하다. )

먼져 실행을 시켜보면...


이런 화면들이 나온다.. (각 장면의 전환은 F1 키를 누르면된다.)

자 그렇다면 이 것들이 왜 이렇게 나왔는지에 대해서 알아보자..

간단히 버텍스를 선언하는 곳을 보자..


상단에 선언부분을 보면.. 각 버텍스 부분에 대한 전역 변수들이 있다..

그리고 그 아래에 define으로 VERTEX의 속성에 대해서 정해놓고,

구조체를 만들었다.. 이 것은 무엇이냐 하면, 앞으로 사용될 버텍스가 가질 정보를 저장하기 위해서 인데, 현재는 XYZ와 디퓨즈 칼라값을 가지겠다는 의미이다.. (이 정보에 대해서 아는 것이 중요하다. 이 것이 나중에 셰이더 와도 상관이 있다. )


그리고 더 아래로 내려오면 각 버텍스 내용을 가지는 포인터가 있다...

저 각 좌표의 의미가 무엇인고하니.. 각 버텍스의 좌표이다. 그리고 아까 XYZ와 칼라값을 넣겠다고 했으니, 뒤에 칼라를 넣어준다. 유심히 보면 알겠지만, point나 line이나 Strip이나.. 다른 점이 없다.. 바로.. 3개의 좌표와 칼라값만 있을 뿐이다..

 그렇다면, 무엇이 이 것들을 다른 방식으로 랜더링을 하게 하는가 가.. 바로 오늘 프리미티브 타입의 핵심이다.. 

 


 일단 내용은 좀 있다가 보고, 선언 한 배열을 설정하는 부분을 보자...

 모든 프로그래밍이 그렇겠지만, 일단 무언가를 하고 싶으면, 그 것에 관한 메모리가 있어야한다.. 이것이 프로그래밍의 기본이다..

 그래서 디바이스를 이용하여, 버텍스 버퍼를 만든다.. 저기 있는 size는 각 배열에 선언한 숫자를 넣고, 나머지는 그대로 넣으면된다. 그리고 저 기 D3DPOOL_DEFAULT라고 넣었는데 이 것은 메모리 관리 방법을 설정하는 것이다. 디폴트라고 넣게 되면, 이 것에 관한 관리를 직접 해주어야한다. 하지만 D3DPOOL_MANAGED를 넣게되면, 다이렉트x가 직접 관리를 해준다. 2가지다 장단점이 있으니 맞는 것을 선택하면된다..


 그럼 다시 타입 정하는 곳을 보려면, 키입력 콜백함수 부분으로 간다.. 그러면 F1이 눌러졌을 때 현재의 상태값을 다른 상태값이로 바꾸는 부분이 있다.

 


그리고 랜더에 가보면, 각 프리미티브 상태값에 따라서 다른 case로 들어가는 것을 확인할 수 있다. 그리고 DrawPrimitive함수를 통하여 어떻게 그릴지 결정하여 화면에 그리는 것이다..

저기 숫자가 바로 그릴 버텍스 점의 갯수 이니, 저 것을 하나씩 조정해봐도 재미있을 것이다. 그리고 다른 기능으로 F2를 누르면...


이런 부분이 있는데, 와이어 프레임으로 화면을 볼 것인지, 꽉 찬 상태로 볼 것인지 정할 수도 있다.. 만약 와이어 프레임으로 보는 상태이면...


이런 화면을 볼 수 있다. 이 것이 아까 봤던 마름모의 와이어 프레임이다..

자.. 그렇다면 이제 대충 감이 좀 잡히는가?

 점들은 그냥 하나의 점이지만, 어떻게 그릴꺼냐를 결정하는 것에 따라 다른 결과를 가져온다. 그리고 그리는 순서도 중요하다.. 

 삼각형의 3점이라고 어디서 부터 그리든 상관없는 것이 아니다. 그리는 점의 순서에 따라 이 삼각형의 앞면일 수도, 뒷 면일수도 있기 때문에, 화면에 그려질 수도 안그려 질수도 있는 것이다.
 (왜 안그려지는 지는 이전 포스팅 랜더링 파이프라인에서....)

물론, 실제로 게임 개발에서는 모델링을 바로 로드해서 사용하기 때문에 이런 것까지 알 필요는 없을 수도 있다. 하지만, 동작원리를 모르고 모델링을 쓴다고 하면, 나중에 정해진 포멧이 아닌 약간의 변형을 주어야 할 때 어디서 부터 손을 데야 할 지 감이 안 올 수도 있다. 
 그래서 기초가 중요한 것이다. 그럼 다음 포스팅에서 버텍스 데이터에 대해서 더 알아보도록 하자...
 
Posted by 바람처럼..
|

[DirectX] DirectX 뷰포트 설정하기!!

이제 기본 화면을 설정했으면, 뷰포트를 한번 설정해보자...

사실 게임을 만든다면 딱히 뷰포트가 필요없다.. 뷰포트라고 하면 랜더링이 찍힐 화면의 범위라고 생각하면된다. 기본적으로 랜더링을 하면 화면 전체에 찍히므로, 화면 전체를 보고 하는 게임에는 필요가 없다.

 하지만 개념적인 이해를 위해 한번 설정하는 법을 알아보자.

 이전에 초기화 하는 부분을 참고하여, 그 이후부터 소스를 하나씩 추가해 보도록 하자.

이전 포스팅 보기

 이번 예제에서는 간단히 띄워볼 주전자를 준비하면된다. (프로젝트 폴더내에
teapot.x파일을 미리 넣어두자. 물론 경로를 다르게 해도 되지만, 그렇게 되면 설정도 다시 해줘야 하므로 그냥 프로젝트 폴더 내에 두는 것이 편하다.)

이제 아래 사진을 보면서 차근차근 하나씩 해보자.


위의 변수들을 더 추가해준다.

LPD3DXMESH        g_pTeapotMesh = NULL;
D3DMATERIAL9      g_teapotMtrl;
D3DLIGHT9         g_pLight0;

DWORD g_dwBackBufferWidth  = 0;
DWORD g_dwBackBufferHeight = 0;

여기서 pTeapotMesh 가 아까 넣어주었던 주전자.x 파일용 변수이다. 다이렉트x는 기본적으로 메쉬 파일인 .x 파일 로드를 지원해 준다. 그래서 다른 모델링이있다면 그 것을 써도 된다. 그리고 아래의 메터리얼과 라이트는 현재, .x 모델에 아무런 텍스쳐가 없기때문에 그냥 띄우면 검게만 나올 뿐이다. 그렇기 때문에 재질 정보와 거기에 따른 빛을 넣어주는 것이다. 그리고 폭은 간단히 화면을 2분할 하기 위해 필요한 정보이기 때문에 크게 신경쓰지 않아도 된다.


그리고 init 함수를 찾아가서...

 

위와 같이 아래의 내용을 추가해준다.

 D3DXMATRIX matProj;
 D3DXMatrixPerspectiveFovLH( &matProj, D3DXToRadian( 45.0f ),
  640.0f / 480.0f, 0.1f, 100.0f );
 g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

 g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
 g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
 g_pd3dDevice->SetRenderState( D3DRS_SPECULARENABLE, TRUE );

 //   // Setup a simple directional light and some ambient...
 g_pLight0.Type = D3DLIGHT_DIRECTIONAL;
 g_pLight0.Direction = D3DXVECTOR3( 1.0f, 0.0f, 1.0f );

 g_pLight0.Diffuse.r = 1.0f;
 g_pLight0.Diffuse.g = 1.0f;
 g_pLight0.Diffuse.b = 1.0f;
 g_pLight0.Diffuse.a = 1.0f;

 g_pLight0.Specular.r = 1.0f;
 g_pLight0.Specular.g = 1.0f;
 g_pLight0.Specular.b = 1.0f;
 g_pLight0.Specular.a = 1.0f;

 g_pd3dDevice->SetLight( 0, &g_pLight0 );
 g_pd3dDevice->LightEnable( 0, TRUE );

 g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, D3DCOLOR_COLORVALUE( 0.2f, 0.2f, 0.2f, 1.0f ) );

 // Setup a material for the teapot
 ZeroMemory( &g_teapotMtrl, sizeof(D3DMATERIAL9) );

 g_teapotMtrl.Diffuse.r = 1.0f;
 g_teapotMtrl.Diffuse.g = 1.0f;
 g_teapotMtrl.Diffuse.b = 1.0f;
 g_teapotMtrl.Diffuse.a = 1.0f;

 // Load up the teapot mesh...
 D3DXLoadMeshFromX( L"teapot.x", D3DXMESH_SYSTEMMEM, g_pd3dDevice,
  NULL, NULL, NULL, NULL, &g_pTeapotMesh );

 //
 // Cache the width & height of the back-buffer...
 //

 LPDIRECT3DSURFACE9 pBackBuffer = NULL;
 D3DSURFACE_DESC d3dsd;
 g_pd3dDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer );
 pBackBuffer->GetDesc( &d3dsd );
 pBackBuffer->Release();
 g_dwBackBufferWidth  = d3dsd.Width;
 g_dwBackBufferHeight = d3dsd.Height;

위의 내용을 간단히 설명하면, 먼저 프로젝션 행렬 설정과, 그 뒤에는 .x 파일 로드와 재질, 빛에 대한 속성정리이다. 그리고 랜더 스테이트들을 설정해서 빛을 사용하고, ZWrite를 하고 하는 등의 속성을 설정했다. 마지막으로는 현재 만들어진 백버퍼를 얻어와 그 크기를 입력받고 다시 해제하는 부분이 들어있다.


 if( g_pTeapotMesh != NULL )
  g_pTeapotMesh->Release();

 위의 부분은 그렇게 로드된 주전자 매쉬를 해제하는 부분이다. 왜 초기화 선언 다음 작업으로 해제를 넣냐면, 가급적이면 초기화를 하는 순간 해제를 같이 넣어야 잊지 않기 때문이다. 물론, 현재 이정도 예제에서 주전자 해제 하나 안했다고 문제가 있진 않겠지만, 이런 프로그램 습관 하나하나가 쌓여야 나중에 큰 프로젝트에서 해제를 안해서 프로그램을 죽이는 불상사를 예방할 수 있는 것이다.


그리고 이제 랜더 부분을...

 

 D3DXMATRIX matView;
 D3DXMATRIX matWorld;
 D3DXMATRIX matRotation;
 D3DXMATRIX matTranslation;

 //
 // Render to the left viewport
 //

 D3DVIEWPORT9 leftViewPort;

 leftViewPort.X      = 0;
 leftViewPort.Y      = 0;
 leftViewPort.Width  = g_dwBackBufferWidth / 2;
 leftViewPort.Height = g_dwBackBufferHeight;
 leftViewPort.MinZ   = 0.0f;
 leftViewPort.MaxZ   = 1.0f;

 g_pd3dDevice->SetViewport( &leftViewPort );

 // Now we can clear just view-port's portion of the buffer to red...
 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
  D3DCOLOR_COLORVALUE( 1.0f, 0.0f, 0.0f, 1.0f ), 1.0f, 0 );

 g_pd3dDevice->BeginScene();
 {
  // For the left view-port, leave the view at the origin...
  D3DXMatrixIdentity( &matView );
  g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

  // ... and use the world matrix to spin and translate the teapot 
  // out where we can see it...
  D3DXMatrixTranslation( &matTranslation, 0.0f, 0.0f, 5.0f );
  matWorld = matTranslation;
  g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

  g_pd3dDevice->SetMaterial( &g_teapotMtrl );
  g_pTeapotMesh->DrawSubset(0);
 }
 g_pd3dDevice->EndScene();

 //
 // Render to the right viewport
 //

 D3DVIEWPORT9 rightViewPort;

 rightViewPort.X      = g_dwBackBufferWidth / 2;
 rightViewPort.Y      = 0;
 rightViewPort.Width  = g_dwBackBufferWidth / 2;
 rightViewPort.Height = g_dwBackBufferHeight;
 rightViewPort.MinZ   = 0.0f;
 rightViewPort.MaxZ   = 1.0f;

 g_pd3dDevice->SetViewport( &rightViewPort );

 // Now we can clear just view-port's portion of the buffer to green...
 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
  D3DCOLOR_COLORVALUE( 0.0f, 1.0f, 0.0f, 1.0f ), 1.0f, 0 );

 g_pd3dDevice->BeginScene();
 {
  // For the right view-port, translate and rotate the view around
  // the teapot so we can see it...
  D3DXMatrixTranslation( &matTranslation, 0.0f, 0.0f, 5.0f );
  matView = matTranslation;
  g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

  // ... and don't bother with the world matrix at all.
  D3DXMatrixIdentity( &matWorld );
  g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

  g_pd3dDevice->SetMaterial( &g_teapotMtrl );
  g_pTeapotMesh->DrawSubset(0);
 }
 g_pd3dDevice->EndScene();

 //
 // We're done! Now, we just call Present()
 //

 g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

이렇게 바꿔주면 된다.

소스를 살펴보면, 랜더링에 Begin과 end가 2번있다. 즉, 각각의 뷰포트 마다 한번씩 그려서 2번을 그린것이다.

 그렇다.. 우리가 하는 2개 뷰포트 분할 작업은 각각 따로 랜더링된 화면을 다른 위치에 그리는 것이다.. 머 간단히 미니맵 같은데 응용해 쓰거나, 아니면 머 다른 어떤 곳에 새로 랜더링된 어떤것을 그려야 하면 사용될 수는 있겠지만, 딱히 생각나는 곳은 없다. 요즘은 그냥 Surface에 랜더링을 하고 바로 텍스쳐화 시켜서 화면에 뿌려줄수도 있기 때문이다.

 내용은 보면, 월드 행렬 설정과, 메트리얼 설정 등 간단한 연산만 하고 바로 그렸다. 원래 예제는 마우스 인풋도 해서 주전자를 돌려볼 수도 있지만, 현재 예제는 그 것이 중점이 아니기 때문에 뺴버렸다. 어쩃든, 위의 프로젝트를 랜더링하면 아래 화면이 나온다...


바로 왼쪽과 오른쪽에 서로 다른 화면이 랜더링 된 모습이다.....

뷰포트에 대해서 감이 잡히는가? 머 딱히 어려운 부분은 없으니 이해만 하고 넘어가도록 하고, 이제 진정한 버텍스에 대해 알아보도록 하자..

[DirectX] DirectX 뷰포트 설정하기!!

 

Posted by 바람처럼..
|

 [DirectX] DirectX 초기화 하기!!

 이제 셋팅과 랜더링 파이프라인에 대하여 알았다면, 실제 코딩을 해보도록 하자.. (혹시 예전 포스팅이 궁금하다면 아래 링크로) 

다이렉트x sdk 설치
다이렉트x 랜더링 파이프라인

 기본적으로 먼저 알고 시작해야 할 것은 directx 를 초기화 하기 위해서는 D3D객체와 D3Ddevice 객체가 필요하다.

 이 객체들의 역할은 하드웨어와의 통신이라고 생각하면된다. 즉, 하드웨어에 접근할 매개체라고 보면 된다.

 그리고 D3D객체를 생성, 그 D3D 객체로 디바이스 까지 생성한 후에 랜더링을 계속 화면 화면에 우리가 원하는 화면을 그리게 된다. 그리고 이 선언된 것들을 프로그램을 종료할 때에는 꼭 해제를 해주어야한다.

 랜더링을 하려면 아래와 같은 부분이 필요하다.

 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
  D3DCOLOR_COLORVALUE(0.0f,1.0f,0.0f,1.0f), 1.0f, 0 );

 g_pd3dDevice->BeginScene();

 // Render geometry here...

 g_pd3dDevice->EndScene();

 g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

모든 함수들을 보면 알수 있듯이 모두 Device를 통하여 동작을 한다. (지금은 전역으로 선언되었지만, 따로 클래스로 만들어서 쓸수도 있으므로, 그 것은 개인적으로 쓸모에 따라 쓰면된다. ) 

 그리고 랜더링을 하기 위해서는 실시간으로 루프를 돌면서 계속 저 부분을 불러줘야 하는데, 그렇게 해야 계속 화면을 갱신해 우리가 원하는 화면이 된다. 

 각 부분의 역할들을 보면 먼저 Clear 라는 함수로 초기화를 한다. 

 그리고 BeginScene을 쓰고 EndScene을 쓰는데 이 사이에 있는 것들만 그려지게 된다. 그리고 Present를 부르면 이제 실제로 화면에 찍는 것이다. 

 현재로서는 매우 간단해 보이지만, 이 곳에 다른 함수들이 들어가고 다른 효과들을 넣고 하다보면 매우 복잡해 진다. 

 그럼 실제로 프로젝트를 만들면서 확인해보자.
 지금 가져오는 소스는 튜토리얼 용으로 만들어진 프로젝트에서 필요한 부분만 가져와서 사용했다. (사진이 잘 안보여도, 사용된 소스는 다 붙여놓았기 때문에 대충 이 위치쯤 감만 잡을 수 있다면 별 상관은 없을 것이다.)


 먼저 새로운 프로젝트를 생성한다. 현재 2005 버전으로 하지만 2008이나 2010도 거의 비슷하다. Win32프로젝트를 선택하고 확인을 누른다.


그러면 이런 화면이 나오게 되는데 다음으로 넘어가고,

 윈도우즈 응용프로그램이 선택된 체로 마침을 누른다.


그러면 아래와 같이 소스가 자동으로 생성된다.

그리고 컴파일하면 위와 같은 화면이 뜬다. 이제 조금씩 수정을 해 나가면되는데..


먼저 헤더파일을 추가한다.
#include <d3d9.h>
#include <d3dx9.h>


그리고 속성에서 링커->일반->추가 종속성에..

d3dx9.lib d3d9.lib 2개를 추가한다.


그리고 이제 전역 변수를 추가한다. 전역 변수들은..
HWND              g_hWnd       = NULL;
LPDIRECT3D9       g_pD3D       = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;

이 3가지를 추가한다.


그리고 이제 다이렉트용 함수 3개를 추가하는데 각각

void init();
void shutDown();
void render();

이 것으로 init은 초기화를 shutDown은 해제를 그리고 화면을 그리는 것은 render 함수에 작성한다.

아래의 부분이 초기화 용 함수의 모든 내용이다.
void init(  )
{
    g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

 if( g_pD3D == NULL )
 {
  // TO DO: Respond to failure of Direct3DCreate8
  return;
 }
    D3DDISPLAYMODE d3ddm;

    if( FAILED( g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) )
 {
  // TO DO: Respond to failure of GetAdapterDisplayMode
  return;
 }

 HRESULT hr;

 if( FAILED( hr = g_pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
            d3ddm.Format, D3DUSAGE_DEPTHSTENCIL,
            D3DRTYPE_SURFACE, D3DFMT_D16 ) ) )
 {
  if( hr == D3DERR_NOTAVAILABLE )
   // POTENTIAL PROBLEM: We need at least a 16-bit z-buffer!
   return;
 }

 //
 // Do we support hardware vertex processing? if so, use it.
 // If not, downgrade to software.
 //

 D3DCAPS9 d3dCaps;

 if( FAILED( g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT,
                                 D3DDEVTYPE_HAL, &d3dCaps ) ) )
 {
  // TO DO: Respond to failure of GetDeviceCaps
  return;
 }

 DWORD dwBehaviorFlags = 0;

 if( d3dCaps.VertexProcessingCaps != 0 )
  dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
 else
  dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

 //
 // Everything checks out - create a simple, windowed device.
 //

 D3DPRESENT_PARAMETERS d3dpp;
 memset(&d3dpp, 0, sizeof(d3dpp));

    d3dpp.BackBufferFormat       = d3ddm.Format;
 d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
 d3dpp.Windowed               = TRUE;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
    d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;

    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                      dwBehaviorFlags, &d3dpp, &g_pd3dDevice ) ) )
 {
  // TO DO: Respond to failure of CreateDevice
  return;
 }
}

내용을 위에서 부터 보면, d3d를 설정하고, 디스플레이 모드를 가져온 후 여러가지 셋팅을 한 후에 device를 생성한다.
 여기서 눈여겨 볼 것은 바로

g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                      dwBehaviorFlags, &d3dpp, &g_pd3dDevice )

 이 CreateDevice 부분이다. 이 곳을 보면 g_hWnd 변수를 넣어주는데 다이렉트 엑스 화면을 그려줄 윈도우 핸들값이다. 잘 생각해보면 다이렉트x를 어떤 화면에 그리고 싶다면 그 것을 설정해 주는 부분이 필요한데 이 부분이 바로 그 부분이다. 그러면 저기 핸들값을 얻으로 가보자.

 


 위의 사진은 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 부분이다. 이 함수를 찾아가보면 실제 윈도우 생성을 하고 있다. 그 곳에서..



핸들 값을 받아오면된다. 아까 init 함수 내부에 이미 처리 함수가 다 적혀있으니, 그냥 핸들 값만 받으면 설정은 끝났다. 이제 루프 부분을 수정할 차례이다.
winmain() 함수에 가보면 기본 루프가 아래와 같이 있다.


이 부분을... 아래 화면처럼..


 memset(&msg,0,sizeof(msg));
 while( msg.message != WM_QUIT )
 {
  if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
  {
   TranslateMessage( &msg );
   DispatchMessage( &msg );
  }
  else
   render();
 }

이렇게 고치면된다. 위의 memset 하는 이유는 msg 값을 초기화 하기 위해서고 WM_QUIT 메세지를 받기 전까지는 while을 계속 돌게된다. 그리고 PeekMessage로 윈도우 메세지를 받는 경우 말고는 계속 render를 돌게 된다.
그 render 함수의 내용은 아까 위에 예를 든 것처럼...

void render( )
{
 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
  D3DCOLOR_COLORVALUE(0.0f,1.0f,0.0f,1.0f), 1.0f, 0 );

 g_pd3dDevice->BeginScene();

 // Render geometry here...

 g_pd3dDevice->EndScene();

 g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}

 이렇게 만들면되고 저기 랜더 지오메트리 부분에 이제 앞으로 내용이 추가될 것이다.


그럼 이제 초기화 부분을 루프 이전에 추가해주고..


그 루프 아래 부분에는..

shutDown(); 함수를 추가해준다. 그 내용은 아래와 같다.

void shutDown( )
{
 if( g_pd3dDevice != NULL )
  g_pd3dDevice->Release();

 if( g_pD3D != NULL )
  g_pD3D->Release();
}

이 모든 것들을 다 하고 난 후에 컴파일을 하면,


아까의 흰 윈도우 밖에 없던 화면이 쨘~ 녹색 화면이 나오고 있다.

위의 화면은 현재 다이렉트x가 그려지고 있는 화면이다.

화면 색이 녹색인 이유는 아까 render 함수에 Clear 부분에 바탕색이 현재 rgb중 g만 1.0f 수치가 되어있기 때문이다.

 각자 한번씩 바꿔보면 바로 적용이 되는 것을 알게 될 것이다.

 자, 이제 초기화를 했으니 간단한 도형 부터 차례대로 띄워보도록 하자.~

 [DirectX] DirectX 초기화 하기!!
Posted by 바람처럼..
|
[DirectX] DirectX 9 버전 랜더링 파이프 라인 알아보기!!

 다이렉트x를 이용하여 간단한 프로그램을 만들기 전 일단 다이렉트x의 동작 원리에 대해서 알아보는게 더 중요할 것 같다.

 흔히 다이렉트x는 3d 랜더링을 위해 많이 사용하므로, 3d에 중점을 두고 설명하겠다. (물론 2d 랜더링도 가능하다. 왜냐하면 UI로 사용하는 화면들은 다 2d 화면이기 때문이다. 하지만 다이렉트x에서 중점을 맞추는 부분은 3d 구성 부분 이므로 3d를 기준으로 말하겠다. )

 3d 화면을 만든다고 쳐도, 실제로 우리 모니터에 표시 되는 것은 2d 이다. 뭐 물론 요즘 3d 모니터니 해도 그 것은 논외로 치고, 현재로서는 2d 모니터라고 보는 것이 맞겠다. (그리고 그 3d가 이 3d랑은 의미가 다르다 -_- )

 그래서 3d 랜더링 파이프라인을 한마디로 정의하면, 3d의 화면을 우리가 보는 2d 화면으로 변환해주는 일련의 과정들 이라고 볼 수 있다.

 파이프라인이라는 의미는 순서대로 변환을 거쳐 오기 때문에, 파이프의 입구로 들어와서 출구로 배출 되는 느낌이기에 그렇게 부른다.

 먼저, 3d 오브젝트가 필요하다. 그래서 우리는 흔히, 맥스 같은 프로그램을 이용해서 3d 모델링을 한다. 그렇게 모델링을 하면, 중점을 기준으로 어느 위치라고 하는 상대적인 위치에 버텍스들이 존재하게 된다.

 여기서 버텍스라고 함은, 모델링을 이루는 기본 폴리곤의 한 점, 즉 포인터다. 폴리곤은 그 점 3개가 모인 삼각형, (혹은, 사각형이라고도 하던데 이 부분은 정확히 몰라서 패스) 그리고 그 폴리곤들이 모이면 하나의 메쉬, 그리고 메쉬가 모이면 오브젝트라고.. 보통 부른다. (하지만 아직, 메쉬와 오브젝트의 기준을 잘 모르겠다... 둘다 폴리곤의 덩어리인데, 어떻게 부르냐의 차이인지도... 하지만 정확히 단어의 뜻을 찾자면, 오브젝트 라는 의미가 맞지 않나 생각된다. )

 그렇게 모델링이 있다면, 기준점을 기준으로 각 위치 값이 생긴다.

(대충 그림판으로 그리는 거라 이상해도 이해를....)

위의 중점을 모델의 피봇 좌표라고 봤을 떄 그 피봇은 0,0,0 이다. (그림은 2d이지만 3d 모델이라고 생각하면 좌표는 3개이다.) 그 점 A는 중점에 비해 y가 +1 만큼 된 0,1,0 의 좌표에 존재한다. 점 b는 x 로 1 y 로는 -0.5정도의 위치에 있다. 그래서 좌표는 1, -0.5, 0 이 된다. 이것이 바로 오브젝트의 로컬 좌표이다.

즉, 로컬 좌표란, 원점이 되는 좌표를 중심으로 각 버텍스의 좌표라고 생각하면된다. 이 것이 결정되는 것이 랜더링 파이프 라인의 첫 단계이다.

 그 로컬 좌표를 지정된 월드 좌표에 곱한다. 이 월드 좌표라는 것은 흔히 생각하는 우리 실세계의 3d 좌표라고 생각하면된다. 예를들면 책이 있다. 그 책은 그 책의 중심이 있고 각 꼭지점, 8개의 점으로 이루어져 있다. 그리고 그 책은 현재 우리가 사는 세계의 특정위치에 존재한다. 그렇다면 그 특정위치에 그 책의 중점을 옮기면 자연스럽게 나머지 8개의 점들은 따라오게 되어있다. 

 


위의 중점은 현재 x,y,z 축을 기준으로 x 축으로 1만큼, z 축으로 1만큼 이동했다. 그렇다면 점A의 현재위치는 중점을 기준으로 0,1,0 의 좌표에 있었지만 자동적으로 1,1,1 의 위치로 이동된 것이다. 이 것이 바로 월드 변환이다. 이 곳에서 스케일, 회전, 위치이동을 모두 계산한다.

 실제 로컬 좌표에 연산하는 행렬 값은 크기조정, 회전, 위치 값을 연산한 행렬을 한번에 곱한다. 하지만 스칼라 값의 연산은 곱셈공식에 의하여 앞뒤가 바뀌어도 상관없지만, 행렬은 그 곱하는 순서에 따라서 값이 달라지게 된다. 그렇기 때문에 연산순서를 신경써야한다. 

 보통 연산 순서는 S(스케일)*R(로테이션)*T(트랜스레이트) 순으로 곱한다. SRT로 외우면 헷갈리지 않는다. (단, opengl은 연산순서나 이런것들이 반대, 다이렉트x 용으로 외우면 된다.) 이 것이 무슨 영향을 끼치냐고 하면, 회전 연산이 로컬 좌표에 연산되면 로컬 축이 변한다. 그 상태에서 T(위치이동)이 생기면 전혀 엉뚱한 방향으로 이동이 된다는 의미이다. 즉, 움직이고 거기서 돌아가는 것이랑 돌고난 이후에 다른 방향으로 움직이느냐는 다른 의미이다.

 곱하는 순서에 따라서 자전과 공전이 된다. (자전은 제자리에서 돌기, 공전은 중점을 기준으로 도는 것이다. 지구의 자전으로 낮밤이 나뉘고, 공전으로 1년 365의 계절이 생긴다.

 이렇게 월드좌표로 움직였다고 보면, 이제 뷰 스페이스 연산을 하면된다. 그 스페이스라는 개념은 아까 로컬좌표도 로컬 스페이스에 존재하는 버텍스들, 월드 좌표도 월드 스페이스에 존재하는 버텍스 좌표, 이런식으로 연산을 거치면서 위치 이동이 되는 중이라고 보면된다. 뷰 스페이스로 연산하는 것은, 쉽게 말해 우리 눈에 보이는 위치로 움직이는 것이다.

 또 다른 사람들은 이런 랜더링의 카메라 연산을 비유할 때 그냥 일반 카메라에 비유한다. 일반 카메라로 사진을 찍는다면, 찍고싶은 피사체가 있다. 그 피사체는 우리가 아까 말한 오브젝트가 된다. 그럼 그 피사체는 각자의 외각선에 로컬좌표로 설정되고, 중점에 따라 같이 움직인다. 그리고 그 움직이던 피사체나 정지된 피사체는 우리의 실세계의 어느 위치에 있다. 이 것이 월드좌표이다. 이때 위치는 같지만 그 물체가 회전하느냐 크기가 변하느냐에 따라서 모양은 천차만별이다. 그렇게 찍고싶은 대상이 정해지면, 이제 뷰 연산이 바로 카메라다. 현재 카메라의 위치가 있고, 그 카메라가 바라보는 방향이 있고, 그 2가지 만으로는 2가지의 형태가 나오니까 업 벡터가 하나더 필요하다.. ( 현재 위치고 바라보는 방향 벡터만 가지고는 위 아래 양방향 모두 가능하기 때문에 정확히 해주어야 정확한 화면을 얻을 수 있다. ) 그렇게 되었을 때 이제 카메라가 사진을 찍으면 그 장면이 필름에 기록된다. ( 이 부분도 나중에 설명이 되겠지만, 이 것이 바로 프로젝션 행렬과 뷰포트 변환이다. )

 그리하여 우리도 카메라의 현재위치, 바라보는 방향, 업벡터 이렇게 3개의 값이 필요하다.


 사진이 점점 더 지저분해지기는 하는데, 이제 저 카메라 라고 적힌 위치가 카메라의 위치고 그 곳에서 바라보는 저 시야가 바로 lookat(바라보는 방향이다. 업 벡터는 현재 위치상 0,1,0 이라고 생각하면된다. (2d라서 정확히 표현이 안된다. )
 그리고, 이해의 편의상 저렇게 범위를 표현했지만, 실제로는 어디서 어디까지인지는 프로젝션을 거쳐야 알 수 있다. 흔히 우리가 카메라에 렌즈에 따라서 같은 위치지만 넓게도, 좁게도, 둥글게도 나오듯이 그런 렌즈의 역할은 프로젝션 연산에서 수행된다.

 여기까지 오면 거의 기본적인 변환은 다 끝났다. 아 하지만 현재 그림에서 원래 월드를 기준으로 카메라의 위치만 그렸기 때문에 월래 월드 좌표랑 같다고 생각할 수 있지만, 현재 개념상 그런 것이지 좌표는 분명 바뀌어 있다. 하지만 이해하기 편하도록 저렇게 그린 것이다.

 그리고 이제 조명 연산과, 컬링, 클리핑 연산이 들어간다. 사실 조명 연산은 어느 위치에서든 미리 해줄 수도 있다. 하지만, 그렇게 되면 연산량이 많기 때문에 보통 뷰 연산 이후에 한다. 그리고 컬링과 클리핑은 뷰 연산이 끝나야 할 수 있는 연산이기 때문에 보통 뷰 연산 이후에 이 것들을 처리한다. 클리핑은 프로젝션이후에 가능하다.

 조명 연산은 말그대로 조명 효과를 계산하는 것이다. 조명의 종류는 3가지가 있는데, 엠비언트, 디퓨즈, 스펙큘러 가 있다. 그리고 자체적으로 포인터 형 조명, 스포트 라이트 같은 조명 효과도 줄 수 있다. (포인터형 조명은 백열등을 생각하면되고, 스포트라이트는 손전등을 생각하면된다. 이 것들은 엠비언트 라이트의 범주에 속한다고 생각되는데, 정확한 것은 아니다. ) 보통 엠비언트, 디퓨즈 스펙큘러는 텍스쳐와 연관되어서 계산되는 경우가 많다.

 그리고 랜더링 연산속도에 영향을 주는 컬링과 클리핑이 남았는데, 컬링 연산은 말그대로 안보이는 부분을 삭제하여 더 이상 연산하지 않는 것이다. 랜더링 파이프라인에 뷰 연산까지 끝냈다면, 이제 안보이는 뒷면과 앞면으로 나누어 지는데 뒷면을 제거해 버리면, 연산양이 줄어들게 되므로, 속도를 위해서는 꼭 컬링 처리를 해야한다. (하지만, 뒷면이 필요한 상황도 있으므로, 사용하기 나름. 어떤 원리인지만 알고 있다면, 응용은 쉬울 것이다. )

 그리고 클리핑은 아까 봤던 뷰의 범위에 없는 오브젝트들을 제거하는 것이다. 월드 상에서는 그렸지만, 카메라 위치와 뷰 연산과 프로젝션연산까지 끝나면, 실제로 절두체 범위에 들어오지 않는 오브젝트 들이 생긴다. 그 오브젝트들은 차후에 연산을 해도 화면에 나오지 않으므로, 클리핑 연산으로 제거한다.
 

위의 빨간 범위를 제거하는 것이 클리핑이고, 현재 보이지는 않지만, 저 안쪽으로 더 가는 삼각형의 뒷면을 제거하는 것이 컬링이다. 즉, 이렇게 까지 연산이 진행되면 현재 점A 부분만 남게 되고 B는 사라지게된다.
 ( 물론, 그렇게 되면 화면에 남는 부분이 없어서 점만 보이게 되지만, 이 것은 예로 드는 것이기 때문에 이해만 하면 된다.)

 이제 마지막, 프로젝션과 뷰 포트 연산, 그리고 레스터라이즈 연산만 남았다.
프로젝션 연산은 아까 카메라 범위를 설명하면서 했던 것처럼 카메라로 사진을 필름에 찍을 때, 좌우의 폭과, 가까이 있는 것은 크게, 멀리 있는 것은 점점 작게 찍히는 것을 말한다.


 위의 그림을 보면 파란선이 카메라에 가까운 near 절두체고, 자주색 선이 카메라에서 먼 far 절두체 범위라고 보면된다. 즉 화면에 찍히는 것은 파란선과 자주선의 사이 영역만 찍힌다. 그리고 가까울 수록 오브젝트는 크게 멀수록 작게 찍히게 된다. 이 영역은 직접 지정해 줄 수가 있는데, 너무 가까운 거리까지 지정하면 카메라 앞에 어떤 물체가 너무 가깝게 붙어도 그 곳에 가려서 보이지 않은 경우가 있기 때문에 적당한 조절이 필요하고, 또 far 절두체가 너무 벌리까지 범위가 되어버리면 연산량이 많아지기 떄문에 적당한 범위에서 끊어야 적정 프레임이 나올 수 있다. 그리고 아까 말햇던 클리핑 연산도 여기서 수행된다.

 보통 게임을 하다보면, 상대방 케릭터에 너무 접근하면, 케릭터가 뻥 뚤리는 경우가 있다. 이 것은 그 near에 범위보다 더 가까이 접근해서 케릭터의 버텍스를 그리지 않다보니 생기는 현상이 되겠다. 

 또 게임을 만들 때 보통 멀어진 산 같은 곳에 안개가 있는 경우는, 일정 거리를 벗어나면, 자동으로 그 범위에 포그를 생성하고 뒷 배경에는 산 같은 가까이 다가가도 일정 크기가 되는 배경을 플랜에 붙이거나 하늘(스카이 돔)을 설치해서 먼가 멀리 까지 배경이 있는 듯한 훼이크를 주기도 한다.
 
 그리고 이 프로젝션 연산을 어떻게 하느냐에 따라서, 볼록렌즈 같은 효과, 원근법 무시 랜더링, 등등 여러가지 효과도 줄수있다. 

 이제 이 연산이 모두 끝나면, 이제 뷰포트 연산이 남았다. 뷰포트가 우리가 궁극적으로 하려고 했던, 바로.. 3d 위치들을 우리가 있는 2d 화면좌표에 밀어넣는 연산이다. 즉 -1~1 범위에있던 프로젝션된 버텍스들의 좌표를 실제 화면좌표 0~1280(?), 1920(?) 등의 각자 화면 좌표에 맞도록 위치를 잡아주는 작업이다.

 아까 이야기를 하다가 그냥 넘어왔는데, 사실 랜더링 한번의 과정은 한번 시작되면 끝날 때 까지 쭉 이어진다. 그런데 지금은 한두가지 모델링만 가지고 이야기를 하니까. 엄청 빨리 될 것 같지만, 실제로 여러가지 오브젝트에 배경에 여러가지 효과가 들어가면, 속도가 많이 느려진다. 그렇게 때문에 중간중간에 이런 연산 속도의 향상을 위한 테크닉들이 들어간다. 가령 z 버퍼 sort 라고 해서 현재 바라보고 있는 z축을 기준으로 오브젝트들을 줄 세워서 정렬을 시켜서, 앞쪽에 오브젝트를 먼저 그리면, 뒷쪽 오브젝트는 안그려지는 부분은 생략이 가능하다. 그래서 오브젝트를 앞에서 부터 먼저 찍어나가면 속도에 이득이 있다. 하지만 이 것이 안먹히는 것들이 있는데 바로 알파오브젝트다. 알파 오브젝트는 뒤의 배경색과 +/ 연산을 통해서 색이 결정된다. 그렇기 때문에 뒷 배경이 있어야 진짜 알파색을 구할 수 있다. 그렇기 때문에 알파 오브젝트는 반대로 뒤에서 그린다. 그래서 이것을 바로 알파 소팅 이라고 한다. 이렇듯 랜더링에 관해서 속도의 이득을 위해 여러가지 테크닉들이 있다. 이 것들을 모두 적재적소에 잘 써야 할 것이다.

 이제 진짜 마지막으로 레스터라이즈 연산이 남았다. 이 것은 궁극적으로 이제 화면 좌표에 딱 맞게 정해진 각 폴리곤들의 픽셀값을 집어넣는 연산이다. 즉 아까 설정된 조명, 텍스쳐 그리고 버텍스 위치 등등을 종합하여 각 위치의 마지막 픽셀 색을 결정하는 것이다. 그래서 같은 오브젝트라도 조명이 들어간 것과 아닌 것의 마지막 픽셀의 색은 차이가 나므로, 그림자 효과 등이 들어가고 안들어 가고 하는 것들이 되는 것이다..

 자.. 이게 기본적인 랜더링의 과정이다.. 이렇게 글로 쓰고 보니 실로 엄청난 과정들인 것 같은데.. 이런 과정들이 보통 초당 60프레임정도... 일어나고, 만약 제한폭없이 간단한 랜더링만 한다면 몇백번은 훌쩍 나오는 것이 요즘 컴퓨터의 성능이다. 하지만.. 그렇다고 최적화나 이런 테크닉들을 배제하고 개발한다면, 나중에 30프레임도 나오지 않아서 뚝뚝 끊기는 프로그램 화면을 보게 될 수도 있으므로, 항상 염두해 두고 코딩을 해야할 것이다.

[DirectX] DirectX 9 버전 랜더링 파이프 라인 알아보기!!

Posted by 바람처럼..
|
[DirectX] DirectX sdk 설치하기![DirectX] DirectX sdk 설치하기!
우리는 게임 등 프로그램을 쓸 때 다이렉트x 런타임 라이브러리를 설치하는 경우가 많다. 만약 그런 적이 없다고 생각된다면 그 것은 실제 게임 설치 파일이 자동적으로 필요한 dll들을 설치해 주기 때문이다.

그렇다면 DirectX란 무엇인가 가 중요한데, 바로 그래픽카드에 접근하여 여러가지 랜더링 효과를 줄 수 있도록 도와주는 라이브러리라고 볼 수 있다.
(물론 정확한 의미는 아닐 수 있지만, 이렇게 이해하면 편하다.)

더욱 정확히 따지면 DirectX는 마이크로 소프트에서 제공하는 라이브러리 이다. 그렇기 때문에 호환성은.. windows에서만 사용이 가능하다.

기타 ios나 리눅스, 유닉스 관련 3D 프로그램들은 대부분 opengl을 사용한다.

2가지의 장,단점은 모두 있기 때문에 일단 넘어가고, 다이렉트 X를 사용하기 위한 기본 셋팅을 해보자.

다운로드 경로 바로가기

위의 경로로 가면 2008년 11월 버전의 sdk를 다운받을 수 있다.

최신버전도 물론 받을 수 있다. 하지만 버전이 상위버전으로 가서 여러가지 기능은 추가 되어 더 좋다고 생각될 수 있지만, 마이크로 소프트에서는 이런 다이렉트x  업데이트 시 함수라던지 기능들이 추가도 되지만 삭제, 변경도 된다. 그렇기 때문에 자신이 개발하던 버전이 아니라면 생각했던 동작을 안할 수도 있다는 의미이다. 그렇기 때문에 자신이 쓰는 sdk의 버전을 잘 기억하는 것이 좋다. (버전은 년 월 로 표시된다. )

그리고 혹 나중에 개발된 프로그램을 배포하게 된다면 같은 버전이나 혹은 기간이 얼마 차이 나지 않는 런타임 라이브러리 설치 파일을 함께 배포 하여야한다.

일단 다운을 다 받았으면 실행을 시킨다. 그리하면 다음과 같은 화면이 뜬다.


 그냥 다음을 누르고,


위에 동의 한다는 라디오버튼을 클리하면된다. 지금은 동의안함이다 보니 다음 버튼이 활성화가 안되어있다.

 


이 화면은 이 설치에 관해 보낼 것인가에 대한 것인데 그냥 NO를 선택하고 다음으로 가면된다.



이 화면에서 다음을 누르면..

 


설치가 시작된다.

자 설치가 모두 완료되어도 사용하기 위해서는 비쥬얼 스투디오에서 설정을 해 주어야한다. 그런데 설정을 해주려고 들어가보니 벌써 설정이 되어있다..
 설치파일이 기본적으로 해주는 것 같다는 생각이 들기는 했는데 혹시, 안되는 사람도 있을 수 있으니 간단한 설정법을 보여주겠다.
비주얼 스투디오 버전은 2005 버전이다. ( 물론 2008, 2003도 같은 방법으로 한다. 하지만 2010부터는 포함폴더 지정법이 조금 바뀌었는데 그것은 다른 포스팅에 있으니 참고)

2010버전 포함폴더 지정법 바로가기

먼저,

 
도구에서 옵션으로 간다. 그러면..


이렇게 프로젝트 및 솔루션에 vc++ 디렉터리 설정에서 제일 위에 다이렉트x 폴더가 추가된 것을 볼 수 있다. 이 것을..


포함 파일과,

 


라이브러리 파일에서도 되어있는지 확인하고 안되어 있다면, 위의 경로를 직접설정해주면..

다이렉트x 사용준비가 모두 끝났다.

[DirectX] DirectX sdk 설치하기![DirectX] DirectX sdk 설치하기!
Posted by 바람처럼..
|