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

index << >>


01.9 Recipientes do directx

1.1 Visão geral
A aplicação de exemplo deste tópico replica a aplicação do tópico anterior com a diferença de utilizar os recipientes de vértices e de índices providos pelas interfaces do directx. De um certo ponto de vista o buffer de índices e o buffer de vértices são formas de recipientes para conter índices e vértices. O mesmo podemos pensar da textura e de outras superfícies de imagens como sendo recipientes de informação de cor. Estes recipientes providos pelas interfaces do directx são chamados recursos ( resources ) na documentação e na codificação da Microsoft. Uma das primeiras vantagens em se utilizar os recipientes do directx é velocidade: de acordo com a configuração dada ao recipiente o directx pode mandá-lo para estocagem e processamento na placa aceleradora fato que melhora a eficiência da aplicação. As outras vantagens consiste no fato do próprio directx fazer a gestão dos dados do recipiente e ainda prover alguns métodos comuns para facilitar algumas operações. Por exemplo, na questão da textura você vai encontrar funções para ler e salvar a textura no disco. A gestão dos dados dos recipientes não é uma coisa simples pois entra em questão a concorrência no acesso aos dados pela aplicação, pelo directx e pela placa aceleradora bem como a questão da troca e da posse de dados entre cada ponto. Então essas questões impactam na configuração inicial e no acesso aos recipientes pela aplicação. É comum na criação dos recipientes pelas interfaces do directx a indicação de uso que a aplicação vai fazer dos dados, se vai ler ou modificar com frequência; a antecipação dessa informação permite ao directx selecionar o melhor local para guardar esses dados que pode ser a memória gerenciada pelo directx, a memória gerenciada pelo Sistema ou na memória da placa de vídeo. Essa diferença de local de memória dá origem a classes de memória também chamado de piscina ou pool pelo directx. Por fim, na solicitação de acesso aos dados, a informação se vai ser apenas para leitura ou escrita permite ao directx fazer otimizações internas de como ele vai liberar o acesso à aplicação esses dados. Para facilitar a fluência do texto a expressão buffer de vértices vai ser referida também pelo termo inglês vertexbuffer e o mesmo ocorrerá com o buffer de índices a ser chamado de indexbuffer. As expressões em inglês e em português serão usadas de maneira intercambiável sem perda do significado do que são estes recipientes. 1.2 Estrutura principal da aplicação
Arquivo: motor.cpp
Aspectos globais Declaração do ponteiro global para o buffer de vértices Declaração do ponteiro global para o buffer de índices initGfx() Inicializa objeto Direct3d Inicializa dispositivo renderizador chama inicializar_Buffers() para inicializar o buffer de índices e o buffer de vértices. chama montar_Geometria() para configurar os vértices no buffer de vértices e os índices no buffer de índices. inicializar_Buffers() Criação do buffer de vértices (vertexbuffer) Criação do buffer de índices (indexbuffer) montar_Geometria() Acessa o buffer de vértices Configura posição dos 4 vértices no vertexbuffer Acessa o buffer de índices Configura posição dos 6 índices no indexbuffer renderizar_Geometria() Declara o formato de vértice utilizado ao directx Indica ao dispositivo o buffer de vértices que será utilizado g_device->SetStreamSource() Indica ao dispositivo o buffer de índices que será utilizado g_device->SetIndices() Renderiza os vértices com g_device->DrawIndexedPrimitive() Renderizar() Limpa a tela Desenha a cena chama renderizar_Geometria() para desenhar o quadrado Apresenta a cena Limpar() Libera o buffer de vértices Libera o buffer de índices Libera dispositivo renderizador Libera objeto Direct3d
2.1.1 Aspectos globais - Arquivo: motor.h
// Projeto: prj_Buffers - Arquivo: motor.h // Esta aplicação ilustra como renderizar um quadrado // utilizando as interfaces do directx para buffer de // índices e buffer de vértices. By www.gameprog.com.br #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 inicializa o buffer de índices e // o buffer de vértices void inicializar_Buffers(void); // 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
void inicializar_Buffers(void); Essa função inicializa o buffer de índices e o buffer de vértices. Na verdade, essa inicialização consiste na criação desses dois recipientes com a configuração básica que cada um exige. Decidimos criar esses dois recipientes aqui na mesma função para facilitar a percepção de que eles têm aspectos e configurações semelhantes. void montar_Geometria (void); Essa função configura os vértices no vertexbuffer e os índices no indexbuffer para formar o quadrado. void renderizar_Geometria (void); Essa função vai renderizar o quadrado que está confinado na configuração e contéudos do vertexbuffer e do indexbuffer. É importante destacar que tanto o indexbuffer e o vertexbuffer podem ser renderizados parcialmente. A aplicação pode possuir vários recipientes mas a melhor eficiência é obtida quando todos os objetos 3d estão no mesmo pacote com a aplicação gerenciando a parte do pacote que quer ter renderizada, certamente com cada parte do recipiente global e geral confinando uma forma geométrica. 2.1.2 Aspectos globais - Arquivo: motor.cpp
// Representa o buffer de vértices IDirect3DVertexBuffer9* g_vbVertices = NULL; // Representa o buffer de índices IDirect3DIndexBuffer9* g_ibIndices = NULL;
IDirect3DVertexBuffer9* g_vbVertices = NULL; Esta é a interface que representa o buffer de vértices. Depois na aplicação segue o padrão do directx que já vimos: eles vão ser inicializados por uma função Create...() e depois dispensados com Release() no final da aplicação. IDirect3DIndexBuffer9* g_ibIndices = NULL; Essa é a interface que representa o buffer de índices. 2.2 Inicializando o motor gráfico Segue a listagem de initGfx() que chama as funções inicializar_Buffers() e montar_Geometria() para o trabalho de estabelecer o cenário inicial da aplicação.
// initGfx() - Inicializa o Direct3D HRESULT initGfx( HWND hJanela ) { // Cria o objeto Direct3D que é necessário para criar o dispositivo gráfico g_Direct3d = Direct3DCreate9( D3D_SDK_VERSION); // Verifica se o 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 transfere 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 inicializar_Buffers(); montar_Geometria(); return S_OK; } // initGfx().fim
2.3 Criação do buffer de vértices e do buffer de índices
void inicializar_Buffers(void) { // Segue abaixo os argumentos utilizados na criação do buffer // de vértices. Alguns deles aparecem também na criação do // buffer de índices. // Quantidade de vértices do vertexbuffer UINT nVertices = 4; // Tamanho do vertexbuffer em bytes UINT vbTamanho = sizeof(CustomVertex_TransformedColored) * nVertices; // Dica de uso. // É usado usado também na configuração do buffer de índices DWORD uso_flags = D3DUSAGE_WRITEONLY; // Formato do vértice dos vértices do vertexbuffer DWORD vbFormato = CustomVertex_TransformedColored_Format; // Definição da classe de memória (alojamento) // É usado também na configuração do buffer de índices D3DPOOL Piscina = D3DPOOL_MANAGED; // Argumento sem uso // Aparece também na criação do buffer de índices HANDLE* hSem_Uso = NULL; // Criação efetiva do buffer de vértices (vertexbuffer) g_hr = g_device->CreateVertexBuffer(vbTamanho, uso_flags, vbFormato, Piscina, &g_vbVertices, hSem_Uso); // Verifica falha na criação if(FAILED (g_hr) ) { MessageBox (NULL, "Falha na criação do buffer de vértices", "inicializar_Buffers()", MB_OK); return; } // endif // Quantidade de triângulos UINT nTriangulos = 2; // Cálculo do tamanho do buffer de índices UINT ibTamanho = nTriangulos * 3 * 2; // Formato do tipo de dado do buffer de índices D3DFORMAT ibFormato = D3DFMT_INDEX16; // Criação efetiva do buffer de índices g_hr = g_device->CreateIndexBuffer(ibTamanho, uso_flags, ibFormato, Piscina, &g_ibIndices, hSem_Uso); // Verifica falha na criação if(FAILED (g_hr) ) { MessageBox (NULL, "Falha na criação do buffer de vértices", "inicializar_Buffers()", MB_OK); return; } // endif } // inicializar_Buffers().fim
Segue abaixo os argumentos utilizados na criação do buffer de de vértices. Alguns deles aparecem também na criação do buffer de índices e os demais acabam guardando certa semelhança de finalidade. Vamos discutir primeiro os argumentos que são iguais para ambos: DWORD uso_flags = D3DUSAGE_WRITEONLY; O valor deste flag indica que o recipiente vai ser usado apenas para escrita. É uma das melhores configurações.Geralmente o recipiente vai ter o conteúdo escrito na configuração inicial e depois ficará aos cuidados exclusivos do directx. Geralmente você vai usar isso para cenários ou objetos 3d estáticos. Lembramos que este flag representa um dica de uso que a aplicação passa ao directx para que ele tome a a melhor providência de alojamento e gerenciamento do recurso. Essa melhor providência não é uma fatalidade e pode ser que devido aos limites do contexto o directx não consiga as condições ideais para o recurso. Outro valor comum para este flag é D3DUSAGE_DYNAMIC para recipientes dinâmicos que vão re-escrever ou adicionar elementos novos ao recipiente; um exemplo de uso é um sistema de partículas com as partículas sendo modificadas ou adicionadas gradualmente. // Definição da classe de memória (alojamento) // É usado também na configuração do buffer de índices D3DPOOL Piscina = D3DPOOL_MANAGED; A variável Piscina define a classe de memória para o alojamento do recurso. Esta opção, D3DPOOL_MANAGED, é uma das melhores opções para a maioria das aplicações pois é gerenciada pelo directx e de fácil acesso ao dispositivo renderizador. Outra vantagem dessa classe é que quando o dispositivo renderizador é perdido o próprio directx recupera os recursos que estão nesta classe de memória. O dispositivo renderizador pode ser perdido quando o usuário alterna o foco de atenção para outra aplicação. HANDLE* hSem_Uso = NULL; Este é o último argumento que coincide exatamente na criação dos dois recipientes. Este argumento não tem uso dentro do directx e existe apenas como reserva para o futuro. Seu valor deve ser sempre NULL. // Quantidade de vértices do vertexbuffer UINT nVertices = 4; // Tamanho do vertexbuffer em bytes UINT vbTamanho = sizeof(CustomVertex_TransformedColored) * nVertices; A função de criação dos recipientes necessita saber qual o tamanho que estes recipientes devem ter. Aqui é a preparação da informação do tamanho do vertexbuffer. // Formato do vértice dos vértices do vertexbuffer DWORD vbFormato = CustomVertex_TransformedColored_Format; A função de criação dos recipientes necessita saber qual o formato do tipo do dado principal dos recipientes. Aqui é a indicação do formato dos vértices que vão no vertexbuffer. g_hr = g_device->CreateVertexBuffer(vbTamanho, uso_flags, vbFormato, Piscina, &g_vbVertices, hSem_Uso); Aqui ocorre a criação efetiva do buffer de vértices (vertexbuffer). Agora vamos adentrar na configuração inicial do buffer de índices. Os argumentos Piscina, uso_flags e hSem_Uso já foram discutidos pois são exatamente os mesmos na mesma configuração ideal. Destacamos que seria mais natural configurar o buffer de índices em uma função própria para isso e dar a ele configurações diferentes conforme a necessidade da aplicação. Mas aqui aproximamos os dois para mostrar a estreita semelhança na criação. O mesmo vale para o acesso aos dados em montar_Geometria() e na rederização em renderizar_Geometria() que apresentam fatores em comuns no trabalho com estes recipientes. // Quantidade de triângulos UINT nTriangulos = 2; // Cálculo do tamanho do buffer de índices UINT ibTamanho = nTriangulos * 3 * 2; Aqui é a preparação da informação do tamanho do indexbuffer. O tamanho é obtido pela multiplicação sucessiva da quantidade de triângulos (nTriangulos) que vão ser montados pela ocupação de índices que cada triângulo toma (3) pelo tamanho em bytes (2) do tipo de dado desse índice ( D3DFMT_INDEX16 ). D3DFORMAT ibFormato = D3DFMT_INDEX16; Aqui é a definição do formato do tipo de dado do buffer de índices. Atualmente (2014) como é comum computadores e sistemas de 32 bits para melhor performance utilize sempre que puder formatos de 32bits onde for possível (D3DFMT_INDEX32). Naturalmente a placa aceleradora precisa suportar as configurações feitas. Aqui utilizamos o mínimo para rodar em qualquer placa de vídeo. g_hr = g_device->CreateIndexBuffer(ibTamanho, uso_flags, ibFormato, Piscina, &g_ibIndices, hSem_Uso); Aqui ocorre a criação efetiva do buffer de índices (indexbuffer). 2.4 Montagem da geometria
void montar_Geometria(void) { // Posicionamento de profundidade float zpos = 1.0f; // Segue abaixo os argumentos utilizados no acesso ao buffer // de vértices. Alguns deles aparecem também no acesso ao // buffer de índices. // Ponteiro de acesso aos dados do buffer de vértices CustomVertex_TransformedColored* pVerts; // nPosInicial e nQtd permite acesso parcial ao buffer de vértices // Ambos configurados como zero (0) indica acesso ao conteúdo total UINT nPosInicial = 0; UINT nQtd = 0; // Estabelece o modo de acesso ao buffer DWORD tranca_flags = 0; // nPosInicial, nQtd, tranca_flags também são utilizados no // acesso ao buffer de índices com os mesmos significados. // Aqui a aplicação ganha acesso à memória do buffer de vértices g_vbVertices->Lock( nPosInicial, nQtd, (void**) &pVerts, tranca_flags ); // Configuração normal dos vértices agora pVerts[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos, 0.7f, vermelho); pVerts[1] = CustomVertex_TransformedColored( 512.0f, 384.0f, zpos, 0.8f, verde); pVerts[2] = CustomVertex_TransformedColored( 128.0f, 384.0f, zpos, 1.0f, azul); pVerts[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos, 1.0f, cinza); // Buffer de índices local temporário WORD Indices[6] = {0, 1, 2, 0, 3, 1}; // Ponteiro de acesso aos dados do buffer de índices WORD *pIndices = NULL; // Aqui a aplicação ganha acesso à memória do buffer de índices g_ibIndices->Lock( nPosInicial, nQtd, (void**) &pIndices, tranca_flags ); // Configuração normal do buffer de índices // Copiando dados do buffer local para o indexbuffer definitivo for (int ncx=0; ncx <6; ncx++) pIndices[ncx] = Indices[ncx]; // Liberação dos buffers (vertexbuffer, indexbuffer) ao directx // que pode mandá-los ou não para a memória de vídeo da placa g_vbVertices->Unlock(); g_ibIndices->Unlock(); } // montar_Geometria().fim
Segue abaixo os argumentos utilizados no acesso ao buffer de de vértices. Alguns deles aparecem também no acesso ao buffer de índices e os demais acabam guardando certa semelhança de finalidade. Vamos discutir primeiro os argumentos que são iguais para ambos: UINT nPosInicial = 0; UINT nQtd = 0; nPosInicial e nQtd permite acesso parcial aos dados do recipiente. Ambos configurados como zero (0) indica que a aplicação quer acesso ao conteúdo total do recipiente. DWORD tranca_flags = 0; Esse flag estabelece a forma de acesso ao buffer. O zero (0) quer dizer que sua aplicação não se preocupa com ele. Valores comuns são D3DLOCK_READONLY ( apenas leitura ); D3DLOCK_NOOVERWRITE ( append ou adição de dados ) e D3DLOCK_DISCARD (totalmente descartável ) que só podem ser usados quando o uso do recipiente foi indicado como dinâmico ( D3DUSAGE_DYNAMIC ). CustomVertex_TransformedColored* pVerts; Aqui é o ponteiro que vai receber o endereço de acesso aos dados do vertexbuffer. g_vbVertices->Lock( nPosInicial, nQtd, (void**) &pVerts, tranca_flags ); Aqui a aplicação ganha acesso ao local de memória dos dados do recipiente. pVerts[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos, 0.7f, vermelho); // (...) pVerts[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos, 1.0f, cinza); Veja que na sequência segue a configuração normal dos vértices como se estivessem em uma array de usuário. Agora seguimos para a configuração do buffer de índices. WORD Indices[6] = {0, 1, 2, 0, 3, 1}; Este é o indexbuffer de usuário do tópico anterior cujos dados serão copiados para o indexbuffer do directx. WORD *pIndices = NULL; Aqui é o ponteiro que vai receber o endereço de acesso aos dados do indexbuffer. g_ibIndices->Lock( nPosInicial, nQtd, (void**) &pIndices, tranca_flags ); Aqui a aplicação ganha acesso ao local de memória dos dados do recipiente. Lembramos que para ambos os recipientes, nPosInicial, nQtd e tranca_flags foram todos configurados como zero (0) para acesso total e incondicional aos dados nesse momento inicial de configuração. for (int ncx=0; ncx <6; ncx++) pIndices[ncx] = Indices[ncx]; Aqui é a configuração normal do buffer de índices como se estivessem em uma array de usuário. Aqui copiamos os dados do buffer local para o indexbuffer definitivo. // Liberação dos buffers (vertexbuffer, indexbuffer) ao directx // que pode mandá-los ou não para a memória de vídeo da placa g_vbVertices->Unlock(); g_ibIndices->Unlock(); Aqui é uma operação fundamental: a liberação dos recipientes para uso exclusivo do directx e placa de vídeo. 2.5 Renderização da geometria
void renderizar_Geometria() { // Declara o formato de vértice utilizado pela aplicação g_device->SetFVF( CustomVertex_TransformedColored_Format); // Argumentos da função DrawIndexedPrimitive() // A finalidade destes 5 argumentos é permitir a renderização // parcial ou uma região localizada do vertexbuffer. // A configuração deste contexto é para renderizar totalmente // o buffer de vértices. UINT nVerticeBase = 0; UINT nIndiceInicial = 0; UINT nMinVertexIndice = 0; UINT nVerticeQtd = 4; UINT nContagemPrim = 2; // O passo (stride) permite ao directx navegar com segurança pelos // vértices apresentados. UINT nPasso = sizeof(CustomVertex_TransformedColored); // Indicação do stream fonte dos dados UINT idStream = 0; // Informação do buffer de vértices utilizado g_device->SetStreamSource( idStream, g_vbVertices, nVerticeBase, nPasso); // Informação do buffer de índices utilizado g_device->SetIndices( g_ibIndices); // Renderização efetiva dos vértices g_device->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, nVerticeBase, nMinVertexIndice, nVerticeQtd, nIndiceInicial, nContagemPrim); } // renderizar_Geometria().fim
// Declara o formato de vértice utilizado pela aplicação g_device->SetFVF( CustomVertex_TransformedColored_Format); Aqui é a configuração do formato de vértice que deve ser usado para renderizar o vertexbuffer apresentado. Esta configuração não precisa bater exatamente com a configuração dos vértices mas não pode ser mais volumosa que a configuração do formato dos vértices; não posso renderizar luz ou textura se o formato de vértice não trouxer essa informação mas posso escolher não renderizar esses dados se eles estiverem presentes. Agora discutiremos os argumentos da função DrawIndexedPrimitive(). UINT nVerticeBase = 0; UINT nIndiceInicial = 0; UINT nMinVertexIndice = 0; A finalidade destes argumentos é permitir que ocorra a renderização parcial do vertexbuffer.Como todos estão configurados como zero (0) que é o índice base de qualquer array indicamos que queremos renderizar totalmente o vertexbuffer. UINT nVerticeQtd = 4; UINT nContagemPrim = 2; Aqui indicamos a quantidade presente de vértices no vertexbuffer e quantas primitivas ( triângulos ) queremos produzir com eles. UINT nPasso = sizeof(CustomVertex_TransformedColored); O passo (stride) permite ao directx navegar com segurança pelos vértices apresentados usando a matemática dos ponteiros. // Indicação do stream fonte dos dados UINT idStream = 0; Este argumento é pouco esclarecido na documentação original do directx e ao longo de todo o nosso curso esse valor é zero (0). Ele indica um fluxo de dados que traz apenas um componente dos vértices tal como posicionamento ou apenas coordenadas de textura. // Informação do buffer de vértices utilizado g_device->SetStreamSource( idStream, g_vbVertices, nVerticeBase, nPasso); Aqui é indicado ao dispositivo renderizador o vertexbuffer de trabalho. g_device->SetIndices( g_ibIndices); Aqui é indicado ao dispositivo renderizador o indexbuffer de trabalho. g_device->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, nVerticeBase, nMinVertexIndice, nVerticeQtd, nIndiceInicial, nContagemPrim); Aqui ocorre a renderização efetiva do vertexbuffer. 3. Código fonte do projeto de exemplo: prj_Buffers
// Projeto: prj_Buffers - Arquivo: motor.h // Esta aplicação ilustra como renderizar um quadrado // utilizando as interfaces do directx para buffer de // índices e buffer de vértices. By www.gameprog.com.br #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 inicializa o buffer de índices e // o buffer de vértices void inicializar_Buffers(void); // 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_Buffers - arquivo: motor.cpp // Esta aplicação ilustra como renderizar um quadrado // utilizando as interfaces do directx para buffer de // índices e buffer de vértices. By 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; // Representa o buffer de vértices IDirect3DVertexBuffer9* g_vbVertices = NULL; // Representa o buffer de índices IDirect3DIndexBuffer9* g_ibIndices = NULL; // Essa variável recebe informação de erro do Directx HRESULT g_hr = 0; // Tamanho da janela 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 // initGfx() - Inicializa o Direct3D HRESULT initGfx( HWND hJanela ) { // Cria o objeto Direct3D que é necessário para criar o dispositivo gráfico g_Direct3d = Direct3DCreate9( D3D_SDK_VERSION); // Verifica se o 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 transfere 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 inicializar_Buffers(); montar_Geometria(); return S_OK; } // initGfx().fim void inicializar_Buffers(void) { // Segue abaixo os argumentos utilizados na criação do buffer // de vértices. Alguns deles aparecem também na criação do // buffer de índices. // Quantidade de vértices do vertexbuffer UINT nVertices = 4; // Tamanho do vertexbuffer em bytes UINT vbTamanho = sizeof(CustomVertex_TransformedColored) * nVertices; // Dica de uso. // É usado usado também na configuração do buffer de índices DWORD uso_flags = D3DUSAGE_WRITEONLY; // Formato do vértice dos vértices do vertexbuffer DWORD vbFormato = CustomVertex_TransformedColored_Format; // Definição da classe de memória (alojamento) // É usado também na configuração do buffer de índices D3DPOOL Piscina = D3DPOOL_MANAGED; // Argumento sem uso // Aparece também na criação do buffer de índices HANDLE* hSem_Uso = NULL; // Criação efetiva do buffer de vértices (vertexbuffer) g_hr = g_device->CreateVertexBuffer(vbTamanho, uso_flags, vbFormato, Piscina, &g_vbVertices, hSem_Uso); // Verifica falha na criação if(FAILED (g_hr) ) { MessageBox (NULL, "Falha na criação do buffer de vértices", "inicializar_Buffers()", MB_OK); return; } // endif // Quantidade de triângulos UINT nTriangulos = 2; // Cálculo do tamanho do buffer de índices UINT ibTamanho = nTriangulos * 3 * 2; // Formato do tipo de dado do buffer de índices D3DFORMAT ibFormato = D3DFMT_INDEX16; // Criação efetiva do buffer de índices g_hr = g_device->CreateIndexBuffer(ibTamanho, uso_flags, ibFormato, Piscina, &g_ibIndices, hSem_Uso); // Verifica falha na criação if(FAILED (g_hr) ) { MessageBox (NULL, "Falha na criação do buffer de vértices", "inicializar_Buffers()", MB_OK); return; } // endif } // inicializar_Buffers().fim void montar_Geometria(void) { // Posicionamento de profundidade float zpos = 1.0f; // Segue abaixo os argumentos utilizados no acesso ao buffer // de vértices. Alguns deles aparecem também no acesso ao // buffer de índices. // Ponteiro de acesso aos dados do buffer de vértices CustomVertex_TransformedColored* pVerts; // nPosInicial e nQtd permite acesso parcial ao buffer de vértices // Ambos configurados como zero (0) indica acesso ao conteúdo total UINT nPosInicial = 0; UINT nQtd = 0; // Estabelece o modo de acesso ao buffer DWORD tranca_flags = 0; // nPosInicial, nQtd, tranca_flags também são utilizados no // acesso ao buffer de índices com os mesmos significados. // Aqui a aplicação ganha acesso à memória do buffer de vértices g_vbVertices->Lock( nPosInicial, nQtd, (void**) &pVerts, tranca_flags ); // Configuração normal dos vértices agora pVerts[0] = CustomVertex_TransformedColored( 128.0f, 50.0f, zpos, 0.7f, vermelho); pVerts[1] = CustomVertex_TransformedColored( 512.0f, 384.0f, zpos, 0.8f, verde); pVerts[2] = CustomVertex_TransformedColored( 128.0f, 384.0f, zpos, 1.0f, azul); pVerts[3] = CustomVertex_TransformedColored( 512.0f, 50.0f, zpos, 1.0f, cinza); // Buffer de índices local temporário WORD Indices[6] = {0, 1, 2, 0, 3, 1}; // Ponteiro de acesso aos dados do buffer de índices WORD *pIndices = NULL; // Aqui a aplicação ganha acesso à memória do buffer de índices g_ibIndices->Lock( nPosInicial, nQtd, (void**) &pIndices, tranca_flags ); // Configuração normal do buffer de índices // Copiando dados do buffer local para o indexbuffer definitivo for (int ncx=0; ncx <6; ncx++) pIndices[ncx] = Indices[ncx]; // Liberação dos buffers (vertexbuffer, indexbuffer) ao directx // que pode mandá-los ou não para a memória de vídeo da placa g_vbVertices->Unlock(); g_ibIndices->Unlock(); } // 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 DrawIndexedPrimitive() // A finalidade destes 5 argumentos é permitir a renderização // parcial ou uma região localizada do vertexbuffer. // A configuração deste contexto é para renderizar totalmente // o buffer de vértices. UINT nVerticeBase = 0; UINT nIndiceInicial = 0; UINT nMinVertexIndice = 0; UINT nVerticeQtd = 4; UINT nContagemPrim = 2; // O passo (stride) permite ao directx navegar com segurança pelos // vértices apresentados. UINT nPasso = sizeof(CustomVertex_TransformedColored); // Indicação do stream fonte dos dados UINT idStream = 0; // Informação do buffer de vértices utilizado g_device->SetStreamSource( idStream, g_vbVertices, nVerticeBase, nPasso); // Informação do buffer de índices utilizado g_device->SetIndices( g_ibIndices); // Renderização efetiva dos vértices g_device->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, nVerticeBase, nMinVertexIndice, nVerticeQtd, nIndiceInicial, nContagemPrim); } // 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 buffer de vértices if( g_vbVertices != NULL) g_vbVertices->Release(); // Libera o buffer de índices if( g_ibIndices != NULL) g_ibIndices->Release(); // 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_Buffers - arquivo: entrada.cpp // Esta aplicação ilustra como renderizar um quadrado // utilizando as interfaces do directx para buffer de // índices e buffer de vértices. By 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_Buffers"; 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