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