블로그 이미지
대갈장군

calendar

1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

Notice

'프로그래밍'에 해당되는 글 102

  1. 2009.03.16 필터 추가후 렌더링 하기
  2. 2009.03.16 Null Rendering
  3. 2009.03.14 COM (Component Object Model) 과 DirectShow2
  4. 2009.03.13 객체 / 클래스 / 객체화 / 추상 클래스
  5. 2009.03.11 Device Context의 개념 + GetDC / ReleaseDC
  6. 2008.10.22 [MSDN] /MT, /MTd, /MD, /MDd (C Runtime Library Option)12
  7. 2008.10.22 프로그램, 프로세스, 스레드2
  8. 2008.10.18 객체 지향 언어(C++) V.S. 비 객체 지향 언어(C)2
  9. 2008.10.17 [MSDN] COM (Component Object Model)2
  10. 2008.10.16 [MSDN] End of DLL War!
  11. 2008.10.15 [MSDN] DLL HELL!!!! (1)
  12. 2008.10.14 [MSDN] Manifest 파일?!?!
  13. 2008.07.21 [MSDN] Understanding Dependencies of a Visual C++ Application
  14. 2008.02.23 [DX Tutorial - Game Display ] Lessong 5: Buildig a Game Display2
  15. 2008.02.23 [DX Tutorial - Game Display] Lesson 4: Adding Text to the Display
  16. 2008.02.23 [DX Tutorial - Game Display] Lesson 3: Animation
  17. 2008.02.20 [DX Tutorial - Game Display] Lesson 2: More about Sprites
  18. 2008.02.20 [DX Tutorial - Game Display] Lession 1: Using Sprites
  19. 2008.02.20 [DX Tutorial - Mesh] Lesson 3: Loading Textured Meshes
  20. 2008.02.19 [DX Tutorial - Mesh] Lesson 2: Loading Meshes From X Files
  21. 2008.02.16 [DX Tutorial - Mesh] Lesson 1: Using Basic Meshes
  22. 2008.02.16 [DX Tutorial] Lesson 12: Using Color-Keys
  23. 2008.02.16 [DX Tutorial] Lesson 11: Blending Colors for Special Effects
  24. 2008.02.15 [DX Tutorial] Lesson 10: More on Lighting
  25. 2008.02.14 [DX Tutorial] Lesson 9: Rendering WIth Vertex Lighting
  26. 2008.02.14 [DX Tutorial] Lesson 8: Adding Textures
  27. 2008.02.12 [DX Tutorial] Lesson 7: Simple Modeling
  28. 2008.02.12 [DX Tutorial] Lesson 6: Rendering Depth
  29. 2008.02.07 [DX Tutorial] Lesson 5: Transforming Vertices
  30. 2008.02.06 [DX Tutorial] Lesson 4: Drawing a Triangle
2009. 3. 16. 23:49 프로그래밍/DirectShow
앞에서 본 NULL Rendering의 경우에는 입력 파일에서 곧바로 렌더링을 했지만 필터 추가후 렌더링은 원하는 특정 필터를 먼저 필터 그래프 매니져에 올려놓고 렌더링을 하는 것을 말한다.

이렇게 하면 필터 그래프가 만들어질때 이미 삽입시킨 필터를 최대한 포함시켜 렌더링을 하게 된단다.

1. 필터 그래프 매니저 생성
JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGB);


2. 추가 시키고 싶은 필터 생성
IBaseFilter *pFilterOverlayMixer;
LIF(CoCreateInstance(CLSID_OverlayMixer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pFilterOverlayMixer));


3. 필터를 그래프 매니져에 추가
LIF(m_pGB->AddFilter(pFilterOverlayMixer, L"Overlay Mixer");


4. CoCreateInstance에 의해 생성된 필터 COM 객체 Release 시키기
SAFE_RELEASE(pFilterOverlayMixer);


5. Null Rednering 기술 사용
JIF(m_pGB->RenderFile(wFileName, NULL));

posted by 대갈장군
2009. 3. 16. 23:39 프로그래밍/DirectShow
DirectShow에서 Null Rendering 하는 방법

1. 우선 설명서 (CLSID_)와 기능 (IID_) 정의 해주고 CoCreateInstance 실행 하면 m_pGB 멤버 변수에 포인터 리턴됨. 이로써 필터 그래프 관리자 생성됨.
JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGB));

2. 미리 가지고 있는 파일 이름을 넘겨주고 RenderFile() 명령 수행.
JIF(m_pGB->RenderFile(wFileName, NULL));

참 쉽죠잉~

재생중인 스트림이 있을경우 재생 중지 하고 필터 매니저 그래프 종료하기

1. IMediaControl interface를 QueryInterface 함수를 이용해 포인터 얻어오기.
IMediaControl * pMC;
JIF(m_pGB->QueryInterface(IID_IMediaControl, (void**)&pMC);

2. 재생중인 스트림이 있으면 정지 시키고 pMC 와 필터 그래프 관리자 해제
if(pMC != NULL)
     LIF(pMC->Stop()); // 재생하고 싶은 경우는 LIF(pMC->Run());
SAFE_RELEASE(pMC);

SAFE_RELEASE(m_pGB);

posted by 대갈장군
2009. 3. 14. 04:00 프로그래밍/DirectShow
참 이상한것이 DirectShow에 대한 책이나 정보가 한국어로 된것은 매우 드물다는 점이다. 신화선님이 출판한 DirectShow 멀티미디어 프로그래밍이란 책을 제외하고는 한국어로 된 DirectShow 서적은 없다고 봐야한다.

원어로 된 서적도 없기는 마찬가지다. 그래서 그런지 더더욱 자료를 찾기가 어렵다.

DirectShow의 각종 Application 작성에 대해서 블로그를 작성하기 전에 COM에 대해서 분명히 말해두고자 한다.

왜냐면 DirectShow의 필터들이 사실 전부 COM 기반이기 때문이다. 사실 따지고 보면 COM은 C++과 크게 다르지 않다. 다만 '어떻게 사용하나?' 를 알아야 한다는 점이 중요할뿐.

C++의 클래스는 전적으로 사용자에게 권한을 100% 준다. 내가 만들고 싶은 뭐든 내 맘대로 작성하면 그만이다. 멤버 함수, 변수 등 구현, 정의 모두 내 맘대로다.

헌데 거기서 한가지 문제점이 발생하는데 프로그램을 짜본 사람은 알겠지만 제법 규모가 있는 프로그램, 뭐 예를 들자면 동영상 재생 프로그램, 오디오 재생 프로그램 같은거를 만들고 싶다고 하자. 

C++의 내 맘대로 작성하여 만든 클래스를 사용하여 만드는 프로그램은 그 규모가 공사에 비교했을때 소형 주택 짓는 정도라면 동영상 재생 프로그램과 같은 것은 거의 10 층 빌딩 만드는 수준의 전문적 공사이다.

그렇다면 일반 프로그래머가 지하부터 공사시작해서 10층까지 빌딩을 완성시킬수 있을까? 불가능하다... 뭐 아주 불가능 하지는 않지만 시간이 오~~~래 걸린다.

바로 그렇기 때문에 프로그램 회사 (Microsoft) 같은 곳에서 일반 프로그래머도 10층 빌딩을 신속하게 지어 올릴수 있도록 친절하게 다양한 빌딩을 짓기 위한 '뼈대'와 '재료'를 제공을 해준다.

그것이 바로 COM기반의 DirectShow이다. DirectShow는 멀티미디어 (동영상, 오디오) 를 사용해 결과물을 작성해야 하는 개발자들을 위해 제공되는 다양한 함수와 클래스이다.

하지만 일반 C++ 클래스와는 달리 COM은 매우 인공지능적이다. 우선 스스로 자신의 객체가 몇번이나 복사되었는지를 스스로 관리하여 카운터가 0이되면 스스로 자신을 파괴한다. 고로 컴퓨터에 유령처럼 떠도는 메모리 leak를 원초적으로 차단하고자 했다.

물론 여전히 프로그래머가 메모리 leak를 유발할 수는 있지만 그래도 C++ 클래스 원조보다는 훨씬 영리하다.

또한 '조립'과 '대화'가 가능한 클래스이다. 조립이 가능하다는 것은 COM 객체 생성함수인 CoCreateInstance() 함수의 첫번째 인자와 네번째 인자를 사용자가 적절히 선택함으로써 어떤 '건물의 외관'에 '내부의 기능'을 만들것인지를 정할수 있게 했다.

건물의 외관이 바로 CLSID_ 머리말이 붙은 식별자이며 사실상 이것은 클래스이고 즉, '설명서' 일 뿐이다. CLSID는 Class ID의 약자임을 알았다면 당연한 이야기다.

네번째 인자인 '내부의 기능'IID_ 머리말이 붙은 식별자이며, 이것은 Interface ID의 약자이다. Interface는 한국어로 해석하기 좀 애매하지만 간단히 말하자면 걍 '함수'다. 이 인터페이스는 내가 어떤 기능 (함수)를 사용하고 싶다는 것을 알려준다.

고로 CoCreateInstance()는 사실 내가 만들고자 하는 클래스와 함수를 컴퓨터에 알려주고 COM 라이브러리에서 내가 원하는 형태의 객체를 뚝딱 만들어서 나에게 돌려준다.

다른 한가지 특별한 점으로 '대화'를 꼽았는데 이는 바로 QueryInterface()라는 함수 때문이다. 기본적으로 모든 COM객체는 IUnknown 인터페이스를 가지고 있다. 즉, 모든 COM 객체는 IUnknown 인터페이스가 제공하는 3가지의 함수(QueryInterface,AddRef,Release)를 가지는데 그중 하나가 바로 QueryInterface()함수이다.

이 함수는 COM 객체에게 '너 이 기능 할줄 아니?' 라고 물어보는 역활을 한다. 만약 가능하면 COM 객체는 그 기능으로 향하는 출입문인 Interface Pointer를 리턴하고 만약 안되면 NULL을 리턴하여 불가능 하다고 대답한다.

이전에 내가 COM에 대해서 쓴 글에서도 보면 이 점 때문에 COM은 '뇌를 가진 클래스'라고 했었다. 프로그램 개발자가 모든 함수를 다 알고 있어서 바로 그 함수를 찍어서 호출하는 방법보다 훨씬더 안정적이다. 왜냐면 컴퓨터마다 차이가 있으므로 어떤 컴퓨터에서는 되고 안되고를 알수 없는데 COM 객체를 이용해 QueryInterface를 하면 쉽게 현재의 컴퓨터에서 그 기능이 가능한지 알수 있기 때문이다.

예전에도 말했듯이 이런 COM과 같은 놈들은 C++과는 전혀다른 새로운 언어가 아니다. 이런 COM은 프로그램을 작성하는 '틀'과 '방법'을 제공해주는 거대한 도서관 같은 놈이다. 사용자가 알아야 하는 것은 몇 층의 무슨 책을 찾아야 하는지대출은 어떻게 하는지만 알면 된다. 

DirectShow에서 COM을 사용한 이유는 정확히는 모르지만 동영상과 오디오를 주로 다루는 DirectShow에서는 여러개의 필터들을 연결해서 재생하는데 이런 과정에서 필터 간의 안전한 연결과 실제로 중요한 부분인 Encoding 과 Decoding 부분의 완벽한 정보 은폐, 그리고 아마도 COM 기술을 프로그램 업계의 기반으로 만들려고 그러지 않았을까?... 그럴까?

posted by 대갈장군
2009. 3. 13. 04:29 프로그래밍
프로그래밍 언어는 사실 사용 방법 보다는 이해가 중요한 것이 요즘 현실인 듯 하다. 오래전부터 차근 차근 개발되어온 언어들은 다양한 특징을 가지게 되었고 그 특징 만큼이나 이름도 다양해 졌다.

그래서 어떤 언어가 어떤 특성을 지니고 있는지와 프로그래밍에 대한 근본적인 이해가 없으면 어떤 언어부터 공부해야 하는지를 전혀 감을 잡을 수가 없다.

그런데 객체 / 객체화 / 추상 클래스는 모든 프로그램 언어에 걸쳐서 등장하는 단골 손님이다. 이 놈들의 정확한 의미에 대해서 잘 이해하고 있다면 프로그래밍이 어떤것인지 알수 있다.

우선 객체와 클래스는 대립되는 의미이다. 클래스는 다른 말로 조립 설명서 이다. 로봇을 만든다고 가정한다면 어떤 어떤 부품이 어떻게 어떻게 연결되는지를 설명해 놓은 조립 설명서이다.

반면 객체화는 이 조립 설명서대로 로봇을 찍어내는 것을 말한다. 이것을 컴퓨터 입장에서 이야기 하자면 클래스는 메모리상에 존재하지는 않지만 객체 (로봇)이 어떤 어떤 속성과 멤버 함수를 가져야 되는지를 설명해 놓은 것이다.

그리고 객체화는 이 클래스가 컴퓨터의 메모리 상에 공간을 차지하게 되는 순간을 말한다. 이것을 수행하는 것이 바로 new 명령이다.

즉, 클래스의 조립 설명서에 따라서 로봇 (객체)를 만들어 내는 것이다.

왜 이런 번거로운 과정을 거쳐야 하느냐 하면, 우선 클래스는 객체의 재사용성과 범용성을 증가 시킨다. 잘 만들어 놓은 클래스 (조립 설명서) 하나로 여러개의 같은 형태의 객체를 마구 마구 찍어낼 수 있다. 게다가 상속과 오버로딩을 통해서 조금 변형된 모습의 새로운 로봇도 쉽게 쉽게 만들어 낼수 있게 된다.

바로 이런 점이 객체 지향 언어의 강점이다.

그래서 고급 개발자들이 멎진 클래스와 그에 딸린 함수들을 만들어 배포하면 (Microsoft) 일반 개발자들이 그 클래스를 바탕으로 객체를 생성하여 함수들을 사용하는 것이다.

물론, 일반 개발자들이 배포되는 클래스에 자신만의 함수를 추가하여 그 동작을 변경하고 싶을 때가 있기 마련이다. 그런 경우에 사용하는 것이 바로 추상 클래스다.

이 추상 클래스는 구현된 것이 전혀 없는 목차만 적혀있는 설명서인 클래스이다. 이것을 사용하는 일반 개발자는 추상 클래스가 가지고 있는 형태 (목차) 만 그대로 지키되 자신이 원하는 함수를 구현하기만 하면 된다.

자바와 같은 언어의 경우 이 추상 클래스가 참 많은데 이런 클래스는 반드시 클래스 내의 특정 함수 (목차) 를 사용자가 구현 해야만 작동하는 클래스다. 고로, 추상클래스는 일반 개발자에게 진정한 의미의 '개발' 을 할수 있도록 도와 주지만 어떤 어떤 함수를 반드시 구현해야 하는지를 목차로써 알려주는 '목차만 있는 설명서' 같은 놈이 바로 추상 클래스다.






posted by 대갈장군
2009. 3. 11. 04:45 프로그래밍/Windows API
OpenGL 프로그램을 주로 작성하면 사실 Windows API에 대해서 많은 지식이 필요하지 않지만 늘 듣는 말에 대해서는 알아둘 필요가 있는데 아무래도 넘버 1이 Device Context가 아닌가 싶다.

알고 있으면 쉽지만 모르면 알쏭달쏭한 것이 Device Context이다.

기본적으로 Windows는 세가지 동적 연결 라이브러리 (DLL)로 구성되는데 메모리와 프로그램을 관리하는 KERNEL, 유저 인터페이스와 윈도우를 관리하는 USER, 그리고 화면 처리와 그래픽을 담당하는 GDI이다.

GDI는 풀어서 쓰면 Graphic Device Interface인데 한글로 다시 풀어 설명하면 '그래픽 장치 접속 (접근) 함수'이다.

한글로 풀어쓰니 우끼다.. ㅋㅋㅋ

GDI를 통해서 우리는 화면에 글도 쓰고 그림도 넣는 것이다. 그러므로 간단히 생각해도 GDI와 Device Context (DC)가 관계되어 있다는 것을 알수 있다.

일단 답부터 말하면 DC는 걍 구조체이다. 그 구조체 안에는 각종 멤버 함수들이 정보를 담고 있는데 이 정보는 특정한 '하나의' 윈도우에 대한 그래픽 정보이다. 예를 들자면 폰트, 선의 색상, 굵기, 채움 무늬, 그리기 모드, 시작 위치 등이다.

여기서 중요한 점은 '하나의' 윈도우에 대한 것이라는 점이다. Windows API 함수를 이용해 프로그램을 작성하다 보면 항상 임의의 윈도우에 뭔가를 그리기 위해서는 'GetDC()' 함수를 이용해서 '해당 윈도우'의 Device Context를 작성해야 한다.

이 점을 잊어버리게 되면 Device Context를 변경하지 않거나 해제 하지 않고 다른 윈도우에 이전 윈도우의 DC를 사용하는 오류를 저지를 수 있지 않을까 하는 생각이 든다...

아무튼 Device Context는 한글로 해석하면 '장치 내용' 정도가 되겠는데 푸훗.. 우끼다... 이 Device라는 단어를 한국어로 해석하면 '장치'이지만 사실 그 의미는 컴퓨터에 삽입된 하드웨어 장치만 의미하는 것이 아니라 특정한 어떤것이 그 하나로써 독립적이면 전부 Device라고 하는 듯 하다.

즉, 윈도우 한개, 동영상 파일 한개, 그림 이미지 한개 등 그 하나로써 독립성을 완벽하게 가지는 것을 Device라고 한다면 Device Context의 의미가 좀더 쉽게 다가온다.

Device에 대한 각종 정보를 담고 있는 구조체가 바로 Device Context이고 Windows API에서 Device는 바로 '하나의 윈도우'를 의미한다.

이런 DC를 작성하는 것이 바로 GetDC(HWND hWnd) 함수이다. 이 함수는 입력으로 들어오는 HWND hWnd 윈도우에 적당한 DC를 만들어서 (각종 그리기 정보들) 그 값을 리턴하여 준다.

즉, hdc = GetDC(hWnd); 하게 되면 hWnd 윈도우에 알맞는 DC를 만들어서 hdc에 대입하고 이 hdc를 이용해서 그 윈도우에다가 마구 마구 그리면 된다. 참 쉽죠잉~

GetDC() 함수 대신 BeginPaint() 함수도 있는데 이 함수의 특징은 '오직' WM_PAINT 루틴에서만 사용가능하다는 점이다. 흠. 흥미롭죠~

주의할 건 GetDC로 특정 윈도우의 DC를 얻어왔다면 반드시 ReleaseDC로 해제해주는 것을 잊지 말자는 것.




'프로그래밍 > Windows API' 카테고리의 다른 글

윈도우 - 차일드, 팝업  (0) 2009.11.17
윈도우 - SetClassLongPtr(), SetWindowLongPtr()  (0) 2009.11.17
윈도우 - WNDCLASS 구조체  (0) 2009.11.17
윈도우 - CreateWindow()  (0) 2009.11.17
메모리, Win32 vs. Win16  (2) 2009.05.22
posted by 대갈장군
2008. 10. 22. 06:20 프로그래밍/MSDN

참고 자료: http://msdn.microsoft.com/en-us/library/abx4dbyh(VS.80).aspx

관련글: http://diehard98.tistory.com/entry/MSDN-Manifest-파일 (manifest 파일에 대한 설명 및 재배포 방법)


Visual Studio 2005의 C++를 이용해서 프로그램을 작성하다 보면 프로젝트의 Properties 에서 Code Generation 속성에 보면 /MT, /MTd, /MD, /MDd 와 같은 다양한 형태의 옵션 설정이 가능하다.

이것을 어떻게 설정하는가에 따라서 내가 만드는 프로그램이 어떤 DLL과 연결이 되는지정적(Static)으로 라이브러리를 링크 할 것인지 아니면 동적(Dynamic)으로 라이브러리를 링크 할 것인지를 결정할 수 있다.

이 속성을 우습게 보았다가 나는 큰 코 다쳤다. =_+ 헐

Standard C++ Library

Characteristics

Option

Preprocessor directives

LIBCPMT.LIB

Multithreaded, static link

/MT

_MT

MSVCPRT.LIB

Multithreaded, dynamic link (import library for MSVCP80.dll)

/MD

_MT, _DLL

LIBCPMTD.LIB

Multithreaded, static link

/MTd

_DEBUG, _MT

MSVCPRTD.LIB

Multithreaded, dynamic link (import library for MSVCP80D.DLL)

/MDd

_DEBUG, _MT, _DLL


일단, 위의 테이블은 내가 프로그램에서 Standard C++ Library에 해당되는 헤더 파일을 불러왔을 경우에 연결되는 라이브러리들을 보여준다.

Standard C++ Library라 함은 http://en.wikipedia.org/wiki/C_Standard_Library 에 나와 있는 헤더들을 말한다. 아마도 기본적인 프로그램을 작성한다면 최소한 저것 중에 하나는 들어가게 된다.

물론 아닌 사람도 있겠지만... 헐헐헐...

아무튼, 그런 경우가 아니면, 링크 되는 라이브러리가 바뀌긴 한다만 영어 철자 하나 차이로 바뀐다. 일단은 Standard C++ Library를 사용한다고 생각하자.

원래 과거에는 (정확히는 모르겠지만 Visual Studio 6 까지 일것이다.) Single Thread 옵션도 지원이 되었다. 하지만 Visual Studio 2005에서는 무조건 Multi Thread로 프로그램을 작성해야 한다.

앞서 내가 작성한 글에도 있지만 C 런타임 라이브러리가 옛날에 작성될 때 Thread란 개념이 없다보니 Multi Thread로 동작하는 프로그램의 경우 '자원 경쟁'에 의한 오작동 했다.

아마도 이런 문제를 싸그리 날려버리기 위해서 2005에서는 오직 Multi Thread만 지원하나 보다.

표에 보면 /MT와 /MTd가 있는데 이 소문자 d는 Debug 버전을 의미한다. 나도 사실 Debug와 Release의 차이를 전혀 모르다가 다른 컴퓨터에 내 프로그램을 설치하면서 그 차이를 알게 되었다.

간단히 말해서 Debug 버전은 해당 Visual Studio가 설치된 컴퓨터에서만 '테스트'용으로 사용한다. 즉, Visual Studio가 설치안된 컴퓨터에다가 Debug 버젼으로 완성된 .exe 파일 들고가서 실행해봐야 실행이 안되고 The system cannot run this program. 어쩌구 저쩌구 그런다.

왜냐면 Debug 버젼은 실행시 링크되어야 할 DLL 파일이 Visual Studio가 설치되지 않은 컴퓨터의 .NET Framework Assembly 폴더인 Windows/WinSxS 폴더에 존재하지 않기 때문이다.

또한 ELUA인가 뭔가에 의해서 Debug 버전의 DLL 파일은 재배포가 금지되어 있다. 즉, 배포를 목적으로 한다면 Release 버전으로 작성하라~ 이말이다.

아무튼, 테이블에서 보이는 한가지 더 특별한 차이는 Static 과 Dynamic 링크이다. Static 링크는 실행 파일에다가 필요한 DLL 을 같이 묶어서 .exe 파일로 만든다. (개념적으로 그렇긴 한데 DLL 전체를 포함시키는 것인지 아닌지는 잘 모르겠다.)

그리고 Dynamic의 경우는 Static과는 달리 실행을 할때 찾아서 링크를 시키게 되므로 반드시 실행 파일과 같이 제공되어야 한다.

고로 배포를 목적으로 하는 프로그램이라면 반드시 /MD 옵션으로 만들어야 하고 따라서 MSVCPRT.LIB가 프로그램 작성시 링크가 된다.

그런데 프로그램이 만들어진 다음에 다른 컴퓨터에서 이것을 실행하기 위해서는 MSVCPRT.LIB가 필요한 것이 아니고, MSVCP80.DLL이 필요하다.

내가 생각할때 이 두 파일의 차이는 '개발자용'과 '사용자용'의 차이이다.

MSVCPRT.LIB는 Visual Studio를 설치해야만 제공되는 '개발자용' 라이브러리인 반면, MSVCP80.DLLMicrosoft Visual C++ 2005 Redistribution Package SP1 (x86)을 설치하면 Windows/WinSxS 폴더에 자연스럽게 설치가 되는 DLL이다. 즉, '사용자가 프로그램을 돌리기 위해서 배포되는 DLL'이다.

추가로 MSDN에서 주의를 요하는 부분은 프로그램을 작성할 때 동적으로 라이브러리를 링크하는 DLL과 동적으로 라이브러리를 링크하는 DLL 혹은 프로그램이 덩어리로 묶여서 실행될 때 발생할 수 있는 문제점을 지적하고 있다.

물론 정적으로 링크된 라이브러리가 홀로 사용된다면 아무 문제 없겠지만 DLL 간에 데이터 전송이나 호출이 발생하게 되면 링크된 라이브러리가 반드시 일치한다는 보장이 없기 때문에 문제가 될수도 있다는 것을 말하고 있다.

따라서 왠만하면 모든 것을 동적으로 링크하는게 좋다 이말이군... 업데이트하기에도 좋고, 일관성도 있고... 그렇겠군. :)

결론적으로 말하자면,

1. Visual Studio 2005는 Multi Thread로 코드를 작성하는 것만 지원한다.
2. 배포를 목적으로 한다면 반드시 /MD 옵션을 사용하라.
3. 설치할 컴퓨터에 Visual Studio가 설치되어 있지 않다면 Redistribution Package 를 다운 받아서 설치하라.
4. 되도록이면 작성하는 모든 프로그램 및 DLL은 /MD 옵션으로 통일하라.
posted by 대갈장군
2008. 10. 22. 05:36 프로그래밍

프로그램, 프로세스, 스레드의 차이를 알아보자.

일반적으로 프로그램(Program)은 메모장과 같은 하나의 실행 가능한 단위를 의미한다.

그러나 아시다시피 메모장은 여러개를 동시에 실행할 수 있다. 이때 각각의 메모장은 서로 다른 프로세스(Process)에 의해서 실행이 된다.

즉, 프로세스는 프로그램을 객체화 시킨 것.

각 프로세스는 4GB의 개별 주소 공간과 파일, 메모리, 스레드를 소유하게 된다. 이 말에서도 나온것 처럼 스레드는 다시 프로세스에 속한다.

하나의 프로세스는 여러개의 스레드(Thread)를 소유할 수 있으며 프로세스 자체는 껍데기일 뿐이고 사실은 스레드가 모든 명령을 수행한다.

프로세스가 최초로 생성될때 각종 관련 변수와 메모리 등을 생성하면서 메인 스레드를 생성하게 되는데 이 메인 스레드가 사실 모든 명령들을 처리한다.

그러나 사용자가 원하면 멀티 스레드 (Multi-Thread)가 가능한데 즉, 하나의 프로세스가 여러개의 스레드를 가지고서 다양한 작업을 분산 시켜 처리하도록 하는 것이다.

사용자가 임의로 타이머를 만들어서 규칙적인 시간에 일정한 작업을 처리하는 것보다 (또는 PeekMessage를 이용한 방법보다) 운영체제가 알아서 시간을 쪼개에 스레드에게 작업을 나누어 주는 것이 훠어어얼씬 효율적이고 빠르단다.

다만, 멀티 스레드가 가지는 문제점은 하나의 프로세스안에 존재하는 스레드들은 같은 코드와 주소공간, 그리고 전역 변수를 '공유'하게 됨으로써 생기는 '자원 경쟁' 혹은 '비동기 문제'이다.

하나의 프로세스 안에 있는 1번 스레드가 전역 변수를 변경하고 있는 동안 다른 2번 스레드가 그 값을 참조하거나 다시 변경하게되면 사용자가 예상치 못한 이상한 값들로 바뀌어 버리는 문제가 생기게 된다.

그래서 이것을 해결하기 위해서는 가장 좋은 것이 스레드들이 공유하는 전역 변수 자체를 안만들거나, 혹은 스레드 지역 기억장소 (Thread Local Storage) 공간을 사용해야 한다.

TLS는 다른 말로 쓰자면 '하나의 스레드에 대해서만 전역 변수인 공간'이다.

이렇게 복잡하게 설정을 해야 하는 이유는, C의 런타임 라이브러리가 애당초 Multi-Thread개념이 생기기 전에 만들어 졌기 때문이다.

기존 C 런타임 라이브러리는 함수들이 주로 Static 을 많이 사용하고 있는데 (특정 함수에서) 이 Static 변수들이 스레드 간에 '자원 경쟁' 혹은 '비동기' 때문에 엉뚱한 결과를 내는 현상을 가져왔다.

그래서 이걸 뜯어고치기되 기존 C 런타임 라이브러리를 동시에 유지하기 위해서 MLS가 도입된 것이다.

posted by 대갈장군
2008. 10. 18. 01:25 프로그래밍

내가 대학 다닐 시절에 처음으로 듣는 컴퓨터 관련 교양 과목은 아마도 '컴퓨터의 역사'나 '컴퓨터 개론' 뭐 이런거 였지 않나 싶다.

중요한 점은 그 과목들은 '어떻게' 컴퓨터가 발전해 왔는가에 대한 대답은 해주지만 '왜' 그렇게 발전해 왔는가에 대한 대답은 주지 않는다.

프로그래밍 언어에 대한 첫 수업을 들었을 때에도 마찬가지였다. C가 무엇인지는 가르쳐 주었지만 프로그래밍 언어의 큰 '흐름' 이나 '관계'에 대해서는 전혀 듣지 못했다.

이런 '개념'에 대한 부족은 사실 큰 그림으로 보면 한 가족인 COM, MFC, .NET Framework, JAVA 에 대한 '이해'를 매우 어렵게 만들었다. 이해가 힘들다 보니 배우기도 어렵고 '공포'스런 존재로 다가왔다.

이런 문제는 나에게도 어김없이 나타났다. 하지만 이것을 이겨내기 위한 나의 첫 단계는 C와 C++의 정복이었다. 나에게는 격하게 아끼는 책 두권이 있다. 바로 김상형님이 지은 혼자 연구하는 C/C++이라는 책인데 이책들이 나에게는 프로그램으로 향하는 길을 열어주었다.

이 책을 읽기 전에는 그저 C++은 C를 모태로한 발전된 형태의 언어라는 어렴풋한 개념을 가지고 있었는데 두 권을 읽으면서 확실한 개념적 차이를 알게 되었다. 이제부터 그책에서 얻은 지식과 잡다한 나의 지식을 모아 모아 최대한 쉽게 C와 C++에 대해서 이야기 하고자 한다. (수업에서는 얻을 수 없었던 그 이야기를)

사실 C와 C++는 '서로 틀리다'라고 말하는 것보다는 '프로그램을 구현(작성)하는 방식이 서로 틀리다' 라고 말해야 정확하다.

바로 '객체 지향 언어'와 '비 객체 지향 언어'의 차이에서 오는 '프로그램 작성 방식의 차이'가 C와 C++의 결정적 차이점이다.

비 객체 지향 언어라는 말은 객체 지향의 개념이 없으면 이해가 안되므로 다른 말로 바꿔서 '구조적 언어'라 할 수 있다.

과거 C로 작성된 언어들의 특징을 보면 '단일 프로그램'으로써 하나 혹은 몇개의 파일안에 하나의 프로그램을 돌리기 위한 함수들과 코드들이 모조리 들어가 있다.

이들의 연결 구조와 호출 관계는 프로그래머가 정의하는 것이 곧 '원칙'이요 '룰' 이기 때문에 서로 복잡하게 엉켜 있다. 즉, 프로그램을 구성하는 함수들이 '독립적'이지 못하고 서로가 서로에게 의존적이며 이곳 저곳에 난무 하는 전역 변수들이 전체 프로그램을 컨트롤 했다.

이런 방식의 프로그램 작성 기법을 '구조적' 이라 표현했는데 사실 적당히 어울리는 단어가 생각이 안난다. 아무튼... 그건 그렇고.. -_-

바로 이렇게 작성된 프로그램은 '재사용성'이 거의 없었다. 재사용성이라 함은 내가 짜놓은 코드를 다른 프로그램에서 가져다 사용하는 건데 '프로그래머 맘대로' 작성한 C 프로그램들은 필요한 일부의 코드만 가져다 쓸 수가 없었다. 왜? 전역 변수와 이곳 저곳에 흩어진 많은 다른 함수들이 서로 엉켜 있기 때문이다.

바로 이런 문제가 '소프트웨어의 위기'로 찾아온다. 80년 이후 하드웨어의 기술은 급격히 빨라져 자고나면 하드웨어 값이 떨어지고 하드디스크 용량이 2배로 뛰어 오를 그 시기에 소프트웨어의 발전은 더뎠다.

그 원인으로 지적된 것이 바로 C의 구조적 언어 작성법이었다. 지 맘대로 짜다보니 내가 짜 놓고도 몇 개월 지나서 보면 멍~ 때리게 되는 것이다.

그래서 이것을 해결하기 위해 C++, '객체 지향 언어'가 등장했다. 이 객체 지향 언어의 목표는 요약해서 말하자면,

1. 전역 변수를 쓰지말라! (프로그램의 독립성을 심히 훼손하므로)
2. 재사용 가능한 클래스로 모든 것을 구현하라!

이 두가지 룰을 지키게 되면 C++로 작성한 프로그램은 사용하는 모든 함수들이 클래스의 멤버 함수로 정의되고 전역변수를 전혀 사용할 필요가 없게 된다.

이 말을 쉽게 하면 프로그램을 블럭으로 나누어 작성하라는 말이다. 이 각각의 블럭이 바로 클래스 이고 이 클래스는 완전한 '독립체'로써 그냥 가져다 다른 프로그램에 쓸수가 있다. 바로 '재사용성'의 확보다.

그렇다면 왜 '재사용성'이 그리도 중요한가 궁금하지 않은가? :) 일단 '효율' 때문이다. 잘 만들어 놓은 클래스 하나는 어디서도 다시 사용이 가능하므로 개발자의 효율을 급격히 증가 시킨다.

이 효율의 장점은 곧바로 '돈'과 직결되므로 많은 개발자들이 이 방법을 따르기 시작했다.

이런 객체 지향 언어의 등장은 과거와는 다른 프로그래머와 사용자간의 계층을 만들게 되었다. 원래 과거에는 '개발자'와 '사용자' 딱 두개의 구분만 존재 했다. 개발자가 만들고 사용자가 사용하고. 간단하다.

지금은 '상위 개발자', '중급 개발자', '사용자' 이렇게 나뉜다. 상위 개발자는 중급 개발자에게 자신들이 공들여 안전하게 만든 수많은 클래스와 함수들을 라이브러리로 제공한다.

중급 개발자들은 바로 이 제공되는 아름답고 완벽한 클래스와 함수를 이용해서 자신이 원하는 프로그램을 작성해서, 사용자에게 유통하게 된다.

다시 말해서 객체 지향 언어는 프로그램을 작성하는 일종의 '표준적인 방식 또는 틀' 제시해 줌으로써 언어의 '재사용성'과 효율을 높여 소프트웨어 개발에 큰 도움을 주는 놈이다.

여기서 '표준적인 방식'이라 함이 바로 클래스와 클래스를 구성하는 멤버 함수 및 변수 작성법 그리고 생성자와 파괴자 규칙, 순수 가상 함수와 동적 연결, 추상 클래스와 상속 및 구현등 수많은 C++의 문법이다.

고로 C와 C++은 근본적으로 서로 막연히 다른 언어가 아니라 '작성하는 방식이 다른' 언어인 셈이다.

사실 이제는 C로도 객체 지향을 흉내 낼수 있지만 그래도 원조인 C++ 사용하는게 맞다.

두서 없이 쓴거 같아 잘 쓴건지 모르겠지만 글을 읽는 사람이 C와 C++의 차이를 이해할 수 있었으면 좋겠다. :)

'프로그래밍' 카테고리의 다른 글

객체 / 클래스 / 객체화 / 추상 클래스  (0) 2009.03.13
프로그램, 프로세스, 스레드  (2) 2008.10.22
왜 객체 지향인가?  (0) 2008.01.11
젠장할 Direct Show  (0) 2008.01.10
RPCGEN  (0) 2007.11.09
posted by 대갈장군
2008. 10. 17. 07:00 프로그래밍/MSDN

COM 그 이름만 들어도 살짝 현기증이 날 듯한 이름... Microsoft의 작명 센스는 끝내준다. 몰라도 들어본 것 같고 알아도 헷갈리게 만드는 놀라운 작명 센스... /후아

내 블로그에 남기는 글들은 특정한 기술에 대한 'Teaching' 이 아닌 'Talking' 일 뿐이므로 머리를 쪼그라 들게 하는 깊이 있는 이야기보단 지나가는 이야기이지만 보다 쉽게 이해하기 위해 쓰는 것이므로 이해해 주길 바랍니다.

COM 에 대한 첫 만남은 DirectShow에서 시작되었다. 어찌하다 보니 DirectX를 사용해야 하는 상황이 생겼고 나중에는 DirectShow를 사용해야 하는 상황이 왔던 적이 있다.

물론 전혀 즐겁지 않았다.. -_- DirectShow의 자료를 뒤적거리다 첫 대면을 하게된 COM이란 놈은 DirectShow의 '근간' 이요 '근본' 이며 '바탕'이므로 꼬옥! 알아야 한다는 거였다. 줴엥장.

그때는 시간이 없던 관계로 COM에 대한 이해를 할 시간도 없이 '어떻게' 사용하는가만 집중적으로 판다음 완전히 잊어버리고 살았다.

하지만, 오늘 MSDN에서 COM에 대한 문서를 읽어보니 내가 참 무식했구나 하는 생각이 들었다. 물론 '방법'만 알면 오케이인 일도 많지만 '원리'와 함께 알면 절대 잊어버리지도 않을 뿐더러 연결되는 기술이나 개념에 대한 이해가 훨씬 더 쉽다. 그래서 많은 시간을 투자해가면서 MSDN의 미로를 헤메는 중이다.

COM의 정의는 서로간에 의사 전달이 가능한 프로그램의 구성 요소를 플랫폼에 독립적이고 객체 지향적으로 만들어주는 일종의 System이다.


이말 읽고 바로 이해되는 사람이 있다면 이글을 읽어볼 필요도 없는 사람이라고 생각한다.-_- 기본적인 프로그램만 짜본 사람의 입장으로써는 이글은 '김밥 옆구리 터지는' 소리다. 한마디로 뭔소린지 잘 모른다.

내가 생각하는 COM의 정의는 '뇌를 가진 C++ 클래스' 정도? 헐헐헐.. 이건 또 뭔소리? 라고 할수 있다. 이제 차근 차근 설명해 보겠다. 왜 COM이 뇌를 가진 C++ 클래스라 생각하는지.

우선 Interface와 Methods가 뭔지 알아야 하는데... 흠... 이걸 여기서 설명하게 되면 COM의 개념만 쉽게 풀고자 했던 내 의도가 딱딱한 MSDN 문서를 읽어주는 것과 다를게 없어지는 것 같아서 안쓰는게 좋을것 같다.

우선 COM의 단어적 의미를 집어내 보면 '요소 객체 모델'인데 모델은 어떤 체계를 말하므로 빼버리고 C가 의미하는 Component인 요소에 집중해 보자.

내가 앞서 말했듯이 DirectShow는 COM을 기반으로 하는데 DirectShow로 짜는 프로그램의 구성을 보게 되면 마치 전산시간에 배웠던 Flow Chart를 그리는 것과 똑같다.

전산 시간에 네모를 그러서 그 사이를 화살표로 연결하여 그렸던 흐름도 혹은 논리 흐름도를 기억하는가?

DirectShow의 시작은 입력과 출력을 가지는 Filter 라고 불리는 사각형들이 서로 물고 물리는 관계를 형성함으로써 시작된다. 하나의 Filter 출력이 다른 Filter의 입력으로 들어가게 되고 또 그 출력이 다른 입력으로 들어간다.

이런식으로 Data가 '물 흐르듯이' Stream을 가지게 되고 이처럼 쉽게 하나의 프로그램을 완성 할수 있다.

바로 여기서 보면 각각의 Filter가 하나의 COM이고 바로 '요소' 인 것이다. 프로그램을 구성하는 본질적인 요소임과 동시에 마치 레고 블록처럼 독립적이어서 가져다 붙여주고 서로 잘 이어주기만 하면 되는 것이다.

사실 이것은 JAVA의 상속과 크게 다르지 않다. C++의 상속과 재활용성과도 일맥 상통한다. 하지만 결정적인 차이가 있으니 바로 '뇌를 가진' 이라는 단어다.

COM으로 만들어진 객체는 이른바 QueryInterface라는 함수를 가지고 있다. 이건 무조건 모든 COM 객체가 가지는 함수로써 마치 C++의 생성자/파괴자와 같이 무조건 필요한 놈이다.

이 QueryInterface 함수의 특이점은, 프로그래머는 반드시 이 함수를 이용해서 COM객체의 다른 함수의 포인터 (진입점) 을 얻어와야 한다는 것이다.

Query의 단어 뜻 그 자체가 '질의한다'라는 의미로써 프로그래머가 COM객체에게 '야, 너 이런이런 함수 지원해주니?' 라고 물어보는 것이다.

더 놀라운 것은 이 COM 객체는 프로그래머가 저렇게 물어왔을때, 만약 지원이 가능하면 해당 기능으로 들어가는 문 (포인터)를 알려주고 만약 지원이 안되면 '안되요!' 라고 하면서 NULL 을 리턴한다는 것이다.

이게 뭐가 대단한가? 라고 의아해 한다면 당신은 아마도 잘못된 함수의 호출로 인해 공포의 퍼런 화면을 한번도 안띄워 본 사람! 우후훗!

뇌를 가졌다고 말하는 이유는 바로 이 기능 때문이다. COM 객체는 스스로가 할수 있는 일과 없는 일은 명확히 구분하여 프로그래머의 사용 미숙이나 강제 호출에도 스스로 'NO' 라고 말할 수 있기 때문에 나는 COM이 생각하는 클래스라고 말했다.

사실 여전히 COM 객체는 프로그래머에게 종속적이기는 하지만 스스로의 독립성과 안전성을 지키려고 하는 것은 예전에 C++의 클래스에서는 찾아보기 힘든 점이다.

MSDN에서 말하기를 'COM 객체는 프로그래머 (혹은 클라이언트) 와 Negotiate 한다.' 라고 했다. 즉, '협상'을 한다는 말인데 프로그래머와 대등한 입장에서 서로 대화를 주고 받는다는 의미라 하겠다. 세상 참 좋아졌다... 컴퓨터가 나랑 협상을 다하고... ㅋㅋ

많은 사람이 착각을 하는 것이 COM이 그 자체로 또다른 '언어'라고 생각하는 것인데 (C나 C++처럼) 사실 그렇지 않다고 생각한다.

다만 COM과 같은 '체계' 혹은 '모델'은 사용자에게 보다 나은 방법의 프로그램 설계 방식을 '제안'하고 있을 뿐이다.

그것을 이해한다면 COM이나 MFC에 대해 겁부터 덜컥 먹을 일이 전혀 없다. C와 C++부터 차근 차근 알아나가면 점점 탄력이 붙어 나중에는 자연스레 이해할 수 있게 된다.

이제 결론을 말하자면,

COM은 '생각하는' 혹은 '협상할줄 아는' 함수들의 집합체로써 사용자와 '대화'를 통해 만들어져 있는 수많은 좋은 함수들을 공짜로 '제공'해준다.


다만 이 COM을 제대로 사용하기 위해서 반드시 따라야 하는 조건들만 잘 지키면 무리없이 잘 사용할 수 있다는 점.

그리고 COM의 장점이라면,

무조건 프로그래머의 명령에 'YES"라고 대답하지 않고 우선 할 수 있는건가 아닌가 찾아보는 '똑똑함' 이랄까? :)
posted by 대갈장군
2008. 10. 16. 02:47 프로그래밍/MSDN
앞서 본 DLL 이야기 처럼 DLL은 많은 장점을 가지고 있으나 관리하기 쉽지 않다는 문제가 있다.

사람들이 흔히 만드는 DLL의 오류나 에러는 3가지 타입으로 분류된다고 하는데,

Type 1은 사용자나 일부 잘못된 프로그램이 OS가 사용하는 핵심 DLL의 버전을 지 맘대로 바꿔버릴는 경우를 말하는데 최신 버전의 DLL이 구버전으로 바뀌어 버림으로써 오는 문제다.

이 Type 1 DLL 에러는 과거 Windows 98 시절에는 셀수없이 많았다고 한다. 하지만 지금은 그 자취를 거의 감추었다는데 그 이유는 차차 알아보자.

Type 2Side-Effect 라 불리는 오류인데 이것은 DLL이 업데이트 되면서 (최신 버전으로 바뀌면서) 사용자가 의도와는 다르게 변경된 DLL의 함수들이 예상치 못한 작동을 하는 것이라는데 이것은 뭐 DLL을 만든사람의 실수가 아닌가 싶다만... 구버전의 DLL로 잘 작동하던 프로그램은 반드시 새 버전의 DLL과 잘 작동해야 하지만 100% 그렇지는 않다는게 문제란다.

Type 3는 가장 드물지만 DLL 자체에 버그가 있는 경우다. 물론 이건 아주아주 드물기는 해도 발생할 수 있음을 명심해야 한다. 베타 버젼을 즐겨 사용하는 사람이라면 더더욱 그렇겠지...

이런 3가지 타입의 DLL 오류의 제거를 위해서 Windows 2000에서 아주 기발하고 유용한 방법이 제시되는데 그것이 바로 WFP (Windows File Protection) 이라는 것이다.

우선 이것을 이해하기 위해서는 System DLLPrivate DLL을 알아야 한다. 하나의 컴퓨터에는 무수히 많은 DLL이 존재하는데 이것들은 딱 크게 나눠서 두가지의 DLL로 분류가 된다.

System DLL은 Windows/System32 폴더안에 주로 존재하는 '핵심적인' DLL들이다. 즉, OS가 빈번히 사용하며 여러가지의 프로그램에 대해서 많이 사용하는 DLL을 말한다.

고로 이런 System DLL들은 '잘못' 건드리게 되면 윈도우 전체가 멈춰버리거나 죽음의 '퍼런 화면'을 경험하게 될것이다.

그래서 Windows 2000부터는 이런 System DLL들은 사용자가 변경을 하려해도 DLL에 내장되어 있는 Key를 체크하여 잘못된 변경을 하더라도 윈도우가 알아서 재복사를 해버린다.

즉, Service Pack 과 같은 '시스템이 제공하는 업데이트'가 아니고서는 이 System DLL들을 변경 할수 없다. 고로 사용자의 실수로 인한 시스템 전체의 오류는 완전히 차단하는 것이다.

두번째로 Private DLL이다. 이 Private DLL은 다른말로 하면 Side-by-side DLL이다. 흐흐흐... 나는 이 말을 듣는 순간, Side-by-side Assembly가 빠악! 하고 머리에 떠올랐는데 과연 다른 사람도 그럴까? :)

이 Private DLL은 어떻게 보면 DLL의 기본 원리중에 최대 장점이었던 '통일성'을 완전히 역행하는 반역자이지만 '독립성'을 확보하게 해주는, 즉 소소한 프로그램을 많이 사용하는 나같은 프로그래머에게는 꼬옥 필요한 것이다.

원래 DLL은 통일된 하나의 DLL을 사용함으로써 모든 프로그램이 공유하는 DLL 하나를 관리함으로써 유지 보수 및 관리를 쉽게 한다는 장점이 디스크나 메모리 공간 절약보다 더 크다고 말했다.

하지만 Visual Studio를 써본 사람은 알겠지만 Visual Studio는 하나의 프로젝트를 컴파일 할때 .NET Framework가 제공하는 Assembly 들 (일종의 DLL)을 사용하는데 이놈들의 버전이 컴퓨터마다 달라서 기껏 만들어논 프로그램이 다른 컴에서 돌리면 실행조차 되지 않는 이상한 경험을 해보았을 것이다.

이때는 물론 다른 컴퓨터에 Visual Studio를 설치하거나 .NET Framework를 설치해서 업데이트 하면 되겠지만 만약 그런게 없는 컴퓨터라면 어쩔건가?

바로 이런 점이 '통일성'이 가지는 문제점이다. 다른 환경을 가지는 다른 컴퓨터에 대한 '이식성'이 떨어지는데 이걸 해결하기 위한 것이 'Static한 링크' 즉, 통일성과 반대되는 의미로 '유동성 혹은 프로그램의 독립성'이다.

이 Private DLL은 내가 실행하고자 하는 .exe파일과 같은 폴더에 존재하며 .exe 파일에 필요한 DLL 파일을 말한다. 이것은 당연히 System DLL이 아니기 때문에 사용자가 원하는, 필요로 하는 특정한 버전의 DLL을 .exe 파일과 함께 놔두면 되는 것이다.

내가 참고한 MSDN의 문서에서는 Private DLL을 사용한다는 것을 OS인 윈도우에게 알려주기 위해서는 .exe가 있는 폴더에 (Private DLL에 있는 폴더) 간단하게 실행하고자 하는 실행 파일과 같은 이름 + .local 파일을 만들어주면 된다고 되어 있는데 아직까지 이 룰이 적용되는지는 잘 모르겠다.

내가 알기로는 .exe 파일은 기본적으로 현재 실행 파일이 있는 폴더에 필요로 하는 DLL이 있는지 먼저 검색하는 것으로 알고 있다. 물론 테스트 해본적은 없다... -_-

이 Private DLL을 .exe 파일과 함께 두면 .exe 파일은 실행시 Private DLL을 읽게되고 프로그램 제작자는 이 프로그램이 잘 작동하는 Private DLL을 .exe 파일과 같이 제공하면 그만인 것이다. 이렇게 함으로써 '통일성'은 잃게 되지만 Private DLL을 사용하는 .exe 프로그램은 대신에 System DLL에 영향을 전혀 받지 않는 '독립성'을 가지게 된다.

자, 이 두가지의 DLL 분류와 WFP를 이용하면 위에서 나열한 거의 모든 Type 에러를 해결할 수 있다.

System DLL은 윈도우가 자동으로 관리함으로써 사용자의 미숙으로 인한 잘못을 예방하고, Private DLL을 이용함으로써 환경이 다른 컴퓨터로 이식성 및 독립성을 증가 시켰다.


이 문서에서는 DLL Universal Problem Solver라는 프로그램(DUPS: 둡스!?)을 이용해서 네트워크 상에 있는 모든 연결된 컴퓨터에 대해 DLL 버전을 체크하고 문제가 있는지 검사하는 프로그램에 대해 간단히 설명하고 있는데 사실 나에게는 아직 필요가 없는 듯해서 한번 읽고 넘어 갔지만, 게임방 사장님들에게는 필요한 유틸리티가 아닌가 싶다...

워낙 우리나라 게이머들께서 겜방 PC로 이것 저것 많이 테스트 하시니...  ^^
posted by 대갈장군
2008. 10. 15. 07:00 프로그래밍/MSDN

DLL HELL이라는 단어를 처음 접하고 '풋' 하고 웃었다면 당신은 이미 DLL로 인해 머리 한움큼 정도는 뽑아본 사람이 분명하다..

MSDN에서 머리에 쏙쏙 들어올만한 정보를 찾아 수없는 클릭질을 해댄지 어언 10분. 재미있는 재목의 글을 찾았다. 이름하여 THE END OF DLL HELL.

이름만 봐도 재미있을 것 같아서 차근 차근 읽어보았다.

DLL에 대해서 그 개념 정도는 충분히 이해하고 있었으나 정작 DLL이 일으켜온 문제와 어떤 방식으로 DLL의 문제를 종식시켜 나갔는지는 전혀 몰랐다. 이번을 계기로 확실히 알고 넘어가게 되었다.

DLL은 풀어서 쓰면 Dynamic Library Link인데 한국어로 하면 '동적 라이브러리 링크'이다.

C 프로그램을 작성하게 되면 우리는 생각없이 헤더 파일을 포함하는데 대표적인 헤더 파일이 바로 stdio.h파일이라던가 iostream 같은 파일들이다.

이 헤더 파일들은 실행 코드를 담고 있는, 즉 다시말해서, 실행에 필요한 모든 코드를 가지고 있는 것이 아니라 prototype만 담고 있는 글에 불과하다. 책의 '목차'에 불과한 것이다.

실제 '내용'은 바로 라이브러리가 담고 있는데 라이브러리는 참 많다... -_- 짜증나게 많고 다양하다.

대표적으로 Visual Studio 가 사용하는 라이브러리는 MSVCRT.DLL이다. 걍 이놈이란 놈이 자주 쓰인다고 알고 있자.

이 라이브러리는 수많은 윈도우 프로그램들에서 사용이된다. 프로그래머가 짜는 거의 대다수의 프로그램도 이 라이브러리를 사용하게 되는데 자 그렇다면 여기서 생각을 해보자.

만약에 저 라이브러리를 사용하는 모든 프로그램들이 저 MSVCRT.DLL을 하나씩 소유한다면 얼마나 비효율적일까?

우선 하드드라이브에 저 파일이 수십 수백개 만들어질꺼고, 게다가 내 피같은 RAM (메모리)에 저 파일이 수십 수백개가 생길것이다.

즉, 메모리 낭비요 게다가 하드디스크의 낭비다. 물론 요즘 메모리나 하드디스크가 똥값이긴 해도 그래도 낭비는 낭비다!

DLL을 개발해낸 사람의 최후의 카드는 바로 'Consistency' 즉 '일관성' 혹은 '통일성'에 있다. 바로 이것이 DLL의 가치를 드높여 주는 것인데, 이것이 무엇인가 하면...

많은 프로그램이 공유하게 되는 저 MSVCRT.DLL 파일 하나를 수정함으로써 혹은 업뎃함으로써 모든 프로그램의 동작을 수정할 수 있게된다. 즉, 사용되는 파일을 단 하나로 통일 시킴으로 얻게되는 장점이 바로 그것이다.

허나 반대로 말하면 저 파일 하나가 잘못됨으로써 저것을 사용해서 프로그램을 돌리는 모든 것이 한방에 동작을 안할 수도 있다는 말이다. 허걱. 이 얼마나 위험한 일인가...

바로 이런 문제들이 Windows 98이전에는 수도없이 빈번했으며 인증되지 않은 프로그램에 의해서 혹은 사용자의 실수에 의해서 중요한 DLL 파일이 바뀌어지는 바람에 수많은 프로그램이 갑자기 동작을 멈추게 되었고 이로인해 수많은 머리털이 뽑히거나 하드디스크의 쓸데없는 포멧으로 이어진것이다.... 헐헐헐..

이것을 어떻게 고쳤는가는... 다음 글에서 써야 겠다. 집에 가야 할 시간이 왔군요.. ^^

참조 http://msdn.microsoft.com/en-us/library/ms811694.aspx 

posted by 대갈장군
2008. 10. 14. 06:28 프로그래밍/MSDN

사실 Visual Studio 2005 (VS 2005)를 사용해 오면서 신경쓰지 않았던 부분이 바로 이 manifest file의 존재다.

아마 이글을 본사람들 중에 상당수도 '저게 뭐임?' 이라고 생각하는 사람이 많을 것이라 생각된다.

이 manifest의 중요성은 내가 만든 프로그램을 다른 컴퓨터에서 돌려볼려고 할때 나타난다. 골 깨지는 소리와 함께... 두둥.

전반적인 설명을 위해 자세한 내용은 무시하고 개념만 적어보자. 내가 잘 까먹으므로 이렇게 적어놓는게 훨씬 도움이 됨...

일단 .NET Framework를 알아야 한다. 다 알거는 없고, 이 녀석은 간단히 말해서 컴퓨터의 spec에 관계 없이 VS 2005로 작성한 프로그램이 잘 돌아가도록 해주는 놈이라고 생각하자.

그니까 int가 4바이트인 컴퓨터건 8바이트인 컴퓨터건 (32 비트 컴퓨터냐 64 비트 컴퓨터냐) 관계없이 내가 짠 프로그램이 잘 돌아가도록 해준다.

상식적으로 .NET Framework가 컴퓨터에 관계없이 내 프로그램을 잘 돌아가게 만들어 준다는 것은 .NET Framework가 어떤 공통적인 무언가를 가지고 있거나 아니면 통일된 규정에 따라서 해당 컴퓨터에 맞게 Binary code (실행 코드)를 만들어 낸다는 것이다.

사실 많은 중요하지만 이해하기 쉽지 않은 단어들이 있다. CLR, CRL, CIL, MSIL, 등등등... 이런것들은 VS 2005 책이나 VS 2008 졸라 두꺼운 책 (빨강색) 에 보면 잘 나와있다. 아니면 MSDN 혹은 Wiki를 머리 뽑으면서 읽으면 이해할 수 있다.

이런거 모두 제끼고, .NET Framework Assemblies을 보자. 이놈들은 일부만 컴파일된 dll 파일들이다. 이놈들은 프로그램이 실행되는 시점 (CLR이 Just-in-time complier를 이용해 machine code를 생성해 내는 시점) 에 호출되어 사용된다.

한마디로 필요한 정보가 저장된 도서관(library)이다. 중요한 것은 이제부터인데, VS 2005를 이용해서 프로그램을 작성하게 되면 기본적으로 VS 2005는 manifest 파일이라는 것을 만들어낸다.

이 manifest 파일은 만들어진 프로그램이 어떤 .NET Framework Assemblies를 사용하는지 그리고 그 버젼은 무엇인지, 또 의존성은 어떻게 되는지 등등등을 설명하고 있다.

한번이라도 VS2005를 이용해서 프로그램을 만들어 본 사람이라면 Debug 혹은 Release 폴더에 manifest 라는 단어가 들어가 있는 파일이 생성된 것을 보았을 것이다.

그 파일을 열어서 보게 되면 안에 내용중에 이런 부분이 있다,
<assemblyIdentity type="win32" name="Microsoft.VC80.DebugCRT" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>

이것이 바로 .NET Assemblies 중에 어떤 버전을 사용하고 있는지 그리고 Debug인지 Release인지, 또 x86 인지 아닌지, 게다가 publickey 까지 자세히 보여주고 있다.

여기서 주목할 것은 name과 version이다. 윈도우 폴더 아래에 있는 WinSxS 라는 폴더를 열어보자. 여기를 열어보면 저 name과 비스무리한 폴더들이 보인다. 그리고 폴더 이름뒤에 version 번호도 보인다.

바로 WinSxS 폴더가 현재 컴퓨터에 설치되어 있는 Shared Side-by-Side Assemblies를 모두 보여준다. 한마디로 이 컴퓨터에서 제공되는 모든 .NET Framework Assemblies를 담고 있는 폴더인 셈이다.



만약 어떤 프로그램을 실행했는데 터무니 없게도 This system cannot execute from this program. 이라든가 위 그림처럼 어이없는 에러가 터진다면 가장 먼저 실행 파일과 함께 있는 manifest 파일을 열어보자. 그리고 그 컴퓨터의 WinSxS 폴더를 열어서 해당 어셈블리가 존재하는지 확인하자.
사용자 삽입 이미지

만약 manifest 파일에 나와있는 Version과 같은 폴더가 존재하지 않는다면 이 컴퓨터는 프로그램을 돌리기 위한 자료가 없기 때문에 실행할 수 없다는 '어이가 없는' 에러 메세지를 내 보낸다.

이것을 해결하기 위한 방법으로는, (확인해 본적은 없지만)
1) 만약 컴퓨터에 VS 2005나 VS 2008이 설치되어 있다면 업데이트를 해라. (업데이트를 하면 새로운 버젼의 어셈블리들이 자동으로 설치된다.)
2) Manifest를 열어서 해당되는 버젼의 DLL 파일을 찾아서 실행파일이 있는 폴더에 복사해 넣어라. 이것은 100% 된다고 장담할 수 없다. .NET Framework가 설치조차 안되어 있다면 아마도 작동 안될 것이다.
3) Microsoft Visual C++ Redistribution (재배포) Package 를 다운받아 설치하라. 

3번이 가장 합리적인 방법인데 구글에서 Visual C++ Redistribution package를 검색하면 Visual Studio 2005 와 2008 버전에 대한 각각의 다운로드들이 뜬다. 이 파일을 다운로드 하여 설치하면 WinSxS 폴더에 해당 버전의 어셈블리들이 자동으로 설치되며 Visual Studio가 없는 사람들도 프로그램을 돌릴수 있게 된다.

재배포 파일의 설치가 가장 중요하다... 이 프로그램은 이 컴퓨터에서 돌아갈수 없다는 말도 안되는 문제를 거의 100% 해결해 준다.


posted by 대갈장군
2008. 7. 21. 23:45 프로그래밍/MSDN
내가 만든 프로그램이 어떤 Visual C++ Library들을 사용하는지 확인하고 싶다면 Property Pages에 있는 General Tab에 들어가보면 쉽게 알수 있다.

예를 들어서 내 프로젝트가 MFC나 ATL을 쓰는지도 쉽게 알수 있다. 만약 내가 MFC를 Dynamic Library에서 사용하고 있다면 내 프로그램은 MFC DLLs에 의존하게 된다. (mfc90.dll) 만약 내 프로그램이 MFC나 ATL을 사용하지 않는다해도 Multi-threaded debug DLL (/MDd)를 Runtime Library로 사용할 경우 프로그램은 여전히 CRT library에 의존하게 된다.

가장 좋은 방법은 depends.exe (Dependency Walker)를 실행해보는 방법이라는데 이것은 /Microsoft Visual Studio 2005/Common7/Tools/bin 에 설치 되어 있단다. 이건 Win32 SDK Tool을 설치한 경우에만 설치되어 있단다.

이 depends.exe를 실행하면 모든 로딩되는 DLLs를 다 알수 있다. (static이건 dynamic 이건간에)

이 프로그램을 실행할때 주의해야 할것은 하나의 DLL은 또다른 DLL 파일 혹은 특정 버젼의 DLL에 의존할수도 있다는 점이다.

모든 DLLs 리스트를 받고나서는 이중에서 어떤 DLLs들이 다른 컴퓨터에서 실행될때 redistricution되어야 하는지를 결정해야 한다는데 대부분의 경우는 system DLLs들은 redistribution 할 필요가 없다. 하지만 Visual C++ library는 redistribution 할 필요가 있다는 군. 아마도 이게 문제 아닐까?

'프로그래밍 > MSDN' 카테고리의 다른 글

[MSDN] /MT, /MTd, /MD, /MDd (C Runtime Library Option)  (12) 2008.10.22
[MSDN] COM (Component Object Model)  (2) 2008.10.17
[MSDN] End of DLL War!  (0) 2008.10.16
[MSDN] DLL HELL!!!! (1)  (0) 2008.10.15
[MSDN] Manifest 파일?!?!  (0) 2008.10.14
posted by 대갈장군
2008. 2. 23. 04:28 프로그래밍/DirectX
이 장을 끝으로 이 부분은 끝나네. DX에 대해서 더 알고 싶다면 어떤 것을 더 읽어보아야 할까?

이 장에서 만들어 볼것은 우선 레이더 스크린과 체력바, 그리고 총알 숫자 표시다.

1. A Too-Simple Wrapper for Sprites
처음으로 Wrapper라는 단어가 나온듯 하다. 원래 부터 복잡한 DX의 속성상, 모든것을 단순화 시켜주는 함수가 있다면 좋을 것이다.

이 튜토리얼에서는 두개의 함수에 대해서 랩 함수를 만드는데 하나는 텍스쳐 파일 로딩 ㅎ마수 다른 하나는 파일의 일부만 렌더링 하는 함수다.

고로 의역하자면 wrapper 함수란 복잡한 인자를 가지는 함수를 간단하게 표현하는 둘러싸기 함수다.

1-1. LoadTexture()

void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename)
{
    D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
        D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
        D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL, texture);

    return;
}

복잡했던 텍스쳐 로딩 함수를 단 두인자로 압축해 버렸다. 특이점은 color key가 핫 핑크로 설정된점, 그리고 alpha 채널이 포함된점이다.

1-2. DrawTexture()

이 놈은 쉽지 않다는 구나. 여기는 더 많은 인자가 필요로 하다. 각각의 그래픽이 그려지기 위해서는 텍스쳐의 위치 사이즈 스크린에서의 위치등을 정해 줘야 한다. 게다가 어떤 텍스쳐를 사용할지도 지정해야 한다. 벌써 7개의 인자가 필요하다.

일단 RECT 구조체를 사용해서 텍스쳐의 사이즈와 위치를 나타내자.

void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, int x, int y, int a)
{
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
    d3dspt->Draw(texture, texcoords, &center, &position, D3DCOLOR_ARGB(a, 255, 255, 255));

    return;
}

2. Constructing the Radar

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.1 - The Radar Sprites


위의 그림이 레이더를 구성하는 요소들이다. 중요한건 각각의 스프라이트는 핑크로 배경색이 채워져 있어 컬러키를 이용해서 필요한 부분만 렌더링이 가능하다.

2-1. The Code
레이더를 그리기 위해서는 그리는 순서를 정확하게 할 필요가 있다. 왜냐구? 만약 순서가 틀리면 (빨간점이 가장 나중에 그려지지 않으면) 잘못된 결과가 나온다. 그래서 그리는 순서는 1. 투명한 그리드 2. 빨간점. 3. 테두리 이다.

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;

void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);
     
    // display the enemy
    SetRect(&Part, 341, 14, 344, 17);
    DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    return;
}

여기서 보면 두개의 함수로 하는데 LoadDisplay() 함수와 DrawDisplay() 함수다.

우선 LoadDisplay() 함수로 텍스쳐를 로딩해 놓는다. 이 텍스쳐에는 필요한 모든 이미지가 들어가 있고 배경은 핑크색이다. 그런다음 SetRect 함수를 이용해서 원하고자 하는 영역만 설정한다. (그리고자 하는 영역만 설정, 텍스쳐 안에서)  그런다음에 적당한 좌표에다가 그려준다.

2-2. Fixing the Radar

이것의 문제점이 하나 있으니 바로 적군을 표시하는 빨간점이 레이다 영역 밖에서도 표시된다는 점이다. 고로 이걸 해결하기 위해서는 빨간점을 특정한 영역 내에 있을때만 표시하도록 해야 한다.

점 1과 점 2사이의 거리를 측정하는 방법은 다음과 같지요.

Distance = √(x1 - x2)² + (y1 - y2)²

이걸 이용해서 빨간점과 중심과의 거리를 잰다. 만약 넘어가면 그리지 않는다. 고로 다음과 같이 코드가 바뀐다.

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;

void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);
     
    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    return;
}

3. Constructing the Health Bar

이제 피가 남아있는 걸 보여주는 바를 만들 차례인데, 이 아이템은 딱 두개의 그래픽을 가진다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.2 - The Health Bar Sprites

그림이 조금 잘 안보이지만 윗 그림의 제일 오른쪽 편에 보면 조그마한 막대로 하나 있다.

방법은 간단하다. 우선 바를 그리고 %만큼 필터를 채워나가면 된다. 여기서 필터란 윗 그림에서 잘 안보이는 조그만 막대를 말한다.
Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.3 - The Health Bar Pieced Together at Varying Percentages

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;


void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 1, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);


    return;
}

흠. 복잡해 보이지만 (상수가 많아서) 그닥 어려운 코드는 아니다. 다만 정확한 텍스쳐 내부의 좌표를 꿰고 있어야 한다는 단점이 있네... 왜 텍스쳐를 따로 쓰지 않을까? :)

4. Constructing the Ammo Indicator

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.4 - The Ammo Indicator Sprites

위의 두 그림을 이용할 텐데 오른쪽 그림 하나만 있어도 될거 같은디?

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;
int ammo = 10394;
LPD3DXFONT dxfont;


void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    D3DXCreateFont(d3ddev, 12, 0, FW_NORMAL, 1, false, DEFAULT_CHARSET,
                   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
                   L"Arial", &dxfont);


    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 14, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);

    // DRAW THE AMMO INDICATOR
    // display the backdrop
    SetRect(&Part, 351, 14, 456, 40);
    DrawTexture(DisplayTexture, Part, 530, 449, 127);

    // display the border
    SetRect(&Part, 351, 45, 457, 72);
    DrawTexture(DisplayTexture, Part, 530, 449, 255);

    // display the font
    SetRect(&Part, 535, 453, 630, 470);
    static char strAmmoText[10];
    _itoa_s(ammo, strAmmoText, 10);
    dxfont->DrawTextA(NULL,
                      (LPCSTR)&strAmmoText,
                      strlen((LPCSTR) &strAmmoText),
                      &textbox,
                      DT_RIGHT,
                      D3DCOLOR_ARGB(255, 120, 120, 255));

      return;
}

위에서 보면 _itoa_s 함수로 정수를 문자열로 바꾼다. 근데 저기 계속 해서 등장하는 LPCSTR등은 참으로 짜증난다.

5. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface
LPD3DXFONT dxfont;    // the pointer to the font object
float enemyX = 60.0f, enemyY = 60.0f;    // the enemy position
int health = 300;    // the player's current hitpoints
int maxhealth = 1000;    // the player's max hitpoints

int ammo = 10394;    // the player's current ammo

// sprite declarations
LPDIRECT3DTEXTURE9 DisplayTexture;    // the pointer to the texture

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void load_display();
void draw_display();
void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename);
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, float x, float y, int a);


// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    load_display();

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // begin sprite drawing with transparency

    draw_display();

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    DisplayTexture->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}


// this loads the display graphics and font
void load_display()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    D3DXCreateFont(d3ddev, 20, 0, FW_BOLD, 1, false, DEFAULT_CHARSET,
                   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
                   L"Arial", &dxfont);

    return;
}


// this draws the display
void draw_display()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)

    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 1, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);

    // DRAW THE AMMO INDICATOR
    // display the backdrop
    SetRect(&Part, 351, 14, 456, 40);
    DrawTexture(DisplayTexture, Part, 530, 449, 127);

    // display the border
    SetRect(&Part, 351, 45, 457, 72);
    DrawTexture(DisplayTexture, Part, 530, 449, 255);

    // display the font
    SetRect(&Part, 535, 453, 630, 470);
    static char strAmmoText[10];
    _itoa_s(ammo, strAmmoText, 10);
    dxfont->DrawTextA(NULL,
                      (LPCSTR)&strAmmoText,
                      strlen((LPCSTR) &strAmmoText),
                      &Part,
                      DT_RIGHT,
                      D3DCOLOR_ARGB(255, 120, 120, 255));

    return;
}


// this loads a texture from a file
void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename)
{
    D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
        D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
        D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL, texture);

    return;
}


// this draws a portion of the specified texture
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, float x, float y, int a)
{
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
    d3dspt->Draw(texture, &texcoords, &center, &position, D3DCOLOR_ARGB(a,255, 255, 255));

    return;
}

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.5 - The enemy is coming, and you're low on health.  Good thing there's LOTS of ammo!
posted by 대갈장군
2008. 2. 23. 02:28 프로그래밍/DirectX

이제 글자를 넣어보자.. 이거네

1. All About Fonts
font를 결정하는 세가지 요소가 있으니 바로 typeface, style 그리고 size다.

1-1. Typeface
타입페이스는 폰트의 모양, 길이 그리고 스트로크등을 결정하는 요소다. serif도 typeface를 결정하는 요소인데 이 serif라는 놈을 찾아보니 글자 모양 중에 끝부분에 각이진것을 serif라 하고 없는 것을 san serif라 한다. 다음 그림을 보면 그 예가 있는데

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.1 - Sans Serif and Serif

Times New Roman의 글자 중 M자를 보면 아래쪽에 꺽이는 짧은 라인이 들어간다. 위에도 들어간다. 하지만 San serif에는 그런게 없다. 그것이 차이점이란다... 흠... 그랬군..

1-2. Style
Style은 사실 typeface의 변형이다. 이것은 글자의 무게나 기울기등을 담당한다. 무게라함은 사실 blod냐 아니냐를 말하고 기울기라 함은 italics냐 아니냐를 말한다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.2 - Italic and Bold Styles

1-3. Size
이제는 크기다. 일반적으로 크기라 함은 글자의 제일 윗부분에서 제일 아랫부분의 크기인데 아래 그림에서 보다시피 의외로 낭비되는 공간이 있네.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.3 - Arial Font Size


이것들을 외우고 있을 필요는 없지만 나중에 사용할때는 반드시 이해하고 있는것이 좋다. 당삼하지요.

2. Creating Fonts and Displaying Text
글자를 그려내는것은 과정상으로 보면 되게 간단한데 다음과 같이 2단계만 거치면 된다.

1. Create the font
2. Draw the text using the created font

2-1. Create the font
폰트를 만들어내야 하는데 만들기 위해선 D3DXCreateFont() 함수를 호출해야 한다.

HRESULT D3DXCreateFont(LPDIRECT3DDEVICE9 pDevice,
                       INT Height,
                       UINT Width,
                       UINT Weight,
                       UINT MipLevels,
                       BOOL Italic,
                       DWORD CharSet,
                       DWORD OutputPrecision,
                       DWORD Quality,
                       DWORD PitchAndFamily,
                       LPCTSTR pFacename,
                       LPD3DXFONT* ppFont);

LPDIRECT3DDEVICE9 pDevice : 이건뭐 d3ddev다

INT Height : g의 아래쪽 부터 M의 위쪽까지의 크기다. 일반적인 사이즈는 12이다.

UINT Width : 폰트의 폭인데 이것은 각각의 글자마다 다르므로 딱히 하나로 지정할 수는 없고 0으로 설정하면 알아서 적당한 크기의 글자로 찍어낸다. 이 인자는 초기값을 주는 것으로써 아마도 크게 주면 크게 줄수록 글자들 자체가 옆으로 늘어날것으로 예상. (테스트 해보자)

UINT Weight : 글자의 두께. 0에서 1000까지 된다는 군. 0으로 놓으면 기본값으로 표현하기. 이 인자에 대해서는 특별히 두께 플래깅이 존재하는데 다음 테이블과 같다.

Flag Value
FW_DONTCARE 0
FW_THIN 100
FW_ULTRALIGHT 200
FW_LIGHT 300
FW_REGULAR 400
FW_MEDIUM 500
FW_DEMIBOLD 600
FW_BOLD 700
FW_ULTRABOLD 800
FW_BLACK 900
[Close Table]

UINT MipLevels : 밉맵 사용 레벨 설정인데 여기서는 1로 해놔도 됨

BOOL Italic : 이것은 이탤릭 체를 사용할 건가 말건가에 대한 설정. TRUE혹은 FALSE설정

DWORD CharSet : 이것은 어떤 글자셋을 사용할 건지 설정하는 건데 일반적으로는 English set으로 설정한다. 왜? 여기는 미국이니까... -_- 각각의 셋에 대해서 알고 싶다면 (한글, 중국어등) 문서를 찾아보면 된단다. 일단 영어를 기본으로 하니까 DEFAULT_CHARSET으로 쓰면 OK

DWORD OutputPrecision : 흠, 이것은 매치되는 글자체가 없는 경우 윈도우가 선택해야 할 글자체를 말하나 본데 일반적으로는 OUT_DEFAULT_PRECIS를 선택해서 사용한단다. 이 인자는 조금 이해가 안된다만 일단 넘어가자.

DWORD Quality : 폰트의 질을 말하는데 다음 테이블과 같은 값이 들어갈 수 있다.

Value Description
DEFAULT_QUALITY This indicates that the quality of the font does not matter.
NONANTIALIASED_QUALITY This indicates that the edges of the font will not be smooth.  It is faster and more efficient than quality types where smooth edges are used.
ANTIALIASED_QUALITY This indicates that the edges of the font are smooth.

Note that if you don't use either this flag or the prior one, DirectX will use antialiased fonts only if the user has selected "Smooth Screen Fonts" in the Control Panel.
PROOF_QUALITY This indicates that the quality of the font is very high, and exactly matches the font image.  However, because font images are not given in every size, this setting limits which sizes you can use, and using different fonts will cause DirectX to choose the closest font size supported.
DRAFT_QUALITY This indicates that the quality of the font is less important than in PROOF_QUALITY, but it allows you to select different sizes of fonts.  Be warned, however, using various styles, such as bold and italic, can slow this one down if you have a lot of fonts.
[Close Table]

DWORD PitchAndFamily : pitch와 font family는 폰트에 추가되는 새로운 2가지 속성이라는데 pitch는 1 인치에 들어가는 글자의 갯수를 말하고 Font Family는 비슷한 형태와 스타일을 가지는 그룹을 말한다.

일반적으로 내가 굉장히 폰트에 민감해 하지 않는다면 DEFAULT_PITCH || FF_DONTCARE로 놓으면 된다만 정말로 정확하게 하고 싶다면 문서를 뒤져봐라.

LPCTSTR pFacename : 폰트 이름을 지정하는 문자열에 대한 포인터. 일반적으로 Arial, Times New Roman 같은게 되겠죠.

LPD3DXFONT* ppFont : 폰트 오브젝트에 대한 포인터. 이 오브젝트가 폰트에 대한 모든 정보를 담게 된다.

LPD3DXFONT font_1;    // create a font object

D3DXCreateFont(d3ddev,    // the Direct3D Device
               20, 0,    // font size twenty with the default width
               FW_NORMAL,    // normal font weight
               1,    // no mipmap levels
               FALSE,    // not italic
               DEFAULT_CHARSET,    // default character set
               OUT_DEFAULT_PRECIS,    // default precision
               DEFAULT_QUALITY,    // default quality
               DEFAULT_PITCH || FF_DONTCARE,    // more defaults...
               L"Arial",    // typeface "Arial"
               &font_1);    // address of the font object created above

이제 다 만들었다.

2-2. Draw the text using the created font
이제 만들어진 폰트를 이용해서 그려보자. 음 DrawText라는 함수가 따로 존재하는구나...

INT DrawText(LPD3DXSPRITE pSprite,
             LPCTSTR pString,
             INT Count,
             LPRECT pRect,
             DWORD Format,
             D3DCOLOR Color);

LPD3DXSPRITE pSprite : 쪽화면 인자네... 이것을 사용하여 글자를 그려내는데 만약 안 만들었다면 NULL로 놓아도 무방하나 만약 빈번하게 재사용할 글자라면 만들어서 사용하는것이 효율적인 면에서 낫다.

LPCSTR pString : 디스플레이 하고 싶은 글자다.

INT Count : 디스플레이 할 글자수.

LPRECT pRect : 어디에 글자가 디스플레이 되어야 할지 설정해주는 사각형 좌표.

DWORD Format : 레이아웃을 결정하는 인자. 다음 테이블 참조.

[Table 4.3 - Font Format Flags]
Value Description
DT_SINGLELINE Displays the text on a single line, regardless of line breaks or carraige returns.
DT_CALCRECT Calculates the height and width of the rectangle for you, regardless of the values stored specified in the pRect parameter.
DT_CENTER Centers the text in the rectangle.
DT_LEFT Aligns the text to the left side of the rectangle.
DT_RIGHT Aligns the text to the right side of the rectangle.
DT_TOP Justifies the text to the top of the rectangle you select.
DT_BOTTOM Justifies the text to the bottom of the rectangle you select.  It only works when combined with DT_SINGLELINE
DT_VCENTER Justifies the text in the center of the rectangle.  It only works on single lines.
DT_WORDBREAK Lines are automatically broken between words when text reaches the end of a rectangle, thus adding an additional line.
[Close Table]

D3DCOLOR Color : 글자의 색상.

dxfont->DrawTextA(NULL,
                      "Hello World...FINALLY!",
                      22,
                      &textbox,
                      DT_CENTER | DT_VCENTER,
                      D3DCOLOR_ARGB(255, 255, 255, 255));

여기서 textbox는 전체 화면 사이즈 인듯.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXFONT dxfont;    // the pointer to the font object

// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateFont(d3ddev,    // the D3D Device
                   30,    // font height of 30
                   0,    // default font width
                   FW_NORMAL,    // font weight
                   1,    // not using MipLevels
                   true,    // italic font
                   DEFAULT_CHARSET,    // default character set
                   OUT_DEFAULT_PRECIS,    // default OutputPrecision,
                   DEFAULT_QUALITY,    // default Quality
                   DEFAULT_PITCH | FF_DONTCARE,    // default pitch and family
                   L"Arial",    // use Facename Arial
                   &dxfont);    // the font object


    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    // create a RECT to contain the text
    static RECT textbox; SetRect(&textbox, 0, 0, 640, 480);

    // draw the Hello World text
    dxfont->DrawTextA(NULL,
                      "Hello World...FINALLY!",
                      22,
                      &textbox,
                      DT_CENTER | DT_VCENTER,
                      D3DCOLOR_ARGB(255, 255, 255, 255));

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    d3ddev->Release();
    d3d->Release();

    return;
}



Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.5 - Hello World, DirectX Style
posted by 대갈장군
2008. 2. 23. 01:18 프로그래밍/DirectX
이제까지는 멈춰있는 스프라이트만 봤지요. 2차원 영상이라면 정지해 있는게 정상 아닌가? 어쨌든 이번 장에서는 움직이게 만든단다. 2차원 영상이 이리저리 움직이는데 어떻게 로딩하고 어떻게 프로그래밍하는지 알아보자.

1. Rectangles
왜 갑자기 사각형 이야기가 나오는 거지? 사각형은 매우 쉽지만 중요한 부분이란다. C++에서는 사각형을 표현할때 RECT 구조체를 이용하는데 이것은 4개의 LONG값을 가진다. top, left, bottom, right 4개의 값을 사용하여 4각형을 표현한다. 각각의 값은 어디서 부터 얼마나 떨어져 있는지를 나타낸다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.1 - The Four Values of RECT

이 구조체의 정의를 살펴보면,
typedef struct _RECT
{
    LONG left;    // the left value
    LONG top;    // the top value
    LONG right;    // the right value
    LONG bottom;    // the bottom value
} RECT, *PRECT;

이고 이것을 사용하는 예를 보자면,

RECT rectangle = {100, 100, 300, 250};    // create the rectangle in Image 3.1

이 될것이다.

초기값을 설정하거나 중간에 값을 변경하기 위해서는 SetRect 함수를 사용하면 된다는 구나.

RECT rectangle;    // create the rectangle
SetRect(&rectangle, 100, 100, 300, 250);    // initialize the rectangle

2. Animation with Sprites
근데 어떻게 이 rect 구조체가 animation을 만든단 말인가? 일단 Draw 함수를 다시 한번 살펴보자.

HRESULT Draw(LPDIRECT3DTEXTURE9 pTexture,
             CONST RECT* pSrcRect,
             CONST D3DXVECTOR3* pCenter,
             CONST D3DXVECTOR3* pPosition,
             D3DCOLOR Color);

두번째 인자를 보면 CONST RECT* pSrcRect라고 되어 있는데 RECT 구조체에 대한 포인터다.이 RECT 구조체는 그려져야할 스프라이트의 일부를 담고 있다. 예를 들면 다음 그림처럼 앞장에서 그렸던 그리드 중에 일부만 선택적으로 보여 줄 수도 있다는 말. (원래는 NULL이었음)

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.2 - Only Some of Panel2.png

물론 이 부분이 들어감으로써 아주 놀라운 변화를 줄수는 있다만 이게 어떻게 애니매이션이 되지? 흠. 그러니까 아주 긴 (위로든 아래로든) 그림이 있는데 그 그림은 사실 연속된 화면의 컷들이다. 각각의 컷은 핫 핑크색을 배경색으로 해서 중간쯤에 움직이는 물체 (우리가 보여주길 원하는 것) 가 존재한다.

내 생각에는 배경색이 핫 핑크이므로 color key를 이용하면 간단히 제거 가능할 테고 중간 주변에 물체를 그려내고 있으므로 한 프래임씩 RECT 구조체의 값을 바꿔가면서 보여주면 애니매이션이 될것이다.

아래가 간단한 연속 컷 화면의 예인데 너무 커서 겨우 3컷 정도만 보인다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.3 - Part of the Animation Strip

자, 1번 프래임은 다음과 같은 화면일 것이고,

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.4 - The First Frame

2번 프래임은 다음과 같을 건데 (좌측으로부터 181 픽셀이 증가한 위치부터 시작)


Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.5 - The Second Frame

이런식으로 착착 다음 프레임을 보여주게 될것이다.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"Panel3.png",    // the file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &sprite);    // load to sprite

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // // begin sprite drawing with transparency

    // DRAW THE SPRITE FRAME

    // count from 0 to 22 to determine the current frame
    static int frame = 21;    // start the program on the final frame
    if(KEY_DOWN(VK_SPACE)) frame=0;     // when the space key is pressed, start at frame 0
    if(frame < 21) frame++;     // if we aren't on the last frame, go to the next frame

    // calculate the x-position
    int xpos = frame * 182 + 1;

    // draw the selected frame using the coordinates
    RECT part;
    SetRect(&part, xpos, 0, xpos + 181, 128);

    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, &part, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}





posted by 대갈장군
2008. 2. 20. 05:34 프로그래밍/DirectX

스프라이트에 대해서 좀더 알아보자. 앞에서 실험해본 결과 특정 이미지를 특정 위치에 그리는건 좋은데 문제는 투명도 설정이나 그런게 안되서 배경색과 매치가 안되는 경우 심각한 문제를 발생시키더라. 허, 그러고 보니 생각나는게 color key 방법이네 :)

1. Color-Keys
알다시피 지난번에 컬러 키를 이용한 색 제거를 했었다. 이것은 텍스쳐를 사용하는 3차원 물체에 적용했던 것이었는데 스프라이트도 텍스쳐를 사용하므로 컬러 키를 그대로 사용가능하단다.

잘 기억이 나지 않으면 lesson 12에 D3DXCreateTextureFromFileEx() 함수를 참조하라.

Alpha Blending with Sprites
이전에 사용했던 스프라이트를 그리기 위한 함수가 생각나나?

d3dspt->Begin(NULL);    // begin sprite drawing

BeginScene()과 EndScene() 사이에 썼던 바로 그 함수인데 컬러 키를 사용하기 위해서는 이 함수에 추가적인 플래그가 필요하다는데...

d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // begin sprite drawing with transparency

바로 알파 블랜딩 플래그이구나. 바로 이 플래그를 추가함으로써 우리는 스프라이트를 그릴때 컬러 키를 사용할수도 있고 반투명도를 사용할수도 있단다.

아마도 투패스 코드에도 이런 기본 셋팅 (DX용)이 들어가 있을것이다. 물론 스프라이트로 그리지는 않겠지만 말이다.

Using Color-Keyed Textures
내가 테스트 해본 바로 그 결과를 그림이 보여주는데, 만약 백그라운드 색상이 뻘건 색이면 이렇게 된다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.1 - Panel1.png Displayed Against a Colored Background

그리고자 하는 이미지는 사실 검은색 테두리 안에 있는 사각형일 뿐인데 배경색과 달라서 문제가 있다.

그래서 이런 걸 해결하기 위해서 드디어 컬러 키를 사용하는데,

D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                            L"Panel2.png",    // the new file name
                            D3DX_DEFAULT,    // default width
                            D3DX_DEFAULT,    // default height
                            D3DX_DEFAULT,    // no mip mapping
                            NULL,    // regular usage
                            D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                            D3DPOOL_MANAGED,    // typical memory handling
                            D3DX_DEFAULT,    // no filtering
                            D3DX_DEFAULT,    // no mip filtering
                            D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                            NULL,    // no image info struct
                            NULL,    // not using 256 colors
                            &sprite);    // load to sprite

헐, 여기서 잠깐 혼란에 빠졌는데 이 쓰뎅구리가 제대로 설명안해놨다. 바로 요 전에 사용했던 텍스쳐 파일은 배경이 검정색이지만 이 레슨에서 사용하는 새로운 텍스쳐 파일은 배경인 hot pink 색이다.

고것때문에 헷갈려서 한참을 뒤졌네. 젠장할쓴. 결론적으로 위에서 마지막에서 4번째 인자로 지정하는 색이 투명해 지는 것이고 주로 핫 핑크를 쓴단다.

고로 저렇게 하면 다음과 같은 결과가...

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.2 - Panel2.png Displayed with Alpha

2. Semi-Transparency
이제는 좀더 발전된 단계로 진행해보자. 여기서 추가로 반투명도 까지 올리는 것인데, 흠.. 재미있겠는데?

 

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.3 - DirectX vs DirectXTutorial

바로 저런걸 해보자는 건데... 반투명한 패널뒤로 물체가 사알짝 비취는 거다.

이렇게 하는것은 매우 매우 간단하다는데 다음 명령만 바꿔주면 된단다.

d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

이것은 그려지는 스프라이트를 50%의 alpha값을 가지도록 하여 반투명하게 만드는 역확을 한다... 너무 간단하다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.4 - The Panel Drawn with Semi-Transparency


결과는 위와 같은 것이다. (물론 화면 왼쪽 상단 일부만 캡춰한거임)

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"Panel2.png",    // the new file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &sprite);    // load to sprite

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a bright red
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // // begin sprite drawing with transparency

    // draw the sprite
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}




 

posted by 대갈장군
2008. 2. 20. 04:37 프로그래밍/DirectX
이 sprites라는 놈은 뭐길래 이렇게 내 눈에 자주 띄는건가? 2D 그래픽이 필요 없을 것 같아도 3D 영상에도 항상 2D는 사용되고 있으며 2D 가 많이 이곳 저곳에 사용되고 있단다.

이번 강의에서는 2D 그래픽을 그리는데 가장 많이 사용되는 sprites라는 기술에 대해서 알아 볼 것이다. 

1. Loading and Dislpaying Sprites
sprites를 사용하는 방법은 매우 간단하고 쉽단다. 구차하게 설명하기 보다 바로 어떻게 하는지 살펴보자.

1. D3DXSprite interface 초기화
2. texture 로딩
3. texture 그리기

헐, 너무 간단하게 말하는걸? :)

1-1. Initializing the D3DXSprite Interface
D3DXSprite는 COM 객체로써 2D 그래픽을 쉽게 만들도록 해준다. 시스템에서 관리를 해주는 아주 적절하고 좋은 방법이라는데... 문제는 COM 객체니까 초기화가 되어야 하는데 이것을 초기화 하는 방법은 다른 DirectX interface를 초기화 하는 방법과 유사하다. 우선 인터페이스에 대한 포인터를 만들고 함수를 호출하여 인터페이스를 생성(객체화) 한단다.

예전에 Direct3D interface를 초기화 했던 방법을 살펴보면,

LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface

d3d = Direct3DCreate9(D3D_SDK_VERSION);    // create the Direct3D object

인데 interface 포인터를 만들고 함수를 호출하여 생성하였다. 이것 외에도 Direct3D Device를 생성할때 이런 비슷한것을 했다는데 그건 좀 더 복잡했단다.
 
D3DXSprite도 위와 거의 유사한 방식인데 조금더 쉽다.

LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

첫번째 인자는 늘 보던거고 두번째 인자가 생성된 인터페이스 객체를 받을 주소인갑다.

1-2. Loading a Texture
Sprites는 이미지를 그리기 위해서 텍스쳐를 사용한단다. 고로 sprite를 디스플레이 하기 위해서는 반드시 텍스쳐가 로딩되어야 한다는데... sprite 찾아보니..  (헐헐 찾는데 2시간 넘게 걸렸다. 졸고 졸고 놀고 놀고... 미친놈) 스프라이트는 음료수가 아니라 쪽화면이라는 뜻이다. 화면상의 일부를 말한다.

LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

D3DXCreateTextureFromFile(d3ddev, L"graphic.bmp", &sprite);

음, 이전에도 했던 것과 똑같은데... 그냥 텍스쳐 포인터 선언하고 파일로 부터 읽어 들인다. 아무런 변화는 없다. 다만 조금 느낌이 이상하다면... 흠... 아니다, 이상할게 없다. 스프라이트건 아니건 간에 텍스쳐를 읽어오고 메모리상에 저장하는 과정은 다를 이유가 없지 않나.

여기서 하나 집고 갈것은 2의 승수가 아닌 텍스쳐는 컴퓨터가 알아서 스트레칭을 하는데 스프라이트의 경우 스트레칭이 일어나면 상당히 어글리하다는 점... 고로 사이즈를 잘 잡아야 한다는 점.

1-3. Drawing the Texture
아시다시피 3차원 물체를 그리는 것은 두가지 함수로 시작하고 멈춘다. BeginScene() 함수와 EndScene() 함수다. 헌데 이 스프라이트를 그리는 것은 2차원 영상을 그리는 것으로써 3차원 영상을 그리는 함수와 구별된다. 그래서 일반적으로 위의 두 함수 사이에 스프라이트를 그리고 멈추는 함수가 따로 한벌로 존재한다. 다음 예를보자.

d3ddev->BeginScene();

// perform 3D rendering here

d3dspt->Begin(NULL);    // begin sprite drawing

// perform sprite drawing here

d3dspt->End();    // end sprite drawing

d3ddev->EndScene();

여기 보면 스프라이트 그리기 함수에 인자값으로 널이 들어가 있는데 이 것은 다양한 그리기 옵션이란다. 현재로써는 그냥 널을 사용하는 것이 좋고 다음에 한번 자세히 알아보자.

위의 구조를 잠깐 보아하니 우선 3차원 영상을 먼저 그려내고 그다음에 2차원 영상을 그려내는 구나.. 흠.. 이유가 있나? :)

이제 본격적으로 스프라이트를 그리는 명령인 Draw를 알아볼 차례이다. 제법 긴 함수다.

HRESULT Draw(LPDIRECT3DTEXTURE9 pTexture,
             CONST RECT* pSrcRect,
             CONST D3DXVECTOR3* pCenter,
             CONST D3DXVECTOR3* pPosition,
             D3DCOLOR Color);

LPDIRECT3DTEXTURE9 pTexture : 우리가 사용하고자 하는 텍스쳐로의 포인터.

CONST RECT* pSrcRect : 텍스쳐의 어떤 부분을 사용할지 알려주는 좌표 구조체 포인터. 애니매이션을 사용하지 않는 이상은 주로 전체 텍스쳐를 다 사용하게 되는데 이때는 NULL을 넣어주면 전체를 가져온다.

CONST D3DXVECTOR* pCenter : 이 벡터는 이미지의 중심을 표시한다. 헌데 이 중심이라 함은 말그대로 진짜 이미지의 중심이라기 보다는 이미지의 포터스 포인트이다.

설명을 조금 이상하게 해놨는데 마우스 커서를 예로 들었는데 일반적으로 마우스 커서의 왼쪽 끝이 '중심점'이 된다. 내 생각에 글쓴이가 의도하는 설명은, 그림에 따라 중심을 둬야 하는 위치가 제 각각일 수 있다는 것 같다.

근데 한가지 더 남아있는 의문은 왜 z 축 좌표도 필요하냐는 건데 나중에 설명한단다.

CONST D3DXVECTOR3* pPosition :
이것은 스크린에서 이미지 중심의 좌표를 지시하는 벡터 이다. 일반적으로 이미지가 스크린의 50, 50 에 있다고 말하면 그것은 이미지의 중심이 그 좌표에 있다는 것을 의미한다. 흠, 그래서 이미지의 중심이 어디인가가 중요하겠군.

이제 z 축의 사용법에 대해서 알아보면 3차원 영상과는 달리 2차원 영상에서는 z 축의 값이 0.0에서 1.0 까지 밖에 없다. 그리고 그 사이에서의 거리에 따른 크기 감쇄도 전혀 없다. (왜? 2차원 영상 이니까~)

다만 2차원 이미지는 더 가까운 이미지가 뒤에것에 겹쳐 보인다. 아직 이 글쓴이도 어떻게 이것을 사용해야 할지 모르겠다는데... 그럴만도 한게... 2차원 이미지가 겹쳐보일 일이 잘 없지 않은가?

D3DCOLOR Color : 이미지를 칠하고 싶은 색상을 정하는 인자.

어떻게 사용하는지 실제로 살펴보면,

d3dspt->Begin(NULL);    // begin sprite drawing

D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_XRGB(255, 255, 255));  // draw it!

d3dspt->End();    // end sprite drawing

2. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite


// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFile(d3ddev, L"Panel1.png", &sprite);


    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(NULL);    // begin sprite drawing

    // draw the sprite
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_XRGB(255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
      sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 1.1 - Panel1.png Displayed in DirectX



posted by 대갈장군
2008. 2. 20. 01:32 프로그래밍/DirectX
어느새 mesh 마지막 강의군... 일반적으로 메쉬는 .x 파일안에 텍스쳐를 포함하고 있단다. (그랬어?)
하지만 사용하기 위해서는 프로그램에서 약간의 코드를 가미해 줘야 하는데...

1. Loading and Displaying Textures
자, 아래 그림을 보자. 텍스쳐가 입혀지지 않은 비행기다. 이 .x 파일을 바로 전 수업에 배운대로 해보면 아래처럼 나타나게 된다.

Image 3.1 - The Airplane 2, Untextured

Image 3.1 - The Airplane 2, Untextured

근데 이것을 DirectX가 제공하는 메쉬 뷰어로 보면 아래그림처럼 텍스쳐가 입혀져서 나오게 된다.

Image 3.2 - The Airplane 2, Textured

Image 3.2 - The Airplane 2, Textured

그말인 즉슨, 텍스쳐가 존재하나 코딩이 안되었다는 말.. 그렇다면 어떻게 코딩하는지 살펴보자...는 말.

두가지를 해줘야 하는데,
1. Load the textures while loading the mesh
2. Set the subest's texture before each subset is drawn

1-1. Load the textures while loading the mesh
우선 메쉬를 로딩할때 텍스쳐도 같이 로딩해줘야 한다는 말인데. 당연한 이야기 처럼 들린다. 메쉬를 로딩할때 텍스쳐도 같이 로딩할수 있는데 (만약 텍스쳐가 존재 한다면), 우리의 업무는 extract it, read it, and load the texture다.

D3DMATERIAL9* materal;    // a pointer to a material buffer
LPDIRECT3DTEXTURE9* texture;    // a pointer to a texture

// retrieve the pointer to the buffer containing the material information
D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

// create a new material buffer and texture for each material in the mesh
material = new D3DMATERIAL9[numMaterials];
texture = new LPDIRECT3DTEXTURE9[numMaterials];

for(DWORD i = 0; i < numMaterials; i++)    // for each material...
{
    material[i] = tempMaterials[i].MatD3D;    // get the material info...
    material[i].Ambient = material[i].Diffuse;    // and make ambient the same as diffuse
    USES_CONVERSION;    // allows certain string conversions
    // if there is a texture to load, load it
    if(FAILED(D3DXCreateTextureFromFile(d3ddev,
                                        CA2W(tempMaterials[i].pTextureFilename),
                                        &texture[i])))
        texture[i] = NULL;    // if there is no texture, set the texture to NULL

}

자 위가 새롭게 바뀐 코드이고 굵은 글자가 바뀐 부분이다. 하나하나 보자꾸나.

LPDIRECT3DTEXTURE9* texture : 이것은 텍스쳐에 대한 포인터이다. 전역 변수로 선언되어야 함.

texture = new LPDIRECT3DTEXTURE9[numMaterials] : 각각의 재질은 각각의 텍스쳐 버퍼를 가지는데 위에 비행기 그림을 보면 텍스쳐가 없는 서브셋도 존재한다. 그런 경우에도 텍스쳐 버퍼는 일단 만들어 놓는다. (비록 쓰지 않더라도)

USES_CONVERSION : 이건 매크로인데 우리가 특정한 string conversion을 할수있게 해주는 매크로랜다.. 바로 다음 줄에서 LPSTR을 LPCWSTR로 바꿔야 하는데 이것을 이 매크로가 가능케 해준다.

이 매크로는 atlbase.h에 있으므로 꼭 그 헤더 파일을 포함시켜야 한다.

if(FAILED(... : 이거 많이 본건데... RET_FAILED라고 본것 같기도 하고. 이것은 텍스쳐 이름이 해당 서브셋에 유효한가를 체크한다. 아까 본것처럼 텍스쳐가 없는 서브셋의 경우 이것이 실패로 처리될 것이고... 그게 실패한경우 바로 다음줄 명령인 texture[i] = NULL;이 실행되어 해당 서브셋의 텍스쳐는 없는 걸로 저장된다.

D3DXCreateTextureFromFile() : 이건 이미 본적이 있는 거란다. 단순하게 파일로 부터 텍스쳐를 로딩하는 것인데 첫번째 인자는 Direct3D Device인 d3ddev이고 두번째 인자는 파일이름이다. 각각의 서브셋에 해당하는 텍스쳐 파일 이름은 tempMaterials[i].pTextureFilename에 있다. 하지만 이 파일 이름은 LPSTR 형인데 이 형은 Visual C++ 2005에서는 사용 할 수가 없단다. 그래서 LPCWSTR로 바꿔야 하는데 이때 필요한 함수가 바로 CA2W라는 함수랜다..... (젠장할 유니코드) 고로 다음과 같이 써주면 OK

CA2W(tempMaterials[i].pTextureFilename),

세번째 인자는 어디다 저장 할 건지 알려주는 것.

1-2. Set the subset's texture before each subset is drawn
이제 해야 할 일은 각각의 서브셋이 그려지기 직전에 해당 텍스쳐로 착착 바꿔주는 거다. 어떻게 하느냐면,

// draw the spaceship
for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
{
    d3ddev->SetMaterial(&material[i]);    // set the material for the subset
    if(texture[i] != NULL)    // if the subset has a texture (if texture is not NULL)
        d3ddev->SetTexture(0, texture[i]);    // ...then set the texture


    meshSpaceship->DrawSubset(i);    // draw the subset
}

해당 서브셋의 텍스쳐가 NULL이 아니라면 텍스쳐를 설정, 할당한다.

2. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <atlbase.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer
LPDIRECT3DSURFACE9 z_buffer = NULL;    // the pointer to the z-buffer

// mesh declarations
LPD3DXMESH meshSpaceship;    // define the mesh pointer
D3DMATERIAL9* material;    // define the material object
LPDIRECT3DTEXTURE9* texture;    // a pointer to a texture
DWORD numMaterials;    // stores the number of materials in the mesh


// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations
void init_light(void);    // sets up the light and the material

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;    // automatically run the z-buffer for us
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;    // 16-bit pixel format for the z-buffer

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    // create the z-buffer
    d3ddev->CreateDepthStencilSurface(SCREEN_WIDTH,
                                      SCREEN_HEIGHT,
                                      D3DFMT_D16,
                                      D3DMULTISAMPLE_NONE,
                                      0,
                                      TRUE,
                                      &z_buffer,
                                      NULL);

    init_graphics();    // call the function to initialize the triangle
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // SET UP THE TRANSFORMS

    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 16.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               SCREEN_WIDTH / SCREEN_HEIGHT,    // the aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform


      // draw the spaceship
    for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
    {
        d3ddev->SetMaterial(&material[i]);    // set the material for the subset
        if(texture[i] != NULL)    // if the subset has a texture (if texture is not NULL)
            d3ddev->SetTexture(0, texture[i]);    // ...then set the texture


        meshSpaceship->DrawSubset(i);    // draw the subset
    }

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    meshSpaceship->Release();    // close and release the spaceship mesh
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    LPD3DXBUFFER bufShipMaterial;

    D3DXLoadMeshFromX(L"airplane 2.x",    // load this file
                      D3DXMESH_SYSTEMMEM,    // load the mesh into system memory
                      d3ddev,    // the Direct3D Device
                      NULL,    // we aren't using adjacency
                      &bufShipMaterial,    // put the materials here
                      NULL,    // we aren't using effect instances
                      &numMaterials,    // the number of materials in this model
                      &meshSpaceship);    // put the mesh here

    // retrieve the pointer to the buffer containing the material information
    D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

    // create a new material buffer and texture for each material in the mesh
    material = new D3DMATERIAL9[numMaterials];
    texture = new LPDIRECT3DTEXTURE9[numMaterials];

    for(DWORD i = 0; i < numMaterials; i++)    // for each material...
    {
        material[i] = tempMaterials[i].MatD3D;    // get the material info
        material[i].Ambient = material[i].Diffuse;    // make ambient the same as diffuse
        USES_CONVERSION;    // allows certain string conversions
        // if there is a texture to load, load it
        if(FAILED(D3DXCreateTextureFromFile(d3ddev,
                                            CA2W(tempMaterials[i].pTextureFilename),
                                            &texture[i])))
        texture[i] = NULL;    // if there is no texture, set the texture to NULL

      }

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

Image 2.3 - The Spaceship In Action

Image 2.3 - The Biplane In Action

posted by 대갈장군
2008. 2. 19. 06:31 프로그래밍/DirectX
후후. 드디어 조금 쓸만한게 나오는 구만...

1. About Subsets
음. 바로 전 레슨에서 아무 설명없이 넘어갔던 거 설명하려고 하는 구나.

meshTeapot->DrawSubset(0);    // draw the teapot

이 명령을 기억하나? 메쉬를 그려줄때 줬던 명령인데 뭣하는 놈인지는 제대로 설명을 안해줬다.

서브셋이라함은 메쉬의 한 부분, 한 조각이다. 위의 명령에서 0이 의미하는 것은 어떤 서브셋을 그리라고 D3D에 알려주는 역활을 한다.

음, 물론 여기서 의문점이 생기는데 그럼 서브셋이 여러개일수도 있느냐 이다. 물론이다. 티팟의 경우 뚜겅과 본체가 서로 다른 서브셋에 들어가 있을 수도 있고 그런 경우에는 다음과 같은 명령이 들어가야 할 것이다.

meshTeapot->DrawSubset(0);    // draw the teapot
meshTeapot->DrawSubset(1);    // draw the teapot lid

각각의 서브셋은 사실상 분리된 모델이며 각각이 서로 다른 속성을 가질수 있다. (material, texture, animations)등등

자 실제로 이것이 어떻게 적용되는지 함 보자

Image 2.1 - Space Ship 2 'spaceship 2.x'

Image 2.1 - Space-Ship 2 "spaceship 2.x"

이것은 DX에서 제공하는 기본 모델인데 3가지 다른 색을 가지고 있다. 보아하니 텍스쳐를 입힌것도 아닌데 부분별로 다른 색상이 들어가 있다. 그렇다면 어떻게 한걸까?

바로 해답은 다음 그림이다.

Image 2.2 - Space Ship 2: Disassebled

Image 2.2 - Space-Ship 2: Disassebled

사실 이 모델은 3개의 서브셋으로 이루어져 있다. 각각이 다른 색상을 가지고 다른 재질을 표현한다.

자, 결론적으로 서브셋은 메쉬의 일부이지만 서로다른 속성과 특성을 소유 할수 있다는 것이다.

2. Loading an X File to a Mesh
X file을 로딩하는 것은 생각보다 쉽지만은 않다는 구나..허허허

1. X file로 부터 mesh를 로딩한다.
2. 각각의 서브셋에 대해서 material 정보를 추출하고 그정보를 material buffers에 저장한다.
3. materials를 지정하고 subset을 그린다.


2-1. Load the mesh from the .x file

HRESULT D3DXLoadMeshFromX(LPCTSTR pFilename,
                          DWORD Options,
                          LPDIRECT3DDEVICE9 pD3DDevice,
                          LPD3DXBUFFER *ppAdjacency,
                          LPD3DXBUFFER *ppMaterials,
                          LPD3DXBUFFER *ppEffectInstances,
                          DWORD *pNumMaterials,
                          LPD3DXMESH *ppMesh);

후아~ 길다 :) 새로운 인자가 많이도 등장했구나~

LPCTSTR pFilename : 파일 경로를 저장하고 있는 포인터를 받는 인자. 예를 들자면 L"spaceship 2.x" 와 같겠다.

DWORD Options : 이 인자에서는 어떻게 메쉬를 관리할것인가에 대한 전반적이고 다양한 설정을 할수 있다. 물론 플래그로써... 대부분은 메모리 관리에 대한 옵션 설정인데 지금 다 설명하기는 너무 많아서 일단 잘 사용되고 잘 작동되는 적당한 플래그를 사용할것이고 차후에 설명하도록 한다.

여기서 사용하는 플래그는 D3DXMESH_SYSTEMMEM 인데 이 플래그는 D3D에게 System memory를 사용하라고 (메쉬 저장을 위해서) 알려 주는 것이다.

LPDIRECT3DDEVICE9 pD3DDevice : This, as you should know by now, is the pointer to the Direct3D device.  Put 'd3ddev' here.

LPD3DXBUFFER *ppAdjacency : LPD3DXBUFFER 는 저장 버퍼를 가리키는 포인터형인데 Direct3D에 의해 사용된다. 메쉬에 관련해서는 수많은 옵션과 설정이 가능(필요)한데 그것은 보다 나은 성능을 위해서 필요하다. 그리고 이 버퍼가 그에관한 모든것을 저장한단다.

Adjacency 는 어떤 정보로 구성되냐면, 어떤 삼각형이 어떤것 옆에 위치할 것인가에 대한 정보를 가진단다. 이 옵션은 나중에 고급 기술에서 많이 사용되지만 지금은 사용할 곳이 없으니 NULL로 그냥 가져다 놓는다.

LPD3DXBUFFER *ppMaterials : 이건 또 다른 버퍼인데 각각의 서브셋에 대한 material information을 저장하는 버퍼래요. 만약 우리가 materials를 사용하고 있다면 이 정보는 꼭 필요하다. bufShipMaterials라는 버퍼를 하나 만들어서 이 인자에 그 버퍼 주소를 넘겨 줄 것이다.

LPD3DXBUFFER *ppEffectInstances : 또 다른 버퍼다.. 헐헐헐... 이것은 특별한 렌더링 효과를 위해서 사용된다. 이것도 나중에 다룰 내용이라서 지금은 일단 NULL로 해놓는다.

DWORD *pNumMaterials : This is a pointer to a DWORD. 이 인자 값은 몇개의 materials를 사용하는지 설정한다. 이 인자는 몇개의 서브셋이 한개의 메쉬에 들어가 있는지를 말해준다.

이 인자를 위해서 전역 변수를 하나 선언해서 (numMaterials) 이 주소값을 이 인자에 넣는다.

LPD3DXMESH *ppMesh : 메쉬에 대한 포인터다.

// global variables
LPD3DXMESH meshSpaceship;
DWORD numMaterials;

// local variable (in the init_graphics() function)
LPD3DXBUFFER bufShipMaterial;

D3DXLoadMeshFromX(L"spaceship 2.x",    // load this file
                  D3DXMESH_SYSTEMMEM,    // load the mesh into system memory
                  d3ddev,    // the Direct3D Device
                  NULL,    // we aren't using adjacency
                  &bufShipMaterial,    // put the materials here
                  NULL,    // we aren't using effect instances
                  &numMaterials,    // the number of materials in this model
                  &meshSpaceship);    // put the mesh here

전역 변수로 두개를 선언했는데 OpenGL과 마찬가지로 객체 지향성은 잃어버릴수 밖에 없나보다. 저 인자들은 여러함수에서 두루 사용되므로 부득이 하게 전역변수로 선언한다.

이 명령을 실행함으로써 sapceship 2.x라는 파일은 mesh로 저장되게 된다. 모든 material 정보는 bufShipMaterial에 저장될 것이다. 그리고 질감의 수는 3개.

하지만 아직 그려지는 단계는 아닌데 만약 여기서 그냥 그리면 회색 질감의 물체만 나타나게 된다. 왜? material 사용을 셋팅 안해줬으니까...

2-2. Extract the material information for each subset and place them into material buffers
D3DMATERIAL9* materal;    // a pointer to a material buffer

// retrieve the pointer to the buffer containing the material information
D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

// create a new material buffer for each material in the mesh
material = new D3DMATERIAL9[numMaterials];

for(DWORD i = 0; i < numMaterials; i++)    // for each material...
{
    material[i] = tempMaterials[i].MatD3D;    // get the material info...
    material[i].Ambient = material[i].Diffuse;    // and make ambient the same as diffuse
}

허걱, 코드를 보라. 뭔 말인가 알겠나? 어려워 보이지만 뜯어보자.

이코드가 기본적으로 하는 일은 material information이 저장되어 있는 곳의 포인터를 얻어와서 각각의 material 을 분리된 material object로 옮기며, 렌더링을 준비한다.

D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

자 위에 코드는 뭣하는 놈인가... GetBufferPointer() 함수는 단순한게 버퍼의 주소를 리턴한다. 조금 이상한게 있다면 왜 굳이 함수를 이용해서 주소값을 대입하느냐 이다. 사실 캐스팅 연산자를 이용했다면 이미 좌변과 같은 타입이니 바로 대입해도 될텐데? 흠.... 아마도 로딩할때 인자값으로 들어가는 bufShipMaterial의 구조나 처리 방식이 조금 다른가 보다.

material = new D3DMATERIAL9[numMaterials];

여기서는 서브셋의 갯수만큼 material을 저장할 버퍼를 만든다.  

material[i] = tempMaterials[i].MatD3D;

여기서 material information을 임시 재질 버퍼로 부터 복사하여 새로운 material 변수에 (버퍼에) 가져다 복사한다.

material[i].Ambient = material[i].Diffuse;

아, 기본적으로 로딩된 메쉬는 ambient material 정보를 가지고 있지 않은데 만약 이걸 셋팅 안해주면 어두운 주변광이 설정되므로 어둡게 보이게 된다. 고로 diffuse 색상과 같에 만들어줘서 잘보이게 만든다.

2-3. Set the materials and draw the subsets

이제 그릴차례? 

for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
{
    d3ddev->SetMaterial(&material[i]);    // set the appropriate material for the subset
    meshSpaceship->DrawSubset(i);    // draw the subset
}


서브셋의 갯수만큼 루프를 돌면서 각 루프마다 각 재질 선택후 서브셋을 그려준다. 끝~

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer
LPDIRECT3DSURFACE9 z_buffer = NULL;    // the pointer to the z-buffer

// mesh declarations
LPD3DXMESH meshSpaceship;    // define the mesh pointer
D3DMATERIAL9* material;    // define the material object
DWORD numMaterials;    // stores the number of materials in the mesh


// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations
void init_light(void);    // sets up the light and the material

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;    // automatically run the z-buffer for us
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;    // 16-bit pixel format for the z-buffer

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the triangle
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // SET UP THE TRANSFORMS

    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 4.0f, 8.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               SCREEN_WIDTH / SCREEN_HEIGHT,    // the aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform


    // draw the spaceship
    for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
    {
        d3ddev->SetMaterial(&material[i]);    // set the material for the subset
        meshSpaceship->DrawSubset(i);    // draw the subset
    }

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    meshSpaceship->Release();    // close and release the spaceship mesh
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    LPD3DXBUFFER bufShipMaterial;

    D3DXLoadMeshFromX(L"spaceship 2.x",    // load this file
                      D3DXMESH_SYSTEMMEM,    // load the mesh into system memory
                      d3ddev,    // the Direct3D Device
                      NULL,    // we aren't using adjacency
                      &bufShipMaterial,    // put the materials here
                      NULL,    // we aren't using effect instances
                      &numMaterials,    // the number of materials in this model
                      &meshSpaceship);    // put the mesh here

    // retrieve the pointer to the buffer containing the material information
    D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

    // create a new material buffer for each material in the mesh
    material = new D3DMATERIAL9[numMaterials];

    for(DWORD i = 0; i < numMaterials; i++)    // for each material...
    {
        material[i] = tempMaterials[i].MatD3D;    // get the material info
        material[i].Ambient = material[i].Diffuse;    // make ambient the same as diffuse
    }

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}



Image 2.3 - The Spaceship In Action

Image 2.3 - The Spaceship In Action
posted by 대갈장군
2008. 2. 16. 06:34 프로그래밍/DirectX
사실 꼭 필요한 부분은 이제부터 나온다고 생각한다. 과연 Mesh란 무엇인가? 알다시피 지금까지는 정말 무식하게 점을 사용하여 물체를 표현하고 점들을 이어서 면을 표현했다. 하지만 만약 3D MAX같은 프로그램을 사용해봐라... 얼마나 멋진 물체를 만들수 있나...

하지만 그것을 점으로 다 표현한다면? 불가능하다. 고래서 필요한 간단한 방법이 있으니 바로 C++ 객체를 이용하여 이것들을 하나의 패키지로 묶어서 표현하는 것이고 그것이 바로 mesh다.

메쉬에 대한 간단한 정의를 했으니 이지 이것이 뭣하는 놈인지 슬슬 살펴보자.

1. What is the Mesh?
간단하게 말하자면 Mesh는 3D Model이다. 아,,,,,, 하하.. 탄식이 나오는 구나. 다음줄에서 바로 그것이 무엇인지 알려준다.

보다 정확하게 말하자면 3차원 모델에 대한 모든 정보를 담고 있는 파일이다. 그것은 vertices와 texture 그리고 framed animation을 가지고 있다. 매우 유용한 사용 툴을 갖추고 있어서 예전처럼 무식하게 점으로 찍어서 표현할 일은 더이상 없다고 봐야 한다.

한개의 모델은 주로 3D modeling program에서 만들어지고 .x 파일로 출력되어 나온다... 즉, .x 파일은 그저 3차원 모델을 저장한 파일일 뿐이라는 말... (졸라 대단한 건줄 알았네.. 헐) 일단 이 파일이 불러들여져서 메모리상에 올라가면 한개의 함수호출로 즉각 호출이 가능하다...

Image 1.1 - Meshes and Their Contents

Image 1.1 - Meshes and Their Contents

2. Making Preconstructed Meshes
슬슬 재미있어지기 시작하는 구나. 완전히 멋지게 그려진 3차원 물체를 불러와서 멋지게 그려내기 전에 간단한 것부터 배워보자. OpenGL에서도 확장형 함수로 간단한 물체를 그리는 함수가 있었는데 바로 DX에도 그런게 있다. 이것을 Preconstructed mesh라고 하는 구나.

일단 그런 것들을 그려주기 위해서는 다음과 같은 일련의 과정을 공통적으로 거쳐야 하나본데,

1. Creating the Mesh
2. Drawing the Mesh
3. Closing the Mesh

헐헐... 매우 간단하게 설명하고 있구나...

2-1. Creating the Mesh
일단 만들어야 쓸거 아니니.... 물론 여러가지 형태를 만들수 있는 다양한 preconstructed mesh 함수가 있으나 여기서는 주전자 만드는 함수를 살펴보자.

Image 1.2 - The Mythical Teapot Pipe-Joint

Image 1.2 - The Mythical Teapot Pipe-Joint

3D MAX에서 많이 본거다. 어라.. 근데 윈도우 스크린 세이버에서 가끔 저게 등장한다고라? 정말이냐? 허허...

하지만 이건 윈도우 95 98에서만 잠깐 나타난 현상이라는 군... 헐헐헐

HRESULT D3DXCreateTeapot(LPDIRECT3DDEVICE9 pDevice,
                         LPD3DXMESH *ppMesh,
                         LPD3DXBUFFER *ppAdjacency);

바로 위의 함수가 주전자 만드는 함수다. 각각의 인수를 한번 살펴 볼까나.

LPDIRECT3DDEVICE9 pDevice : This is simply the pointer to the Direct3D device.  We'll just put d3ddev here.

LPD3DXMESH *ppMesh : This is a pointer to a pointer to a mesh.  Whoa!

이놈이 메쉬에 대한 포인터라는데.. 왜 후아!? 지금은 이놈에 대해서 무시하자고 하네. 이것은 정의된 포인터의 위치에 메쉬를 가져다 저장하는 것인데... 일단은 남겨두고 다음 으로 넘어가자.

LPD3DXBUFFER *ppAdjacency : This one is advanced, so we'll leave it out for now.  Just set it to NULL.

LPD3DXMESH meshTeapot;    // define the mesh pointer

D3DXCreateTeapot(d3ddev, &meshTeapot, NULL);    // create the teapot


Image 1.3 - The Rendered Utah Teapot
Image 1.3 - The Rendered Utah Teapot

이렇게 해주면 그려진다는 구만. 이외에도 몇가지 더 있는데... 뭐뭐가 있나하면...

2-1-1. The Rectangular Solid
사각형이네.

HRESULT D3DXCreateBox(LPDIRECT3DDEVICE9 pDevice,
                      FLOAT Width,
                      FLOAT Height,
                      FLOAT Depth,
                      LPD3DXMESH *ppMesh,
                      LPD3DXBUFFER *ppAdjacency);

첫번째 인자는 계속 봤던 거고, 두번째 세번째 네번째는 사각형의 크기 지정하는 것이고 ppMesh가 또 나왔는데 메쉬에 대한 포인터라는 것만 언급할뿐 다른 이야기는 없다.

마지막것도 역시나 advanced이므로 언급하지 않았다. 다만 NULL을 넣을뿐.

사용방법을 보면,

LPD3DXMESH meshBox;    // define the mesh pointer

D3DXCreateBox(d3ddev, 7.0f, 3.0f, 3.0f, &meshBox, NULL);    // create a box

메쉬 포인터를 만든다음 d#dXCreateBox 함수를 사용하면 끝이네. 근데 직접 그려주는건 어디서 하는걸까? 위치를 잡아야 할 것 아닌감?

Image 1.4 - The Rectangular Solid In Action
Image 1.4 - The Rectangular Solid In Action

2-1-2. The Cylinder
요번것은 실린더. 둥근 원통인데.

 HRESULT D3DXCreateCylinder(LPDIRECT3DDEVICE9 pDevice,
                           FLOAT Radius1,
                           FLOAT Radius2,
                           FLOAT Length,
                           UINT Slices,
                           UINT Stacks,
                           LPD3DXMESH *ppMesh,
                           LPD3DXBUFFER *ppAdjacency);

내나 위에서 본 사각형 만드는 것과 비슷하고 OpenGL에서도 사용했던 확장 함수와 유사하게 slices와 stack을 설정하는 옵션이 존재한다.

첫번째 인자는 두말하면 잔소리고 원주 1과 원주 2가 있는데 위 아래 뚜껑의 원주를 말한다. 길이는 당연히 원통의 길이를 말하는 것이고 slices는 원통의 둘레를 따라 둥그스름하게 표현하는 정도를 설정하는 것이고 stack은 원통의 높이에 따라서 몇개로 쪼갤것이냐고 묻는 것이다.

ppMesh는 역시나 포인터이고, 마지막은 advanced이라서 여기선 언급 안한다.

LPD3DXMESH meshCylinder;    // define the mesh pointer

D3DXCreateCylinder(d3ddev, 1.5f, 1.5f, 7.0f, 20, 10, &meshCylinder, NULL);



Image 1.5 - The Cylinder In Action
Image 1.5 - The Cylinder In Action

2-1-3. The Sphere
이제는 구
HRESULT D3DXCreateSphere(LPDIRECT3DDEVICE9 pDevice,
                         FLOAT Radius,
                         UINT Slices,
                         UINT Stacks,
                         LPD3DXMESH *ppMesh,
                         LPD3DXBUFFER *ppAdjacency);다.

여기서 나오는 것은 설명할 필요도 없네. 다 앞에서 설명한 것이다.


LPD3DXMESH meshSphere;    // define the mesh pointer

D3DXCreateSphere(d3ddev, 2.0f, 20, 10, &meshSphere, NULL);    // create a sphere

Image 1.6 - The Sphere In Action

Image 1.6 - The Sphere In Action

2-2. Drawing the Mesh
이제 메쉬를 만들었으니 그려야 할 차례... 그리는것은 상당히 쉽다고 하는데.... 심지어 vertex buffer를 그리는 것보다 쉬우니..

// draw the teapot
meshTeapot->DrawSubset(0);

이 다음장에서 왜 저기에 0이 들어갔는지 설명해 준대...

2-3. Closing the Mesh
꼭 해줘야 할게 있으니 바로 메쉬를 닫는 것이다. 이것은 다른 모든 자원을 리턴하는 함수인 cleanD3D에서 해주면 끝~

// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    meshTeapot->Release();    // close and release the teapot mesh
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}

3. The Finished Program
뭐? 벌씨로? 헐헐헐...

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class

// mesh declarations
LPD3DXMESH meshTeapot;    // define the mesh pointer

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations
void init_light(void);    // sets up the light and the material

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the triangle
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

      // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 3.0f, 6.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

      // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

      // set the world transform
    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform


    // draw the teapot
    meshTeapot->DrawSubset(0);


    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    meshTeapot->Release();    // close and release the teapot mesh
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    D3DXCreateTeapot(d3ddev, &meshTeapot, NULL);    // create the teapot

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material

    return;
}



Image 1.7 - The Teapot In Action

Image 1.7 - The Teapot In Action




posted by 대갈장군
2008. 2. 16. 05:11 프로그래밍/DirectX

이건뭘까... Color Key라. 이번 강의에서는 텍스쳐를 로딩하는 새로운 방법을 보여준다는 구나. 바로 color key를 사용하여 그림의 일부만 투명하게 하기도 하도 그리기도 하고 그런다는 구나...

흠, 투명하게 만든다는 부분은 좀 이해가 안가고 일부만 그린다는 것은 OpenGL에서의 subimage 형식으로 이해할수 있겠다만 일단 color key 에 대해서 짚고 넘어가자.

1. About Color-Keys
그렇다면 우선 근본 적인 질문인, '왜 우리는 그림의 일부만 다시 그리고 싶은 걸까?' 에 대해 대답해보자.

예를들어 다음 그림처럼 둥근 도넛 모양을 화면에 그린다고 해보자. 256X256을 사용한다.

Image 12.1 - A Blue-Circle Texture

Image 12.1 - A Blue-Circle Texture

그런데 이상하게도 우리는 이 모양의 사각형을 회색 배경을 가진 화면에서 그리고 싶다고 하자... 다음 그림과 같이 되겠죠.

Image 12.2 - The Blue Circle As Displayed

Image 12.2 - The Blue Circle As Displayed

뭔가 이상하다. 우리가 그리고자 하는 것은 사실 파란 부분만인데 여기서는 다 보여지고 있다. 자 여기서 바로 color-key의 개념이 사용된다.

color key는 텍스쳐에 있는 특정한 색상을 렌더링시 그리지 않는 것을 말한다. 만약 우리가 검정색을 color key로 설정하면 이것은 렌더링 될때 아래와 같이 사라지게 된다.

Image 12.3 - The Blue Circle Using a Color Key

Image 12.3 - The Blue Circle Using a Color Key

상당히 특이한 방법인데, OpenGL에서는 본적이 없는 것 같다. 헌데 문제라면 게임상에서 그려질만한 색상을 선택해서는 안된다는 것인데 그 색상을 표현되지 않을것이다. 고로 잘 골라야함. 절대 쓰지 않을 색상으로... 예를 들면 핫 핑크.... 같은거... 헐헐헐

2. D3DXCreateTextureFromFileEx()
아따 그놈 이름도 참말로 기네잉. 이름만 긴게 아니고라.... 인자수도 졸라게 많다.

HRESULT D3DXCreateTextureFromFileEx(LPDIRECT3DDEVICE9 pDevice,
                                    LPCTSTR pSrcFile,
                                    UINT Width,
                                    UINT Height,
                                    UINT MipLevels,
                                    DWORD Usage,
                                    D3DFORMAT Format,
                                    D3DPOOL Pool,
                                    DWORD Filter,
                                    DWORD MipFilter,
                                    D3DCOLOR ColorKey,
                                    D3DXIMAGE_INFO* pSrcInfo,
                                    PALETTEENTRY* pPalette,
                                    LPDIRECT3DTEXTURE9* ppTexture);

끄아.. 길다만... 자세히 한번 살펴보자.. 차근 차근..

LPDIRECT3DDEVICE9 pDevice : 쉬운거다. d3ddev를 말한다.

LPCTSTR pSrcFile : 이것도 아는 거군. 파일 이름 지정하는 포인터다. L"Whatever.bmp" 같은 식으로 넣어주면 된다.

UINT Width, UINT Height : 뭐, 이름에서 딱 티가 난다. 단순히 생각해서 텍스쳐의 크기라고 생각하겠지만 아니다. 이것은 텍스쳐 이미지를 요 크기대로 늘리거나 줄이라는 말인데, 만약 이미지 그 자체의 크기로 유지하고 싶다면 원래 이미지 크기를 넣어도 된다?  정말? 원하는 크기로 맘대로 늘리고 줄일수 있단다.

헌데, 이것은 추천되지 않는 방법인데 이유는 일단 성능 문제이고 둘째는 모든 비디오 카드가 지원하는건 아니기 때문이라는데... 모든 환경에서 작동하가 위해서는 텍스쳐를 2의 승수로 줄이거나 늘리고, 그런다음 색상을 감추기 위해서 color key를 사용하는 것이 좋단다. D3DX_DEFAULT 를 사용하여 텍스쳐 이미지를 2의 승수로 늘리던가 아니면 D3DX_DEFAULT_NONPOW2 를 사용해 원래 이미지 사이즈를 사용할 수도 있단다.

필자는 D3DX_DEFAULT를 항시 사용하는데 이것이 가장 효율적이고 테스트에 적합하단다.

UINT MipLevels : 밉맵 레벨을 지정하는 것인데 이것은 OpenGL에서 많이 봤던 내용이다. 기본적으로 밉맵은 하나의 텍스쳐의 여러개의 복사본이다. 단, 사이즈가 다르다. 왜 다른 사이즈의 텍스쳐가 필요하냐면, 기본적으로 큰 텍스쳐를 입힌 큰 물체가 시야에서 멀어져서 작아지면 텍스쳐가 찌개져 보이는 (약간 찌개짐) 효과가 발생하며 또한 효율성에 있어서도 멀리 있는 작은 물체를 무진장 큰 텍스쳐로 커버하는 비효율성을 보이게 된다. 이런 점을 커버하기 위해서 여러 가지 크기의 텍스쳐를 준비해서 거리와 크기에 따라 다른 텍스쳐를 사용하는 것이다.

DWORD Usage : Advanced.  The only way that we intend to use it is to draw it, so we don't need to do anything with this.  We'll just set it to NULL.

D3DFORMAT Format : 픽셀 포멧 설정하는 거구나. color key를 사용할 것이므로 알파 채널이 필요하고 고로 D3DFMT_A8R8G8B8 를 사용해야 한다.

D3DPOOL Pool : 이전에 한번 다룬적이 있는 인자인데, 이것은 D3D에게 어디다 텍스쳐를 어떻게 만들것인지를 알려주는 역활을 한다. 현재 사용할 것은 MANAGED인데 video ram에 텍스쳐 이미지를 저장 보관 한다.

[Table 12.1 - D3DPOOL Values]

DWORD Filter : 특별한 결과를 만들어내기 위한 (텍스쳐를 이용한) 기술인데, (특별한 상황에서만) 현재는 사용하지 않는다. 고로 기본 인자값인 D3DX_DEFAULT 로 설정해서 사용.

DWORD MipFilter : 이것도 Filter인자와 매우 유사한 것인데 단지 차이점이라면 이 인자의 설정은 오직 MipMap에만 영향을 미친다는 점이다. 이것도 기본값 사용.

D3DCOLOR ColorKey : 드디어 관계 되는 인자가 나왔구나. D3DCOLOR_XRGB() macro 를 이용하여 가려버리고 싶은 색을 지정하자.

D3DXIMAGE_INFO* pSrcInfo : 이것은 D3DXIMAGE_INFO struct에 대한 포인터로 로딩된 이미지에 대한 모든 정보를 담고 있는 구조체다.

A D3DXIMAGE_INFO struct는 이미지 정보를 담는데 사용하는 아주 유용한 구조체이다. 다음 테이블이 바로 구조체 내부에 자리잡고 있는 인자들에 대한 설명이다.

[Table 12.2 - D3DXIMAGE_INFO Values]

PALETTEENTRY* pPalette : 256 색상을 사용하는 경우 사용되는 인자인데 여기서는 주로 사용하지 않는다. 32 비트 세상인 지금 거의 쓸일이 없다고 봐도 무방하죠.

LPDIRECT3DTEXTURE9* ppTexture : 드디어 마지막 인자. 우리가 로딩하고자 하는 주소 위치를 찍어주는 인자. 어따 넣을건지 알려주는 거죠..

D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                            L"BlueCircle.png",    // the file name
                            D3DX_DEFAULT,    // default width
                            D3DX_DEFAULT,    // default height
                            D3DX_DEFAULT,    // no mip mapping
                            NULL,    // regular usage
                            D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                            D3DPOOL_MANAGED,    // typical memory handling
                            D3DX_DEFAULT,    // no filtering
                            D3DX_DEFAULT,    // no mip filtering
                            D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                            NULL,    // no image info struct
                            NULL,    // not using 256 colors
                            &texture_1);    // load to texture_1

위의 예가 실제에 사용되는 예인데... 후미 길다.. 거의 WndMain 수준인데? 헐헐헐.. 하지만 알고 보면 간단한 것들이다.

3. The Finished Program
우선 결과는..

Image 12.4 - Blue Circles in Action

Image 12.4 - Blue Circles in Action

그리고 코드는

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // the texture

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR; FLOAT U, V; };
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the cube

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    // keep the alpha blending in
    d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);    // turn on the color blending
    d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);    // set source factor
    d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);    // set dest factor
    d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);    // set the operation

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

    // set an ever-increasing float value
    static float index = 0.0f; index+=0.03f;

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 ((float)sin(index) * 20.0f, 2.0f, 25.0f),   // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    // set the stream source
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // set the texture
    d3ddev->SetTexture(0, texture_1);


    // set the first world transform
    D3DXMATRIX matTranslate;
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, -10.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the first circle
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    // set the second world transform
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, 0.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the second circle
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    texture_1->Release();    // close and release the texture
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{

    // Load the BlueCircle.png texture
    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"BlueCircle.png",    // the file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &texture_1);    // load to texture_1

    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        // square 1
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 1, },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 0, },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 1, },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 0, },

        // square 2
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 1, },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 0, },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 1, },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 0, },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(8*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}

이제 드디어 D3D basic이 끝나고 다음으로 D3D mesh로 넘어간다. 좀더 멋진걸 해보자! 유후!
posted by 대갈장군
2008. 2. 16. 03:00 프로그래밍/DirectX
블렌딩 기법이 나왔네. 이건 왜? 두가지 색을 합쳐 새로운 색상을 만드는것을 말하는데 이건 갑자기 왜?

음... OpenGL에서 봤던 바로 앞의 색 흐리멍텅하게 만들기 기법을 설명해 준다는 구나... :)

1. Blend Operations
Image 11.1 - Red and Blue Added Together

Image 11.1 - Red and Blue Added Together

헐 그림만 봐서는 이해가 전혀 안되는데 일단 직독 직해를 해보면, 컬러 블렌딩의 유연성 때문에 DX는 정확하게 내가 무슨 색을 만들어내고 싶은지 (조합하고 싶은지)를 알기 원한다. 고래서 DX는 blend operation이라는 것을 이용한다는데 이것은 단순한 식인데, 이 식에는 두가지 unknown 값이 있다. 이 두값이 채워지면 바로 새로운 색상이 만들어지게 된다. 고로 unknown 값 2개를 정확히 내가 원하는 값으로 넣어야 한다는 점.

아, 이제 위의 그림이 이해가 가는구만. 빨간색 왼쪽편 및의 막대를 보면 빨간색이 가장 높고 녹색과 파랑색은 0이다. 즉, 빨간색만 나타내고 있다는 의미. 반대로 파랑색 오른쪽 편 막대를 보면 파란색만 존재한다. 중간 부분의 막대를 보면 빨간색과 파란색이 혼합되어 나타나는 반면 여전히 녹색은 0이다.

Blend Operation은 총 5가지 파트로 나뉘어 지는데, the source color, the source factor, the operation, the destinatoin color, and the destination factor. 어라... 제법 복잡한데?

Final Color  =  (Source Color  x  Source Factor)  Operation  (Dest Color x Dest Factor)

그러고 보니 OpenGL에서도 제법 복잡하긴 했다. 수식으로 이래저래 설명하긴 했지.

1-1. Source Color and Dest Color
Color Blending은 내가 물체를 back buffer에 그려 넣을때 계산된다. 이 식은 각각의 픽셀에 대해서 계산이 된다. Source Color는 말그대로 그려질 물체의 색상이다. 아래 그림에서 보면 빨간색 삼각형의 빨간색이 되겠다. 반면, Destination Color는 이미 back buffer에 그려져 있는 파란색 사각형의 파란색이다.

Image 11.2 - Source Color and Destination Color

Image 11.2 - Source Color and Destination Color

1-2. Source Factor and Dest Factor
색상 블렌딩시 각 주요색은 pre-determined factor에 의해서 곱해지는데 (혼합되는데) 이게 복잡해 보여도 그렇지 만은 않단다. 내가 렌더링을 시작하기 전에 D3D에게 어떤 factors를 사용할 것인지만 미리 알려주면 된다. 일반적으로는 Alpha channel을 많이 이용한다. (투명도만 설정)

factor는 무조건 float 값으로 0.0f 에서 1.0f 값으로 표현한다. 예를 들어 설명해 놨는데 간단하다. 만약 alpha 채널의 factor를 .5로 해놓게 되면 뒤에 있는 Destination color중 50% 색상을 가져와 겹쳐 보이게 만든다.

1-3. Operation
source factor와 destination factor를 제외하고도 operation에 대한 설정이 가능한데 이것이 바로 OpenGL에서는 이런 저런게 있다하고 넘어간 부분인것 같구나. 여기서는 뭐 다양한 기능이 있는데 더하기, 뺴기, 큰거, 작은거 등등이 있단다.

2. Using Color Blending
이것도 단계별로 잘 설명해 놓았구나.

1. Blending 켜기
2. Blend Equation을 위한 Operation 지정
3. Source와 Destination Factor 정의
4. Alpha 값 지정

2-1. Turning on Bleding
블렌딩 모드 켜기인데 OpenGL에서는 GL_ENABLE(GL_BLENDING) 이었던거 같다. 여기서는,

d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

요렇게 하는구나. SetRenderState가 주로 요런 잡다한 플래그 세팅을 책입지고 있구나...

2-2. Set the Operation for the Blend Equation
이제 수식을 정의 할 차례. 수식이라기 보다는 색상 조합에 적용된 연산 방식을 지정할때이다.

d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

이것도 간단하구나. 역시나 SetRenderState 함수를 이용한다. 보면 플래그로 D3DBLENDOP_ADD가 들어갔는데 당연히 이것 외에도 다양한 형태의 연산 방식이 존재하는데 다음과 같다.

Value Description
D3DBLENDOP_ADD This is the one we'll use in this lesson.  This indicates that the destination color will be added to the source color.
D3DBLENDOP_SUBTRACT This indicates that the destination will be subtracted from the source.
D3DBLENDOP_REVSUBTRACT This indicates the reverse of regular subtraction, that the source will be subtracted from the destination.
D3DBLENDOP_MIN This indicates that the darker of the two colors will be used.  Note that this is done to each primary color separately, not to the whole color.
D3DBLENDOP_MAX This indicates that the brighter of the two colors will be used.
[Close Table]

특이한 점은 REVSUBTRACT라는 플래그 인데 source색이 dest색으로 부터 빼진다는... 역방향 계산이 된다. 흠.. 왜 저게 필요할까라는 의문이 들기는 하는데.. 일단 넘어가자.

2-3. Set the Source and Destination Factors
이제는 soruce와 destination factor를 설정해야 하는데 이것은 조금 달라보이는 구나. 일단 명령을 보면,

d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

여전히 SetRenderState를 쓰고 1번 파라미터만 SRC냐 DEST 차이다. 그리고 그 뒤에오는 플래그 값이 조금 의아 스러운데 다음 테이블을 보고 그 의미를 파악해보자.

Value Description
D3DBLEND_SRCALPHA The factor used is the alpha value of the object being drawn.
D3DBLEND_INVSRCALPHA The factor used is the inverse of the alpha value of the object being drawn.  In other words, because the value is between 0.0 and 1.0, this would be equal to 1 - alpha.
D3DBLEND_SRCCOLOR The factor used is the source color.
D3DBLEND_INVSRCCOLOR The factor used is the inverse of the source color.
D3DBLEND_DESTCOLOR The factor used is the destination color.
D3DBLEND_INVDESTCOLOR The factor used is the inverse of the destination color.
D3DBLEND_ZERO The factor used is zero, meaning the color is treated as if it were black.
D3DBLEND_ONE The factor used is one, meaning the color is not changed at all.
[Close Table]

흠, 여기서 보니 SRCALPHA 플래그는 그려질 물체의 alpha 값을 사용하게 되는 반면 INVSRCALPHA는 그려질 물체의 alpha값을 1에서부터 뺀값이다. 결국 소스 색상의 alpha 값에 dest 색상 1-alpha 값 만큼 색상이 혼합되어 나타날것.

2-4. Set the Alpha Values in the 3D Model
이제는 삼차원 모델 (각각의 vertex에) alpha 값을 지정해주기만 하면 된다.

// create the vertices using the CUSTOMVERTEX struct
CUSTOMVERTEX t_vert[] =
{
    // square 1
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 0, 255), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 0), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 255), },

    // square 2
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 0, 255), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 245, 0), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 255, 255), },
}

중간에 왜 파란줄이 들어갔는지 모르겠다만... ㅡ.ㅡ; 알파값이 제일 앞에 오는데 보면 두개의 사각형중 밑에 꺼는 약간 투명하다. 75%정도만 보인다. 근데 위치는 똑같군. (위치를 다른 두 사각형에 어떻게 적용하는지 궁금. 찾아볼 필요 있을듯)

여기서 달라진 점은 D3DCOLOR_XRGB를 사용하는 것 대신 D3DCOLOR_ARGB를 사용했다. 즉, 알파 채널을 추가한 셈이다.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the cube

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);    // turn on the color blending
    d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);    // set source factor
    d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);    // set dest factor
    d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);    // set the operation

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

    // set an ever-increasing float value
    static float index = 0.0f; index+=0.03f;

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 ((float)sin(index) * 20.0f, 2.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    // set the stream source
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // set the first world transform
    D3DXMATRIX matTranslate;
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, -10.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the first square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    // set the second world transform
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, 0.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the second square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        // square 1
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 0, 255), },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 0), },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 0, 0), },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 255), },

        // square 2
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 0, 255), },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 255, 0), },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 255, 0, 0), },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 255, 255), },
   };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(8*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}

결과는
Image 11.3 - Alpha Squares in Action

Image 11.3 - Alpha Squares in Action

위에서 빨간색으로 칠한 부분을 보면 FVF 로 정의한 vertex 집단에서 정의된 2개의 투명도가 다른 사각형을 어떻게 다른 위치에 그려내는지 보여주고 있다. 우선 첫번째 위치를 잡고 (translate) 물체를 그리되 두개의 폴리곤 (삼각형)을 이용해서 그려낸다. 그리고 다시 위치를 잡고 두번째를 그린다. 이런 것이 반드시 알아둬야 할 기법이라고 생각한다.

또 한가지, 카메라를 마치 좌우로 튕기듯이 자유롭게 이동하는 기술을 구사 했는데 이것은 위의 코드에서 보면 sin 함수를 이용하여 -1에서 1을 왔다리 갔다리 하면서 마치 카메라가 180 각을 두고 좌우로 이동하는 효과를 내고 있는데 이런 것 또한 아주 간단하면서 효과적인 기법이라고 생각한다.
posted by 대갈장군
2008. 2. 15. 04:30 프로그래밍/DirectX

앞서 살펴본 directional 빛은 물론 좋기는 하나 어디나 존재하는 빛이다. 문제는 사용자는 어디나 존재하는 빛 대신 특정한 곳에만 존재하는 빛을 원할때가 있다. 바로 그때 필요한 빛이 point light와 spot light인데 이것에 대해서 더 알아보자.

그리고 사실 빛에는 문제가 있다는데... 무슨 문제가 있을까?

1. The Scaling Problem
한가지 대표적인 문제가 물체의 크기에 따라서 빛의 강도가 바뀌는 문제라는데 정말 그런가? 작으면 작을수록 빛은 밝아지고 크면 클수록 어두워 진단다.

이것이 발생하는 이유는 바로 normal vertor 때문이라는데 물체가 커지거나 작아지면 이 normal vector도 같이 커지고 작아져서 그렇다라고 하는데... 같이 커지고 작아져서 라기 보다는 촘촘해지고 엉성해지고 그래서 그런거 아닌가? 암튼 이유는 정확히 모르나 normal vector가 문제인건 확실하다.

흠, 실제로 테스트 해보니 그렇네... 아, 그런데 여기서 역시나 해결 방법이 있으니 바로 normalize다. 헐헐 여기서 또 만나는 구나.

d3ddev->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);

이것을 설정 함으로써 D3D가 알아서 normal vector들을 관리하게 해준다. 고로 요것들의 길이들을 적절히 알아서 조절해 줌으로써 빛의 밝기 차이가 나타나지 않게 한단다.

헐, 이게 다래.... 그니까 빛을 설치할때는 항상 저 명령 넣는것을 잊지 말라는 말이군.

2. Point Lights
Image 10.1 - Point Light

Image 10.1 - Point Light

흠. 드디어 내가 생각한 부분의 설명이 나오는군. 앞에서 살펴본 directional 빛의 경우는 무한 발산이지만 지금 보게되는 point light는 무한 발산이 아니다. 즉, 거리가 멀면 멀수록 빛이 약해진다는 의미. attenuation이 발생한다는 말이다.

또한 중요한 차이점으로는 정확한 빛의 위치가 있다는 점으로 거기서 부터 빛이 나오게 된다.

2-1. Range
아래처럼 빛의 비춰질 최대 범위를 지정할 수가 있다.
light.Range = 100.0f;

이것을 설정함으로써 포인트 소스에서 100 유니트 이상 떨어진 위치에서는 빛에 대한 계산이 전혀 이루어지지 않으므로 속도의 향상을 기대할 수 있다.

2-2. Attenuation
여기서는 약간의 산수가 들어가기 시작하는데 거리에 따라 감쇠하는 빛의 세기는 이차원 방정식으로 표현 가능하다. (물론 고차원도 가능하겠지만 2차원이 적당한가 보다)

함수 공식은 Atten = 1 / (att0 + att1 * d + att2 * d2) 인데 좌변은 빛의 강도가 되겠고 1.0 에서 0.0까지 분포한다.

d는 물론 distance 이며 att0, att1, att2가 있는데 이들은 중요인자들이죠. 빛의 감쇠 정도를 결정하는 인자들일것이다.

첫번째 인자인 att0는 감쇠하지 않는 빛의 세기이다. 고로 이것은 최대 거리에 있는 물체도 기본적으로 받게 되는 빛의 양이된다.

Image 10.2 - att0 graphed at 0.5

Image 10.2 - att0 graphed at 0.5


두번째 인자인 att1은 역함수 형태인데 거리에 따라서 점점 감소하는 모습을 보인다. 가장 현실성 있는 빛의 감쇠를 나타내고 있다고 할수 있겠다.
 
Image 10.3 - att1 graphed at 1.0

Image 10.3 - att1 graphed at 1.0

이제 마지막인 att2인데 이놈은 두번째 인자와는 달리 빠른 속도로 감쇄하는데 이는 불꽃을 표현하는데 아주 좋다. 아주 가까운 곳만 심하게 밝게 나타낼수 있는 효과를 가지고 있다.

Image 10.4 - att2 graphed at 1.0

Image 10.4 - att2 graphed at 1.0

Using the Equation
자, 자세히는 모르겠지만 (아직) 이런 세개의 상수를 잘 설정 함으로써 (사용자가 직접) 다양한 형태의 빛을 표현 할 수 있을 것이다. 하지만 명심 해야 할 것이 있으니 만약 range를 설정해놓았다면 그 지점을 넘어서는 순간 빛은 바로 없어지게 된다. (감쇄 현상 계산 한계 지점) 고로 이 위치를 속도 향상을 위해서 사용한다손 치더라도 적절한 거리 계산이 반드시 필요하다는 점 명심하라.

또 다른 한가지는 directional light에는 감쇄가 없다. 고로 이런 세팅을 해봐야 아무런 영향을 주지도 않는다.

Coding a Point Light
// this is the function that sets up the lights
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the light struct for use
    light.Type = D3DLIGHT_POINT;    // make the light type point light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)
    light.Range = 100.0f;    // a range of 100
    light.Attenuation0 = 0.0f;    // no constant inverse attenuation
    light.Attenuation1 = 0.125f;    // only .125 inverse attenuation
    light.Attenuation2 = 0.0f;    // no square inverse attenuation

    D3DVECTOR vecPosition = {0.0f, 5.0f, 0.0f};    // the position of the light
    light.Position = vecPosition;    // set the position

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

새로 등장한 놈이 있으니 Type 인자에 들어온 새 플래그 D3DLIGHT_POINT이다. 뭐... 이건 대충봐도 포인트 라이트 소스를 말한다는 것은 알겠다.

이제 포인트 소스를 이용하므로 range설정이 가능한데 여기서는 100.0f로 설정했다.

그리고 앞서 살펴본 빛의 감쇄 인자 3개를 설정하는데 특이한 것은 att0가 0.0f라는 점. :) 전혀 감쇄하지 않는 빛은 없다는 말이다. 고로 오로지 거리에 따라 자연스레 감소하는 att1 인자만 값이 존재한다.

마지막으로 빛의 위치를 정의하는 값이 드디어 등장 했으니... 예측한 대로다.

위의 세팅을 이용해서 결과를 그려보면,

Image 10.5 - The Point Light

Image 10.5 - The Point Light
위와 같은 결과가 나온다.

3. Spot Light

Image 10.6 - Spot Light

Image 10.6 - Spot Light
새로운 빛의 형태가 등장했구나~ 스포트 라이트... 헐헐헐... 이 빛은 시작 위치가 있고 그 지점부터 특정한 방향으로 빛을 비춘다. ambient와 diffuse는 본질적으로 다른 속성이다.

위의 그림에서 보듯이 3개의 물체가 있지만 spot light는 지정한 방향 앞에 존재하는 물체만을 비추게 된다는점.

3-1. Phi and Theta
물론 spot 라이트는 점으로 나가는 레이져가 아니다. 다음 그림처럼 중심에서 부터 조금씩 퍼져나가는 빛의 형태를 띄어야 할것이다.

Image 10.7 - Phi and Theta

Image 10.7 - Phi and Theta

보면 2중 구조로 되어 있는데... 이것은 어두운 방에서 후래쉬를 켜보라.. 그러면 딱 저렇게 된다... 왜그렇지? 헐헐헐...

어쨌든, 결론은 spot light의 경우에는 두개의 각 theta와 phi에 의해서 그 형태가 결정된다는 말.

3-2. Falloff
어두운 방에서 다시 플래스를 켜보라. 다음과 같은 그림의 형태로 빛이 감쇄할 것이다.

Image 10.8 - Spot Light Falloff

Image 10.8 - Spot Light Falloff

안쪽으로 점차 점차 빛이 모여드는 느낌인데 이런 감쇄현상을 falloff라고 한단다. 헌데 이러한 감쇄현상의 값을 조절할수가 있으니 이로써 특별한 느낌을 줄수가 있다고 한다.

Image 10.9 - Various Falloff Values

Image 10.9 - Various Falloff Values

허나 일반적으로 falloff 값으로 1.0을 쓰는데 이유는 계산 시간이 좀 더 걸리기 때문이고 사실상 spot light 자체를 잘 안쓰는데 왜냐면 별도의 계산이 추가로 필요하기 때문이다.

3-3. Coding a Spot Light
자, 본격적으로 어떻게 spot light를 만드는지 보면,
// this is the function that sets up the lights
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the light struct for use
    light.Type = D3DLIGHT_SPOT;    // make the light type spot light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)
    light.Range = 100.0f;    // a range of 100
    light.Attenuation0 = 0.0f;    // no constant inverse attenuation
    light.Attenuation1 = 0.125f;    // only .125 inverse attenuation
    light.Attenuation2 = 0.0f;    // no square inverse attenuation
    light.Phi = D3DXToRadian(40.0f);    // set the outer cone to 30 degrees
    light.Theta = D3DXToRadian(20.0f);    // set the inner cone to 10 degrees
    light.Falloff = 1.0f;    // use the typical falloff


    D3DVECTOR vecPosition = {-8.0f, 0.0f, 30.0f};    // the position of the light
    light.Position = vecPosition;    // set the position

    D3DVECTOR vecDirection = {0.0f, 0.0f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

보면 추가된 것으로 phi와 theta 값 그리고 falloff 값, 빛의 위치는 그전에 사용하던대로이고, 대신 비추는 방향이 바뀌었다.

위대로 하고 실행해보면
 
Image 10.10 - The Spot Light

Image 10.10 - The Spot Light
저런 결과가 나오는데 오른쪽 박스는 일부 스폿 라이트를 받으나 왼쪽 상자는 못받고 있단다... (좀더 빛의 가까이서 강하게 줬으면 좋았을텐데...)

4. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // our first texture

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations
void init_light(void);    // sets up the light and the material

struct CUSTOMVERTEX {FLOAT X, Y, Z; D3DVECTOR NORMAL; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the cube
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light
    d3ddev->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);    // handle normals in scaling

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 40.0f, 30.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    // set the world transform
    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matTranslate;    // the world transform matrix
    D3DXMatrixTranslation(&matTranslate,
                          (float)sin(index) * 12.0f,
                          0.0f,
                          (float)cos(index) * 25.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // select the vertex buffer to display
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // set the texture
    d3ddev->SetTexture(0, texture_1);

    // draw the textured cube
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);

    // set the other world transform
    D3DXMatrixTranslation(&matTranslate,
                          (float)sin(index) * -12.0f,
                          0.0f,
                          (float)cos(index) * -25.0f);
      d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the other textured cube
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);  

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    texture_1->Release();    // close and release the texture
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // load the texture we will use
    D3DXCreateTextureFromFile(d3ddev,
                              L"wood1.png",
                              &texture_1);

    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        // side 1
        { -3.0f, 3.0f, -3.0f, 0, 0, -1, 0, 1, },
        { 3.0f, 3.0f, -3.0f, 0, 0, -1, 1, 1, },
        { -3.0f, -3.0f, -3.0f, 0, 0, -1, 0, 0, },
        { 3.0f, -3.0f, -3.0f, 0, 0, -1, 1, 0, },

        // side 2
        { -3.0f, 3.0f, 3.0f, 0, 0, 1, 0, 1, },
        { -3.0f, -3.0f, 3.0f, 0, 0, 1, 0, 0, },
        { 3.0f, 3.0f, 3.0f, 0, 0, 1, 1, 1, },
        { 3.0f, -3.0f, 3.0f, 0, 0, 1, 1, 0, },

        // side 3
        { -3.0f, 3.0f, 3.0f, 0, 1, 0, 0, 1, },
        { 3.0f, 3.0f, 3.0f, 0, 1, 0, 1, 1, },
        { -3.0f, 3.0f, -3.0f, 0, 1, 0, 0, 0, },
        { 3.0f, 3.0f, -3.0f, 0, 1, 0, 1, 0, },

        // side 4
        { -3.0f, -3.0f, 3.0f, 0, -1, 0, 0, 1, },
        { -3.0f, -3.0f, -3.0f, 0, -1, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 0, -1, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 0, -1, 0, 1, 0, },

        // side 5
        { 3.0f, 3.0f, -3.0f, 1, 0, 0, 1, 0, },
        { 3.0f, 3.0f, 3.0f, 1, 0, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 1, 0, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 1, 0, 0, 0, 1, },

        // side 6
        { -3.0f, 3.0f, -3.0f, -1, 0, 0, 1, 0, },
        { -3.0f, -3.0f, -3.0f, -1, 0, 0, 0, 0, },
        { -3.0f, 3.0f, 3.0f, -1, 0, 0, 1, 1, },
        { -3.0f, -3.0f, 3.0f, -1, 0, 0, 0, 1, },
    };    // that reminds me of programming in binary!

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(24*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_POINT;    // make the light type 'point light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)
    light.Range = 100.0f;    // a range of 100
    light.Attenuation0 = 0.0f;    // no constant inverse attenuation
    light.Attenuation1 = 0.125f;    // only .125 inverse attenuation
    light.Attenuation2 = 0.0f;    // no square inverse attenuation

    D3DVECTOR vecPosition = {0.0f, 5.0f, 0.0f};    // the position of the light
    light.Position = vecPosition;    // set the
position

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material

    return;
}


Image 10.11 - The Point Light in Action

Image 10.11 - The Point Light in Action



posted by 대갈장군
2008. 2. 14. 06:48 프로그래밍/DirectX
이제까지는 빛을 고려하지 않았다. 모든 물체가 골고루 빛을 받는 것이 자동으로 설정되어 있었다. 이제부터는 진짜 실제와 같은 빛을 어떻게 만들어 내는지 한번 연구해 보자.

1. Light in Nature and Light in 3D
상상해보자. 실제로 태양에서 오는 빛을 컴퓨터가 완벽하게 재현하려면 얼마나 힘이든지를... 거의 불가능하고 거기다 이것을 실시간으로 한다는것은 말도 안된다.

고로 Direct3D에서는 실제 빛을 모방하는 차원에서만 빛을 만드는데 빛의 타입빛 소스의 타입을 정의 함으로써 실제 빛과 유사하게 보이는 빛을 만들어 낸다.

Types of Lighting
이건 앞에서 했던 건데, 세가지 종류의 빛이 있다. diffuse lighting, ambient lighting, specular lighting.

Diffuse Lighting은 정면에서 받는 빛을 말한다. 전등 한개 있는 방에서 손을 들이대었을때 손의 밝은 면에 비치는 빛을 말한다.

Ambient Lighting은 위의 빛과는 달리 반사되어 들어오는 모든 방향의 빛을 말하는데 이것은 위의 예에서 어두운 손바닥 부분에 들어오는 빛인데, 어두운 손바닥 부분에도 분명히 빛이 존재한다.

Specular Lighting은 빛이 물체에 닿고 반사 될때 나가는 빛을 말한다. 다음 그림을 보면 조금 이해가 쉽다.


Image 9.2 - Diffuse Light

Image 9.2 - Diffuse Light

Image 9.3 - Ambient Light

Image 9.3 - Ambient Light

Image 9.4 - Specular Light

Image 9.4 - Specular Light


Types of Light Source
허라... 그랬던가? OpenGL에서는 오직 위에서 살펴본 3가지 종류의 빛만 있는 줄 알았는데 그것이 아니라 빛 소스의 형태를 정의하는 것도 있었던 것인가? 아니면 있었는데 내가 정확히 몰랐던 것인가?

자, light source에는 4가지 타입이 있는데,
1. Ambient Light
2. Directional Light
3. Point Light
4. Spot Light

1-1. Ambient Light
자자, 일단 정리를 하자면, 위에서 본 세가지 형태의 빛은 물체의 입장에서 봤을때의 빛이다. 물체의 밝은면, 어두운면, 그리고 반사빛이죠. 하지만 이제부터 다루는 것은 빛의 입장에서 봤을때의 빛이다. 염두해두고 읽자.

Ambient light는 말그대로 Ambient light를 만든다. Ambient라는 단어의 의미는 둘러싼 이라는 의미로서 주변광이라고 한다. 이것은 특정한 방향에서 오는 빛이 아닌, 태양이 떠있을때 야외에 존재하는 빛과 같다.
 
1-2. Directional Light
이건 뭐 이름에서 바로 느낌이 온다. 방향성을 가진 빛이다. 어디서 온다는 것이 명확한 빛이죠. 이 빛이 설정되어 있다면 이 빛은 ambient와 diffuse그리고 specular light를 생성하게 된다. (물체의 입장에서) 왜냐면, 직사 광선이 물체에 비춰지면 일단 반사광 (specular light)가 생기고 정면을 쏴주니까 입사광(diffuse light) 혹은 분산광이 생기게 되고 또한 이것도 일종의 여러방향에서 오는 빛이므로 ambient light (주변광)을 만든다.

1-3. Point Light
Image 9.6 - Point Light

Image 9.6 - Point Light

간단하게 위의 그림으로 설명이 된다. 이 빛 또한 ambient, diffuse, specular 를 만들어 낼 것이다.

1-4. Spot Light
Image 9.7 - Spot Light

Image 9.7 - Spot Light

흠. 이것은 특수화된 빛의 종류인데 지정된 위치에서 지정된 방향으로 빛을 뿜는 것을 말한다. 그렇다면 이것이 Directional Light와 다른 점은 무엇인가? 규모의 차이인가? 그런 설명은 없으나, 내가 생각할때는 그렇다. 아마도 Spot Light는 정밀한 각도 조절 (빛의 분산각)이 가능하리라 본다.

1-5. Materials
어라 이건 위에 없던건데? :)

Image 9.8 - A Wooden Square

Image 9.8 - A Wooden Square

Image 9.9 - A Redwood Square

Image 9.9 - A Redwood Square

Image 9.10 - A Better Redwood Square

Image 9.10 - A Better Redwood Square

Image 9.11 - Purplewood?

Image 9.11 - Purplewood?
자, 위의 4개의 나무 텍스쳐가 입혀진 물체를 보자. 우선 9-8 그림은 그냥 일반적인 흰색 빛으로 비추었을 경우인데, 이때는 당연히 원래 물체의 색이 그대로 나타나게 된다.

이것은 빛이 없을때의 색상과 동일하다. 그렇다면 이제는 그림 9-9 처럼 사각형에 빨강색 속성을 넣어보자. 당연히 흰색 빛을 비추면 빨간색 속성을 가지는 사각형을 빨간색만 반사하고 고로 빨간색으로 보인다. 이때 여기다가 파랑색 조명을 주면 아무것도 보이지 않게 된다.

9-10 그림은 빨간색이 아닌 다른 색도 약간 조절해서 첨가한 것이다. 그림 9-11 은 파르스름한 빛을 비춘것이다. 그렇다면 9-11의 사각형은 무슨 색인가? 그 사각형은 아직 빨간색이다... 즉, 물체의 색상을 결정하는 것은 빛의 색상만이 아니고 물체의 색상도 기여를 한다는 점이다. 이 두가지가 썪여서 나타나게 되므로 색상을 결정하는 일은 항상 조심스럽다.

조언을 하자면, 기본적으로 빛의 색상은 늘 흰색 빛으로 하는 것이 머리가 덜 아플 것이다.

2. Vertex Normals
자, 다음과 같은 간단한 그림을 보면서 왜 이것이 필요한 가를 논해 보자꾸나.
Image 9.12 - A Diffusely Lit Square

Image 9.12 - A Diffusely Lit Square
 
배경이 검정색이 아니라서 조금 어색해 보이는데.. 우쨌든 Diffuse light로 비춰지는 저런 나무 판때기가 있다고 하자.

그러면 아래 그림처럼 물체를 기울인다면 어떻게 되어야 할까? 당연히 멀리 있는 (혹은 입사하는 빛과 경사가 커질수록) 어두워 져야 할 것이다.
Image 9.13 - Some Less Diffusely Lit Squares

Image 9.13 - Some Less Diffusely Lit Squares

바로 이것을 해결하기 위한 것이 Normal이다. 이 물체의 Normal은 이 물체의 표면에 수직한 선이 된다. (다음 그림)
Image 9.14 - The Squares and Their Normals

Image 9.14 - The Squares and Their Normals
이것이 왜 이렇게 되는지 이해가 되나? 당연히 그렇다... -_-

아래 그림은 조금 설명이 좋지 않은데 아마도 글쓴이가 잘 모르는 사람들에게 이해를 돕기 위해서 이렇게 했나보다.

입사하는 벡터 (빛의 한 가닥)은 벡터가 닿는 지점의 (surface) 법선 (normal vector)와의 곱으로 그 양이 계산되어 진다. (물리학 + 전자기학) 헐 이렇때 사용되네.. 헐헐...
Image 9.15 - Sizing Up the Normals

Image 9.15 - Sizing Up the Normals

하지만, 슬픈 소식이 있으니 바로 D3D는 알아서 이런 법선 벡터를 정의해주지 않는다는 것! 젠장.


3. Creating Lights
불행하게도.... 빛을 설정하고 적용하는 과정은 그닥 쉽지가 않단다. 다음과 같은 일련의 과정을 주욱 거쳐야 하는데,

1. Setting Up a New Flexible Vertex Format
2. light 켜기
3. Ambient light 설정(속성)
4. Diffuse light의 속성 설정 및 생성
5. Material 속성 설정 및 생성


아, 여기서는 상당히 정리 정돈을 잘해놓았구나. 이 지은이는 D3D에 대한 충분한 이해가 있구나... :)
아놔 젠장. 여기서만 두번 에러나서 컴퓨터 다시 껐다 켰네 젠장할... 이부분은 나중에 쓸까보다 콱 그냥. 소스 파일 코드에 texture 파일 이름 틀렸다. 쓰붕새

1. Setting Up a New Flexible Vertex Format
struct CUSTOMVERTEX {FLOAT X, Y, Z; D3DVECTOR NORMAL; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

새로 등장한 타입 D3DVECTOR는
typedef struct D3DVECTOR {
    float x, y, z;
} D3DVECTOR, *LPD3DVECTOR;

사실 걍 구조체.

2. Turning the Lighting On
불을 켜자.
d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);

3. Setting the Ambient Light
Ambient light 추가 설치하는데 이 빛은 diffuse나 specular 빛이 없어도 은은하게 퍼지는 주변광이다. 명심하도록... 주변광일뿐 물체를 직접적으로 비추는 빛은 아니다. (Not spot light)

d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

4. Creating and Setting the Diffuse Light

// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the light struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

이제 뭔가 좀 나오는것 같아 보이는데 여기서 새로 등장한 D3DLIGHT9이라는 구조체는 빛의 속성에 대한 구조체로 각종 정보를 저장할 수 있다.

Type인자에서 빛의 종류를 결정하는데 다음과 같은 인자가 들어갈 수 있다.
Value Description
D3DLIGHT_DIRECTIONAL Creates a directional light, a light that comes from everywhere at once, but shines only in one direction.
D3DLIGHT_POINT Creates a point light, a light that emanates equally in all directions from one exact point.
D3DLIGHT_SPOT Creates a spot light, a light that emanates in one direction from one exact point.
[Close Table]

그 다음에 Diffuse 인자에 보면 다시 r,g,b,a로 나뉘어 색상을 정의 가능하게 해놓았고 (빛의 색) Direction인자에서는 어디로 비출것인가를 결정한다.

다음 두개의 함수인 SetLight(0, &light)와 LightEnable(0, TRUE)는 0 번호의 빛에 사용자가 만든 light 구조체를
대입하고 0번 빛을 활성화 하라는 의미다.

5. Creating and Setting the Material

// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material


    return;
}

이제 마지막 단계인 material 설정인데 각 점마다 색상을 주는 방식과는 확연히 다르다. 한번 material을 설정하면 바꾸기 전까지는 계속 그 색상으로 모든 점들이 그려지게 된다.

새로운 구조체가 또 등장을 했는데 D3DMATERIAL9이다. 그리고 여기에 새로운 멤버인 material.Diffuse가 있는데 이 색이 바로 diffuse 빛이 들어올때 어떤 색으로 반사 할 것인가를 지정한다.

이와 유사하게 material.Ambient가 있는데 이것은 ambient light가 들어올때 어떤 색으로 반사할지를 지정한다. 아주 중요한 점은 이 둘은 두개의 다른 빛의 종류에 서로 각각 독립적으로 반사한다는 점을 명심하라.

쏘쓰 코뜨

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // our first texture

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations
void init_light(void);    // sets up the light and the material

struct CUSTOMVERTEX {FLOAT X, Y, Z; D3DVECTOR NORMAL; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the cube
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);

    // set the world transform
    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform

    // select the vertex buffer to display
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // set the texture
    d3ddev->SetTexture(0, texture_1);

    // draw the textured square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    texture_1->Release();    // close and release the texture
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // load the texture we will use
    D3DXCreateTextureFromFile(d3ddev,
                              L"wood1.png",
                              &texture_1);

    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        // side 1
        { -3.0f, 3.0f, -3.0f, 0, 0, -1, 0, 1, },
        { 3.0f, 3.0f, -3.0f, 0, 0, -1, 1, 1, },
        { -3.0f, -3.0f, -3.0f, 0, 0, -1, 0, 0, },
        { 3.0f, -3.0f, -3.0f, 0, 0, -1, 1, 0, },

        // side 2
        { -3.0f, 3.0f, 3.0f, 0, 0, 1, 0, 1, },
        { -3.0f, -3.0f, 3.0f, 0, 0, 1, 0, 0, },
        { 3.0f, 3.0f, 3.0f, 0, 0, 1, 1, 1, },
        { 3.0f, -3.0f, 3.0f, 0, 0, 1, 1, 0, },

        // side 3
        { -3.0f, 3.0f, 3.0f, 0, 1, 0, 0, 1, },
        { 3.0f, 3.0f, 3.0f, 0, 1, 0, 1, 1, },
        { -3.0f, 3.0f, -3.0f, 0, 1, 0, 0, 0, },
        { 3.0f, 3.0f, -3.0f, 0, 1, 0, 1, 0, },

        // side 4
        { -3.0f, -3.0f, 3.0f, 0, -1, 0, 0, 1, },
        { -3.0f, -3.0f, -3.0f, 0, -1, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 0, -1, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 0, -1, 0, 1, 0, },

        // side 5
        { 3.0f, 3.0f, -3.0f, 1, 0, 0, 1, 0, },
        { 3.0f, 3.0f, 3.0f, 1, 0, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 1, 0, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 1, 0, 0, 0, 1, },

        // side 6
        { -3.0f, 3.0f, -3.0f, -1, 0, 0, 1, 0, },
        { -3.0f, -3.0f, -3.0f, -1, 0, 0, 0, 0, },
        { -3.0f, 3.0f, 3.0f, -1, 0, 0, 1, 1, },
        { -3.0f, -3.0f, 3.0f, -1, 0, 0, 0, 1, },
    };    // that reminds me of programming in binary!


    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(24*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material

    return;
}


결과


Image 9.17 - A Wooden Crate

Image 9.17 - A Wooden Crate






posted by 대갈장군
2008. 2. 14. 05:27 프로그래밍/DirectX

1. Texture Coordinates
Texture 좌표계는 일반적인 Cartesian coordinates와 같지 않다는 점을 주의해야 한다. 간단하게 하기 위해서라는데 정확한 이유는 아직 모르겠다. 어쨌든 다음 그림과 같은 좌표계가 Texture에서는 사용된다.

Image 8.1 - Texture Coordinates

Image 8.1 - Texture Coordinates

내가 기억하기 로는 위의 좌표계는 OpenGL과는 다르다. 각각의 점은 주어진 texture의 좌표계에서 정확한 위치를 가지게 되는데 예를 들어 (0.5, 0.5)라고 하면 아래 그림처럼 정확히 정중앙에 위치한 점을 말한다.

Image 8.2 - Texture Coordinate (0.5, 0.5)

Image 8.2 - Texture Coordinate (0.5, 0.5)

자, 예를 들어서 아래 그림과 같이 텍스쳐가 있고 물체 (사각형)이 있을 경우 두개를 더해서 요렇게 나타내고 싶다고 하자.

Image 8.3 - A Textured Square

Image 8.3 - A Textured Square

이렇게 만들기 위해서는 사각형의 각각의 vertex를 다음 그림처럼 맞춰줘야 한다.

Image 8.4 - Vertices Positioned on Texture Corners

Image 8.4 - Vertices Positioned on Texture Corners

사실 위의 그림은 매우 간단한 예로써 텍스쳐의 전체가 사각형 전체에 적용되고 있어서 간단하고 쉽다. 하지만 다음의 그림처럼 텍스쳐중 일부만 사용할 수도 있는데 이때는 텍스쳐 좌표계가 변화한다.

Image 8.5 - Vertices Positioned Inside Texture Corners

Image 8.5 - Vertices Positioned Inside Texture Corners

어떤가 일부만 사용했기 때문에 늘어난 텍스쳐의 모습이 보이는가?

자, 그렇다면 저 사각형에 텍스쳐를 중복해서 깔고 싶다면? 정답은 1이 아닌 2, 3의 숫자를 쓰는 것이다. 다음과 같이 하면 된다.

Image 8.6 - Vertices Positioned Outside Texture Corners

Image 8.6 - Vertices Positioned Outside Texture Corners

보시다시피 텍스처는 반복해서 깔수도 있고 일부만 사용할 수도 있다. 고로 거리에 따라서 물체의 크기에 따라서 텍스쳐의 사이즈를 적절히 적용한다면 사용자의 눈에 무리없는 화면을 연출 할 수 있다. 뿐만 아니라 멀리떨어진 작은 물체에 어마어마한 사이즈의 텍스쳐를 사용하는 바보같은 짓도 안할 것이다.

2. Setting a Texture
Texture를 사용하기 위해서는 다음의 단계를 거쳐야 한다.

1. 새로운 Flexible Vertex Format을 선언한다.
2. Texture를 로딩한다.
3. Texture를 세팅한다.
4. Texture를 Releasing 한다.

하나하나 살펴보자. 2,3,4번은 OpenGL에서도 했던 거라서 그다지 이해하기 어렵지 않으나 1번은 조금 의아하다.

2-1. Setting Up a New Flexible Vertex Format
이전에 FVF 포멧을 우리가 정의하지 않았던가? 바로 그것에 한가지를 더 추가하니 바로 texture coordinates가 되겠다. 바로 다음의 명령으로 간단히 추가가 된다. (다만 2차원 좌표계만 가능한건 아닐텐데 라는 의문이 든다.)

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

단지, 두개의 좌표계가 추가 되었을 뿐이다. U, V다. 그리고 CUSTOMFVF의 선언시 추가로 D3DFVF_TEX1을 추가했다. 바로 이것이 Texture를 사용한다고 알려주는 플래그.

이제 vertices를 선언하면서 텍스쳐 좌표계도 같이 넣어줘야 한다. 다음과 같이..
struct CUSTOMVERTEX t_vert[] =
{
    {1, 0, -1, 0xffffffff, 1, 0,},
    { -1, 0, -1, 0xffffffff, 0, 0,},
    { 1, 0, 1, 0xffffffff, 1, 1,},
    { -1, 0, 1, 0xffffffff, 0, 1,},
};

2-2. Loading the Texture
이제는 텍스쳐 로딩이 되겠다. 걍 간단하게 다음 함수를 사용하면 끝이라는데...
HRESULT D3DXCreateTextureFromFile(LPDIRECT3DDEVICE9 pDevice,
                                  LPCTSTR pSrcFile,
                                  LPDIRECT3DTEXTURE9* ppTexture);

인자들을 살펴보면,

LPDIRECT3DDEVICE9 pDevice : D3D device에 대한 포인터이다. 계속 사용해 오던 d3ddev 를 넣어주면 된다.

LPCTSTR pSrcFile : 이것은 image file의 파일이름에 대한 포인터가 되겠다. 이때 주의 할 것은 반드시 L"texture.bmp"와 같이 따옴표로 묶어주고 유니코드 호환을 위해 L을 붙이는것을 잊지 말자. 다음과 같은 확장자를 가지는 파일은 로딩 가능하다.  .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga.

LPDIRECT3DDTEXTURE9* ppTexture : 요건 새로나온 놈이랜다. 이것은 메모리상에 저장되어 있는 텍스쳐 이미지에 대한 포인터란다. 이것을 위해 꼭 해야 하는 것은 포인터를 만들고 (초기화는 필요없다.) 요 인자위치에 넣으면 된다는데... 내가 생각하기로는 이 포인터의 위치에 읽어들이는 이미지 정보를 가져다 넣겠지요... 아마도.

요 인자를 선언하는 위치는 아무래도 프로그램 초기시 (전역 변수로) 선언해줄 확률이 높다. OpenGL에서도 텍스쳐 넘버를 가지는 int형 변수를 global로 선언했던 기억이 난다.

LPDIRECT3DTEXTURE9 texture_1;

자, 그리고 이제 DirectX를 초기화 해줄때 다음과 같이 제대로 사용을 하면 된다.

D3DXCreateTextureFromFile(d3ddev,    // the Direct3D device
                          L"texture.bmp",    // the filename of the texture
                          &texture_1);    // the address of the texture storage

2-3. Setting the Texture
이제 로딩은 끝났다. 그렇다면 세팅이 남았는데, 다음 함수를 보자.

HRESULT SetTexture(DWORD Sampler,
                   LPDIRECT3DTEXTURE9 pTexture);

함수가 하는 일은 바로 다음부터 그려질 물체에 대해서 지정해준 텍스쳐를 사용하도록 해주는 것이다. 간단하네... OpenGL에서도 GL_ENABLE(GL_TEXTURE) 해주고 난 다음에 어떤 텍스쳐를 사용할지 지정해준다.

인자들을 살펴보면,

DWORD Sampler : 고급용이랜다. 다음 장에서 다룬다.

LPDIRECT3DTEXTURE9 pTexture : 바로 앞에서 로딩시 사용했던 메모리 공간상의 텍스쳐 이미지의 주소값이다. 흘흘흘.. 바로 여기서 사용될것이라고 예측했다.

d3ddev->SetTexture(0, texture_1);    // set the texture

위의 명령처럼 해주면 세팅 끝~

2-4 Releasing the Texture
이제 해제하는 일만 남았는데, 해제도 간단하다.

// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    texture_1->Release();    // close and release the texture
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}

3. The Finished Program
결과는 다음과 같고
Image 8.8 - The Textured Square

Image 8.8 - The Textured Square

소스 코드는 다음과 같다.


// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // our first texture


// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the square

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,   // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection

    // set the world transform
    static float index = 0.0f; index+=0.03f; // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform

    // select the vertex buffer to display
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // set the texture
    d3ddev->SetTexture(0, texture_1);


    // draw the textured square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);


    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    texture_1->Release();    // close and release the texture
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // load the texture we will use
    D3DXCreateTextureFromFile(d3ddev,
                              L"metal.png",
                              &texture_1);


    // create the vertices using the CUSTOMVERTEX
    struct CUSTOMVERTEX t_vert[] =
    {
        {5, 0, -5, 0xffffffff, 1, 0,},
        {-5, 0, -5, 0xffffffff, 0, 0,},
        {5, 0, 5, 0xffffffff, 1, 1,},
        {-5, 0, 5, 0xffffffff, 0, 1,},
      };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}


아, 참고로 주의 할 것은 Direct3D는 어떤 텍스쳐이건 간에 2의 승수로 텍스쳐 사이즈를 바꾸게 된다. 고로 만약 그 사이즈가 아닌 이미지의 경우는 자동으로 확대가 되는데 이것이 때때로 이상하게 보이는 텍스쳐의 원인이다.










posted by 대갈장군
2008. 2. 12. 04:21 프로그래밍/DirectX
1. Building a Quad
Quad라 함은 4각형을 말하는데 아래의 3개는 모두 4각형이다.

Image 7.1 - Various Quads

Image 7.1 - Various Quads

앞서서 말했다 시피 사실 모든 물체는 삼각형으로 구성이 되는데 바로 다음 그림처럼 구성이 된다.

Image 7.2 - Quads Are Dual Triangles

Image 7.2 - Quads Are Dual Triangles


자 그렇다면 주된 질문인 어떻게 그것을 만들어 내느냐에 대한 대답은... 살펴보자꾸나. 이전 프로그램에서 만든 삼각형 대신 이번에는 4각형을 만들어 볼것이다.

// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        { -3.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 3.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { 3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX),    // change to 4, instead of 3
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}

바뀐 부분을 보면 다만 점이 4개라는 것뿐, 그리고 VertexBuffer 사이즈가 하나 더 늘었다는 것 외에는 없다. 이제 바꿔줘야 할 중요한 부분이 있는데 바로 2개의 삼각형을 이용해서 사각형을 구성하라는 명령인 다음 명령이다.

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

보시다시피 바뀐 부분은 아래쪽 명령인데 첫 인자가 D3DPT_TRIANGLESTRIP으로 바뀌어 있다. 이것은 이전에 들어가 있던 인자인 D3DPT_TRIANGLELIST에서 바뀐 것으로 삼각형의 strip으로 구성하겠다는 의미이며 마지막 숫자인 2가 몇개의 strip이 들어가는지를 말해준다.

다음 그림이 간단한 설명을 해주는데 매우 간략하고 보기도 좋다.

Image 7.3 - Making Triangles

Image 7.3 - Making Triangles

2. Combining Quads To Make a Cube
이제 요 사각형들을 조합해서 Cube를 만들어보자.

// create the vertices using the CUSTOMVERTEX struct
CUSTOMVERTEX t_vert[] =
{
    // side 1
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 2
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 3
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 4
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 5
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 6
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },
};

// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(24*sizeof(CUSTOMVERTEX),
                           0,
                           CUSTOMFVF,
                           D3DPOOL_MANAGED,
                           &t_buffer,
                           NULL);

생각보다 복잡시럽다. 이것은 OpenGL에서도 마찬가지다. 상당히 번거롭다. 이제 이걸 그려줘야 하는데 하나의 명령을 쓱싹 그려질줄 알았는데 아니다. 왜냐면 중복되는 점들이 있기 때문이라는데... 그래서 각 면을 2개의 삼각형으로 그려내라는 명령을 따로 따로 각각의 면에 대해서 (총 6개 면) 삼각형을 두개씩 그리라는 명령을 다음과 같이 주면 효율적이다.

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);

이 코드를 삽입해서 실행해 보면 다음과 같은 결과를 얻을수 있다.

Image 7.4 - A Rotating Cube

Image 7.4 - A Rotating Cube

3. More Shapes
A Square Pyramid
Image 7.5 - A Square Pyramid

Image 7.5 - A Square Pyramid


// create the vertices using the CUSTOMVERTEX
struct CUSTOMVERTEX t_vert[] =
{
    // base
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // triangle 1
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 2
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 3
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 4
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
};

// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(16*sizeof(CUSTOMVERTEX),
                           0,
                           CUSTOMFVF,
                           D3DPOOL_MANAGED,
                           &t_buffer,
                           NULL);

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // base
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // triangle 1
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // triangle 2
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 1);    // triangle 3
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 13, 1);    // triangle 4

The Hypercraft

Image 7.6 - The Hypercraft

Image 7.6 - The Hypercraft

// create the vertices using the CUSTOMVERTEX
struct CUSTOMVERTEX t_vert[] =
{
    // fuselage
    { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // left gun
    { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // right gun
    { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // bottom of fuselage
    { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
    { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // opposite side of left gun
    { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // opposite side of right gun
    { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
};

// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(20*sizeof(CUSTOMVERTEX),
                                     0,
                                     CUSTOMFVF,
                                     D3DPOOL_MANAGED,
                                     &t_buffer,
                                     NULL);

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // fuselage
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // left gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // right gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 2);    // bottom of fuselage
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 14, 1);    // other side of left gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 17, 1);    // other side of right gun

4. The Finished Program
완성된 코드를 보자... 간단하다.

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the Hypercraft

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,   // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection

    // set the world transform
    static float index = 0.0f; index+=0.03f; // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform

    // select the vertex buffer to display
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // draw the Hypercraft
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // fuselage
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // left gun
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // right gun
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 2);    // bottom of fuselage
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 14, 1);    // other side of left gun
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 17, 1);    // other side of right gun

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // create the vertices using the CUSTOMVERTEX
    struct CUSTOMVERTEX t_vert[] =
    {
        // fuselage
        { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },

        // left gun
        { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

        // right gun
        { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

        // bottom of fuselage
        { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
        { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },

        // opposite side of left gun
        { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },

        // opposite side of right gun
        { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(20*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}
posted by 대갈장군
2008. 2. 12. 03:34 프로그래밍/DirectX
1. Understanding the Problem At Hand
앞의 예제에서 한가지 빠진 부분이 바로 깊이다. 아래 두개의 그림처럼 원래 물리학적 원리를 따른다면 6-2와 같이 작은 삼각형이 가려져야 하지만 현재의 코드에서는 그렇지 않다는 것이 문제다.

Image 6.1 - Defying the Laws of Physics

Image 6.1 - Defying the Laws of Physics

Image 6.2 - Obeying the Law

Image 6.2 - Obeying the Law



왜 이런 현상이 나타나는가에 대한 해설이 나와 있는데 제법 좋은 설명인것 같다. 한번 정독 해보자.

모델이 렌더링 될때 몇가지 일들이 일어나는데 우선 Direct3D는 우리가 만들어 놓은 pipeline을 호출하게 된다. 그 pipeline은 이미 메모리 상에 압축되어 정리되어 있다. Direct3D는 각각의 모델에 대해서 pipeline을 적용하게 되는데 이때 3차원 이미지가 2차원 이미지로 저장된다.

한개의 모델에 대해서 2차원 이미지가 만들어지는 즉시 그 이미지는 back buffer에 저장된다. 첫번째 모델이 저렇게 저장되고 난 뒤에는 바로 다음 모델에 대해서 같은 동작을 하게 되는데 두번째 모델이 공간의 어떤 지점에 있든지 간에(첫번째 물체보다 앞에 있는 뒤에 있든) 그 위에 그려지게 되는데 바로 그 부분이 문제가 된다.

2. Z-Buffers
Image 6.3 - The Z-Buffer (Or Depth Buffer)

Image 6.3 - The Z-Buffer (Or Depth Buffer)

OpenGL에서도 있는 Depth buffer가 여기서도 역시나 존재하는데 바로 깊이 버퍼이고 이 깊이 버퍼는 각 픽셀에대한 깊이 정보를 가지는 것이다. 내가 생각하기에는 우선 Z-Buffer는 그려진 물체들 중에서 가장 가까운 물체의 깊이만을 저장할 것이다. 혹은 가장 가까운 물체에 대한 정보만 소유할 것이다. 그래서 어떤 물체가 가장 카메라에서 가까이 있는지를 금방 알수 있게 해준다.

Z-Buffer가 하는 일은 우선 카메라에서 가까운 픽셀을 가져온다음 그것을 back buffer에 그려준다. 동시에 그 위치에 해당하는 곳에다가 깊이 정보를 저장하는데 이렇게 함으로써 Direct3D가 각각의 픽셀이 얼마나 가까이 있는지를 알게 해줌으로써 물체가 그려져야 하는지 아니면 안그려져야 하는지를 알려준단다.

3. Including the Z-Buffer
중요한 3단계를 거쳐야 하는데,

1. 적절한 Presentation Parameters를 세팅한다.
2. Z-Buffering을 켠다
3. Z-Buffer를 지운다.


뭐, 여기서 2, 3번은 OpenGL에서도 했던거라서 알겠는데 1번은 뭐지?

3-1. Setting the Appropriate Presentation Parameters
D3DPRESENT_PARAMETERS d3dpp;

ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = FALSE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

위에서 보다시피 추가된 두줄의 코드가 있는데 첫번째 코드인 EnableAutoDepthStencil이 바로 Z-buffering의 종류중에 하나를 선택하는 것인데 가장 기본형을 선택했다. 하지만 물론 다른 여러가지의 옵션을 선택가능하다고 하는구나. (아마도 OpenGL의 GLSL과 유사한 기능이 있겠죠?)

두번째 줄에 있는 AutoDepthStencilFormat은 Z-Buffer의 각각의 픽셀의 format을 정의해 주는 것이다. 미리 정의된 pixel format을 사용하는 것이 아니라 여기서는 D3DFMT_D16을 사용하는데 이것은 깊이 버퍼 한 픽셀당 16비트를 소모한다는 의미.

3-2. Turning On Z-Buffering
아, 위에서는 걍 설정만 했을뿐 정말로 켜주는 것은 여기구나.

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

3-3. Clearing the Z-Buffer
이제는 메모리를 초기화 해줄 단계이죠. 알다시피 이전에 사용했던 명령중에 아래와 같이 back buffer를 지우던 명령이 있는데 바로 이 명령의 인자를 바꾸어서 Z-Buffer도 초기화 한다.

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

요기서 다음과 같이 요렇게 하자.

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);


4. The Finished Program
Image 6.5 - The Rotating Triangles

Image 6.5 - The Rotating Triangles

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    // wc.hbrBackground = (HBRUSH)COLOR_WINDOW;    // not needed any more
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

      ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;    // automatically run the z-buffer for us
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;    // 16-bit pixel format for the z-buffer

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the triangle

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

      // set the view transform
      D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
                       &D3DXVECTOR3 (0.0f, 0.0f, 15.0f),   // the camera position
                       &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
                       &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);     // set the projection


    // select the vertex buffer to display
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    D3DXMATRIX matTranslateA;    // a matrix to store the translation for triangle A
    D3DXMATRIX matTranslateB;    // a matrix to store the translation for triangle B
    D3DXMATRIX matRotateYA;    // a matrix to store the rotation for each triangle
    D3DXMATRIX matRotateYB;    // a matrix to store the rotation for the reverse sides
    static float index = 0.0f; index+=0.05f; // an ever-increasing float value

    // build MULTIPLE matrices to rotate and translate the model
    D3DXMatrixTranslation(&matTranslateA, 0.0f, 0.0f, 2.0f);
    D3DXMatrixTranslation(&matTranslateB, 0.0f, 0.0f, -2.0f);
    D3DXMatrixRotationY(&matRotateYA, index);    // the front side
    D3DXMatrixRotationY(&matRotateYB, index + 3.14159f);    // the reverse side

    // tell Direct3D about each world transform, and then draw another triangle
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateA * matRotateYA));
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateA * matRotateYB));
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateB * matRotateYA));
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateB * matRotateYB));
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
      t_buffer->Release();    // close and release the vertex buffer
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        { 2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}
posted by 대갈장군
2008. 2. 7. 03:37 프로그래밍/DirectX

이 내용은 www.directxtutorial.com 사이트의 내용을 번역한 하여 참조하였습니다.

앞서서 2차원 삼각형을 만들어 봤는데 한가지 특이한 점은 그것은 pre-transformed 였다는 것인데 그 의미는 그것을 3차원 공간 좌표로 전혀 바꾸지 않았다는 것을 의미한단다. 3차원 공간의 좌표를 주는 대신 우리는 D3D에게 screen 좌표를 바로 주었다는 점이랜다.

1. The Geometry Pipeline

Image 5.1 - The Geometry Pipeline

Image 5.1 - The Geometry Pipeline

자 openGL에서 봤던 바로 그 트랜스 포메이션이 그대로 나온다. 일반적으로 물체가 첫 등장 할때는 중심을 (0,0,0)에 두고 있다. 하지만 우리가 원하는것은 서로 다른 물체를 서로 다른 위치에 놓는 것이므로 물체를 만들면서 위치를 잡아 주게 된다. 이때가 바로 World Transformation이다. 아마도 초기 좌표 쯤으로 생각하면 될 듯하다.

자, 이제 우리가 원하는 공간의 점에 이런 저런 물체를 놓았다. 이때까지 좌표계는 절대적 좌표계로 아직 움직이지 않았다. 하지만 우리가 원하는 시점에서 물체를 봐야 하므로 이 좌표계를 원하는 방향으로 돌리고 움직여야 하는데 바로 이것이 View Transformation 이다. 카메라의 위치를 잡는 것이다.

자, 이제는 원하는 위치에 카메라도 놨고 사진만 찍으면 되는데 이때는 화면에 보여질수 있는 부분만 잘라서 뒤에 있는 물체는 가리고 앞에 있는 물체만 보이게 해서 2차원 영상을 만들면 된다. 바로 이것이  Projection Transformation이 되는 것이다.

이렇게 설명하니까 훨씬 이해하기가 쉽다. 물론 내가 OpenGL을 해봐서 그럴수도 있지만...

World Transformation

Image 5.2 - Model Translation

Image 5.2 - Model Translation

좀더 자세한 설명인데, WT (World Transformation)은 model space의 좌표를 world space 좌표로 바꾸는 것을 말한다. 뭐 간단히 말해서 절대적 좌표계에서 사용자가 원하는 지점으로의 이동이다.

Image 5.3 - Model Rotation

Image 5.3 - Model Rotation

Image 5.4 - Model Scaling

Image 5.4 - Model Scaling

그림 5.3 과 5.4는 각각 rotation과 scaling의 예를 보여주고 있다.

View Transformation
View transformation은 3D Coordinate system을 적절한 방향을 향해 셋업 시키는 것을 말한다. 흠, OpenGL에서는 이렇게 설명하지 않았는데 여기서의 설명은 더욱 이해하기 쉽다.

우선, 아래 그림처럼 우리가 절대 좌표계에 임의의 상자를 놓고 Virtual Camera를 어떤 공간에 놓고 그 물체를 바라보고 있다고 하자.

Image 5.5 - 3D Axes Unaligned With Camera

Image 5.5 - 3D Axes Unaligned With Camera

이제 카메라의 중심에 맞게 x와 z축을 움직여야 하는데 그 이유는 그렇게 해야 처리 속도가 개선되기 때문이라고 한다. 바로 아래 그림처럼 좌표 축을 옮기게 된다.

Image 5.6 - 3D Axes Aligned With Camera

Image 5.6 - 3D Axes Aligned With Camera

보다시피 카메라에 찍히게 되는 물체의 모습은 이전과 전혀 변함이 없지만 다만 바뀐것은 좌표 축과 바뀐 좌표축에 따른 새로운 물체의 좌표들이다.

Projection Transformation
사실 View transformation은 종종 카메라의 위치 잡기에 비유가 되는데 Projection Transformation은 종종 렌즈에 비유가 된다. 이제 남은 일은 카메라 셔터를 눌러 3차원 이미지를 2차원 이미지로 바꾸는 일인데 바로 이것이 Projection Transformation이다.

Image 5.7 - Projection Transformation

Image 5.7 - Projection Transformation

2. Untransformed Vertices
앞서 우리는 FVF를 정의 했는데 다음과 같이 정의 했다.

struct CUSTOMVERTEX {FLOAT X, Y, Z, RHW; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

이것이 바로 pre-transformed vertices인데 그 의미는 이것들은 3D pipeline을 통과하지 않는다는 의미다. (그랬던가?)

3D form을 가지는 vertice는 다음과 같이 정의 해야 된다.

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

차이 점이라고는 D3DFVF_XYZ와 RHW가 더 있나 없나 차이일 뿐인데? 하하, 이렇게 바꾸고 프로그램을 실행해보라 화면에는 아무것도 나타나지 않을 것이다. 왜? 왜냐면 pipeline을 전혀 통과시키지 않았기 때문에 점들이 결정되지 않았기 때문이다.

3. What is the Matrix?
알다시피 매트릭스다. C++에서 매트릭스 선언은 대충 다음과 같이 한다.

float TheMatrix [4][4] =
{
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

The Identity Matrix
뭐 설명할 게 없다. 바로 위의 매트릭스다. 초기화 상태의 매트릭스다. 항상 매트릭스를 사용하기 전에는 초기화 해줘야 한다. LoadIdentity(); 함수를 OpenGL에서 사용했던 바로 그 이유다.

The Direct3D Defined Matrix
C++에서 사용하는 2차원 배열은 상당히 불편한 점이 있으니, 이것을 개선하기 위해서는 D3D에서 정의하는 새로운 형태의 매트릭스를 사용하는 것이 편리하다.

typedef struct _D3DMATRIX {
    union {
        struct {
        float        _11, _12, _13, _14;
        float        _21, _22, _23, _24;
        float        _31, _32, _33, _34;
        float        _41, _42, _43, _44;
        };
        float m[4][4];
    }
} D3DMATRIX;

흠, 공용체를 사용한 구조체의 타입이라... 이것을 초기화 하는 함수가 있으니 바로 D3DXMatrixIdentity() 이다.

D3DXMATRIX* D3DXMatrixIdentity(D3DXMATRIX* pOut);

이 함수 인자로써 우리가 초기화하기 원하는 매트릭스 주소값을 주면 알아서 초기화 해준다.

4. Setting the World Transfromation
우선 순서를 보면 1. matrices를 생성한다. 2. D3D에게 그것을 사용한다고 알려준다. (어떤 순서인지도 함께) OpenGL에서는 생성이 아니라 선택이었는데.

4-1. Translation

D3DXMATRIX matTranslate;    // a matrix to store the translation information

// build a matrix to move the model 12 units along the x-axis and 4 units along the y-axis
// store it to matTranslate
D3DXMatrixTranslation(&matTranslate, 12.0f, 4.0f, 0.0f);

// tell Direct3D about our matrix
d3ddev->SetTransform(D3DTS_WORLD, &matTranslate);

위 코드는 간단한 translation 코드인데 SetStreamSource() 함수와 DrawPrimitive() 함수 사이에서 사용이 가능하다.

하나하나 살펴보면,

D3DMATRIX matTranslate; : 이 함수는 바로 위에서 봤던 그 D3D 매트릭스 타입이다. 이것이 4 x 4 매트릭스를 만들게 된다.

D3DMatrixTranslation(&matTranslate, 12.0f, 4.0f, 0.0f); : 이 함수는 매트릭스를 적당한 값으로 초기화 시키는 함수인데 우리가 이동시키기 원하는 좌표 점을 뒤에 넣어준것이 보인다.

d3ddev->SetTransform(D3DTS_WORLD, &matTranslate); : 이제 이것이 아마도 Transform 하라는 직접적인 명령 함수인것 같은데, 이렇게 명령을 줌으로써 D3D에게 바로 이 매트릭스를 모든 임의의 vertices 프로세싱에서 사용하라는 의미가 된다. 흠.. 그러면 각 물체별로 다른 matrix를 적용해야 할 경우에는 어떻게 한단 말인가? (그럴 필요가 없나?)

그럴 필요가 없다라고 생각하는 이유는 처음에 vertices 좌표를 잡고 시작하면 고정된 물체에 대해서 전체적인 이동만 하게 되므로 개별 적용이 필요없고 필요가 있다고 생각하는 이유는 내가 짠 OpenGL 코드처럼 물체에 따라 다른 matrix 적용이 필요한 경우가 있기 때문이다.

위 함수의 첫번째 인자를 보면 알수 있겠지만 바로 저것이 World Transformation 매트릭스를 선택한다는 의미다. OpenGL에서는 뭐였더라.. 헐헐헐.. 기억이.... -_-

4-2. Rotation

D3DXMATRIX matRotateX;    // a matrix to store the rotation information

// build a matrix to rotate the model 3.14 radians
D3DXMatrixRotationX(&matRotateX, 3.14f);

// tell Direct3D about our matrix
d3ddev->SetTransform(D3DTS_WORLD, &matRotateX);

뭐 거의 같은데 한가지 주의 할 건 rotation의 경우 축을 기준으로 회전하므로 3가지 회전 함수가 존재한다. 이경우에는 X 축에 대해서 회전하는 거다.

물론 각도로 회전 시킬수도 있다. 아래처럼,

D3DXMatrixRotationX(&matRotateX, D3DXToRadian(180.0f));

4-3. Scaling

D3DXMATRIX matScale;    // a matrix to store the scaling information

// build a matrix to double the size of the model
// store it to matScale
D3DXMatrixScaling(&matScale, 2.0f, 2.0f, 2.0f);

// tell Direct3D about our matrix
d3ddev->SetTransform(D3DTS_WORLD, &matScale);

각 축에 대해서 몇배로 확대 할 것인가를 설정해줌.

4-4. Combining the Translation, Rotation and Scaling matrices
이게 중요하다. 문제는 SetTransform()함수는 마지막에 변형된 매트릭스만 기억할뿐 앞 단계는 까먹어 버린다는 것이 문제점. 고래서 필요한 것이 Matrix Multiflication 이죠.

D3DXMATRIX matRotateX;    // a matrix to store the rotation information
D3DXMATRIX matScale;    // a matrix to store the scaling information

D3DXMatrixScaling(&matScale, 2.0f, 2.0f, 2.0f);    // double the size of the model
D3DXMatrixRotationX(&matRotateX, D3DXToRadian(90.0f));    // rotate the model 90 degrees

// set the world transform to the two matrices multiplied together
d3ddev->SetTransform(D3DTS_WORLD, &(matRotateX * matScale));

가장 단순한 예로 돌리고 확대하는 예를 보자. 위처럼 두개의 D3DXMATRIX 타입을 선언하고 각각의 매트릭스 타입을 각각의 변환에 맞게 정의한 다음 SetTranform의 두번째 인자로써 둘을 곱해서 넣어주는 것이다.

순서가 중요한데 설명에서는 1번이 90도 회전, 2번이 확대라고 되어 있다.

우선 다음 예를 보자.

D3DXMATRIX matRotateX;
D3DXMATRIX matRotateY;
D3DXMATRIX matRotateZ;
D3DXMATRIX matScale;
D3DXMATRIX matTranslate;

D3DXMatrixRotationX(&matRotateX, D3DXToRadian(50.0f));
D3DXMatrixRotationY(&matRotateY, D3DXToRadian(50.0f));
D3DXMatrixRotationZ(&matRotateZ, D3DXToRadian(50.0f));
D3DXMatrixScaling(&matScale, 5.0f, 1.0f, 1.0f);
D3DXMatrixTranslation(&matTranslate, 40.0f, 12.0f, 0.0f);

d3ddev->SetTransform(D3DTS_WORLD,
                     &(matRotateX * matRotateY * matRotateZ * matScale * matTranslate));

자 이 것은 x축으로 50도 돌리고 -> y 축으로 50도 돌리고 -> z축으로 50도 돌리고 -> x축으로 5배 확대하고 -> 이동시키기다.

자, 다음 그림을 보면 왜 순서가 중요한지 나온다.

Image 5.8 - Rotating Before Translating

Image 5.8 - Rotating Before Translating

Image 5.9 - Rotating After Translating

Image 5.9 - Rotating After Translating

5. Setting the View Transformation

D3DXMATRIX* D3DXMATRIXLookAtLH(D3DXMATRIX* pOut,
                               CONST D3DXVECTOR3* pEye,
                               CONST D3DXVECTOR3* pAt,
                               CONST D3DXVECTOR3* pUp);

gluLookAt 함수와 거의 유사하며 눈이 어디서 무엇을 보는지 그리고 윗 방향이 어딘지 설정하는 것 까지 똑같다.

우선 처음 보는 타입인 D3DXVECTOR3는 아래와 같다.

typedef struct D3DXVECTOR3
{
    FLOAT x;    // contains an x-axis coordinate
    FLOAT y;    // contayns a y-axis coordinate
    FLOAT z;    // containz a z-axis coordinate
} D3DXVECTOR3, *LPD3DXVECTOR3;    // go speling

 각각의 파라미터를 살펴보면,

D3DXMATRIX* pOut, : 이 인자는 우리가 채워 넣고자 하는 매트릭스 타입의 변수를 넣어주면 된다.

CONST D3DXVECTOR3* pEye,: 이 인자는 눈의 위치다.

CONST D3DXVECTOR3* pAt, : 이것은 어디를 보고 있는지를 위한 위치다.

CONST D3DXVECTOR3* pUp, : 이것은 어디가 위인지를 알려준다.
 

D3DXMATRIX matView;    // the view transform matrix

D3DXMatrixLookAtLH(&matView,
                   &D3DXVECTOR3 (100.0f, 100.0f, 100.0f),    // the camera position
                   &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
                   &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction

d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView
실제 코드를 보면 위와 같다.  한가지 더 유의할 것은 SetTranform 함수의 첫번째 인자가 VIEW로 바뀌었다는 점. ;)

6. Setting the Projection Tranformation
The Field of View(FoV)
Image 5.10 - Field of View: 45 Degrees

Image 5.10 - Field of View: 45 Degrees
 
Image 5.11 - Field of View: 22.5 Degrees

Image 5.11 - Field of View: 22.5 Degrees

뭐 굳이 설명 하지 않아도 안다... Fov...

View-Plane Clipping
Image 5.12 - The Viewing Frustum

Image 5.12 - The Viewing Frustum

뭐 이것도 사실 OpenGL에서 많이 했던 것인데, gluPerspective 함수로 했었다.

Setting Up the Projection Matrix
D3DXMATRIX* D3DXMatrixPerspectiveFovLH(D3DXMATRIX* pOut,
                                       FLOAT fovy,
                                       FLOAT Aspect,
                                       FLOAT zn,
                                       FLOAT zf);

헐, 이것도 OpenGL과 똑같은것 같은데? 첫번째 인자는 뭐 보나마나 채워넣을 매트릭스 변수 받는 것이고 fovy는 fov, aspect는 화면 비율, zn은 가까운쪽 깊이, zf는 먼쪽 깊이지 뭐.

D3DXMATRIX matProjection;    // the projection transform matrix

D3DXMatrixPerspectiveFovLH(&matProjection,
                           D3DXToRadian(45),    // the horizontal field of view
                           (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT,    // aspect ratio
                           1.0f,    // the near view-plane
                           100.0f);    // the far view-plane

d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection transform

7. Lighting
어라 이것도 여기서 언급하나? 일단 앞선 코드에서도 라이트를 켜주는 옵션이 있었나 모르겠는데, (없었는데?) 이게 없으면 뵈는게 없단다. 일단 다음 코드를 삽입하자.

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting

헐, 보다시피 light를 끄는 옵션인데? 결국 OpenGL처럼 라이트가 없으면 전부다 균질하게 보이는 옵션이 될것이다.

8. A Quick Review
자, 이제는 앞에서 배운걸 총정리 해보는 시간이다.

우선 헤더 부분에서 vertex format을 변경하였다. 이전에 사용했던 것은 pre-transformed vertices 였으나 이번부터 사용한 것은 not transformed 된 vertices들이었으며 다음과 같이 선언했다.

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

그리고나서 initD3D()함수에 다음과 같이 light를 끄는 명령을 넣는다.

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting

그런 다음 init_graphics() 함수에 보면 CUSTOMVERTEX 타입 array를 사용하여 다음과 같이 사용할 점들의 정보를 저장한다.

// create the vertices using the CUSTOMVERTEX struct
CUSTOMVERTEX t_vert[] =
{
    { 2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
};

이제 마지막으로 render() 함수를 보면,

// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // select which vertex format we are using
    d3ddev->SetFVF(CUSTOMFVF);

    // SET UP THE PIPELINE

    D3DXMATRIX matRotateY;    // a matrix to store the rotation information

    static float index = 0.0f; index+=0.05f;    // an ever-increasing float value

    // build a matrix to rotate the model based on the increasing float value
    D3DXMatrixRotationY(&matRotateY, index);


    // tell Direct3D about our matrix
    d3ddev->SetTransform(D3DTS_WORLD, &matRotateY);

    D3DXMATRIX matView;    // the view transform matrix

    D3DXMatrixLookAtLH(&matView,
                       &D3DXVECTOR3 (0.0f, 0.0f, 10.0f),    // the camera position
                       &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
                       &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction

    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    D3DXMATRIX matProjection;     // the projection transform matrix

    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane

    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    // select the vertex buffer to display
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // copy the vertex buffer to the back buffer
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->EndScene();

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

    return;
}

흠. 다 되었군. 생각보다 쉽다. OpenGL과 거의 유사하나 단 차이점이 있다면 Flexible Vertex Format이다. 매우 편리한 기능인 것 같다.

마지막으로 완성 코드와 실행 결과를 보자.

// include the basic windows header files and the Direct3D header file

#include <windows.h>

#include <windowsx.h>

#include <d3d9.h>

#include <d3dx9.h>

// define the screen resolution and keyboard macros

#define SCREEN_WIDTH 1680

#define SCREEN_HEIGHT 1050

#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)

#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library files

#pragma comment (lib, "d3d9.lib")

#pragma comment (lib, "d3dx9.lib")

// global declarations

LPDIRECT3D9 d3d; // the pointer to our Direct3D interface

LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class

LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL; // the pointer to the vertex buffer

// function prototypes

void initD3D(HWND hWnd); // sets up and initializes Direct3D

void render_frame(void); // renders a single frame

void cleanD3D(void); // closes Direct3D and releases memory

void init_graphics(void); // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR;};

#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

// the WindowProc function prototype

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program

int WINAPI WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

HWND hWnd;

WNDCLASSEX wc;

ZeroMemory(&wc, sizeof(WNDCLASSEX));

wc.cbSize = sizeof(WNDCLASSEX);

wc.style = CS_HREDRAW | CS_VREDRAW;

wc.lpfnWndProc = (WNDPROC)WindowProc;

wc.hInstance = hInstance;

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.lpszClassName = L"WindowClass";

RegisterClassEx(&wc);

hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",

WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,

NULL, NULL, hInstance, NULL);

ShowWindow(hWnd, nCmdShow);

// set up and initialize Direct3D

initD3D(hWnd);

// enter the main loop:

MSG msg;

while(TRUE)

{

DWORD starting_point = GetTickCount();

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

{

if (msg.message == WM_QUIT)

break;

TranslateMessage(&msg);

DispatchMessage(&msg);

}

render_frame();

// check the 'escape' key

if(KEY_DOWN(VK_ESCAPE))

PostMessage(hWnd, WM_DESTROY, 0, 0);

while ((GetTickCount() - starting_point) < 25);

}

// clean up DirectX and COM

cleanD3D();

return msg.wParam;

}


// this is the main message handler for the program

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

switch(message)

{

case WM_DESTROY:

{

PostQuitMessage(0);

return 0;

} break;

}

return DefWindowProc (hWnd, message, wParam, lParam);

}


// this function initializes and prepares Direct3D for use

void initD3D(HWND hWnd)

{

d3d = Direct3DCreate9(D3D_SDK_VERSION);

D3DPRESENT_PARAMETERS d3dpp;

ZeroMemory(&d3dpp, sizeof(d3dpp));

d3dpp.Windowed = FALSE;

d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

d3dpp.hDeviceWindow = hWnd;

d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;

d3dpp.BackBufferWidth = SCREEN_WIDTH;

d3dpp.BackBufferHeight = SCREEN_HEIGHT;

// create a device class using this information and the info from the d3dpp stuct

d3d->CreateDevice(D3DADAPTER_DEFAULT,

D3DDEVTYPE_HAL,

hWnd,

D3DCREATE_SOFTWARE_VERTEXPROCESSING,

&d3dpp,

&d3ddev);

init_graphics(); // call the function to initialize the triangle

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting

return;

}


// this is the function used to render a single frame

void render_frame(void)

{

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

d3ddev->BeginScene();

// select which vertex format we are using

d3ddev->SetFVF(CUSTOMFVF);

// SET UP THE PIPELINE

D3DXMATRIX matRotateY; // a matrix to store the rotation information

static float index = 0.0f; index+=0.05f; // an ever-increasing float value

// build a matrix to rotate the model based on the increasing float value

D3DXMatrixRotationY(&matRotateY, index);

// tell Direct3D about our matrix

d3ddev->SetTransform(D3DTS_WORLD, &matRotateY);

D3DXMATRIX matView; // the view transform matrix

D3DXMatrixLookAtLH(&matView,

&D3DXVECTOR3 (0.0f, 0.0f, 10.0f), // the camera position

&D3DXVECTOR3 (0.0f, 0.0f, 0.0f), // the look-at position

&D3DXVECTOR3 (0.0f, 1.0f, 0.0f)); // the up direction

d3ddev->SetTransform(D3DTS_VIEW, &matView); // set the view transform to matView

D3DXMATRIX matProjection; // the projection transform matrix

D3DXMatrixPerspectiveFovLH(&matProjection,

D3DXToRadian(45), // the horizontal field of view

(FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio

1.0f, // the near view-plane

100.0f); // the far view-plane

d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection

// select the vertex buffer to display

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

// copy the vertex buffer to the back buffer

d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

d3ddev->EndScene();

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

return;

}


// this is the function that cleans up Direct3D and COM

void cleanD3D(void)

{

t_buffer->Release(); // close and release the vertex buffer

d3ddev->Release(); // close and release the 3D device

d3d->Release(); // close and release Direct3D

return;

}


// this is the function that puts the 3D models into video RAM

void init_graphics(void)

{

// create the vertices using the CUSTOMVERTEX struct

CUSTOMVERTEX t_vert[] =

{

{ 3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },

{ 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

{ -3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },

};

// create a vertex buffer interface called t_buffer

d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),

0,

CUSTOMFVF,

D3DPOOL_MANAGED,

&t_buffer,

NULL);

VOID* pVoid; // a void pointer

// lock t_buffer and load the vertices into it

t_buffer->Lock(0, 0, (void**)&pVoid, 0);

memcpy(pVoid, t_vert, sizeof(t_vert));

t_buffer->Unlock();

return;

}

Image 5.13 - The Rotating Triangle

Image 5.13 - The Rotating Triangle
posted by 대갈장군
2008. 2. 6. 06:58 프로그래밍/DirectX
이 내용은 www.directxtutorial.com 사이트의 내용을 번역한 하여 참조하였습니다.

1. Flexible Vertex Formats

Lesson 3에서 배운대로 vertex는 위치 정보 + 속성 정보인데 속성 정보도 결국은 숫자로 나타내는 어떤 값일 것이다.

이러한 정보 표현과 저장을 위해서 Direct3D는 Flexible Vertex Format (FVF)를 사용한단다. 바로 이런 기법을 이용하여 정보를 저장하고 읽어 오는데 어떻게 동작하는지 살펴보자.

vertex는 구조체다. 이미지를 디스플레이 하기 위해서는 이 정보를 Video Ram으로 복사해오고 D3D는 이 정보를 Back buffer에 복사 해서 넣게 된다.

하지만 만약 사용자가 vertex가 될만한 (사실상 모든 vertex를 말하는거 겠죠) 모든 정보 (vertices)를 보낸다면 아래 그림처럼 Video RAM에서 많은 공간을 복사하여 사용할 것이다.

Image 4.1 - A Vertex Format Containing All Possible Data

Image 4.1 - A Vertex Format Containing All Possible Data

하지만, 이렇게 하면 vertex가 무지 많은 3D 이미지의 경우 얼마나 많은 복사와 정보의 공간이 필요할지 예측이 된다. 따라서 좀더 현명하게 저 vertex의 구조체에서 필요한 부분만 잘라와서 Video RAM에 복사해 넣는 것이 나을것이다. (아래 그림처럼)

Image 4.2 - A Flexible Vertex Format Goes Faster

Image 4.2 - A Flexible Vertex Format Goes Faster

근데, 상식적으로 한개의 vertex 구조체에는 좌표 정보 뿐 아니라 각종 속성도 들어가 있으니 당연히 아래 그림처럼 하지 않을까 하는게 내 예측이긴 한데...

아무튼, 바로 이것이 우리가 Flexible Vertex Format을 사용할때 생기는 동작의 원리란다.

FVF Codes
Direct3D에서는 각각의 vertex는 pre-set vertex format으로 부터 만들어지게 된다는데 이것은 혹시 추상 클래스의 개념이 아닐까? 이것은 흔희 API 에서 사용하는 방식대로 OR연산자 | 를 이용하여 플래깅을 표시하여 내가 원하는 형태의 vertex format을 미리 선언해 주는 것 같다.

다음과 같이 선언을 하게 되면 location 정보와 diffuse 색상을 한개의 vertex format에 포함시키겠다는 의미다.

#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

다음 테이블이 바로 플래그 테이블인데,
Flag Description Types Included
D3DFVF_XYZ Indicates that the vertex format includes the X, Y and Z coordinates of an untransformed vertex.  Untransformed means that the vertex has not yet been translated into screen coordinates.  float, float, float
D3DFVF_XYZRHW Indicates that the vertex format includes the X, Y and Z coordinates as well as an additional RHW value of a transformed vertex.  This means that the vertex is already in screen coordinates.  The Z and the RHW are used when building software engines, which we will not get into. float, float, float, float
D3DFVF_DIFFUSE Indicates that the vertex format contains a 32-bit color code for a vertex, used for the color of diffuse lighting. DWORD
D3DFVF_SPECULAR Indicates that the vertex format contains a 32-bit color code for a vertex, used for the color of specular highlighting. DWORD
D3DFVF_TEX0
   through
D3DFVF_TEX8
Indicates that the vertex format contains the coordinates for any textures that will be applied to a model.

물론 더 많은 flag들이 있지만 튜토리얼에서는 이것들 위주로 다룬다.

Creating Vertices
자, 이제 vertices를 만들어내야 하는데 만들때 우리가 선언한 새로운 포멧 (FVF)를 사용해야 한단다. 말이 조금 애매한데 밑에 있는 예를 보면 조금 이해가 된다. 새로운 vertices를 선언하기 위해서 추가로 특별한 함수를 쓰는게 전혀 없고 단지 구조체를 선언하는 것 뿐이다.

위에서 CUSTOMFVF를 선언할때 두가지 타입을 썼는데 위의 테이블을 보면 타입이 나와있다. 바로 float과 DWORD이다. 고로, 이 형식에 맞추어 구조체를 작성하는데,

struct CUSTOMVERTEX
{
    FLOAT x, y, z, rhw;    // from the D3DFVF_XYZRHW flag
    DWORD color;    // from the D3DFVF_DIFFUSE flag
}

이렇게 해주면된단다. 상당히 충격적이다. -_-

이렇게 구조체를 선언했으니 하나의 타입이고 이 타입 형을 이용하여 드디어 점을 선언해보자.

CUSTOMVERTEX OurVertex = {320.0f, 50.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255)};

바로 이렇게 한단다. 물론 array를 선언하고 싶다면 다음과 같이,

CUSTOMVERTEX OurVertices[] =
{
    {320.0f, 50.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255),},
    {520.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 255, 0),},
    {120.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0),},
};

해줘도 된다. OpenGL에 비해서 상당히 놀라운 한가지의 차이점이 있는데 바로 'Flexible'이라는 단어로 요약가능 한것 같다. OpenGL에서는 사용자가 사용하기 원하는 정보만 사용할수 있는 기능이 없다. 하지만 DX에서는 사용자가 원하는 정보만 Video RAM으로 전달할수 있는 말그대로 Flexible한 정보 저장이 가능하다. 바로 이것이 속도의 차이에 한 가지 원인인가?

2. Vertex Buffers
자, 앞서 우리는 두단계를 했다. 1. FVF 코드 작성 (원하는 정보 지정) 2. 삼각형 형태의 점을 지정. 그렇다면 이제 필요한 것은 무엇인가? 바로, D3D가 이 삼각형을 그려내도록 하게 하는 것인데 이때 필요한 것이 Vertex Buffer이다.

Vertex Buffer는 단순한 인터페이스 인데 이것은 시스템 혹은 비디오 메모리의 한 부분에 내가 사용중인 게임에서의 데이터 (vertex/ model data)를 저장한다. 이 인터페이스는 CreateVertexBuffer()라는 명령을 이용해서 생성 가능하다. 이 함수의 원형을 자세히 살펴보면,

HRESULT CreateVertexBuffer(
    UINT Length,
    DWORD Usage,
    DWORD FVF,
    D3DPOOL Pool,
    LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer,
    HANDLE* pSharedHandle);

이함수의 인자들은 쉽게 설명하기 어려운데 하나하나 살펴보면,

UINT Length : 이 인자는 만들어질 버퍼의 크기를 지정한다. 이 값은 한개 vertex의 사이즈에 버퍼에 저장될 vertex의 총 갯수를 곱해서 구할수 있다. 만약, 삼각형의 경우 3개의 vertex가 필요하니까 이 인자는  3 * sizeof(CUSTOMVERTEX) 가 되는 셈이다.

DWORD Usage : 이것은 vertices를 handle하는 DX의 특별한 방식을 지정하는 것인데 현재의 튜토리얼에서는 사용하지 않는다. 어쨌든 지금은 0으로 셋 해놓자.

DWORD FVF : 이것은 우리가 이전에 만든 FVF 코드를 넣는 인자다. 고로 CUSTOMFVF를 넣으면 되겠네.

D3DPOOL Pool : 이 인자는 D3D에게 어디다 버퍼를 만들지 그리고 어떻게 만들것인지를 지정하게 된다. 다음 이어지는 테이블이 올수 있는 플래그를 설명하는데 함 보자. 오... 몇가지 플래그 들이 있는데 상당히 의미 있는 플래그 들이다.

[Table 4.2 - D3DPOOL Values]

LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer : 오 열라 긴 이름이다. 우리가 만드는 vertex buffer interface에 대한 포인터란다. 이 곳에 빈 포인터 변수를 넣어주면 이 함수가 알아서 리턴시에 그 포인터 주소의 값에다가 값을 대입하나 부다.

HANDLE* pSharedHandle : 이건 걍 NULL이란다... 헐... Mic Soft가 그렇게 쓰라고 했단다. 나중에 사용할 인자인가 보다.

자 이제 이것을 선언하는 방법을 보자.

LPDIRECT3DVERTEXBUFFER9 t_buffer;

d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                           0,
                           CUSTOMFVF,
                           D3DPOOL_MANAGED,
                           &t_buffer,
                           NULL);

포인터 변수를 선언해주고 인자로 주는 것외에는 위에서 본 그대로 이네. 자 이제 vertex buffer를 맹글었으니 이제 여기다 vertex들을 넣어줘야 하지 않겠나?

이것은 간단하게 memcpy()를 이용한다는데 상당히 충격적이다. -_-; 왜 memcpy()인가?

너무 단순한 함수를 쓰는것이 맘에 걸리기는 하지만, 어쨌든 그렇단다. 하지만 중요한 것이있으니 바로 buffer에 접근 시에는 먼저 locking을 해야 한다는 사실.

우선 locking을 해야 하는 이유로, 다중 접속하는 프로세스일 경우 당연히 consistence 문제가 생긴다는 것과 Viedo RAM에게 그 영역은 터치 하지 말라고 경고하는 목적도 있다.

잠그기 위해서는 Lock 함수를 사용하는데 다음과 같다.

HRESULT Lock(UINT OffsetToLock,
             UINT SizeToLock,
             VOID** ppbData,
             DWORD Flags);

UINT OffsetToLock, UINT SizeToLock : 만약 특별한 부분만 locking 하고 싶다면 이 것을 셋팅 해주면 된다. 말그대로 offset과 얼마나 locking 할지 사이즈를 알려주는 인자.

VOID** ppbData : 이 인자는 C++를 잘모르면 상당히 겁나는 것 처럼 보이지만 사실 이중 포인터 게다가 void 형을 쓰는 이유는 두번을 거치면 다양한 작업이 가능하기 때문이고 void를 주는 이유는 우리가 사용하고자 하는 타입이 하나로 고정된 것이 아닌 구조체이기 때문일 것이다. (FVF 니까)

DWORD Flags : 이것은 고급 플래그 설정 인자인데 튜토리얼에서는 다루지 않는다. They basically provide special ways to handle the locked memory.  If you are truly interested, they can be researched in the DirectX documentation.  For now, we will just set it to 0.

자 이제 어떻게 쓰는지 함 보자.

VOID* pVoid;    // the void* we were talking about

t_buffer->Lock(0, 0, (void**)&pVoid, 0);    // locks t_buffer, the buffer we made earlier

우선 void * 형 변수 선언후 lock 함수를 호출하는데 이 다음은 memcpy를 이용해 vertex들을 직접 복사하는 과정이다.

memcpy(pVoid, OurVertices, sizeof(OurVertices));    // copy vertices to the vertex buffer

그 다음은 해제다. 해제는 쉽다. 걍 아래와 같이 해주면 끝~

t_buffer->Unlock();    // unlock t_buffer

 자 이모든 것들을 하나로 묶에서 함수에 담으니 바로 init_graphic()라는 함수다.

void init_graphics(void)
{
    // create three vertices using the CUSTOMVERTEX struct built earlier
    CUSTOMVERTEX t_vert[] =
    {
        { 320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 520.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 120.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
    };

    // create the vertex and store the pointer into t_buffer, which is created globally
    d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // the void pointer

    t_buffer->Lock(0, 0, (void**)&pVoid, 0);    // lock the vertex buffer
    memcpy(pVoid, t_vert, sizeof(t_vert));    // copy the vertices to the locked buffer
    t_buffer->Unlock();    // unlock the vertex buffer

    return;
}

헐헐 모르고 보면 상당히 어려워 보이지만 알고 보면 참 쉽다.

3. Drawing the Primitive
화면에 출력하기에 앞서 몇가지 간단한 것을 해줘야 하는데, 살펴보면

SetFVF()
이 함수는 D3D에게 어떤 FVF 코드를 현재 사용할것인지를 알려 준다. 사용자는 물론 여러가지 형태의 vertex를 선택적으로 사용할 수 있으니 반드시 그리기 전에 무엇을 사용하는지 알려줘야 하겠다.

d3ddev->SetFVF(CUSTOMFVF);

SetStreamSource()
그 다음 함수가 바로 요놈인데 D3D에게 어떤 vertex buffer를 그리는데 사용할지 알려 주는 함수다.

HRESULT SetStreamSource(UINT StreamNumber,
                        LPDIRECT3DVERTEXBUFFER9 pStreamData,
                        UINT OffsetInBytes,
                        UINT Stride);

첫번째 인자는 스트림 소스의 번호인데 이것은 차차 공부할 것이고 지금은 일단 0으로 지정한다. 왜냐면 한개의 vertex buffer 만을 사용하기 때문이란다.

두번째는 vertex buffer로의 포인터이고, 세번째는 시작할 바이트 오프셋인데 당연히 우리는 0을 쓸것이고 네번째는 각각의 vertex의 크기이니 당연히 sizeof(CUSTOMVETEX)가 되겠따.

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

DrawPrimitive()
고 다음이 이놈인가본데, 지금까지 우리는 D3D에게 어떤 종류의 vertices를 우리가 사용하고 있으며 어디서 그것들을 얻을수 있는지 말해주었다. 이제 필요한 것은 그리라고 명령하는 것이다.  요 함수가 바로 선택된 primitives를 화면에 그리라는 명령이다.

HRESULT DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType,
                      UINT StartVertex,
                      UINT PrimitiveCount);

첫번째 인자가 어떤 타입의 primitive를 사용하는지 알려주는 플래그 이다.

[Table 4.3 - D3DPRIMITIVETYPE Values]

Value Description
D3DPT_POINTLIST Shows a series of points.
D3DPT_LINELIST Shows a series of separated lines.
D3DPT_LINESTRIP Shows a series of connected lines.
D3DPT_TRIANGLELIST Shows a series of separated triangles.
D3DPT_TRIANGLESTRIP Shows a series of connected triangles.
D3DPT_TRIANGLEFAN Shows a series of triangles with one shared corner.
[Close Table]

이것은 Lesson 3에서 배운것들이네요. :)

두번째 인자는 몇번째 vertex부터 화면에 그릴까 인데 사용자가 원하면 중간에서 부터 뽑아내서 그릴수 있다. 하지만 우리는 0에서부터 시작해서 다 그릴거라는것.

세번째 인자는 우리가 그리고자 하는 vertex 숫자이다. 포인트를 이용할 경우 3개 라인을 이용할 경우도 3개인데 우리가 삼각형 형태를 사용하면 1이된다.

자 이제 이것들을 다 모아서 render_frame() 함수에 다 넣어보자.

// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

        // select which vertex format we are using
        d3ddev->SetFVF(CUSTOMFVF);

        // select the vertex buffer to display
        d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

        // copy the vertex buffer to the back buffer
        d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->EndScene();

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

    return;
}

자 모르는 놈 몇개가 있기는 한데 일단 다른 중요한 것들부터 보자.

4. Releasing Vertex Buffers
바로 해제다. 프로그램 종료 전에 반드시 해제해야 한다.

// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}

5. Finished Program
자, 전체 코드를 보면 되는데, 실행해보니 잘된다. :)

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH
640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z, RHW; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    // wc.hbrBackground = (HBRUSH)COLOR_WINDOW;    // not needed any more
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,    // fullscreen values
                          0, 0,    // the starting x and y positions should be 0
                          SCREEN_WIDTH, SCREEN_HEIGHT,    // set the window to 640 x 480
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the triangle

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

        // select which vertex format we are using
        d3ddev->SetFVF(CUSTOMFVF);

        // select the vertex buffer to display

        d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

        // copy the vertex buffer to the back buffer
        d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        { 320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 520.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 120.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}

결과화면은 다음과 같다.
Image 4.3 - The Drawn Triangle

Image 4.3 - The Drawn Triangle

posted by 대갈장군
prev 1 2 3 4 next