Game Programming/Rendering
DX 9 :: Direct3D 시작VallistA2016. 2. 17. 17:19
DX 관련 글
기본 윈도우 생성 : 클릭
윈도우 생성 : 클릭
리얼타임 메시지 루프 : 클릭
Direct3D 시작 : 클릭
COM
COM은 무엇을 의미를 하는가?
갑작스래 Direct3D를 시작한다고 해놓고 COM을 언급하냐하면 DX를 하기 전 필수적으로 알아둬야 하는 것이기 때문이다.
COM은 Component Object Model 을 의미하며, 고급 객체를 생성하는 방법이라고 보면되겠다.
COM 객체는 실제로 C++ 클래스 또는 함수를 호출하고 원하는 목적을 달성 할 수 있는 클래스의 그룹이다.
작동하는데 다른 클래스의 조건이 필요없으며 같은 일을 동작하기위해 도움을 줄 필요가 없다.
하지만, COM 객체와 연결하거나 프로그램의 나머지 부분을 변경하지 않고 원하는데로 분리가 가능하다는게 매우 큰 장점이다.
예를 들자면 게임의 패키지를 판매를 했는데 전국적으로 1000개가 팔렸다고 하면, 업데이트 시에는 네트워크로 받아야 하는게 맞을 것이다.
근데 이 업데이트를 효율적으로 주기 위해서는 무엇을 해야할 지 생각을 해봤는가? 안에 파일들을 바꿔주면 될 것인데, 다른 클래스에는 영향이 없어야 할 것이다. 바로 이럴때 COM 객체를 쓰게된다.
하지만 이런 COM 객체에 대해서 깊게 알 필요는 없다. 왜냐하면 이러한 것은 천천히 쓰면서 익히면 되는 법이다.
시간을 가지며 공부를 하면 천천히 알게 될 것이다.
그래서 왜 COM을 언급을 했는가?
Direct3D는 DirectX의 COM 객체의 시리즈인데, Direct3D를 사용하면 소프트웨어, 하드웨어, 또는 어떤 웨어든 2D 및 3D 그래픽을 실행하는데 필요한 모든 것이 포함되어 있는 고급 클래스이다.
이러한 과정을 Direct3D 내의 함수와 함께 할 텐데, Direct3D 함수를 호출할 때 별거 없을 것이다. 그저 함수만 호출하면 알아서 해주기 때문이다.
Direct3D의 인터페이스 클래스에서 함수 CreateDevice 및 Release를 사용하기 위해서 간접 멤버 엑세스 연산자(indirect member access operator)를 사용한다.
스왑 체인 및 페이지 스와핑
그래픽 어댑터는 메모리에 있는 스크린 상에 출력되는 이미지를 포함하는 픽셀 버퍼에 대한 포인터를 포함한다.
이러한 3D 모델이나 텍스처와 같은 그래픽 어댑터 갱신이 렌더링 할 것을 배열로 필요하다고 보내면 모니터에 해당 정보들을 출력하게 된다.
그때 모니터는 렌더링에 새로운 부분을 추가하여 위에서 아래로 화면을 다시 그리게 된다.
하지만, 이 과정에 문제가 있는데, 실시간 렌더링을 위해 필요에 따라 화면을 깨끗히 하는 작업을 거치는데 생각만큼 이게 빠르지 않기 때문이다.
모니터의 재생 빈도는 60hz 부터 144hz 등 수많은 재생 빈도가 존재한다. 모니터 리프레쉬 동안 그래픽 어뎁터에 렌더링을 하는 경우 화면이 둘로 절단이 될 것이다. 위에는 최신의 이미지가 나오지만 화면의 하단에는 구형의 이미지가 존재를 하여 찢어지는 현상이 나올 것이다.
물론 그 상태로 게임은 고사하고 아무것도 하지 못할 것이다.
이 문제를 해결하기 위해서 DirectX는 Swapping 이라는 기술을 제공한다.
Swapping
모니터에 직접 새로운 이미지를 렌더링하는 것 대신, Direct3D는 백 버퍼라는 보조 버퍼 공간에 이미지를 그린다.
그리고 전면 버퍼라는 곳에 현재 화면이 보이는 곳에는 플레이어가 보이는 이미지를 그린다.
백 버퍼에서는 출력되는 동안 최신 이미지를 로딩을 하여 완료가 되면 백 버퍼 이미지를 전면 버퍼에 덮어 씌워버리고 전면 버퍼의 이미지 내용은 초기화 한다.
모니터가 새로 고침되는 동안 이미지 전송이 계속 발생할 수 있기 때문에 일을 하는 것은 여전히 무리가 될 수 있다.
이 과정을 방지하기 위해 DirectX는 단순히 그 값을 전환할 때 각 버퍼에 대한 포인터를 사용한다.
백 버퍼는 전면 버퍼의 포인터에 자신의 포인터로 바꿔버리기 때문에 (Move Semantics) 위에서 부터 아래로 그리는 행위가 없어져 한번에 출력이 된다.
이것은 더블 버퍼링이고, 버퍼를 추가해서 더 좋은 성능을 만들 수 있다.
어떻게 백 버퍼를 추가해서 더 나은 성능을 얻을 수 있냐?
가끔씩 백 버퍼 렌더링과 교환 할 준비가 되어 있지만, 화면이 아직 프론트 버퍼의 내용을 그리기 완료되지 않았을 때가 있다.
그때 교체하면 화면 찢어짐에 원인이 된다. 그래서 프로그램은 중지되어 화면이 완료될 때 까지 기다리게 된다.
이 귀중한 시간을 여러 버퍼를 사용하여 해결 할 수 있다.
새로운 프레임을 렌더링시마다 교환 한다.
기본적인 Direct 3D 프로그램
Direct 3D를 기본적으로 설정할 때는 Hello World를 만들진 않는다. Direct3D은 자체 언어가 아니기 때문에 Hello World가 아닌 새로운 창을 작성한다.
과정은 아래와 같다.
1. 전역 변수와 함수 프로토 타입 제작
2. Direct3D를 초기화 하는 기능을 작성하고 Direct3D의 장치를 만들기
3. 프레임을 렌더링 하는 기능 만들기
4. Direct3D를 닫는 함수를 만들기
전역 변수와 함수 프로토 타입 제작
처음에 Direct3D 작업을 시작하기 전에 프로그램 상단에 몇 가지를 추가해야 한다. 그것은 다음과 같다.
#include <d3d9.h>
d3d9.h 안에 있는 d3d의 함수들을 사용하게 해주는 헤더이다.
#pragma comment (lib, "d3d9.lib")
Direct3D 9의 라이브러리 파일들이 포함되어 있는 lib 파일이다.
#pragma comment는 속성에서 lib 를 포함하지 않아도 쓸 수 있게 해준다.
첫번째 인자는 등록할 확장명, 두번째 인자는 파일 이름.
LPDIRECT3D9 d3d;
이 변수는 Direct3D의 포인터다.
의미하는 바는 Direct3D9 라는 클래스를 만드는 일을 하는데, 위에서 언급한 COM 클래스라고 보면된다.
d3d 를 간접멤버 엑세스 연산자를 사용해서 Direct3D 관련함수를 호출할 수 있다.
이 d3d를 이용해서 생성되는 것을 만들어 볼 것이다.
LPDIRECT3DDEVICE9 d3ddev;
Direct3D의 디바이스 인터페이스, 그래픽 드라이버, 비디오 카드에 대한 정보를 가지고 있다.
그 외적으로 그래픽 하드웨어 측면으로 사용할 때 이 클래스에 대한 포인터를 사용한다.
Direct3D 초기화 함수 작성 및 Direct3D Device 생성
Direct3D를 코딩하는 처음 단계는 인터페이스를 생성하고 그래픽 디바이스를 초기화 하는 것이다.
이는 두 가지 기능과 구조체에 들어있는 그래픽 디바이스 정보를 이용하여 수행한다.
코멘트를 보면서 이해해도 된다.
하지만 밑에서 좀더 상세히 다룰 것이다.
d3d = Direct3DCreate9(D3D_SDK_VERSION)
이 구문은 초기화를 담당한다.
주요 목적은 Direct3D 인터페이스를 만드는 역할을 담당한다.
반환 값은 생성된 인터페이스의 주소값이다. 그래서 이전에 D3D를 만든 변수에 주소를 저장한다.
매개 변수 D3D_SDK_VERSION이 하는일은 컴퓨터 간의 호환성 문제로 인해서 사용하게 된다.
Direct X의 최신 버전이 올라가면, 일반적으로 DirectX를 업그레이드 하게 된다. 차후에 개발한 게임의 Direct X 개발 버전을 알려주게된다.
다이렉트 3D 9C 버전에서는, 이 값은 32를 리턴한다.
이전 버전의 DirectX는 다른 방식으로 작동하는 원인중 하나인데, 왜냐하면 서로 다른 값을 반환하기 때문이다.
그렇기 때문에 그냥 D3D_SDK_VERSION을 사용하면 되겠다. 이렇게 두면 모든 버전에서 작동하게 된다.
D3DPRESENT_PARAMETERS d3dpp
게임에서 그래픽 장치에서 쓰이는 정보를 필요로 하기 때문에 시작할 때 게임 프로그래밍 정보를 받아야 한다.
D3DPRESENT_PARAMETERS는 그래픽 장치에 대한 정보를 포함하는 구조체이며, 이 값을 이용해 그래픽 카드의 정보를 받을 것이다.
ZeroMemory(&d3dpp, sizeof(d3dpp))
d3dpp를 사용하기에 앞서 메모리를 초기화 시키는 역할을 담당한다.
d3dpp.Windowed = TRUE
Direct3D를 실행하여 창으로 띄울 경우 TRUE로 설정하고, 전체화면으로 띄울경우 TRUE로 설정한다.
전체 화면을 하고자 이 변수만 변경을 하려 하면 전체화면으로 변경되지 않는다. 그 것에 대해서는 나중에 서술하도록 한다.
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD
Direct3D의 화면에 이미지를 변경하기위해 사용되는 방법, 설명했던 스왑체인이다.
스왑 체인의 유형이 3가지가 있는데 보도록하자. 아래와 같다.
인자 값 |
설명 |
D3DSWAPEFFECT_DISCARD |
최상의 속도를 얻기 위해 사용되며, 사용되었던 백 버퍼를 가지고 있지 않는다. |
D3DSWAPEFFECT_FLIP |
백 버퍼들을 저장하거나 완벽한 처리를 위해서 시간을 소모하기 때문에 상당히 느림. |
D3DSWAPEEFECT_COPY |
전면 버퍼에 후면 버퍼의 픽셀을 복사하여 서로 다른 두 개의 이미지의 픽셀의 포인터를 전환한다. 고급 기술이 들어가지 않는 이상 추천하지 않는다. |
이 중에 필자가 사용하는 것은 D3DSWAPPEFFECT_DISCARD 를 사용한다.
다른 두가지 방법을 사용하여 추가로 글을 쓸 때는 나중이 될 것 같다.
d3dpp.hDeviceWindow = hWnd
이 값은 Direct3D를 사용해야하는 창 핸들을 설정한다. 항상 사용했던 hWnd를 사용하면 된다.
d3d->CreateDevice()
이 함수의 안에 들어있는 내용은 매우 크고 중요하지만 사용할 때에는 굉장히 간단하다. 매개 변수의 대부분은 아마 쓰는 모든 게임에서 동일하게 유지될 것이다.
이 함수의 기능은 그래픽 장치 인터페이스를 생성하는 것으로 알면 되겠다.
이 함수를 사용후 이후의 클래스를 사용하면 필요한 모든 그래픽을 처리할 수 있게 생성이 될 것이다.
Direct3D에서 사용하는 기능의 대부분은 d3d에서 올 것이며, d3d의 내부 디바이스를 생성했기 때문에 d3ddev를 이용해 렌더링 처리를 하면 된다.
이제 함수 원형을 보도록 하자.
Parameter들을 하나씩 보도록하자.
UINT Adapter
그래픽 어댑터, 비디오 카드등의 값을 저장하는 부호없는 정수이다.
어떤 그래픽카드를 쓸 것인지 등에 대해서 알 수 있으며, 컨트롤 할 필요가 없다.
기본적으로 D3DADAPTER_DEFAULT 를 사용하게 된다.
D3DDEVTYPE DeviceType
이 매개변수에는 네 가지의 변수 입력 값이 있는데, 그 중에 우리는 한개만 사용한다고 생각하면 된다.
우리가 사용하는 것은 D3DDEVTYPE_HAL 이다.
D3DDEVTYPE_HAL은 하드웨어 추상화 계층에서 사용하는 Direct3D를 알려준다.
하드웨어 추상화 계층이라고 불리는 HAL은 Direct3D의 그래픽을 처리하기 위해 어떤 하드웨어를 사용해야 한다고 전해주는데 사용된다.
어떠한 이유로 그래픽 장치가 어떤 이유에 의해서 하드웨어를 사용할 수 없게 되는 경우에 다른 소프트웨어로 렌더링 되게 전달될 것이다.
이건 자동적으로 실행되지만 새 시대의 카드 (그래픽 카드 라던가) 의 기능들을 고려하여 수행되지 않게 될 수도 있다.
HWND hFocusWindow
윈도우의 핸들을 나타낸다. hWnd 넣으면 된다.
DWORD BehaviorFlags
이 매개변수에 사용할 수 있는 값들은 많지만 우리는 3개에 대해서 다룰 것이다.
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
D3DCREATE_MIXED_VERTEXPROCESSING,
D3DCREATE_HARDWARE_VERTEXPROCESSING
이 세개의 값은 이름 그대로 정말 정직한 행동을 하는데, 먼저 software의 경우에는 3D 차원의 계산을 소프트웨어에서 하는 것이고,
MIXED는 분할하여 처리, HARDWARE는 하드웨어에서 처리하는 것이다.
사용하는 것은 호환성을 위해서 SOFTWARE 값을 사용하지만 원하는 데로 실험을 해보길 권한다. 아무런 차이도 못 느낄수도 있는데, 나중에 폴리곤 몇백만개를 뿌려보면서 테스트 해보면 대충 답이 나올 것이다.
D3DPRESENT_PARAMETERS *pPresentationParameters
d3dpp 구조체의 포인터를 가져오는데 사용된다. &d3dpp 로 포인터값을 보낸다.
IDirect3DDevice9 **ppReturnedDeviceInterface
그래픽 디바이스 인터페이스에 대한 포인터에 대한 포인터인데 (그래서 2차원 포인터)
우리는 d3ddev 포인터를 정의 했는데 그것을 넣어줄 것이다. &d3ddev를 넣어주면 된다.
렌더 프레임 함수의 생성
이 함수에서 하나의 프레임을 렌더링 할 것인데, 출력을 하게 되면 파란색 화면이 나올 것이다.
파란색 색상이 싫다면 다른 색으로도 할 수 있다. 아래는 소스코드.
함수 내부에서는 이런 형식으로 제작이 되는데, 아래는 사용되었던 함수들을 보도록 하자.
d3ddev->Clear()
화면을 지정된 색으로 버퍼를 초기화 해주는 함수이다. 백 버퍼를 비워주는 역할을 한다 보면 된다.
첫번째와 두번째 매개변수는 사용되지 않으므로 0과 nullptr을 써주자.
세번째 매개변수는 백버퍼를 비워줘야 하므로 D3DCLEAR_TARGET으로 비워주는 역할을 해주도록 하고,
네번째 매개변수는 색을 지정해준다. 255가 1.0 값이며, 자신이 원하는 색으로 변경할 수 있다.
남은 두 개의 매개변수는 다음에 설명하도록 하겠다. 1.0f과 0이 의미하는 의미가 무엇인지 생각해보도록 하자.
d3ddev->BeginScene()
다음은 BeginScene()이란 함수인데, 이 함수는 Direct3D에서 렌더링을 시작하는 함수이다.
이 함수는 2개의 의미로 호출이 될 수 있는데, 첫번째는 Direct3D의 메모리를 컨트롤 할때 쓰이고
두번째의 경우에는 이 기능을 사용하면 메모리에 단독으로 액세스 권한을 부여하며, 비디오 RAM 버퍼를 잠금 또는 해지할 때 쓴다
d3ddev->EndScene()
BeginScene으로 액세스한 메모리등 다른 프로세스등에서 그것을 사용할 수 있도록 잠금을 해제 한 것을 다시 잠금한다.
비디오 RAM을 잠금 하는 것은 무조건 필요하다. CPU 프레임 한번당 BeginScene과 EndScene을 호출 해주어야 한다.
d3ddev->Present()
Present() 함수를 마지막으로 호출한다.
4개의 매개변수는 전부 nullptr로 되어있다. 아직은 사용하지 않으므로 나중에 사용되면 예제를 보여주도록 하겠다.
Direct3D를 종료하는 함수의 생성
함수는 아래와 같다.
전역으로 만들었던 두개의 인터페이스의 release를 호출한다.
release를 호출하게 되면 메모리를 해제하게 된다.
이 작업을 해주지 않을 경우 백그라운드에서 device는 계쏙 동작을 할 것이고 컴퓨터의 백그라운드에 자원이 남으므로 게임이 클수록 안좋을 것이다.
출력 해보기
소스코드
'Game Programming > Rendering' 카테고리의 다른 글
DX 9 :: 3차원의 대해서 알아보기 (0) | 2016.02.24 |
---|---|
DX 9 :: 전체화면 (0) | 2016.02.19 |
DX 9 :: 리얼타임 메시지 루프 (0) | 2016.02.16 |
DX 9 :: 윈도우 생성 (1) | 2016.02.16 |
DX 9 :: 기본 윈도우 생성 (0) | 2016.02.12 |
댓글
VallistA
병특이 끝나서 게임에서 웹으로 스위칭한 프로그래머.
프로그래밍 정보등을 공유합니다.
현재는 이 블로그를 운영하지 않습니다.
vallista.kr 로 와주시면 감사하겠습니다!
자고 싶습니다. ㅠㅠ
Github :: 링크
궁금한점 문의 주시면 답변드리도록 하겠습니다
VISITED
Today :
Total :
Lately Post
Lately Comment