Programming/DirectX

인덱스 버퍼(Index Buffer) / 버텍스 버퍼(Vertex Buffer)

사기꾼프로드 2013. 12. 12. 15:59

Dxd9를 사용하며 제가 용책이라 불리는 책을 공부한 내용입니다. 소스코드는 제일 밑에 있습니다.

쉐이더를 사용하지 않은 버전입니다. 쉐이더를 통한 버전은 나중에 다시 올리겠습니다.

버텍스 버퍼 =  점들의 위치를 저장한 것이라 보면 된다. ( 0, 0, 0 ) ( 1, 1, 0 ) 같은 버텍스를 가지고 있는 주머니인 것이다.

인덱스 버퍼 =  말 그대로 인덱스 데이터를 보관하는 주머니다.


인덱스를 사용하는 이유

 - 인덱스를 사용하지 않고 그리려면 중복되는 점도 또 저장해 줘야 한다.  하지만 인덱스를 사용하면 ( 1, 0. 2 ) 같은 버텍스를

저장하는 대신 몇번째 점 인지만 숫자 하나 써주면 되기 때문에 효율적이다.

※ 인덱스 순서는 되도록 시계방향으로 하도록 하자.

  - 컬모드로 매쉬의 앞 뒤를 구분할때 시계방향으로 되어있으면 앞, 반시계로 되어있으면 뒤 로 구분할 수 있다.


버퍼 함수에 대해 알아보자

버텍스 버퍼 는 CreateVertexBuffer 를 사용하며

인자로 UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9** ppVertexBuffer,

HANDLE* pShareHandle 을 받는다.

인덱스 버퍼 는 CreateIndexBuffer 를 사용하며

인자로 UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** ppIndexBuffer,

HANDLE* pShareHandle 을 받는다.


인자의 설명

 - Length       버퍼에 할당할 바이트 수 보통 보통 sizeof(버텍스구조체) * 개수로 사용한다.

                인덱스는 조금 다르다 1개의 사각형을 가지고 생각을 해 보자 사각형은 4개의 버텍스로 만들어 진다.

                또 그 사각형은 2개의 삼각형으로 만들어진다. 삼각형은 3개의 버텍스로 만들어지므로 1개의 사각형일 때

                인덱스의 개수는 3 * 2 가 됩니다. 그러므로 인덱스 개수는 사각형의 수 * 6입니다.

                햇갈리시는 분은 사각형 개수를 더 많이 해놓고 노트에 그리며 생각해보세요~


 - Usage               버퍼가 이용되는 방법을 경정하는 특성을 지정한다. 0 혹은 NULL을 쓰거나 플래그를 하나이상 쓸 수 있다.

                            몇가지만 예를 든다면 이런것들이 있다.

                            ■ D3DUSAGE_DYNAMIC - 버퍼를 동적 상태로 만든다. 정적 버퍼는 버퍼갱신(비디오 메모리 접근) 속도가

                                                                  느리기 때문에 정적 버퍼는 지형이나 건물 기하정보등의 자주 변경되지 않는

                                                                  데이터를 보관하는데 적합하다. 하지만 동적 버퍼는 매우 빠른 속도로 갱신이

                                                                  가능한 AGP 메모리 내에 보관되며 비디오 메모리로 전송되는 과정이 필요해 정적

                                                                  버퍼만큼 빠르지 않지만 버퍼 갱신이 빠른 장점이 있다. 따라서 파티클 같이 매

                                                                  프레임 마다 기하정보를 반환 해야 하는 것들은 동적버퍼가 더욱 좋다.

                ■ D3DUSAGE_POINTS - 버퍼 포인트가 기본형을 보관할 것임을 지정한다. ( 버텍스 버퍼에서만 사용 )

                ■ D3DUSAGE_SOFTWAREPROCESSING - 버텍스 프로세싱을 소프트웨어로 처리한다.

                ■ D3DUSAGE_WRITEONLY - 메모리 버퍼를 쓰기용으로 사용할 것을 말한다. 읽기를 하려하면 오류가 난다.

                    http://msdn.microsoft.com/en-us/library/windows/desktop/bb172625(v=vs.85).aspx  이외는 참조.


 - FVF                   버텍스 버퍼에 보관될 버텍스의 유연한 버텍스 포맷

 - Pool                  버퍼가 위치할 메모리 풀

 - ppVertexBuffer 만들어질 버텍스 버퍼를 받을 포인터

 - ppIndexBuffer  만들어질 인덱스 버퍼를 받을 포인터

 - pShardHandle  이용되지 않는다. NULL 혹은 0으로 쓴다.


버퍼 메모리에 접근하는법

버퍼 메모리에 접근하기 위해 LockUnlock 을 사용한다. 반드시 Lock을 한 후 이용이 끝난 이후에는 Unlock를 사용하여 잠금 해제를 하도록 한다.

Lock 을 사용하려면 인자를 입력해야 하는데 UINT OffsetToLock, UINT SizeToLock, BYTE** ppbData, DWORD Flags 가 있다.

 - OffsetToLock  잠금을 시작할 버퍼의 위치

 - SizeToLock   잠글 바이트 수 ( 만약 전체 버퍼를 잠그고자 한다면 OffsetToLock 과 SizeToLock 둘 다 0을 입력한다. )

 - ppbData        잠근 메모리의 시작을 가르키는 포인터

 - Flags            잠금이 이루어지는 방법을 지정 ( 0 이나 한개이상의 조합 사용 가능 )

                        ■ D3DLOCK_DISCARD - 동적 버퍼만 사용할 수 있으며, 하드웨어에게 버퍼를 버리도록 지시하고 새로 할당된                                                                    버퍼의 포인터를 리턴하게 한다. ( 새로 할당된 버퍼에 접근하면서 버려진 버퍼에서 렌더링                                                            을 수행할 수 있도록 해줘 유용하다. 하드웨어 지연을 막는데 도움이 된다. ) 

                        ■ D3DLOCK_NOOVERWRITE - 동적 버퍼만 사용할 수 있으며, 버퍼에 데이터를 추가하는 작업만 가능하게 한다.                                                                         ( 이 플래그를 지정하면 현재 렌더링 중인 메모리를 덮어 씌울 수 없게 되며,                                                                      버퍼에 새 데이터를 추가하는 동안에도 렌더링을 계속할 수 있도록 해준다. )

                        ■ D3DLOCK_READONLY - 일기전용으로 버퍼를 잠근다. ( 약간의 내부 최적화를 가능하게 해준다 한다. )

                            http://msdn.microsoft.com/en-us/library/windows/desktop/bb172568(v=vs.85).aspx 에 더 정보가 있다.


버퍼에 대한 정보 얻기

버텍스 버퍼의 이름 : VBuffer 이라 할 때

D3DVERTEXBUFFER_DESC vbDesc;
VBuffer->GetDesc(&vbDesc); // 버텍스 정보를 얻는다.


소스코드

Winmain.cpp

#include <Windows.h> #include <d3dx9.h> #pragma comment ( lib, "d3d9.lib" ) #pragma comment ( lib, "d3dx9.lib" ) #pragma comment ( lib, "winmm.lib" ) //사용자 추가 전역 변수 int ActiveFlage = TRUE; // 반복 체크 LPDIRECT3D9 pDX = NULL; // dx객체 LPDIRECT3DDEVICE9 pDevice = NULL; // dx디바이스 D3DPRESENT_PARAMETERS stParam; // 파라메터 구조체 float lastTime = (float)timeGetTime(); // 마지막 시간 float y = 0.0f; // y축 회전값 struct Vertex { Vertex(){} // 생성자 Vertex(float _x, float _y, float _z) // 생성자 { x = _x; y = _y; z = _z; } float x, y, z; }; Vertex* Vtx; WORD* Idx; IDirect3DVertexBuffer9 *VB = 0; IDirect3DIndexBuffer9 *IB = 0; #define MY_FVF D3DFVF_XYZ // 사용자 추가 핸들 // 콜백 함수 LRESULT CALLBACK WndProc ( HWND, UINT, WPARAM, LPARAM ); // 다이렉트x 초기화 VOID Device_Init( INT , INT , HWND ); // 버텍스 인덱스 설정 VOID VertexIndex( VOID );// 버텍스와 인덱스를 설정하는 함수 // 회전 함수 VOID RotationCube ( VOID ); int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) { static TCHAR szAppName[] = "My Project"; MSG msg; HWND hwnd; WNDCLASSEX wndclass; //HDC hdc; int WIDTH = 800,\ HEIGHT = 600; int roop = 1; bool bWindow = TRUE; // 창모드 체크 wndclass.cbSize = sizeof( wndclass ); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon( NULL, MAKEINTRESOURCE( IDI_APPLICATION ) ); wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); wndclass.hbrBackground = ( HBRUSH )GetStockObject( WHITE_BRUSH ); wndclass.lpszMenuName = NULL; // 메뉴없음 wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon( NULL, IDI_APPLICATION ); RegisterClassEx ( &wndclass ); hwnd = CreateWindow( szAppName, szAppName, WS_SYSMENU, ( GetSystemMetrics( SM_CXSCREEN ) - WIDTH ) / 2, ( GetSystemMetrics( SM_CYSCREEN ) - HEIGHT ) / 2, WIDTH, HEIGHT, NULL, NULL, hInstance, NULL ); ShowWindow( hwnd, iCmdShow ); UpdateWindow ( hwnd ); //다이렉트 X 관련 Device_Init(WIDTH, HEIGHT, hwnd); D3DXMATRIX mWorld; // 월드 행렬 D3DXMATRIX mView; // 뷰 행렬 D3DXMATRIX mProj; // 투영 행렬 // 투영행렬 셋팅 D3DXMatrixPerspectiveFovLH ( &mProj, D3DX_PI * 0.5f, (float)WIDTH / (float)HEIGHT, 1.0f, 1000.0f ); pDevice->SetTransform( D3DTS_PROJECTION, &mProj ); // 투영행렬 적용 float fCamera_x = 0.0f; float fCamera_y = 0.0f; float fCamera_z = -5.0f; VertexIndex(); // 버텍스와 인덱스를 설정하는 함수 while( roop ) { while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) != 0 ) { if( GetMessage( &msg, NULL, 0, 0 ) == 0 ) { roop = 0; } TranslateMessage( &msg ); DispatchMessage( &msg ); } if ( ActiveFlage == TRUE ) // 실제 게임 코딩영역 { if ( GetAsyncKeyState(VK_ESCAPE) ) { ActiveFlage = FALSE; roop = NULL; } if ( GetAsyncKeyState( 'W' ) ) { fCamera_z-=0.01f; } if ( GetAsyncKeyState( 'S' ) ) { fCamera_z+=0.01f; } if ( GetAsyncKeyState( 'A' ) ) { fCamera_x+=0.01f; } if ( GetAsyncKeyState( 'D' ) ) { fCamera_x-=0.01f; } if ( GetAsyncKeyState( VK_SPACE ) ) { fCamera_x = 0.0f; fCamera_y = 0.0f; fCamera_z = -5.0f; } RotationCube();//회전 // 카메라 셋팅 D3DXMatrixLookAtLH ( &mView, &D3DXVECTOR3(fCamera_x, fCamera_y, fCamera_z), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f) ); pDevice->SetTransform(D3DTS_VIEW, &mView); // 뷰 행렬 적용 pDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); // 화면을 흰색으로 지움 pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); // 필모드를 와이어 프레임으로 그래야 선이 보임 pDevice->BeginScene();// 그리기 시작 pDevice->SetStreamSource(0, VB, 0, sizeof(Vertex)); pDevice->SetIndices(IB); pDevice->SetFVF(MY_FVF); pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 8, 0, 12); pDevice->EndScene();// 그리기 종료 pDevice->Present( NULL, NULL, NULL, NULL );// 화면에 뿌려줌 lastTime = (float)timeGetTime(); } } pDevice->Release(); // 디바이스 해제 VB->Release(); IB->Release(); // 버퍼 해제 return msg.wParam; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { switch ( iMsg ) { case WM_ACTIVATE: { break; } case WM_CREATE: { ActiveFlage = TRUE; break; } case WM_CLOSE: { ActiveFlage = FALSE; PostQuitMessage(0); break; } case WM_PAINT: break; default: break; } return DefWindowProc( hwnd, iMsg, wParam, lParam ); } VOID Device_Init(INT WIDTH, INT HEIGHT, HWND hwnd) { stParam.AutoDepthStencilFormat = D3DFMT_D24X8; // 깊이 버퍼를 24비트로 만듬 stParam.BackBufferCount = 1; // 1개의 백버퍼를 만듬 stParam.BackBufferFormat = D3DFMT_X8R8G8B8; // 백버퍼 포멧형태 stParam.BackBufferHeight = HEIGHT; // 버퍼 크기 stParam.BackBufferWidth = WIDTH; // 버퍼 크기 stParam.EnableAutoDepthStencil = TRUE; // 스텐실 버퍼 자동생성 사용 stParam.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; // 시스템 자체가 백버퍼를 건딜 수 있게 한다 ( 하지만 작동 안함 ) stParam.Windowed = TRUE; // 창모드로한다 stParam.hDeviceWindow = hwnd; // 디바이스가 어느 윈도우랑 연결될지 체크 윈도우 핸들을 넣어준다 stParam.SwapEffect = D3DSWAPEFFECT_DISCARD; // 백버퍼에서 프론트버퍼로 가져올 방식 stParam.FullScreen_RefreshRateInHz = 0; // 모니터 주사율 창모드는 0 stParam.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // 프레젠트 명령어가 호출되면 바로 프론트 버퍼에 뿌림 stParam.MultiSampleQuality = D3DMULTISAMPLE_NONE; // 멀티셈플링 옵션으로 안씀(느려서) pDX = Direct3DCreate9( D3D_SDK_VERSION ); pDX->CreateDevice(0, D3DDEVTYPE_HAL, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &stParam, &pDevice ); // 맨앞 0번은 몇번째 그래픽 카드를 쓸것인지 0번이 현재 모니터에 뿌려주고있는 그래픽카드 } VOID VertexIndex( VOID ) { pDevice->CreateVertexBuffer( 8 * sizeof(Vertex), D3DUSAGE_WRITEONLY, MY_FVF, D3DPOOL_MANAGED, &VB, 0 ); pDevice->CreateIndexBuffer( 36 * sizeof(WORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &IB, 0 ); VB->Lock( 0, 0, (void**)&Vtx, 0 ); Vtx[0].x = -1.0f; Vtx[0].y = -1.0f; Vtx[0].z = -1.0f; Vtx[1].x = -1.0f; Vtx[1].y = 1.0f; Vtx[1].z = -1.0f; Vtx[2].x = 1.0f; Vtx[2].y = 1.0f; Vtx[2].z = -1.0f; Vtx[3].x = 1.0f; Vtx[3].y = -1.0f; Vtx[3].z = -1.0f; Vtx[4].x = -1.0f; Vtx[4].y = -1.0f; Vtx[4].z = 1.0f; Vtx[5].x = -1.0f; Vtx[5].y = 1.0f; Vtx[5].z = 1.0f; Vtx[6].x = 1.0f; Vtx[6].y = 1.0f; Vtx[6].z = 1.0f; Vtx[7].x = 1.0f; Vtx[7].y = -1.0f; Vtx[7].z = 1.0f;//노가다 버전 /*Vtx[0] = Vertex(-1.0f, -1.0f, -1.0f); Vtx[1] = Vertex(-1.0f, 1.0f, -1.0f); Vtx[2] = Vertex( 1.0f, 1.0f, -1.0f); Vtx[3] = Vertex( 1.0f, -1.0f, -1.0f); Vtx[4] = Vertex(-1.0f, -1.0f, 1.0f); Vtx[5] = Vertex(-1.0f, 1.0f, 1.0f); Vtx[6] = Vertex( 1.0f, 1.0f, 1.0f); Vtx[7] = Vertex( 1.0f, -1.0f, 1.0f);*/// 단순 버전 VB->Unlock(); IB->Lock(0, 0, (void**)&Idx, 0); Idx[0] = 0; Idx[1] = 1; Idx[2] = 2; Idx[3] = 0; Idx[4] = 2; Idx[5] = 3; Idx[6] = 4; Idx[7] = 6; Idx[8] = 5; Idx[9] = 4; Idx[10] = 7; Idx[11] = 6; Idx[12] = 4; Idx[13] = 5; Idx[14] = 1; Idx[15] = 4; Idx[16] = 1; Idx[17] = 0; Idx[18] = 3; Idx[19] = 2; Idx[20] = 6; Idx[21] = 3; Idx[22] = 6; Idx[23] = 7; Idx[24] = 1; Idx[25] = 5; Idx[26] = 6; Idx[27] = 1; Idx[28] = 6; Idx[29] = 2; Idx[30] = 4; Idx[31] = 0; Idx[32] = 3; Idx[33] = 4; Idx[34] = 3; Idx[35] = 7; IB->Unlock(); } VOID RotationCube ( VOID ) { float currTime = (float)timeGetTime(); float fangle = (currTime - lastTime)*0.001f; // 1회 루프 돌은 시간 * 0.001 D3DXMATRIX Rx, Ry; D3DXMatrixRotationX(&Rx, 3.14f / 4.0f); // x축으로 pie/4 만큼 회전 // 매 프레임마다 y축 회전값 증가 y += fangle; D3DXMatrixRotationY(&Ry, y); // y축으로 회전 적용 // 한바퀴를 돌면 y축회전각을 리셋시킨다. if( y >= 6.28f ) y = 0.0f; // x와 y의 회전된 행렬을 p에 합쳐준다. D3DXMATRIX p = Rx * Ry; pDevice->SetTransform(D3DTS_WORLD, &p); // 월드행렬에 p를 대입 }