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

index << >>


03.1 Triângulo iluminado

1.1 Visão geral
Na computação gráfica de forma geral o processo de aplicação de luz necessita de um ponto de apoio que indique o local de entrada da luz na superfície a ser iluminada. Esse ponto de apoio é chamado de normal e matematicamente é representado por um vetor 3d, geralmente desenhado como um raio perpendicular entrando ou saindo da superfície.
Formato de vértice Vamos utilizar na aplicação de exemplo o formato de vértice que permite inserir a posição da normal para cada vértice. Na ilustração acima todos os pontos p0, p1, p2 recebem o ponto g_normal que indica a posição da normal utilizada na iluminação do triângulo. A posição g_normal está localizada perto de um ponto paralelo ou perpendicular ao ponto central da face triângulo. Geralmente as aplicações de modelagem 3d já exportam junto com a geometria do modelo 3d as coordenadas das normais dos vértices, mas na ausência dessa informação o directx tem uma função para calculá-la. Materiais A iluminação básica da cena é uma combinação de dois fatores: a luz em contato com o material da superfície a ser iluminada. O directx geralmente possui uma coleção de luzes pré-existentes guardadas em uma array de 6 a 8 luzes conforme os limites variáveis das diversas placas de vídeo. Essas luzes são configuradas através da alteração das propriedades contidas na estrutura D3DLIGHT9 que depois é passada para o dispositivo renderizador. A aplicação de materiais na cena é realizada de forma semelhante através da configuração das propriedades da estrutura D3DMATERIAL9. Formato de cor A composição da cor utilizada na configuração de luzes e materiais é feita através do preenchimento da estrutura D3DCOLORVALUE na qual os componentes rgba são expressos em percentuais entre 0.0f e 1.0f. Certamente que depois, em algum processo interno, estes percentuais são multiplicados pelo valor 255 para se chegar no valor real do componente de cor. Ajuda visual Criamos uma função para colocar uma esfera dentro de um ponto 3d com uma cor especificada como argumento. Isso é para ajudar na percepção concreta do posicionamento dos vértices no espaço 3d. Utilizamos essa função para assinalar aproximadamente a posição aonde a luz foi colocada com uma esfera amarela e utilizamos uma esfera verde para indicar a posição da normal (g_normal) que foi projetada na frente do triângulo. Tipo de luz Luz difusa: esse tipo de luz viaja numa direção particular e quando atinge uma superfície se reflete igualmente para todas as direções. Devido ao fato dessa luz ser refletida para todas as direções é certeza que ela atinge o olho do espectador local não importando o seu ponto de vista. Com esse contexto, a implementação desse tipo de luz não leva em consideração a posição do espectador, considerando apenas a direção da luz e as propriedades da superfície atingida que vão impactar na quantidade, intensidade e direção dos raios refletidos. Geralmente a luz difusa representa o maior volume primário de luz emitido por uma fonte de luz. Luz ambiente: Esse tipo representa o tipo secundário de iluminação e consiste no uso dos raios refletidos da luz primária por outras superfícies para iluminar a cena de maneira geral. Geralmente, objetos ou partes de objetos ainda que não estejam em contato direto com a fonte de luz primária são iluminados por esta luz secundária cujos raios vieram rebatidos por outras superfícies. Luz especular: Esse tipo de luz surge quando uma determinada parte de uma superfície reflete para uma mesma direção os raios de uma fonte de luz causando um clarão ou um brilho muito intenso na região. A percepção desse tipo de luz ocorre apenas dentro de um certo ponto de vista do espectador com relação ao objeto e a fonte de luz. Esse brilho é comum aparecer em superfícies esféricas bem polidas como bolas de boliche, metal polido, bolinhas de natal etc. Tipos de fonte de luz O directx simula a iluminação da cena através de 3 tipos de fontes de luz: - Luz apontada (PointLight)
Essa fonte de luz representa a luz que se espalha para todas as direções. Geralmente, aquela lâmpada doméstica em forma de pêra, é um bom exemplo de luz que se irradia para todas as direções. - Luz direcionada (DirectionalLight)
Essa luz tem direção mas não tem posição no espaço. Um exemplo de emanação dessa luz é o sol cuja direção da luz se modifica ao passar das horas. É claro que o sol tem uma posição relativa no espaço mas dentro de uma grande região iluminada é a direção tomada pelo raio de luz que mais importa. - Holofote (SpotLight)
Esse tipo de fonte de luz simula um holofote que ilumina fortemente uma região circular da cena. Todos esses três tipos de fonte de luz são obtidos através da configuração particular dos elementos da estrutura D3DLIGHT9. A aplicação desse tópico vai mostrar um exemplo com o tipo de luz apontada (PointLight) mas ao longo do curso você vai ver amostras de uso da luz ambiente e de luz direcionada que aumenta a beleza visual da cena. 1.2 Estrutura principal da aplicação
Arquivo: motor.cpp
criarMaterial() limpa a estrutura de material recebida ( D3DMATERIAL9 ) configura cor difusa e ambiente com a cor recebida ( D3DCOLORVALUE ) colocar_PointLight() prepara uma estrutura de luz ( D3DLIGHT9 ) configura tipo da luz para pointlight ( D3DLIGHT_POINT ) configura posição da luz com posição recebida ( D3DXVECTOR3 ) configura cor da luz com a cor recebida ( D3DCOLORVALUE ) configura fator de atenuação e faixa de alcance da luz Instala a luz configurada no dispositivo Liga a luz. calcularNormal() Recebe 3 vértices e projeta na frente deles um vértice perpendicular montar_Geometria() calcula a normal para os vértices com calcularNormal() configura posição dos vértices initGfx() chama montar_Geometria() para configurar os vértices chama inicializar_Camera() para configurar a câmera cria um material vermelho para o triângulo com criarMaterial() coloca uma luz branca na cena com colocar_PointLight() ajuda_visual() Renderiza um esfera com a cor e posição especificadas Isso envolve: criar material para o objeto com criarMaterial() criar uma matriz de mundo particular para posicionar a esfera criar um objeto mesh do tipo ID3DXMesh posicionar o objeto no mundo 3d com D3DXMatrixTranslation() renderizar o objeto no mundo 3d com ID3DXMesh->DrawSubset(0) liberar o objeto ID3DXMesh utilizado renderizar_Geometria() Declara o formato de vértice utilizado: CustomVertex_PositionNormal_Format Renderiza os vértices com g_device->DrawPrimitiveUP() desenha esfera de ajuda visual para identificação espacial de posicionamento atualizar_Camera() atualiza ângulo de rotação atualiza matriz mundial com ângulo de rotação joga matriz mundial no dispositivo renderizador configurar_cenaEstados() Habilita o uso de iluminação própria da aplicação configura alguns estados referentes à iluminação configura o material vermelho no dispositivo Renderizar() configura alguns estados do dispositivo com configurar_cenaEstados() chama atualizar_Camera() para girar o triângulo 3d chama renderizar_Geometria() para desenhar o triângulo
2.1.1 Aspectos globais - motor.h
// Projeto: prj_Luz - Arquivo: motor.h // Esta aplicação ilustra como iluminar um triângulo // 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 monta formas geométricas void montar_Geometria (void); // Configura a câmera 3d da cena void inicializar_Camera(void); // Atualiza a câmera void atualizar_Camera(void); // Renderiza os vértices em formas geométricas void renderizar_Geometria (void); // Configura alguns estados de renderização void configurar_cenaEstados(void); // Cria um material void criarMaterial(D3DMATERIAL9 *mtl, D3DCOLORVALUE cvCor); // Configura e coloca uma luz pointlight na cena void colocar_PointLight(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor); // Faz o cálculo da normal para iluminar o triângulo void calcularNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2, D3DXVECTOR3* pSaida); // Ajuda a visualizar a localização do ponto 3d void ajuda_visual(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor); // Declaração da função que atende as mensagens da janela LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam); #endif
void configurar_cenaEstados(void); Essa função configura os estados do dispositivo renderizador, tais como o culling, material e estados ligados com a iluminação da cena. void criarMaterial(D3DMATERIAL9 *mtl, D3DCOLORVALUE cvCor); Essa função realiza o trabalho simples de limpar e configurar uma estrutura de material. void colocar_PointLight(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor); Essa função configura e coloca uma luz do tipo pointlight na cena. void calcularNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2, D3DXVECTOR3* pSaida); Essa função realiza o cálculo da normal. É utilizada para configurar o vetor 3d g_normal que na sequência é adicionado a cada vértice do triângulo. void ajuda_visual(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor); Essa função renderiza uma esfera colorida em um ponto 3d dando assim um aspecto concreto e visual desse ponto na tela. Isso facilita o entendimento de localizações no espaço 3d. É usada para marcar o local de colocação do ponto g_normal e do ponto aproximado de colocação da luz. 2.1.2 Aspectos globais - Arquivo: motor.cpp
// Constante para cores const DWORD azul = 0xFF0000FF; // Constante para cores com rgba expressado como float's const D3DCOLORVALUE cvBranco = { 1.0f, 1.0f, 1.0f, 1.0f }; const D3DCOLORVALUE cvVermelho = { 1.0f, 0.0f, 0.0f, 1.0f }; const D3DCOLORVALUE cvAmarelo = { 1.0f, 1.0f, 0.0f, 1.0f }; const D3DCOLORVALUE cvVerde = { 0.0f, 1.0f, 0.0f, 1.0f }; // Variável para o material D3DMATERIAL9 g_mtlVermelho; // Indicação de entrada de luz D3DXVECTOR3 g_normal; // Definição do formato de vértice utilizado pela aplicação #define CustomVertex_PositionNormal_Format( D3DFVF_XYZ | D3DFVF_NORMAL) // Definição da estrutura de vértice customizado struct CustomVertex_PositionNormal { float x, y, z; float nx, ny, nz; // Construtor default CustomVertex_PositionNormal() {} // Construtor com inicialização CustomVertex_PositionNormal( float _x, float _y, float _z, float _nx, float _ny, float _nz) { // entrada da posição x = _x; y = _y; z = _z; // entrada da Normal nx = _nx; ny = _ny; nz = _nz; } // fim do construtor }; // fim da estrutura CustomVertex_PositionNormal // Declaração dos vértices para o triângulo CustomVertex_PositionNormal g_Vertices[3 ];
D3DXVECTOR3 g_normal; Este aqui é o vetor 3d para a normal que apoia a iluminação do triângulo. const D3DCOLORVALUE cvBranco = { 1.0f, 1.0f, 1.0f, 1.0f }; Esta é uma outra maneira de definir uma cor dentro do directx através do uso da estrutura D3DCOLORVALUE cujos elementos são todos do tipo float e devem estar na faixa de valor entre 0.0f até 1.0f. Estes valores representam percentuais do componente final da cor. Esta forma de expressão de cor está muito ligada com as teorias científicas da cor e da interação da luz com os materiais. D3DMATERIAL9 g_mtlVermelho; Aqui definimos uma instância da estrutura D3DMATERIAL9 para produzir um material vermelho para colorir o triângulo. Esta estrutura está definida desta forma dentro do arquivo d3d9types.h:
typedef struct _D3DMATERIAL9 { D3DCOLORVALUE Diffuse; // Cor difusa ARGB D3DCOLORVALUE Ambient; // Cor ambiente ARGB D3DCOLORVALUE Specular; // Brilho especular ARGB D3DCOLORVALUE Emissive; // Cor de emissão ARGG float Power; // Taxa de definição (sharpness) do brilho especular } D3DMATERIAL9;
Diffuse - Representa a cor normal do objeto diante da luz principal Ambient - Cor que o objeto emite na presença da luz ambiente. A luz ambiente é uma luz secundária formada pelos raios da luz principal que foram refletidos por outros objetos da cena. Geralmente esses dois elementos são configurados com a mesma cor. Power - Fator que controla a definição visual do brilho especular. Specular - Brilho especular. É aquela bolinha brilhante que fica sobre uma bola de boliche ou de uma superfície redonda bem polida. Emissive - É utilizada para simular superfícies auto-iluminadas como neon, lâmpadas etc. #define CustomVertex_PositionNormal_Format( D3DFVF_XYZ | D3DFVF_NORMAL ) Esta linha é a definição do formato de vértice que comporta posição e o vetor 3d normal. Veja abaixo a estrutura de implementação:
// Definição da estrutura de vértice customizado struct CustomVertex_PositionNormal { float x, y, z; float nx, ny, nz; // Construtor default CustomVertex_PositionNormal() {} // Construtor com inicialização CustomVertex_PositionNormal( float _x, float _y, float _z, float _nx, float _ny, float _nz) { // entrada da posição x = _x; y = _y; z = _z; // entrada da Normal nx = _nx; ny = _ny; nz = _nz; } // fim do construtor }; // fim da estrutura CustomVertex_PositionNormal
CustomVertex_PositionNormal g_Vertices[3]; Aqui declaramos 3 vértices para produzir o triângulo iluminado. 2.2 Inicialização do motor gráfico
// 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 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 // Monta as formas geométricas montar_Geometria(); // Faz a configuração inicial da câmera inicializar_Camera(); // Cria um material vermelho para o triângulo criarMaterial(&g_mtlVermelho, cvVermelho); // Configura e coloca uma luz na cena D3DXVECTOR3 pos(2.0f, 1.0f, -4.0f); colocar_PointLight (pos, cvBranco); return S_OK; } // initGfx().fim
montar_Geometria(); Antes de montar o triângulo esta função chama calcularNormal() para obter um valor adequado para o vetor 3d g_normal. criarMaterial(&g_mtlVermelho, cvVermelho); Cria um material vermelho para o triângulo. Isto envolve simplesmente limpar a estrutura do material e configurar os elementos Diffuse e Ambient para cvVermelho. D3DXVECTOR3 pos(2.0f, 1.0f, -4.0f); Definimos aqui a posição da luz. Utilizamos a função ajuda_visual() para dar uma noção aproximada mas concreta da localização deste ponto na tela. colocar_PointLight (pos, cvBranco); Esta função configura e coloca uma luz do tipo pointlight na cena. 2.3 Configuração da luz do tipo pointlight
void colocar_PointLight(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor) { // Declara a estrutura D3DLIGHT9 luz; // Limpa a estrutura ZeroMemory( &luz, sizeof(D3DLIGHT9) ); // Configura tipo luz.Type = D3DLIGHT_POINT; // Configura cor luz.Diffuse = cvCor; // Configura posição luz.Position = pos; // Configura fator de atenuação luz.Attenuation0 = 0.2f; // Configura faixa de alcance luz.Range = 100.0f; // Instalação e acionamento da luz no dispositivo renderizador g_device->SetLight( 0, &luz ); g_device->LightEnable( 0, TRUE); } // colocar_PointLight().fim
// Declara a estrutura D3DLIGHT9 luz; Aqui declaramos a estrutura utilizada na criação da fonte de luz. ZeroMemory( &luz, sizeof(D3DLIGHT9) ); Aqui é o processo rotineiro de limpar a estrutura que vai ser utilizada. luz.Type = D3DLIGHT_POINT; Aqui o tipo de luz pointlight é estabelecido. // Configura cor luz.Diffuse = cvCor; // Configura posição luz.Position = pos; Aqui ocorre a configuração da cor e da posição da luz pointlight. // Configura fator de atenuação luz.Attenuation0 = 0.2f; Essa linha configura o primeiro fator de atenuação da luz em função da distância percorrida. Esse elemento junto com os outros dois similares é utilizado para simular o esmaecimento da intensidade da luz à medida em que o raio de luz está mais longe da fonte de emissão. // Configura faixa de alcance luz.Range = 100.0f; Este elemento configura a faixa de alcance da luz. Depois dessa distância a luz não tem mais efeito. // Instalação e acionamento da luz no dispositivo renderizador g_device->SetLight( 0, &luz ); Esta linha instala a luz no dispositivo. Você pode ter centenas de luzes pré-configuradas mas pode ter apenas algumas luzes ativas simultâneamente cujo número vai depender do limite dado pela placa de vídeo. É comum este número máximo ficar abaixo de 8 luzes. A iluminação deve ser usada sabiamente pois é um recurso que demanda bastante tempo para ser calculado. g_device->LightEnable( 0, TRUE); Aqui a luz é ligada. Desligue a luz quando for fazer algum trabalho de reconfiguração de algum aspecto da luz. 2.4 Elaboração de materiais
void criarMaterial(D3DMATERIAL9 *mtl, D3DCOLORVALUE cvCor ) { // Limpa a estrutura ZeroMemory( mtl, sizeof(D3DMATERIAL9) ); // Configura cor ambiente e difusa mtl->Ambient = cvCor; mtl->Diffuse = cvCor; } // criarMaterial().fim
// Configura cor ambiente e difusa mtl->Ambient = cvCor; mtl->Diffuse = cvCor; Essa função configura apenas os elementos Diffuse e Ambient do material para a cor especificada. Uma dica legal é escrever uma cópia dessa função configurando os outros elementos do material. 2.5 Ajuda visual
void ajuda_visual(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor) { // Variável de controle do objeto 3d ID3DXMesh* obj3d; // Matriz mundo para posicionamento do objeto 3d D3DXMATRIX mtxMundo; D3DXMatrixIdentity( &mtxMundo ); // Configuração do material do objeto 3d D3DMATERIAL9 mtl; criarMaterial(&mtl, cvCor); // Cria o objeto 3d float nRaio = 0.2f; UINT nSegs = 16; D3DXCreateSphere(g_device, nRaio, nSegs, nSegs, &obj3d, 0); // Configura posição do objeto no mundo 3d D3DXMatrixTranslation(&mtxMundo, pos.x, pos.y, pos.z); g_device->SetTransform(D3DTS_WORLD, &mtxMundo); // Configura material g_device->SetMaterial (&mtl); // Renderiza o objeto obj3d->DrawSubset(0); // Libera o objeto utilizado obj3d->Release(); } // void ajuda_visual().fim
// Variável de controle do objeto 3d ID3DXMesh* obj3d; Esta é a interface do directx para lidar com objetos 3d ou modelos 3d mais elaborados. Geralmente o modelo 3d é chamado de mesh que pode ser traduzido como malha em língua portuguesa. O ponteiro obj3d vai ser usado para receber a geometria de uma esfera 3d. // Matriz mundo para posicionamento do objeto 3d D3DXMATRIX mtxMundo; D3DXMatrixIdentity( &mtxMundo ); Aqui definimos uma matriz de mundo para posicionar a esfera na tela. // Configuração do material do objeto 3d D3DMATERIAL9 mtl; criarMaterial(&mtl, cvCor); Aqui é o código que aplica o material colorido na esfera. // Cria o objeto 3d float nRaio = 0.2f; UINT nSegs = 16; D3DXCreateSphere(g_device, nRaio, nSegs, nSegs, &obj3d, 0); Este bloco de código faz a criação da esfera 3d com as definições de raio ( nRaio ) e de segmentação horizontal e vertical ( nSegs ) que passamos. // Configura posição do objeto no mundo 3d D3DXMatrixTranslation(&mtxMundo, pos.x, pos.y, pos.z); g_device->SetTransform(D3DTS_WORLD, &mtxMundo); Aqui é um bom exemplo de uso da função D3DXMatrixTranslation() que posiciona a esfera no ponto 3d especificado. // Configura material g_device->SetMaterial (&mtl); Configuramos aqui a esfera com um material colorido. // Renderiza o objeto obj3d->DrawSubset(0); A esfera e os modelos 3d em geral são renderizados com o método DrawSubset() e o número da peça do modelo (0) a ser renderizada. Cada parte de um modelo 3d é chamada de subset dentro do directx. Os objetos formados de uma só parte, como é o caso da esfera, são renderizados com zero (0) em DrawSubset(). // Libera o objeto utilizado obj3d->Release(); Depois da esfera renderizada liberamos o objeto no final da função. 2.6 Cálculo da normal
void calcularNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2, D3DXVECTOR3* pSaida) { D3DXVECTOR3 u = *p1 - *p0; D3DXVECTOR3 v = *p2 - *p0; D3DXVec3Cross(pSaida, &u, &v); D3DXVec3Normalize(pSaida, pSaida); } // calcularNormal().fim
O aspecto mais importante a saber dessa função é que ela recebe 3 vértices (p0, p1, p2) e retorna um vértice perpendicular (pSaida) aos vértices fornecidos. Poderíamos ter fornecido diretamente o valor da normal sem a presença dessa função, mas consideramos que esta função pode ser útil em outros contextos dentro da programação de jogos para localizar ou projetar pontos na frente de uma superfície. Não vamos discutir os aspectos de matemática envolvidos para não avolumar este tópico. Vamos deixar apenas recomendações de pesquisa com as palavras chaves em inglês e português. A temática envolvida está toda dentro de vetores matemáticos. D3DXVECTOR3 u = *p1 - *p0; D3DXVECTOR3 v = *p2 - *p0; Pesquise por subtração de vetores ou vector subtraction D3DXVec3Cross(pSaida, &u, &v); Pesquise por produto cruzado de vetores ou vector cross product D3DXVec3Normalize(pSaida, pSaida); Pesquise por normalização de vetores ou normalizing vectors. 2.7 Configuração de estados do dispositivo renderizador
void configurar_cenaEstados(void) { // Mostra a parte interna do polígono g_device->SetRenderState (D3DRS_CULLMODE, D3DCULL_NONE); // Habilita iluminação g_device->SetRenderState (D3DRS_LIGHTING, true); // Aplica o material no dispositivo renderizador g_device->SetMaterial(&g_mtlVermelho); // Corrige as normais g_device->SetRenderState(D3DRS_NORMALIZENORMALS, true); // Desabilita cálculo de luz especular g_device->SetRenderState(D3DRS_SPECULARENABLE, false); } // configurar_cenaEstados().fim
g_device->SetRenderState (D3DRS_LIGHTING, true); Depois que uma luz foi colocada na cena é necessário habilitar a iluminação configurando esse estado para true. g_device->SetMaterial(&g_mtlVermelho); Esta linha configura o material no dispositivo. Depois dessa linha tudo que for renderizado ganha a aplicação desse material. g_device->SetRenderState(D3DRS_NORMALIZENORMALS, true); Essa linha indica ao directx fazer uma correção nas normais que podem perder a sintonia com os vértices durante as transformações. g_device->SetRenderState(D3DRS_SPECULARENABLE, false); O cálculo de luz especular é dispendioso. Esta linha desliga a aplicação de luz especular na cena. Habilite este estado para true quando for usar o efeito de brilho especular que também deve estar presente na configuração de luz e do material. 2.8 Montagem do triângulo iluminado
void montar_Geometria(void) { // Posicionamento de profundidade float zpos = 0.0f; // Faz o cálculo da normal para configurar os vértices calcularNormal (&D3DXVECTOR3(0.0f, 1.0f, zpos), &D3DXVECTOR3(-1.0f, -1.0f, zpos), &D3DXVECTOR3(1.0f, -1.0f, zpos), &g_normal); // p0 - primeiro vértice g_Vertices[0] = CustomVertex_PositionNormal( 0.0f, 1.0f, zpos, g_normal.x, g_normal.y, g_normal.z); // p1 - segundo vértice g_Vertices[1] = CustomVertex_PositionNormal( -1.0f, -1.0f, zpos, g_normal.x, g_normal.y, g_normal.z); // p2 - terceiro vértice g_Vertices[2] = CustomVertex_PositionNormal( 1.0f, -1.0f, zpos, g_normal.x, g_normal.y, g_normal.z); } // montar_Geometria().fim
calcularNormal (&D3DXVECTOR3(0.0f, 1.0f, zpos), &D3DXVECTOR3(-1.0f, -1.0f, zpos), &D3DXVECTOR3(1.0f, -1.0f, zpos), &g_normal); Aqui a função calcularNormal() utilizando os valores de [x,y,z] presentes nos vértices calcula o valor adequado para o vértice 3d g_normal. // (...) // p2 - terceiro vértice g_Vertices[2] = CustomVertex_PositionNormal( 1.0f, -1.0f, zpos, g_normal.x, g_normal.y, g_normal.z); Na sequência os vértices são configurados adequadamente com a coordenada [x,y,z] de posição e com a coordenada [x,y,z] da normal. 2.9 Renderização do triângulo
void renderizar_Geometria() { // Configura formato de vértice no dispositivo g_device->SetFVF( CustomVertex_PositionNormal_Format); // Quantidade de primitivas possíveis de acordo com o uso de vértices UINT nContagem = 1; // Tamanho do passo para achar o próximo vértice UINT nPasso = sizeof(CustomVertex_PositionNormal); // Desenha forma geométrica g_device->DrawPrimitiveUP( D3DPT_TRIANGLELIST, nContagem, &g_Vertices, nPasso); // Mostra localização aproximada da luz ajuda_visual( D3DXVECTOR3(1.5f, 0.5f, -3.5f), cvAmarelo); // Mostra posição da normal ajuda_visual( g_normal, cvVerde); } // renderizar_Geometria().fim
// Configura formato de vértice no dispositivo g_device->SetFVF( CustomVertex_PositionNormal_Format); // Tamanho do passo para achar o próximo vértice UINT nPasso = sizeof(CustomVertex_PositionNormal); Repare que a cada mudança no formato de vértice essa função é reajustada para se adequar ao formato de vértice utilizado na configuração dos vértices. // Mostra localização aproximada da luz ajuda_visual( D3DXVECTOR3(1.5f, 0.5f, -3.5f), cvAmarelo); // Mostra posição da normal ajuda_visual( g_normal, cvVerde); A maior novidade dessa função é a função auxiliar ajuda_visual() que grafa na tela a posição aproximada da colocação da luz e a posição da normal. O espaçamento dos pontos no mundo 3d se modifica quando se muda o tamanho do frustrum da câmera. Então para se acostumar com um padrão de espaçamento é necessário fixar um tamanho padrão para a área do frustrum. 2.10 Renderização da cena A função Renderizar() está bem modularizada com o destaque da função configurar_cenaEstados() que configura os estados do dispositivo renderizador interessantes para a aplicação.
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, azul, 1.0f, 0); // Configure estados de renderização configurar_cenaEstados(); // Atualize a câmera atualizar_Camera(); // Começa a cena if( SUCCEEDED( g_device->BeginScene() ) ) { // Vamos renderizar as formas geométricas 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
3. Código fonte do projeto de exemplo: prj_Luz
// Projeto: prj_Luz - Arquivo: motor.h // Esta aplicação ilustra como iluminar um triângulo // 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 monta formas geométricas void montar_Geometria (void); // Configura a câmera 3d da cena void inicializar_Camera(void); // Atualiza a câmera void atualizar_Camera(void); // Renderiza os vértices em formas geométricas void renderizar_Geometria (void); // Configura alguns estados de renderização void configurar_cenaEstados(void); // Cria um material void criarMaterial(D3DMATERIAL9 *mtl, D3DCOLORVALUE cvCor); // Configura e coloca uma luz pointlight na cena void colocar_PointLight(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor); // Faz o cálculo da normal para iluminar o triângulo void calcularNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2, D3DXVECTOR3* pSaida); // Ajuda a visualizar a localização do ponto 3d void ajuda_visual(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor); // 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_Luz - arquivo: motor.cpp // Esta aplicação ilustra como iluminar um triângulo // 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; // Essa variável recebe informação de erro do Directx HRESULT g_hr = 0; // Variáveis para controlar a rotação do triângulo float g_Angulo = 0.0f; float nVelocidade = 0.1f; // Matrizes de configuração da câmera // Matriz de mundo D3DXMATRIX g_mtxMundo; // Matriz de visão D3DXMATRIX g_mtxVisao; // Matriz de projeção D3DXMATRIX g_mtxProj; // Tamanho da janela extern int g_xtela; extern int g_ytela; // Constante para cores const DWORD azul = 0xFF0000FF; // Constante para cores com rgba expressado como float's const D3DCOLORVALUE cvBranco = { 1.0f, 1.0f, 1.0f, 1.0f }; const D3DCOLORVALUE cvVermelho = { 1.0f, 0.0f, 0.0f, 1.0f }; const D3DCOLORVALUE cvAmarelo = { 1.0f, 1.0f, 0.0f, 1.0f }; const D3DCOLORVALUE cvVerde = { 0.0f, 1.0f, 0.0f, 1.0f }; // Variável para o material D3DMATERIAL9 g_mtlVermelho; // Indicação de entrada de luz D3DXVECTOR3 g_normal; // Definição do formato de vértice utilizado pela aplicação #define CustomVertex_PositionNormal_Format(D3DFVF_XYZ | D3DFVF_NORMAL) // Definição da estrutura de vértice customizado struct CustomVertex_PositionNormal { float x, y, z; float nx, ny, nz; // Construtor default CustomVertex_PositionNormal() {} // Construtor com inicialização CustomVertex_PositionNormal( float _x, float _y, float _z, float _nx, float _ny, float _nz) { // entrada da posição x = _x; y = _y; z = _z; // entrada da Normal nx = _nx; ny = _ny; nz = _nz; } // fim do construtor }; // fim da estrutura CustomVertex_PositionNormal // Declaração dos vértices para o triângulo CustomVertex_PositionNormal g_Vertices[3 ]; // 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 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 // Monta as formas geométricas montar_Geometria(); // Faz a configuração inicial da câmera inicializar_Camera(); // Cria um material vermelho para o triângulo criarMaterial(&g_mtlVermelho, cvVermelho); // Configura e coloca uma luz na cena D3DXVECTOR3 pos(2.0f, 1.0f, -4.0f); colocar_PointLight (pos, cvBranco); return S_OK; } // initGfx().fim void colocar_PointLight(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor) { // Declara a estrutura D3DLIGHT9 luz; // Limpa a estrutura ZeroMemory( &luz, sizeof(D3DLIGHT9) ); // Configura tipo luz.Type = D3DLIGHT_POINT; // Configura cor luz.Diffuse = cvCor; // Configura posição luz.Position = pos; // Configura fator de atenuação luz.Attenuation0 = 0.2f; // Configura faixa de alcance luz.Range = 100.0f; // Instalação e acionamento da luz no dispositivo renderizador g_device->SetLight( 0, &luz ); g_device->LightEnable( 0, TRUE); } // colocar_PointLight().fim void criarMaterial(D3DMATERIAL9 *mtl, D3DCOLORVALUE cvCor ) { // Limpa a estrutura ZeroMemory( mtl, sizeof(D3DMATERIAL9) ); // Configura cor ambiente e difusa mtl->Ambient = cvCor; mtl->Diffuse = cvCor; } // criarMaterial().fim void ajuda_visual(D3DXVECTOR3 pos, D3DCOLORVALUE cvCor) { // Variável de controle do objeto 3d ID3DXMesh* obj3d; // Matriz mundo para posicionamento do objeto 3d D3DXMATRIX mtxMundo; D3DXMatrixIdentity( &mtxMundo ); // Configuração do material do objeto 3d D3DMATERIAL9 mtl; criarMaterial(&mtl, cvCor); // Cria o objeto 3d float nRaio = 0.2f; UINT nSegs = 16; D3DXCreateSphere(g_device, nRaio, nSegs, nSegs, &obj3d, 0); // Configura posição do objeto no mundo 3d D3DXMatrixTranslation(&mtxMundo, pos.x, pos.y, pos.z); g_device->SetTransform(D3DTS_WORLD, &mtxMundo); // Configura material g_device->SetMaterial (&mtl); // Renderiza o objeto obj3d->DrawSubset(0); // Libera o objeto utilizado obj3d->Release(); } // void ajuda_visual().fim void calcularNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2, D3DXVECTOR3* pSaida) { D3DXVECTOR3 u = *p1 - *p0; D3DXVECTOR3 v = *p2 - *p0; D3DXVec3Cross(pSaida, &u, &v); D3DXVec3Normalize(pSaida, pSaida); } // calcularNormal().fim void configurar_cenaEstados(void) { // Mostra a parte interna do polígono g_device->SetRenderState (D3DRS_CULLMODE, D3DCULL_NONE); // Habilita iluminação g_device->SetRenderState (D3DRS_LIGHTING, true); // Aplica o material no dispositivo renderizador g_device->SetMaterial(&g_mtlVermelho); // Corrige as normais g_device->SetRenderState(D3DRS_NORMALIZENORMALS, true); // Desabilita cálculo de luz especular g_device->SetRenderState(D3DRS_SPECULARENABLE, false); } // configurar_cenaEstados().fim void montar_Geometria(void) { // Posicionamento de profundidade float zpos = 0.0f; // Faz o cálculo da normal para configurar os vértices calcularNormal (&D3DXVECTOR3(0.0f, 1.0f, zpos), &D3DXVECTOR3(-1.0f, -1.0f, zpos), &D3DXVECTOR3(1.0f, -1.0f, zpos), &g_normal); // p0 - primeiro vértice g_Vertices[0] = CustomVertex_PositionNormal( 0.0f, 1.0f, zpos, g_normal.x, g_normal.y, g_normal.z); // p1 - segundo vértice g_Vertices[1] = CustomVertex_PositionNormal( -1.0f, -1.0f, zpos, g_normal.x, g_normal.y, g_normal.z); // p2 - terceiro vértice g_Vertices[2] = CustomVertex_PositionNormal( 1.0f, -1.0f, zpos, g_normal.x, g_normal.y, g_normal.z); } // montar_Geometria().fim void renderizar_Geometria() { // Configura formato de vértice no dispositivo g_device->SetFVF( CustomVertex_PositionNormal_Format); // Quantidade de primitivas possíveis de acordo com o uso de vértices UINT nContagem = 1; // Tamanho do passo para achar o próximo vértice UINT nPasso = sizeof(CustomVertex_PositionNormal); // Desenha forma geométrica g_device->DrawPrimitiveUP( D3DPT_TRIANGLELIST, nContagem, &g_Vertices, nPasso); // Mostra localização aproximada da luz ajuda_visual( D3DXVECTOR3(1.5f, 0.5f, -3.5f), cvAmarelo); // Mostra posição da normal ajuda_visual( g_normal, cvVerde); } // renderizar_Geometria().fim void inicializar_Camera(void) { // *************************************************************************** // Inicializa todas as matrizes para elemento neutro D3DXMatrixIdentity( &g_mtxMundo ); D3DXMatrixIdentity( &g_mtxVisao ); D3DXMatrixIdentity( &g_mtxProj ); // *************************************************************************** // Configura a matriz de mundo no dispositivo renderizador g_device->SetTransform( D3DTS_WORLD, &g_mtxMundo ); // *************************************************************************** // Dados para a configuração da matriz de visualização // Aonde está a câmera? - posição da câmera D3DXVECTOR3 cam_pos (0.0f, 0.0f, 5.0f); // Para aonde a câmera está apontada ou olhando? Alvo da câmera D3DXVECTOR3 cam_alvo (0.0f, 0.0f, 0); // A câmera está de cabeça pra baixo? - orientação da câmera D3DXVECTOR3 cam_vetorcima (0.0f, 1.0f, 0.0f); // Configura a matriz de visão D3DXMatrixLookAtLH( &g_mtxVisao, &cam_pos, &cam_alvo, &cam_vetorcima ); // Configura a matriz de visão no dispositivo renderizador g_device->SetTransform( D3DTS_VIEW, &g_mtxVisao ); // *************************************************************************** // Argumentos de configuração da matriz de projeção // aspecto dos gráficos float aspecto = (float) g_xtela / g_ytela; // campo de visão float campo_visao = D3DX_PI / 4; // Trapézio de visualização da câmera ( Frustrum ) float corte_perto = 1.0f; float corte_longe = 1000.0f; // Configura a matriz de projeção D3DXMatrixPerspectiveFovLH( &g_mtxProj, campo_visao, aspecto, corte_perto, corte_longe); // Configura a matriz de projeção no dispositivo renderizador g_device->SetTransform( D3DTS_PROJECTION, &g_mtxProj ); } // inicializar_Camera().fim void atualizar_Camera(void) { // Variável para receber o ângulo final float angulo_final = 0.0f; // Atualiza ângulo para dar movivento g_Angulo = g_Angulo + nVelocidade; angulo_final = g_Angulo / D3DX_PI; // Rotaciona o triângulo indiretamente através da rotação // dos eixos da matriz de espaço mundial. D3DXMatrixRotationY( &g_mtxMundo, angulo_final ); // Configura a matriz de mundo no dispositivo renderizador g_device->SetTransform( D3DTS_WORLD, &g_mtxMundo ); } // atualizar_Camera().fim 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, azul, 1.0f, 0); // Configure estados de renderização configurar_cenaEstados(); // Atualize a câmera atualizar_Camera(); // Começa a cena if( SUCCEEDED( g_device->BeginScene() ) ) { // Vamos renderizar as formas geométricas 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 // 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
//----------------------------------------------------------------------------- // Projeto: prj_Luz - arquivo: entrada.cpp // Esta aplicação ilustra como iluminar um triângulo // 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_Luz"; 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