Curso completo de DirectX 9 com C\C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 01-8

index << >>


01.8 Buffer de índices

1.1 Visão geral
Na renderização do quadrado tinha um problema: utilizamos 6 vértices para montá-lo enquanto o bom senso indica que apenas 4 vértices seriam suficientes. O buffer de índices permite o compartilhamento de vértices na formação dos triângulos e assim permite uma nova maneira de combinar os vértices para montar a forma geométrica. Outra forma de ver o buffer de índices é imaginá-lo como o caminho que determina a ligação dos vértices. Na montagem do quadrado do programa exemplo utilizamos esse caminho para fazer dois triângulos de composição do quadrado: p0, p1, p2 e p0, p3, p1; também seguindo uma ordem horária no caminho de ligação dos quatro vértices. Veja na ilustração acima que o pilar fundamental do buffer de índices é simplesmente uma array que indica os vértices na sequência que devem ser conectados. 1.2 Estrutura principal da aplicação
Arquivo: motor.cpp
Aspectos globais Define uma pequena array de índices cujos valores sequenciais indicam o caminho de conexão dos vértices dando origem aos triângulos e assim chegando até aos objetos 3d mais complexos. initGfx() Inicializa objeto Direct3d Inicializa dispositivo renderizador chama montar_Geometria() para configurar os vértices montar_Geometria() Configura posição dos 4 vértices renderizar_Geometria() Declara o formato de vértice utilizado ao directx Renderiza os vértices com g_device->DrawIndexedPrimitiveUP() Renderizar() Limpa a tela Desenha a cena chama renderizar_Geometria() para desenhar o quadrado Apresenta a cena Limpar() Libera dispositivo renderizador Libera objeto Direct3d
2.1.1 Aspectos globais - Arquivo: motor.h
// Projeto: prj_Indexbuffer01 - Arquivo: motor.h // Esta aplicação ilustra como renderizar um quadrado // utilizando buffer de índices #ifndef motor_h #define motor_h // Esta função inicializa o Direct3D HRESULT initGfx (HWND hJanela); // Essa função libera os objetos utilizados void Limpar(); // Essa função desenha a cena void Renderizar(); // Essa função monta formas geométricas void montar_Geometria (void); // Renderiza os vértices em formas geométricas void renderizar_Geometria (void); // Declaração da função que atende as mensagens da janela LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam); #endif
// Essa função monta formas geométricas void montar_Geometria (void); // Renderiza os vértices em formas geométricas void renderizar_Geometria (void); 2.1.2 Aspectos globais - Arquivo: motor.cpp
// Declaração de 4 vértices para produzir o quadrado CustomVertex_TransformedColored g_Vertices[4 ]; // Buffer de índices - indica a ordem de conexão dos vértices WORD g_Indices[6] = { 0,1,2, 0,3,1 };
// Declaração de 4 vértices para produzir o quadrado CustomVertex_TransformedColored g_Vertices[4]; A novidade aqui em relação à aplicação anterior é que os vértices foram reduzidos para 4 unidades ao invés dos seis (6) anteriores. Então a primeira vantagem do uso do buffer de índices é economia. // Buffer de índices - indica a ordem de conexão dos vértices WORD g_Indices[6] = { 0,1,2, 0,3,1 }; O buffer de índices desta aplicação é simplesmente isso: uma array com valores que representam os vértices se conectando. O primeiro triângulo está representado pelo trio de vértices { 0, 1, 2 } e o segundo triângulo pelo trio { 0, 3, 1 }. É importante saber que o tipo de dado desse buffer de índices é o WORD que tem dois (2) bytes ou 16 bits. Posteriormente o directx cobra essa informação da aplicação. Uma outra relação interessante para se notar é que para cada triângulo é necessário 3 índices. Dessas primeiras observações resulta que o tamanho do buffer de índices em bytes é dado por esta fórmula: nTamanhoBuffer = nTriângulosQuantidade * 3 * 2; É fundamental essa compreensão do espaço ocupado pelo buffer de índices para responder adequadamente ao directx quando ele perguntar por esse dado ou quando sua aplicação recuperar o buffer de índices de um objeto 3d carregado do disco. A segunda vantagem do uso do buffer de índices é que os vértices se transformam em entidades mais independentes ficando livres no espaço podendo ser reunidos em qualquer ordem que for interessante para a aplicação. A aplicabilidade do buffer de índices nos lembra um papel em branco com vários pontos dispersos sobre ele com o potencial de serem conectados por vários caminhos diferentes dando origem a formas diferentes com o mesmo conjunto de pontos. 2.2 Montando o quadrado
void montar_Geometria(void) { // Posicionamento de profundidade float zpos = 1.0f; // Vértices para a montagem do quadrado g_Vertices[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos, 0.7f, vermelho); g_Vertices[1] = CustomVertex_TransformedColored( 512.0f, 384.0f, zpos, 0.8f, verde); g_Vertices[2] = CustomVertex_TransformedColored( 128.0f, 384.0f, zpos, 1.0f, azul); g_Vertices[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos, 1.0f, cinza); } // montar_Geometria().fim
// Vértices para a montagem do quadrado g_Vertices[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos, 0.7f, vermelho); g_Vertices[1] = CustomVertex_TransformedColored( 512.0f, 384.0f, zpos, 0.8f, verde); g_Vertices[2] = CustomVertex_TransformedColored( 128.0f, 384.0f, zpos, 1.0f, azul); g_Vertices[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos, 1.0f, cinza);
É importante notar aqui que foi suprimido a duplicação de vértices de mesma configuração. 2.3 Renderizando com buffer de índices
void renderizar_Geometria() { // Declara o formato de vértice utilizado pela aplicação g_device->SetFVF( CustomVertex_TransformedColored_Format); // Argumentos da função DrawIndexedPrimitiveUP() UINT nVerticeInicial = 0; UINT nVerticeQtd = 4; UINT nContagemPrim = 2; D3DFORMAT nIndiceFormato = D3DFMT_INDEX16; UINT nPasso = sizeof(CustomVertex_TransformedColored); // Renderiza os vértices utilizando o buffer de índices g_device->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, nVerticeInicial, nVerticeQtd, nContagemPrim, &g_Indices, nIndiceFormato, &g_Vertices, nPasso); } // renderizar_Geometria().fim
Discutimos abaixo os argumentos da função DrawIndexedPrimitiveUP() que renderiza vértices com indexação. UINT nVerticeInicial = 0; UINT nVerticeQtd = 4; UINT nContagemPrim = 2; Com estes três argumentos é possível definir apenas uma seção do buffer a ser renderizada. Temos o menor índice inicial ( nVerticeInicial ), a quantidade de vértices a ser renderizada ( nVerticeQtd ) e quantas primitivas vão ser renderizadas ( nContagemPrim ). Ao invés de ter um buffer para cada forma geométrica é mais eficiente ter um grande buffer contendo vários objetos 3d e selecionando através destes argumentos o objeto que vai ser renderizado. D3DFORMAT nIndiceFormato = D3DFMT_INDEX16; Aqui é a definição do formato do buffer de índices com a indicação de que seu tipo de dado tem dois bytes ( 16 bits). O outro formato que pode ser utilizado é o tipo de dado DWORD indicado por D3DFMT_INDEX32. UINT nPasso = sizeof(CustomVertex_TransformedColored); Aqui é a indicação do tamanho em bytes entre um vértice e outro. // Renderiza os vértices utilizando o buffer de índices g_device->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, nVerticeInicial, nVerticeQtd, nContagemPrim, &g_Indices, nIndiceFormato, &g_Vertices, nPasso); Essa é a função que renderiza o buffer de vértices ( &g_Vertices ) indexado pelo buffer de índices ( &g_Indices ) dentro da forma primitiva selecionada ( D3DPT_TRIANGLELIST ). Lembramos que o directx tem interfaces próprias que representam o buffer índices e o buffer de vértices. A vantagem de usar estes dois recipientes providos pelo directx é que o directx transfere ambos para processamento dentro da placa de vídeo aproveitando os recursos de aceleração eletrônica. 3. Código fonte do projeto de exemplo: prj_Indexbuffer01
// Projeto: prj_Indexbuffer01 - Arquivo: motor.h // Esta aplicação ilustra como renderizar um quadrado // utilizando buffer de índices #ifndef motor_h #define motor_h // Esta função inicializa o Direct3D HRESULT initGfx (HWND hJanela); // Essa função libera os objetos utilizados void Limpar(); // Essa função desenha a cena void Renderizar(); // Essa função monta formas geométricas void montar_Geometria (void); // Renderiza os vértices em formas geométricas void renderizar_Geometria (void); // Declaração da função que atende as mensagens da janela LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam); #endif
// ----------------------------------------------------------------------------- // Projeto: prj_Indexbuffer01 - arquivo: motor.cpp // Esta aplicação ilustra como renderizar um quadrado // utilizando buffer de índices // Produzido por www.gameprog.com.br // ----------------------------------------------------------------------------- #include <windows.h> #include <d3d9.h> #include <d3dx9.h> #include <stdio.h> #include "motor.h" // Inclui as bibliotecas do Direct3D #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") // Variáveis globais // Representa o dispositivo Direct3D LPDIRECT3D9 g_Direct3d = NULL; // Representa o dispositivo Renderizador IDirect3DDevice9* g_device = NULL; // Essa variável recebe informação de erro do Directx HRESULT g_hr = 0; extern int g_xtela; extern int g_ytela; // Constante para cores const DWORD vermelho = 0xFFFF0000; const DWORD branco = 0xFFFFFFFF; const DWORD verde = 0xFF00FF00; const DWORD azul = 0xFF0000FF; const DWORD cinza = 0xFF101010; // Definição do formato de vértice utilizado por esta aplicação #define CustomVertex_TransformedColored_Format(D3DFVF_XYZRHW | D3DFVF_DIFFUSE) // Estrutura do vértice customizado struct CustomVertex_TransformedColored { float x, y, z; float rhw; DWORD cor; // Construtor default CustomVertex_TransformedColored() {} CustomVertex_TransformedColored( float _x, float _y, float _z, float _rhw, DWORD _cor) { x = _x; y = _y; z = _z; rhw = _rhw; cor = _cor; } }; // fim da estrutura CustomVertex_TransformedColored // Declaração de 4 vértices para produzir o quadrado CustomVertex_TransformedColored g_Vertices[4 ]; // Buffer de índices - indica a ordem de conexão dos vértices WORD g_Indices[6] = {0,1, 2, 0, 3, 1}; // initGfx() - Inicializa o Direct3D HRESULT initGfx( HWND hJanela ) { // Cria o objeto D3D que é necessário para criar o dispositivo gráfico g_Direct3d = Direct3DCreate9( D3D_SDK_VERSION); // Verifica se objeto Direct3D foi criado if(g_Direct3d == NULL) { MessageBox (NULL, "Falha na inialização do Direct3D", "InitGfx()", MB_OK); return E_FAIL; } // endif // Declara a variável para os parâmetros de apresentação D3DPRESENT_PARAMETERS pps; // Limpa a estrutura ZeroMemory( &pps, sizeof(pps) ); // Configura os parâmetros de apresentação // A aplicação vai ter janela pps.Windowed = TRUE; // Esse método vira rapidamente o backbuffer para a tela imediata pps.SwapEffect = D3DSWAPEFFECT_DISCARD; // Esse formato vai procurar se encaixar no modo de video corrente pps.BackBufferFormat = D3DFMT_UNKNOWN; // Configuração do renderizador a ser criado // Adaptador default (0) int nAdaptador = D3DADAPTER_DEFAULT; // Tipo de dispositivo Hardware ou emulador de referência (software) D3DDEVTYPE dispositivo_tipo = D3DDEVTYPE_HAL; // Flags de configuração do dispositivo DWORD create_flags = D3DCREATE_SOFTWARE_VERTEXPROCESSING; // Criamos aqui o dispositivo renderizador g_hr = g_Direct3d->CreateDevice( nAdaptador, dispositivo_tipo, hJanela, create_flags, &pps, &g_device ); // Verifica se houve falha no processo if( FAILED( g_hr ) ) { MessageBox (NULL, "Falha na criação: g_device", "initGfx()", MB_OK); return E_FAIL; } // endif montar_Geometria(); return S_OK; } // initGfx().fim void montar_Geometria(void) { // Posicionamento de profundidade float zpos = 1.0f; // Vértices para a montagem do quadrado g_Vertices[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos, 0.7f, vermelho); g_Vertices[1] = CustomVertex_TransformedColored( 512.0f, 384.0f, zpos, 0.8f, verde); g_Vertices[2] = CustomVertex_TransformedColored( 128.0f, 384.0f, zpos, 1.0f, azul); g_Vertices[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos, 1.0f, cinza); } // montar_Geometria().fim void renderizar_Geometria() { // Declara o formato de vértice utilizado pela aplicação g_device->SetFVF( CustomVertex_TransformedColored_Format); // Argumentos da função DrawIndexedPrimitiveUP() UINT nVerticeInicial = 0; UINT nVerticeQtd = 4; UINT nContagemPrim = 2; D3DFORMAT nIndiceFormato = D3DFMT_INDEX16; UINT nPasso = sizeof(CustomVertex_TransformedColored); // Renderiza os vértices utilizando o buffer de índices g_device->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, nVerticeInicial, nVerticeQtd, nContagemPrim, &g_Indices, nIndiceFormato, &g_Vertices, nPasso); } // renderizar_Geometria().fim // Esta função é chamada por DispatchMessage() LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam) { switch (mensagem) { case WM_DESTROY: // Coloca uma mensagem WM_QUIT na fila de mensagem Limpar(); PostQuitMessage (0); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) { Limpar(); PostQuitMessage( 0); } // endif break; // Essa mensagem vai ocorrer a todo momento case WM_PAINT: // Renderiza a cena Renderizar(); // Invalida a tela para chamar WM_PAINT novamente InvalidateRect( hJanela, NULL, false); break; // Processamento default de mensagens não tratada pela aplicação default: return DefWindowProc (hJanela, mensagem, wParam, lParam); } // endswitch return 0; } // processaJanela().fim // Limpar() - Libera todos os objetos previamente inicializados // ----------------------------------------------------------------------------- VOID Limpar() { // Libera o dispositivo gráfico if( g_device != NULL) g_device->Release(); // Libera o motor do Direct3D if( g_Direct3d != NULL) g_Direct3d->Release(); } // Limpar().fim // ----------------------------------------------------------------------------- // Renderizar() - Desenha a cena // ----------------------------------------------------------------------------- VOID Renderizar() { // Retorne se o dispositivo estiver nulo if( g_device == NULL) return; // Limpa o backbuffer com uma cor branca g_device->Clear( 0, NULL, D3DCLEAR_TARGET, branco, 1.0f, 0); // Começa a cena if( SUCCEEDED( g_device->BeginScene() ) ) { // Vamos renderizar a geometria renderizar_Geometria(); // Finalizando a cena g_device->EndScene(); } // endif // Apresenta o conteúdo do backbuffer na tela g_device->Present( NULL, NULL, NULL, NULL); } // Renderizar().fim
//----------------------------------------------------------------------------- // Projeto: prj_Indexbuffer01 - arquivo: entrada.cpp // Esta aplicação ilustra como renderizar um quadrado // utilizando buffer de índices // Produzido por www.gameprog.com.br //----------------------------------------------------------------------------- #include <windows.h> #include <d3d9.h> #include <d3dx9.h> #include "motor.h" // Variável global da classe da janela char sclasseJanela[ ] = "cls_directx"; // Dimensões da janela int g_xtela = 640; int g_ytela = 480; int WINAPI WinMain (HINSTANCE app_instancia, HINSTANCE app_anterior, LPSTR sComando,int nExibir) { // alça da janela HWND hJanela; // Estrutura de recepção das mensagens MSG mensagem; // Estrutura de descrição da janela WNDCLASSEX wcls; // Estrutura que descreve a janela wcls.hInstance = app_instancia; wcls.lpszClassName = sclasseJanela; wcls.lpfnWndProc = processaJanela; wcls.style = CS_HREDRAW | CS_VREDRAW; wcls.cbSize = sizeof (WNDCLASSEX); // O cursor e os ícones da aplicação são default wcls.hIcon = LoadIcon (NULL, IDI_APPLICATION); wcls.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wcls.hCursor = LoadCursor (NULL, IDC_ARROW); // Aplicação sem menu wcls.lpszMenuName = NULL; // Nada de espaço extra atrelado a classe da janela (wcls) wcls.cbClsExtra = 0; // Nada de espaço extra atrelado a janela wcls.cbWndExtra = 0; // Cor default da janela wcls.hbrBackground = ( HBRUSH) COLOR_BACKGROUND; // Registra a janela e retorna se esta operação falhar int status = RegisterClassEx (&wcls); if(status == 0) { MessageBox(NULL, "Registro falhou!", "WinMain()", MB_OK); return 0; } // endif // Com a classe criada pode-se criar a janela DWORD estiloExtra = 0; const char janelaTitulo[] = "prj_Indexbuffer01"; DWORD controleEstilo = WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX; int xpos = 160; int ypos = 120; HWND hjanelaPai = HWND_DESKTOP; HMENU sem_menu = NULL; LPVOID dadoExtra = NULL; // Cria a janela hJanela = CreateWindowEx(estiloExtra, sclasseJanela, janelaTitulo, controleEstilo, xpos, xpos, g_xtela, g_ytela, hjanelaPai, sem_menu, app_instancia, dadoExtra ); // Verifica se janela foi criada if(hJanela == NULL) { MessageBox(NULL, "Falha na criação da janela!", "WinMain()", MB_OK); return 0; } // endif // Essa variável recebe informação de erro do Directx HRESULT hr; // Inicia o Direct3D hr = initGfx ( hJanela ); // Encerre a aplicação se houve falha if(FAILED (hr) ) { MessageBox (hJanela, "Direct3D: falha na inicialização", "WinMain()", MB_OK); UnregisterClass( sclasseJanela, wcls.hInstance); return E_FAIL; } // endif // Mostra a janela ShowWindow(hJanela, nExibir); UpdateWindow(hJanela ); // Rode a bombeamento de mensagens até GetMessage() retornar 0 while (GetMessage(&mensagem, NULL, 0, 0)) { // Traduz mensagem de tecla virtual em mensagem de caracteres TranslateMessage( &mensagem ); // Despacha a mensagem para a função processaJanela */ DispatchMessage( &mensagem ); } // endwhile // O valor de retorno é zero(0) passado por PostQuitMessage() UnregisterClass( sclasseJanela, wcls.hInstance); return mensagem.wParam; } // WinMain().fim

index << >>

Produzido por Gameprog: Jair Pereira - Agosto/2014 © gameprog.br@gmail.com http://www.gameprog.com.br