Curso Professional Lucas Euzébio Machado.

Slides:



Advertisements
Apresentações semelhantes
Continuidade: sem interrupções
Advertisements

Sistemas Operacionais
Programação em Java Prof. Maurício Braga
Real Time Rendering.
Interações cliente/servidor usando o UDP
Paulo Marques Hernâni Pedroso
António Ramires Fernandes & Luís Paulo Santos – Adaptado por Alex F. V. Machado Computação Gráfica Pipeline Gráfico.
Algoritmos e Estrutura de Dados I
Árvores.
COMPUTAÇÃO GRÁFICA.
Claudio Esperança Paulo Roma Cavalcanti
Introdução à Computação Gráfica Geometria
Geometria Computacional Interseção de Segmentos
Parte 1 – Conceitos de Real Time Rendering. a. Pipeline Gráfico.
Medida do Tempo de Execução de um Programa
Computação Gráfica: Rendering e Rasterização
Técnicas para algoritmos de superfície visível eficientes (Techniques for efficient visible-surface algorithms) Fabio Utzig Rodrigo Senger.
Threads.
FEUPDEECRedes de Computadores, 4º Ano de EEC, ramo de ACI Sockets Abril, 98Isidro Vila Verde 1 Formato das estruturas de dados Estrutura.
Listas Encadeadas.
Software de Rede Willamys Araújo.
JAVA: Conceitos Iniciais
Algoritmos em Grafos.
Informática Teórica Engenharia da Computação
Aula prática 9 Alocação Dinâmica Monitoria de Introdução à Programação
Aula prática 8 Ponteiros Monitoria de Introdução à Programação
Aula prática 13 Orientação a Objetos – C++ Parte 1
Aula prática 6 Vetores e Matrizes
Aula prática 9 Alocação Dinâmica Monitoria de Introdução à Programação
Introdução à Computação Gráfica Projeções
Conceitos básicos de orientação a objetos
PROGRAMAÇÃO ESTRUTURADA II
Ordenação e Pesquisa de Dados Marco Antonio Montebello Júnior
Treinamento do Microsoft® Word 2010
Algorítmos e estrutura de dados III
Aula Prática 12 Operações com Arquivos Monitoria
Professor Mário Dantas
Computação Gráfica Aula 3 Transformações Geométricas
Computação Gráfica – Visibilidade
Aula 13 - Armazenamento de Dados em Arquivos
Educação Profissional Técnica de Nível Médio Curso Técnico de Informática Disciplina: Estrutura de Dados Professor: Cheli dos S. Mendes da Costa Arquivo.
Hardware assisted rendering of csg models
Capítulo 5 Structures. A - Sequence E - Formula Node B - Case F - Variável Global C - For Loop G - Variável Local D - While Loop ABCD FG E.
Curso de Aprendizado Industrial Desenvolvedor WEB Disciplina: Programação Orientada a Objetos I Professora: Cheli Mendes Costa Classes e Objetos em Java.
Aulas 2 e 3 – Java – Prof. Marcelo Heitor # O método main e argumentos na linha de comando; # Fluxo padrão de entrada e saída; # A classe JOptionPane;
Educação Profissional Técnica de Nível Médio Curso Técnico de Informática
SISTEMAS OPERACIONAIS I Gerenciamento de Arquivos
Estruturas de Dados Aula 8: Tipos Abstratos de Dados 30/04/2014.
Go3D! A 3D Graphics Engine Carlos Tosin. Divisão Estrutura dividida em 4 componentes Core (46 classes) Áudio (4 classes) Script (4 classes) Renderer (37.
Computação Gráfica – Visibilidade Profa. Mercedes Gonzales Márquez.
Realidade Virtual Aula 5
Estruturas de Dados Aulas 3 e 4: Uso da memória e Vetores
Introdução ao MATLAB 5.3 para Hidrólogos
8088 Assembly Software Básico Mitsuo Takaki.
Visualização Tridimensional
Introdução à Computação Gráfica
Linguagem de programação I A Carlos Oberdan Rolim Ciência da Computação Sistemas de Informação Versão: _01.
Fundamentos de linguagens de programação
Computação Gráfica – Visibilidade Profa. Mercedes Gonzales Márquez.
CES-10 INTRODUÇÃO À COMPUTAÇÃO
Realidade Virtual Aula 2 Remis Balaniuk. Enxergando grande, começando pequeno Quem começa a aprender RV normalmente sofre um primeiro impacto negativo.
Programação Computacional Aula 8: Entrada e Saída pelo Console Prof a. Madeleine Medrano
Computação Gráfica – Visibilidade Profa. Mercedes Gonzales Márquez.
Estruturas de Dados Murilo Salgado Razoli.
Estrutura de Dados Aula 3 - Listas
Estática Estática Histórico
Arrays Outline 7.1 Introdução 7.2 Arrays 7.3 Declarando e Criando Arrays 7.4 Exemplos usando arrays 7.5 Ordenando arrays 7.6 Procurando em.
TV de Bolso TV de Bolso apresenta Tutorial: editando seu vídeo no Movie Maker.
SOCKET - É um canal de comunicação entre processos que estabelece uma conexão entre eles na forma de cliente-servidor. Por meio de sockets, os computadores.
Linguagem de Programação – Aula 04 Prof. Me. Ronnison Reges Vidal.
Transcrição da apresentação:

Curso Professional Lucas Euzébio Machado

Tópicos do Curso C++, Algoritmos e Estruturas de Dados (36) Computação Gráfica (36) Programação Distribuída (6) Física (6) Inteligência Artificial (6) Fundamentos de Programação de Jogos (6) Projeto Final (24)

Tópicos para C++, Algoritmos e Estruturas de Dados Variáveis, Ponteiros, Referência, Alocação e Liberação de Memória Testes de condições Loops Funções Módulos, Compilação e Link-Edição Orientação a Objetos Classes e Structs Encapsulamento, Herança e Polimorfismo Construtores e Destrutores Exceções Funções e Variáveis estáticas Recursão Lista Encadeada, Fila e Pilha Árvore Binária Grafo Hashtable Templates Operator Overloading Lendo e Gravando Arquivos Multithreading

Variáveis

Ponteiros Ponteiros são variáveis que armazenam endereços de memória.

Variáveis de referência Referências servem como um segundo nome para um objeto. Elas permitem acesso indireto de forma semelhante a ponteiros.

Alocação de Memória Para alocar memória da heap:

Liberando memória

Vetores Vetores são agregações de elementos de um mesmo tipo que ocupam a memória de forma sequencial e adjacente.

Vetores Uma variável vetor armazena na realidade o endereço de memória do primeiro elemento do vetor.

Vetores Vetores podem ser multidimensionais

Testes de Condições

Testes de Condições

Loops Instruções usada para causar repetições de trechos de código

Loops

Exercício Crie um programa que gera um buffer de inteiros de 10 posições. Cujos valores sofrem um decremento de 10 até 1. Adicione um trecho de código que busca pelo numero 4 no vetor e salva o endereço da posição do vetor em um ponteiro.

Exercício Crie um programa que crie uma matriz 4x4 identidade usando um buffer bidimensional.

Funções Funções são subprogramas que realizam operações definidas por usuários. Ferramenta fundamental para redução de complexidade e reuso de código. Em qualquer projeto, funções serão utilizadas sem que se conheça suas implementações internas.

Funções

Exercício Crie uma função que adiciona dois vetores de 4 elementos e retorna o resultado em um terceiro vetor

Módulos, Compilação e Link-Edição Módulos permitem a divisão de um sistema em diferentes partes, cada uma responsável por uma tarefa específica. Um módulo para nós será definido como um .h e um .cpp. O arquivo .h possui declarações. O arquivo .cpp possui implementações.

Exemplo de um modulo Vector3.h Vector3.cpp

Usando um módulo

Usando Módulos Ao usar include é incluído todo o arquivo de texto na linha chamada.

Compilação Ao compilar um arquivo de implementação .cpp é gerado um arquivo .obj com código binário e tabelas com referências externas ainda não resolvidas e símbolos públicos.

Link-Edição A link edição resolve referências externas e junta todo o código em um só código binário gerando assim o arquivo executável.

Exercício Implemente um módulo Vector4.h e Vector4.cpp que possui as funções de adição, subtração e multiplicação de vetores de 4 elementos e salvando o resultado em um outro vetor. Crie um arquivo .cpp com a main e inclua o header do Vector4.h. Compile ambos arquivos .cpp e gere o executável.

Orientação a Objeto Um objeto é uma entidade(podendo se basear em algo concreto ou abstrato) que possui um comportamento ou representa algo. Você interage com um objeto através de uma interface formada por funções. Isso permite uma interação bem comportada. A divisão de um sistema em objetos permite uma enorme redução em complexidade e aumento em reusabilidade.

Exemplo de um sistema dividido em objetos

Classes Objetos são representados em software através de classes

Encapsulamento Apenas os membros públicos de uma função podem ser acessados por outros objetos.

Encapsulamento Fundamental para redução de complexidade. Você chama um método de uma classe e um problema é resolvido para você. Não há necessidade de saber como o problema foi resolvido. Permite interação comportada. Como você controla o acesso às variáveis através de funções não é possível partes externas do programa agirem de forma errada com suas variáveis.

Exercício Implemente a classe AIManager. Essa classe possui duas funções. AddObject e Think. A função AddObject recebe uma string com um nome, uma posição x e uma posição y. A função Think mostra na tela os objetos adicionados.

Herança Certos objetos compartilham características porém possuem particularidades suficientes de forma que precisam de novos objetos para serem apropriadamente representados.

Herança

Herança O objeto Person e o objeto Stone são ambos objetos de cena que podem ser desenhados na tela. Então ambos podem pertencer à mesma classe de objetos SceneObject. Se por exemplo um SceneObject possuir uma posição 3d. Tanto Person quanto Stone possuirão essa variável de posição pois a posição é uma variável que existe em todo objeto da classe SceneObject.

Herança

Exercício Reimplemente o exercício anterior fazendo o AIManager ter funções para adicionar Dragon e Orc que são classes derivadas de AIEntity. A classe AIEntity possui membros públicos para armazenar um nome e uma posição x e y. A classe Dragon tem uma string com o tipo de baforada do dragão e a classe Orc possui um inteiro que indica a arma sendo usada. Implemente a função Think para tratar dos tipos específicos e desenhar suas partes específicas.

Polimorfismo Quando um conjunto de classes deriva de uma mesma classe base, todo o conjunto compartilha uma interface. É possível escrever partes de código levando em consideração apenas classes base de determinados objetos. Isso é vantajoso para aumentar o reúso no código.

Polimorfismo

Polimorfismo

Exercício Reimplemente o AIManager para receber objetos AIEntity. Crie uma função virtual pura em AIEntity chamada Think que imprime os dados da entidade. Faça a classe Orc e Dragon implementarem a função Think. Mude o AIManager para fazer uso das funções Think dos objetos.

Construtores e Destrutores Construtores são métodos de uma classe chamada para “construir” a classe. Todo objeto criado tem seu construtor chamado.

Construtores e Destrutores É possível definir diferentes tipos de construtores

Construtores e Destrutores Destrutores fazem o trabalho inverso dos construtores. Eles fazem a destruição do objeto, liberando recursos adquiridos como memória alocada, arquivos abertos, etc.

Construtores e Destrutores Em classes base use destrutores virtuais. Ao deletar um objeto usando um ponteiro base o comportamento é indefinido caso o destrutor da classe base não seja virtual.

Exceções Eventos anormais são normais.  Programas executam normalmente até que algum problema ocorra como falta de memória, acesso à uma área de memória ilegal, etc etc etc etc etc etc etc etc etc etc..... É possível testar esses problemas ou exceções usando ifs ou fazendo uso de ferramentas de C++ criadas específicamente para tratar de exceções.

Exceções Ao detectar uma anomalia, você gera uma exceção através da operação throw.

Exceções Um exceção é uma classe

Exceções Para capturar uma exceção deve-se usar o operador try e catch.

Exercício Implemente uma classe chamada Buffer que cria um buffer de ints. A classe possui um construtor que recebe o número de elementos, e um destrutor que destroi os elementos. A classe possui também um função Add que recebe um vetor de ints pra adicionar ao buffer e uma função Remove que recebe n e remove n elementos de buffer. Jogue exceções caso sejam adicionados mais elementos do que o Buffer suporta ou sejam removidos mais elementos do que existem no buffer.

Funções e Variáveis Estáticas Apesar de todo o perigo, às vezes acesso à informações globais é necessário e até simplifica designs. Todos os objetos com um membro estático compartilham a mesma instância desse membro. Ou seja, se um objeto altera a variável estática, isso será visível por todos os outros objetos da mesma classe.

Declarando membros estáticos

Usando membros estáticos

Usando membros estáticos

Variáveis estáticas e constantes

Exercício Implemente uma classe que possui internamente uma variável estática ponteiro do tipo da própria classe. Inicialize essa variável para NULL Implemente uma função estática da classe chamada GetInstance que retorna a variável quando o ponteiro é diferente de NULL, se o ponteiro for NULL é alocada memória para a variável e seu endereço é armazenado no ponteiro.

Recursão A recursão é uma forma de solucionar certos problemas que envolve fazer uma função chamar a si mesma para resolver o problema.

Exemplo

Exercício Implemente uma função recursiva que busque em um vetor de inteiros ordenado crescentemente por um determinado valor. A função deve retornar o índice do valor ou , -1 caso o valor não seja encontrado.

Busca Binária

Complexidade de Algoritmos Complexidade não é de difícil de entender ... é de difícil de computar. É nossa missão encontrar os algoritmos mais eficientes para resolver algum problema. Os algoritmos mais eficientes para resolver algum problema são chamados de algoritmos ótimos.

Complexidade da Busca Linear No pior caso, é necessário realizar n testes para achar (ou não) um valor. É usada uma notação O(n) para representar o algoritmo busca linear. O(n) ~= se você colocar n elementos como input, o custo da função é uma função linear.

Complexidade da Busca Binária Teste 1 = n elementos Teste 2 = n / 2 elementos Teste 3 = n / 4 elementos Teste final quando você tem 1 elemento. Dado n elementos, quantos testes são feitos? Temos que achar i quando n/2^i = 1. n/2^i = 1 -> n = 2^i log n = log 2^i Log n = i Complexidade = O(log n) Busca binária é melhor que busca linear.

Lista Encadeada, Fila e Pilha As vezes, não é possível saber o número máximo de elementos que serão adicionados à uma classe. Quando isso acontece é necessário criar uma estrutura capaz de crescer de acordo com a necessidade. Lista Encadeada, Fila e Pilha são estruturas de dados fundamentais usadas em diversos algoritmos.

Lista Encadeada A lista encadeada é uma lista criada dinamicamente que se liga através de ponteiros.

Lista Encadeada - implementação Uma classe consiste na lista em si. A classe da lista usa outra classe que representa os nós da lista. Cada nó armazena os dados e o ponteiro para o próximo nó da lista. O último nó aponta para NULL. A classe da lista aponta para o primeiro elemento da lista.

Exercício Implemente uma lista encadeada cujos nós armazenam posições x,y e z em floats e possuem também um id inteiro. Crie um método na classe para adicionar elementos no final da lista. Crie método na classe para buscar um nó dado um inteiro, o método deve retornar as posições x, y e z. Crie um método para imprimir toda a lista.

Lista Encadeada

Lista Encadeada

Fila Implementada de maneira semelhante à lista encadeada, porém elementos só podem ser inseridos no final e removidos na frente. Funciona em forma FIFO (First In First Out). Imagine uma fila de cinema!

Pilha Também implementada de maneira parecida com a lista encadeada porém sua operação é LIFO (Last In First Out). A pilha mantém um ponteiro para o seu topo. Quando se deseja adicionar um elemento realiza-se um push adicionando um elemento ao topo da lista, quando se deseja remover um elemento, faz-se um pop, removendo o elemento do topo.

Exercício Implemente uma pilha. A pilha possui um ponteiro chamado top que aponta para o topo da pilha. Crie o método push que adiciona um elemento ao topo da pilha. Crie o método pop que remove o elemento do topo da pilha. Crie o método print para imprimir a pilha.

Árvore Binária de Busca Estrutura de dados semelhante à uma arvore invertida onde cada nó possui até 2 filhos. Cada nó possui um id, um ponteiro para o filho esquerdo e um ponteiro para o filho direito. Cada nó possui a seguinte característica, o filho a esquerda possui um id menor que o id do nó e o filho a direita possui um id maior que o id do nó.

Árvore Binária de Busca A busca por um valor possui um custo O(h). Se a árvore estiver desbalanceada isso pode representar O(n) Se a árvore estiver balanceada isso pode representar O(log n) Uma árvore balanceada é uma árvore em que para todo nó, suas subárvores à esquerda e à direita diferem no máximo em 1 na altura. Considere uma subárvore vazia como de altura -1. Folha é um nó sem filhos.

Árvore Binária de Busca Através de uma árvore balanceada é possível achar uma árvore perfeita de mesma altura. Árvore perfeita é uma árvore em que todas as folhas possuem a mesma altura e todos os nós possuem 2 ou 0 filhos. Número de elementos da árvore completa = Somatório(2^i, i varia de 0 até a altura). n = 1 * (2^(h+1) – 1) / 2 – 1 (somatório de progressão geométrica) n = 2^(h+1) – 1 n + 1 = 2^(h+1) log(n+1) = h + 1 log(n+1) – 1 = h Logo a altura de uma árvore balanceada é um logaritmo de n ... O que é muito bom!!!!

Exercício Implemente uma árvore binária onde os nós possuem um id inteiro e armazenam um valor inteiro. Implemente o método de busca por um id. Implemente o método de busca pelo maior elemento. Implemente o método de adição de elementos. Implemente o método que desenha na tela os valores dos nós de maneira ordenada crescentemente por id.

Grafo Grafos são uma estrutura de dados onde um nó pode apontar para um número qualquer de outros nós. Grafos podem ser cíclicos se possuirem referências que façam um ciclo (como um filho apontando para um pai). Caso não existam referências cíclicas, o grafo é definido acíclico e se assemelha à uma árvore n-ária, onde cada nó possui n filhos.

Grafos Acíclicos – Uma Implementação Cada grafo possui um ponteiro para o pai e uma estrutura de dados(lista encadeada, vetor) que armazena os ponteiros para os filhos. Se assemelha a uma árvore n-ária.

Exercício Implemente um grafo acíclico que armazena seus filhos em uma lista encadeada. Para isso implemente uma classe GraphNode que possui os métodos AddChild e Print. Coloque os GraphNodes possuindo um inteiro internamente. AddChild adiciona um filho ao nó. Print imprime os nós do grafo. Construa um grafo e o imprima.

Hash Table É uma estrutura de dados que permite acessos em O(1) .... Ou seja ... Muito rápidos. Para adicionar um elemento, é utilizada uma chave que identifica o elemento. Através dessa chave, é calculada a posição do elemento (essa posição geralmente é um índice de um vetor). A função que pega essa chave e calcula uma posição é chamada de função de hash.

Hash Table - Exemplo Para armazenar objetos é usado um vetor onde cada elemento desse vetor é um ponteiro para um objeto. A chave é um inteiro. A posição é calculada achando o resto da divisão da chave pelo tamanho do vetor de ponteiros. ( index = chave % tam_vec; ) Quando uma chave gerar uma posição que já contém um objeto, outra estrutura de dados é utilizada para salvar os objetos (lista encadeada, arvore binária de busca, outra lista encadeada).

Exercício Implemente uma hash table com as mesmas características do exemplo anterior. Para salvar objetos na mesma posição do vetor de ponteiros utilize uma lista encadeada. Crie uma função de inserção que recebe a chave e o objeto e o salva na hash table. Crie uma função de search que procura pelo objeto dada uma chave.

Template Templates são como receitas para criar classes que recebem um ou mais tipos como parâmetros. São úteis para reaproveitar algoritmos em diferentes tipos de dados.

Template

Template Toda vez que um template é utilizado para um determinado tipo, é criado o código da classe para fazer uso daquele tipo. Então no exemplo anterior, são criadas 2 classes com a mesma lógica porém só com os tipos int e float diferentes. Como as classes são geradas em tempo de compilação, é necessário saber a implementação de todos os métodos de cada template. Se um template define seus métodos em um .cpp, outras classes não serão capazes de ver essa implementação e criar as novas classes (você provavelmente terá problemas de link-edição). Por esse motivo, geralmente os templates são criados completamente no .h (incluindo a implementação das funções).

Exercício Crie uma lista encadeada que consegue armazenar qualquer tipo usando um template.

Operator Overloading Operadores como +, - , [] , == , etc, podem ter seu comportamento redefinido nas classes. Isso facilita a programação usando determinados tipos (ex: vetores 3d, strings).

Operator Overloading - Exemplo

Exercício Implemente uma classe String. Faça overload do operador = que recebe um vetor de caracteres e armazena na string. Faça overload do operador += que recebe um vetor de caracteres e adiciona os caracteres ao final da string. Faça overload do operador == que recebe um ponteiro para caracteres constantes (const char*)

Lendo e Gravando Arquivos Para ler ou gravar arquivos é necessário criar uma variável ponteiro para FILE. É preciso incluir o stdio.h. Para abrir um arquivo, usa-se a função fopen da seguinte forma FILE* fopen(path do arquivo, tipo de abertura)

Lendo e Gravando Arquivos O tipo de abertura de fopen pode definir diferentes formas de abertura do arquivo: “r” : abre o arquivo para leitura. “w”: abre o arquivo para escrita. Se o arquivo já existir, seu conteúdo é destruído. “a”: abre o arquivo permitindo a escrita ao final do arquivo.

Lendo e Gravando Arquivos Para a escrita de arquivos, usa-se a função fwrite que possui o seguinte protótipo: size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)

Lendo e Gravando Arquivos Para ler dados de arquivos, usa-se a função fread: size_t fread(void* buffer, size_t size, size_t count, FILE* stream)

Lendo e Gravando Arquivos Para fechar arquivos usa-se fclose int fclose(FILE* stream)

Lendo e Gravando Arquivos

Exercício Crie um programa que salve as seguintes informações do jogador: Estágio atual Nome do personagem Nível do Personagem Posição x, y e z do personagem E depois crie um outro programa que lê esses dados e mostra na tela!

Multithreading Multithreading permite a criação de trechos de código que executam paralelamente. Se houver um único processador na máquina, as threads concorrem pelo processador. O sistema operacional dá um time slice para cada thread. Ao término do time slice, o SO interrompe a execução da thread e coloca outra thread para processar.

Multithreading Isso é útil em máquinas com múltiplos processadores por permitir processamento paralelo. É útil também quando existem operações que podem bloquear e não é desejável que o sistema bloqueie por essas operações (ex: bloqueio enquanto uma leitura de um arquivo grande é realizada).

Multithreading Para criar uma thread, utilizar a função _beginthread (incluir o process.h): uintptr_t _beginthread(função da thread, tamanho da pilha, ponteiro para a lista de parâmetros)

Multithreading

Multithreading Variáveis que são acessadas por diferentes threads correm o risco de se tornarem corrompidas. Imagine um programa onde existe uma lista de jogadores. Uma thread está adicionando um jogador na lista e outra thread está removendo outro jogador da lista. Pode acontecer da lista acabar com um dos jogadores apontando para lixo e até coisas piores. Por isso é necessário usar funções que permite acesso à regiões críticas.

Multithreading Para proteger regiões acessadas por múltiplas threads utilize CRITICAL_SECTION (inclua windows.h) Use a API InitializeCriticalSection, EnterCriticalSection, LeaveCriticalSection e DeleteCriticalSection para usar regiões críticas.

Multithreading

Exercício Crie uma aplicação com 2 threads adicionais. 1 thread incrementa um contador e o imprime na tela 1 thread decrementa esse mesmo contador e o imprime na tela. Cada thread repete esse processo 50 vezes. A main thread fica em espera ocupada esperando as threads terminarem. No final ela imprime THE END. Faça testes usando critical sections e sem usar critical sections.

Tópicos para Computação Gráfica Aplicações Win32 Pipeline gráfico do Direct3D e Dispositivo Direct3D Buffer de Vértices e Índices Effect Files, Vertex Shaders e Pixel Shaders com Cg Renderizando triângulos Matrizes e Transformações Sistemas de Coordenadas e o Processo de Visualização Renderizando triângulos com perspectiva Renderizando triângulos texturizados Produto Escalar Iluminação Renderizando Objetos com Iluminação por pixel Exportando e carregando modelos de arquivos .X (e renderizando!) Animação usando interpolação entre Key Frames Alpha Blending Volumes de Sombras Bump Mapping Grafos de Cena Culling em cenários usando Octrees

Programando aplicações Win32 Para criar uma aplicação Win32 é preciso incluir o windows.h O processamento em aplicações Win32 começa na WinMain.

Programando aplicações Win32 hinstance é um handle. Um handle é simplesmente um número que identifica alguma coisa. hinstance é um identificador da aplicação. Esse parâmetro é necessário para alguma funções. hprevinstance é um parâmetro não mais utilizado e é definido como 0. cmdline é a linha de comando usada para rodar o programa. cmdshow indica como o programa deveria ser inicialmente mostrado.

Programando aplicações Win32 Para criar a janela da aplicação é preciso instanciar e preencher a struct WNDCLASS que possui a seguinte cara.

Programando aplicações Win32

Programando aplicações Win32 style: define propriedades da janela. CS_VREDRAW fazem com que a janela seja redesenhada caso haja uma mudança na altura da janela. lpfnWndProc: recebe a função que trata os eventos enviados para a janela de sua aplicação. cbClsExtra: número de bytes extras para alocar para a windows class struct. cbWndExtra: número de bytes extras além das instância da janela. hInstance: handle da aplicação. hIcon: handle do ícone usado. hCursor: handle do cursor usado. hbhBackground: handle do pincel usado para pintar o background da janela. lpszMenuName: um nome que identifica o recurso de menu usado na aplicação. lpszClassName: especifica o nome dessa classe de janela.

Programando aplicações Win32 Após definir as características da classe de janela, é preciso registrá-la.

Programando aplicações Win32 Após o registro da classe de janela, é preciso criar a janela e mostrá-la.

Programando aplicações Win32 Após criar a janela é necessário entrar no loop de captura e envio de mensagens. Nesse loop também são colocadas funções para executar o jogo.

Programando aplicações Win32 A última parte que falta é a parte da função de tratamento de eventos

Exercício Junte todo o código mostrado e crie uma aplicação win32. Implemente a aplicação para mostrar a posição x e y do mouse quando o botão esquerdo é pressionado. Quando a tecla pra cima é pressionada mostrar também.

Dispositivo Direct3D API Gráfica do DirectX Permite acesso à placas de aceleração gráfica de maneira única e independente do dispositivo.

Pipeline Gráfico do Direct3D

Pipeline Gráfico do Direct3D Vertex Data: Dados geométricos dos objetos 3D do cenário. Por exemplo, as posições dos vértices do objeto 3D. Fixed Function Pipeline: Uma sequência de processamento fixa do Vertex Data. As alterações nessa sequência de processamento são pouco flexíveis. Programable Pipeline: Se usado, permite a implementação de um programa que processa os vértices. Permite muita mais flexibilidade de processamento dos vértices.

Pipeline Gráfico do Direct3D Clipping: Esse estágio recorta todas as partes dos triângulos que estão para fora da tela.

Pipeline Gráfico do Direct3D Back Face Culling: Elimina os triângulos que estão de “costas para a câmera”. Esse estágio elimina triângulos não visíveis em objetos fechados.

Pipeline Gráfico do Direct3D Rasterization: Nesse estágio, os triângulos são desenhados.

Pipeline Gráfico do Direct3D Pixel Shader: Se usado, permite ao programador criar um processamento que executa a cada pixel desenhado. Permite grande flexilidade sobre a forma como os pixels são renderizados. Texture Sampler: Esse estágio, se usado, captura amostras das textura para serem usadas no Pixel Shader.

Pipeline Gráfico do Direct3D Alpha Test: Esse estágio permite configurar a aceitação de um pixel de acordo com o seu alpha. Depth Test: Esse estágio permite configurar a aceitação de um pixel de acordo com a sua profundidade. Stencil Test: Esse estágio permite configurar a aceitação de um pixel de acordo com o Stencil Buffer.

Pipeline Gráfico do Direct3D Fogging: Esse estágio permite configurar fumaça na cena a medida que os objetos se distanciam. Alpha Blending: Esse estágio permite que o pixel sendo renderizado seja combinado com o pixel já desenhado no buffer de cor que será apresentado na tela.

Pipeline Gráfico do Direct3D A geometria dos modelos e cenários passa pelo pipeline do Direct3D e ao final é gerada uma imagem 2D do objeto 3D, que é mostrada na tela.

Integração de Sistemas

Sistemas de Coordenadas Direct3D usa o sistema de coordenadas da mão esquerda.

O objeto Direct3D É o primeiro objeto a ser criado e o último a ser liberado.

O dispositivo Direct3D Objeto principal de renderização. É com esse objeto que passamos a geometria de modelos para a placa gráfica.

Criando um dispositivo Direct3D Preencher D3DPRESENT_PARAMETERS

Criando um dispositivo Direct3D BackBufferWidth, BackBufferHeight: Largura e altura do Backbuffer. BackBufferFormat: Formato do backbuffer. BackBufferCount: Número de Backbuffers. MultiSampleType e MultiSampleQuality: Parâmetros usados para antialiasing. SwapEffect: Maneira como o backbuffer vira o front buffer. hDeviceWindow: handle da janela Windowed: Se a aplicação vai rodar em janela ou full screen. EnableAutoDepthStencil: Permissão se o Direct3D pode gerenciar o seu buffer de depth e stencil. AutoDepthStencilFormat: O formato do buffer de depth e stencil. Flags: ... FullScreen_RefreshRateInHz: A taxa com que o monitor é atualizado. PresentationInterval: A taxa com que o backbuffer pode ser apresentado na tela.

Criando um dispositivo Direct3D Criar o dispositivo usando a função CreateDevice do objeto Direct3D.

Criando um dispositivo Direct3D Adapter: número que identifica a placa gráfica usada. DeviceType: O tipo de dispositivo a ser usado (Ex. HAL ou Ref). hFocusWindow: handle para a janela BehaviorFlags: flags pPresentationParameters: a estrutura PRESENT_PARAMETERS preenchida anteriormente. ppReturnedDeviceInterface: retorna o dispositivo Direct3D criado.

Criando um dispositivo

Exercício Crie um dispositivo Direct3D em 800x600 fullscreen. Experimente com outros tamanhos. Crie um dispositivo para versão Windowed. Para isso coloque Windowed = TRUE, e o fullscreen refresh rate = 0.

Cores As cores no Direct3D são definidas geralmente pela combinação de vermelho, verde e azul. Um componente chamado alpha também é usado eventualmente e representa o grau de transparência da cor.

Cores Em Direct3D, cores de vértices são geralmente definidas como números hexadecimais com o seguinte formato: 0xalpharedgreenblue 0x00ff0000 = vermelho puro 0x0000ff00 = verde puro 0x000000ff = azul puro 0x00000000 = preto 0x00ffffff = branco

Limpando Buffers Toda vez que se deseja renderizar um frame, é necessário limpar os buffers de cor e profundidade. Caso o buffer de cor não seja limpo, partes das cenas de renderizações passadas serão mostradas na tela, já que os backbuffers são reutilizados. Caso um jogo no entando sempre desenha na tela toda, não é necessário limpar esse buffer. O buffer de profundidade é usado para desenhar corretamente os objetos 3D com relação à distância para a câmera. Basicamente esse buffer garante que objetos 3D que estão na frente de outros objetos, são renderizados corretamente na frente desses objetos. Esse buffer sempre precisa ser limpo a cada nova renderização.

Limpando Buffers Para limpar os buffers de cor e profundidade é necessário usar a função Clear do dispositivo Direct3D.

Limpando Buffers Count: Número de retângulos em pRects. pRects: Buffer de retângulos que especificam as regiões que irão sofrer limpeza. Flags: flags que especificam os buffers que serão limpos. Color: cor usada para limpar o buffer de cor. Z: valor de profundidade usado para limpar o buffer de profundidade. Stencil: valor de stencil usado para limpar o buffer de stencil.

Limpando Buffers

Apresentando Back Buffers Toda a renderização por default é feita no backbuffer. Após a renderização é necessário apresentar o backbuffer, fazendo seus dados serem copiados para o front buffer, que é o buffer sendo mostrado na tela.

Apresentando Back Buffers Para apresentar o back buffer deve ser usada a função Present do dispositivo Direct3D.

Apresentando Back Buffers pSourceRect: Um retângulo que define a parte que será apresentada do Back Buffer. pDestRect: Um retângulo que define o local onde será apresentado o back buffer. hDestWindowOverride: handle da janela onde será apresentado o back buffer. pDirtyRegion: Define a região que precisa ser atualizada.

Apresentando Back Buffers

Exercício Crie uma aplicação que limpa o buffer de cor para a cor vermelha (0x00ff0000).

Objetos 3D Um objeto 3D pode possuir diversas informações que são usadas na sua renderização.

Objetos 3D

Objetos 3D Objetos 3D podem usar texturas(imagens) para dar detalhe à seus triângulos. Coordenadas de textura podem ser usadas para aplicar imagens à um objeto 3D.

Objetos 3D Normais também podem ser usadas para cálculos de iluminação por exemplo.

Objetos 3D (Resumo) Para desenhar objetos 3D nós podemos usar: Posições dos vértices Índices das faces Coordenadas de textura Normais dos vértices

Buffer de Vértices e Índices Objetos 3D são renderizados passando sua informação geométrica para a placa gráfica. A informação geométrica dos objetos é armazenada em VertexBuffers e IndexBuffers. Os vértices dos objetos podem possuir variações em seu conteúdo. Nos VertexBuffers ficam armazenadas informações tipo posição x,y,z, coordenadas de textura u, v, normal, etc.

Criando um Vertex Buffer Para isso é necessário usar a função CreateVertexBuffer do dispositivo Direct3D.

Criando um Vertex Buffer Length: tamanho do vertex buffer em bytes. Usage: Flags que indicam usos especiais. FVF: Flags que indicam o conteúdo do vertex buffer. Pool: Memória onde esse buffer ficará armazenado (Memória de Vídeo, AGP ou RAM normal). ppVertexBuffer: Retorna o vertex buffer. pSharedHandle: colocar NULL. (bacalhau M$)

Criando um Vertex Buffer

Carregando Dados no Vertex Buffer Para carregar dados em um vertex buffer deve-se usar a função Lock do vertex buffer. Após carregar os dados, deve-se usar a função Unlock.

Carregando dados no Vertex Buffer OffsetToLock: Distância do início do vertex buffer onde o carregamento começará. SizeToLock: Tamanho da região no vertex buffer onde os dados serão carregados. ppbData: Ponteiro retornado que aponta para o conteúdo do vertex buffer. Flags: ...

Carregando dados no Vertex Buffer

Vertex Buffers Vertex buffers podem armazenar diferentes informações sobre um vértice. Ex: x,y,z Coordenadas de textura u, v Normal Cor do vértice

Exercício Crie um vertex buffer e carregue dados x,y,z de um triângulo. Leia esses dados em outro buffer usando o mesmo vertex buffer.

O processo de renderização de primitivas

Processo de renderização de primitivas As informações de renderização (x,y,z, u, v, ...) do objeto são armazenadas em vertex buffers. Esses buffers são colocados em uso através da função SetStreamSource do dispositivo Direct3D. A função DrawPrimitive faz as informações dos buffers serem carregadas juntando a informação completa de um vértice. Quem define como os vertex buffers são combinados formando um vértice é a declaração do vértice ...

Colocando vertex buffers em uso Para colocar em uso um vertex buffer, deve-se usar a função SetStreamSource do dispositivo Direct3D.

Colocando vertex buffers em uso StreamNumber: define o número do vertex buffer, começa em 0. pStreamData: o vertex buffer OffsetInBytes: a distância em bytes no buffer onde começa a informação dos vértices. Stride: A distância entre cada componente do vertex buffer.

Colocando vertex buffers em uso

Exercício Coloque o vertex buffer criado no exercício anterior para ser usado como o vertex buffer 0. Crie um vertex buffer para armazenar uma cor para cada um dos vértices do triângulo. Use D3DCOLOR. Colocar esse vertex buffer com as cores como o vertex buffer 1.

Declarações de Vértices Os vertex buffers podem conter diferentes informações sobre o vértice. A declaração do vértice informa ao Direct3D como ele deve processar os vertex buffers usados para adquirir a informação de um vértice. Declarações de vértices dizem coisas do tipo: “O vertex buffer 0 tem a informação x,y,z do objeto, já o vertex buffer 1 tem a informação u,v do objeto”.

Criando uma declaração de vértices Uma declaração de vértices é feita através da criação de um buffer onde cada elemento é um D3DVERTEXELEMENT9.

Criando uma declaração de vértices Stream: o número do vertex buffer. Offset: a distância no vertex buffer onde começa a informação. Type: O tipo de dado armazenado no buffer. Method: Define como o tesselator vai operar na informação do vertex buffer. Usage: Define o objetivo do uso do vertex buffer. UsageIndex: Dado adicional ao objetivo do vertex buffer.

Criando uma declaração de vértices

Criando uma declaração de vértices Após criar o buffer de D3DVERTEXELEMENT9, uma declaração é criada chamando a função CreateVertexDeclaration do dispositivo Direct3D.

Criando uma declaração de vértices

Usando uma declaração de vértices Para usar uma declaração de vértices usa-se a função SetVertexDeclaration do dispositivo Direct3D.

Exercício Crie a vertex declaration para usar os buffers do exercício anterior.

Effect Files Effect files são arquivos que estabelecem uma configuração do pipeline gráfico para renderizar alguma coisa. Eles armazenam o vertex e o pixel shader.

Composição de um effect file.

Vertex Shader É responsável pelo processamento dos vértices. É um programa que executa para cada vértice passado para o pipeline. Recebe como parâmetro as informações dadas pelos vertex buffers. O pipeline espera obrigatoriamente pela posição do vértice. Opcionalmente, é possível enviar outras informações como coordenadas de textura e cores. Essas informações serão interpoladas por toda a face do triângulo sendo desenhado.

Vertex Shader Ao final do processamento do Vertex Shader, o vértice é passado para o estágio de clipping. O vértice estará na tela, se estiver entre as coordenadas (-1, -1, 0, 1) e (1, 1, 1, 1). Se o vértice estiver fora do volume mostrado na imagem, ele fará com que o triângulo a que ele pertence seja clipado.

Vertex Shader

Vertex Shader Dados de input: Dados de output: POSITION: define que a variável receberá a informação de posição dos vertex buffers. TEXCOORD0: define que a variável receberá a informação de coordenadas de textura 0 do vertex buffers. Dados de output: POSITION: parâmetro de retorno obrigatório. Recebe uma posição no espaço de clip homogêneo. TEXCOORD0: recebe uma coordenada de textura que será interpolada linearmente pela face do triângulo.

Pixel Shader O parâmetro de retorno obrigatório do pixel shader é a cor final do pixel.

Pixel Shader Parâmetros de input: Parâmetros de output: TEXCOORD0: coordenada de textura interpolada.[ Parâmetros de output: COLOR: a cor final do pixel. (no exemplo estamos colocando a cor branca)

Usando Effect files no Direct3D Para criar um efeito no Direct3D usa-se a função D3DXCreateEffectFromFile.

Usando Effect files no Direct3D pDevice: o dispositivo Direct3D pSrcFile: path do effect file pDefine: permite a definição de macros do arquivo de efeitos. pInclude: permite a definição de includes no arquivo de efeitos. Flags: ... pPool: Usado para permitir o uso compartilhado de parâmetros em múltiplos efeitos. ppEffect: retorna o objeto efeito. ppCompilationErrors: retorna uma lista de erros de compilação do efeito.

Usando Effect files no Direct3D

Renderizando Triângulos Para renderizar triângulos é preciso usar a função DrawPrimitive do dispositivo Direct3D.

Renderizando Triângulos PrimitiveType: Define o tipo de primitiva para renderizar. (lista de ponto, de linhas, de triangulos, etc ...) StartVertex: índice do primeiro vértice a ser carregado para renderização. PrimitiveCount: número de primitivas a serem renderizadas.

Renderizando Triângulos Antes de qualquer DrawPrimitive deve ser chamada a função BeginScene do dispositivo Direct3D. Depois de todas as chamadas de DrawPrimitive deve ser chamada a EndScene do dispositivo Direct3D.

Resumo Básico de Renderização de Objetos 3D Definir os vertex buffers usados pelo objeto 3d. Definir a declaração de vértice do objeto 3d. Com isso o Direct3D consegue entender a informação que existe dentro dos buffers. Renderizar o objeto 3d com a função DrawPrimitive.

Renderizando triângulos

Exercício Use os vertex buffers e o vertex declaration dos exercícios anteriores e renderize um triângulo. Para isso é necessário alterar o vertex shader para receber um float4 color : COLOR0 e retornar também um COLOR0. Altere também o pixel shader para receber esse COLOR0 e colocá-lo na tela. Implemente um código que mexe o triângulo em x e y baseado nas teclas recebidas.

Matrizes e Transformações Matrizes são uma parte fundamental na computação gráfica. Elas permitem: Posicionar objetos 3D no mundo. Posicionar câmeras no mundo. Projetar o cenário 3D em um plano permitindo a visualização em monitores.

O que são matrizes? São um conjunto de elementos organizados em linhas e colunas

Operações Básicas

Multiplicação Não é Comutativa

Vetores Podem ser representados por matrizes 1xN ou Nx1. Consistem em uma entidade matemática que possui direção e tamanho. Representado por uma linha com uma ponta de flecha.

Transformações Matrizes são usadas para realizar transformações em vetores.

Transformações São operações realizadas em pontos que permitem até alterar o sistema de coordenadas do ponto. (Ex: um ponto 3D vira um ponto 2D).

Transformações

Transformações Simples

Transformações Simples Matriz Identidade Matriz de Rotação 2D Matriz de Escala Uniforme Como Implementar Translação?

Coordenadas Homogêneas Coordenadas homogêneas permitem que translações sejam realizadas com multiplicações de matrizes. As coordenadas homogêneas de um ponto de n dimensões possuem n+1 dimensões. A coordenada adicional é um escalar que multiplica todas as outras coordenadas. (x,y,z) = (wx, wy, wz, w)

Coordenadas Homogêneas Representando um ponto 2D em coordenadas homogêneas: (x, y) = (x, y, 1) ou (2x, 2y, 2) Para achar um ponto 2D representado em coordenadas homogêneas: (2x, 2y, 2) = (2x/2, 2y/2, 2/2) = (x, y, 1)

Adicionando Translação Para realizar a translação, basta usar o escalar com valor 1.

Sistemas de Coordenadas e Processo de Visualização O pipeline gráfico pega a geometria dos objetos em 3D e cria uma imagem em 2D a partir dela. Para gerar uma imagem 2D de um cenário 3D visto de um determinado ângulo é necessário fazer com que os vértices de um objeto passem por vários sistemas de coordenadas.

Sistemas de coordenadas e processo de visualização

Sistema de coordenadas locais ou do objeto ou do modelo Quando um objeto está sendo modelado, seus vértices em 3D são especificados com relação a alguma origem. Geralmente essa origem fica em alguma região próxima ao objeto ou no centro do objeto.

Sistema de coordenadas do mundo Os vértices do objeto no espaço precisam ser transformados para que o objeto possa ser posicionado no mundo. Essa transformação é quem permite posicionar um objeto em cima de uma mesa, ou dois orcs lado a lado. Isso se dá através da matriz de mundo. Após a transformação os vértices estão no espaço do mundo. Todas as transformações são feitas baseadas em uma mesma origem de mundo, por isso elas permitem posicionar os objetos no mundo. A transformação que transforma uma posição do espaço do objeto para o espaço do mundo é chamada de transformação de mundo.

Sistema de coordenadas de olho ou visão Após transformar os vértices para o espaço de mundo, eles devem ser transformado para o espaço do olho. Isso se dá com a matriz de visão. O espaço do olho transforma o mundo de acordo com a posição e orientação da câmera. Ao final da transformação a câmera está na posição 0,0,0 olhando para +z. O mundo é transformado de maneira que a câmera continue a ver o que ela deveria ver em sua posição original. Na imagem, é mostrado como a câmera é posicionada no OpenGL. Em Direct3D a câmera aponta para +z. Não existe um objeto câmera que é transformado. A posição e orientação da câmera são utilizadas apenas para transformar o mundo de maneira que a câmera fique na posição 0,0,0 e olhando para +z. Essa transformação faz esse reposicionamento do mundo para permitir que a próxima etapa de visualização aconteça de maneira rápida e eficiente.

Sistema de coordenadas de clipping homogêneo Após os vértices serem reposicionados levando em consideração a câmera, é hora de projetá-los na tela. Os vértices são transformados pela matriz de projeção. Após a transformação eles estão num espaço chamado espaço de clipping homogêneo que permite o pipeline gráfico executar o algoritmo de clipping que remove as partes das primitivas que não são visíveis.

Sistema de coordenadas normalizadas de dispositivo Após o clipping os vértices têm seu x,y e z divididos por w, o que os deixa no espaço normalizado de dispositivo.

Sistemas de coordenadas da tela A última transformação é a transformação de viewport. Essa transformação é quem coloca os vértices na posição correta na tela e permite que os triângulos sejam rasterizados.

Transformações principais As principais transformações que nós trabalharemos serão as transformações de mundo, de visão e de projeção.

Matrizes de Mundo Essas matrizes variam dependendo da forma como se deseja posicionar os objetos no mundo.

Matrizes de Visão Essa matriz que coloca a posição da câmera no processamento gráfico.

Matrizes de Projeção A matriz de projeção vai definir a forma como os vértices serão projetados.

Renderizando triângulos com perspectiva Após as matrizes de mundo, visão e projeção serem definidas, é preciso multiplicá-las na ordem World * View * Proj e depois passar a matriz resultando para o vertex shader. No vertex shader, a matriz tem que ser usada para multiplicar as posições dos vértices.

Renderizando triângulos com perspectiva

Renderizando triângulos com perspectiva

Exercício Renderize 3 triângulos, um atrás do outro. Utilize 1 único vertex buffer para os 3 triângulos. (Dica: Use uma matriz de mundo para cada triângulo). Posicione a câmera em um ângulo que permita ver os 3 triângulos Trate o input do teclado e faça a câmera se mover.

Exercício Anime os 3 triângulos da seguinte forma: 1 triângulo rotaciona no eixo y fixo na posição 0, 0, 0 1 triângulo rotaciona no eixo y fixo na posição 10, 0, 0. 1 triângulo rotaciona no eixo y na posição 20, 0, 0

Renderizando triângulos com textura Texturas são um dos principais elementos que dão realismo à um cenário 3D. Texturas são imagens que são colocadas nos modelos 3d que dão detalhes aos modelos.

Renderizando Triângulos com texturas Para criar texturas, nós usaremos a função D3DXCreateTextureFromFile.

Renderizando Triângulos com texturas Na hora de renderizar, usamos a função SetTexture do efeito.

Renderizando triângulos com textura Temos que definir as coordenadas de textura, carregar o vertex buffer e definir o vertex declaration.

Renderizando triângulos com textura

Exercício Coloque os triângulos do exercício anterior usando texturas. Use texturas diferentes para cada triângulo.

Produto Escalar Vamos achar o tamanho de b, usando os lados a e c e o ângulo entre a e c? a^2 = h^2 + c1^2 .... b^2 = h^2 + c2^2 ... h^2 = a^2 – c1^2 b^2 = a^2 – c1^2 + c2^2 .... c = c1 + c2 b^2 = a^2 – c1^2 + (c-c1)^2 b^2 = a^2 – c1^2 + c^2 – 2cc1 + c1^2 ... cos theta = c1/a b^2 = a^2 + c^2 – 2cc1 ... b^2 = a^2 + c^2 – 2ca cos theta

Lei dos Cosenos A fórmula achada é a lei dos cosenos. Ela permite achar o tamanho de b usando os lados a e c e o ângulo entre eles. b^2 = a^2 + c^2 – 2*a*c*cos(ab)

Antes uma informação ... |a| = tamanho do vetor a |a| = raiz(ax^2 + ay^2 + az^2)

Usando vetores |b-a|^2 = |a|^2 + |b|^2 – 2*|a|*|b|*cos (ab) (bx-ax)^2 + (by-ay)^2 + (bz-az)^2 – ax^2 – ay^2 – az^2 – bx^2 – by^2 – bz^2 bx^2 – 2bxax + ax2 + by^2 – 2byay + ay^2 + bz^2 – 2bzaz + az^2 - ... 2bxax - 2byay – 2bzaz = -2*|a|*|b|*cos(ab) axbx + ayby + azbz = |a|*|b|*cos(ab) Produto escalar: a * b = axbx + ayby + azbz. Ou seja, o produto escalar entre 2 vetores calcula uma informação de ângulo entre os vetores. Se os 2 vetores tiverem tamanho 1, o produto escalar dá o coseno entre os vetores.

Projetando um vetor em outro O que acontece quando um dos vetores possui tamanho 1? a * b = |a| * cos (ab) Nesse caso a * b dá o tamanho do vetor a projetado no vetor b. Então para achar o vetor projetado de a em b basta achar o a * b e multiplicar pelo vetor b .... a * b = t .... proj_a = multiplicar(b, t)

Exercício Crie uma classe Vector3d com x,y e z como floats. Crie a função Length que acha o tamanho do vetor Crie a função Normalize que normaliza esse vetor (Basta dividir os componentes pelo tamanho do vetor) Crie a função Dot que acha o produto escalar entre 2 vetores Ache o vetor projetado de um vetor em outro normalizado.

Produto Vetorial Outra ferramenta extremamente importante para programadores gráficos é o produto vetorial. O produto vetorial entre dois vetores não paralelos é outro vetor perpendicular aos dois vetores. O produto vetorial é escrito como: a x b = c Onde a, b e c são vetores e c é perpendicular aos vetores a e b.

Como calcular o produto vetorial? Abaixo a * b significa o produto escalar entre os vetores a e b. a * (a x b) = 0 b * (a x b) = 0 Ou seja: axcx + aycy + azcz = 0 bxcx + bycy + bzcz = 0

Calculando o produto vetorial Multiplicando a primeira linha por bx/ax: bxcx + bxaycy/ax + bxazcz/ax = 0 Subtraindo a linha acima da segunda linha: bxcx + bycy + bzcz – bxcx – bxaycy/ax – bxazcz/ax = 0 bycy + bzcz = bxaycy/ax + bxazcz/ax axbycy + axbzcz = bxaycy + bxazcz axbycy – bxaycy = bxazcz – axbzcz (axby – bxay)cy = (bxaz – axbz)cz

Calculando o produto vetorial Multiplicando a primeira linha por by/ay: byaxcx/ay + bycy + byazcz/ay = 0 Subtraindo da segunda linha: bxcx + bycy + bzcz – byaxcx/ay – bycy – byazcz/ay = 0 bxcx + bzcz = byaxcx/ay + byazcz/ay aybxcx + aybzcz = byaxcx + byazcz aybxcx – byaxcx = byazcz – aybzcz (aybx – byax)cx = (byaz – aybz)cz

Logo... (axby – bxay)cy = (bxaz – axbz)cz (aybx – byax)cx = (byaz – aybz)cz Vamos alterar a segunda linha: -1*(axby – bxay)cx = (byaz – aybz)cz (axby – bxay)cx = (aybz – byaz)cz

Logo .... (parte 2) (axby – bxay)cy = (bxaz – axbz)cz (axby – bxay)cx = (aybz – byaz)cz Queremos encontrar cx, cy e cz que são nossas incógnitas. Que valores cx, cy e cz possuem? Ele podem possuir infinitos valores ... Porém se você selecionar um valor qualquer ... Coisas ruins podem acontecer ... como divisão por zero ... ouch.

Logo .... (parte 3) (axby – bxay)cy = (bxaz – axbz)cz (axby – bxay)cx = (aybz – byaz)cz E se selecionarmos cz = axby – bxay? Nesse caso: cy = bxaz – axbz cx = aybz – byaz Para achar as incógnitas não precisa de nenhuma divisão ... o que é bom. Portanto, o produto vetorial entre vetores a x b: a x b = c = (aybz – byaz, bxaz – axbz, axby – bxay)

Exercício Adicione o calculo vetorial no último exercício e faça testes

Iluminação Iluminação permite a geração de cenas mais interessantes e realistas. Existem diversas formas de calcular a iluminação de uma cena. Nós utilizaremos um modelo de iluminação semelhante ao Direct3D e OpenGL, porém usaremos o cálculo de iluminação por pixel.

Iluminação A cor de um pixel é calculada da seguinte forma: Cor = Contribuição Emissiva + Contribuição Ambiente + Contribuição Diffusa + Contribuição Especular.

Contribuição Emissiva Essa contribuição representa a luz emitida pelo próprio objeto.

Contribuição Ambiente Consiste no quanto o objeto reflete a iluminação espalhada pelo ambiente. A contribuição ambiente é calculada da seguinte forma: Material Ambiente * Iluminação Ambiente Material Ambiente representa o quanto da iluminação ambiente o objeto reflete.

Contribuição Difusa Essa contribuição representa o quanto o objeto reflete para todos os lados a iluminação direta das luzes. A contribuição difusa é calculada da seguinte forma: Max((N * L),0) * Material Diffuso * Iluminação Diffusa N = Normal normalizada do ponto em que está sendo calculada a iluminação. L = Vetor para luz normalizado. Material Diffuso representa o quanto da iluminação diffusa o objeto reflete.

Contribuição Especular A contribuição especular representa o quanto de luz direta é refletida na direção principal de reflexão de uma luz. A contribuição especular é calculada da seguinte forma: Max((H * N),0)^shininess * Material Especular * Iluminação Especular H = vetor da luz + vetor do olho shininess define o quão brilhoso é o objeto. Material Especular define o quanto o objeto reflete a iluminação especular.

Renderizando objetos com iluminação por pixel

Renderizando objetos com iluminação por pixel

Transformando Normais Para transformar normais é necessário multiplicá-las pela inversa da transposta da matriz de mundo. Ou seja: n’ = n * (world^t)^-1

Compreendendo Transformações de Normais* Primeiro: Estudar equação do plano (ver parte do ppt de física) [nx ny nz –nP0] * [px py pz 1]^t = 0 q * p^t = 0 q * M * (p * R)^t = 0 q * M * R^t * p^t = 0 M * R^t = Identidade M = (R^t)^-1

Exercício Implemente o exemplo anterior usando iluminação por pixel.

Exportando, Carregando e Renderizando modelos .X Criar objetos 3D no código é relativamente extraordinariamente complexo de se fazer quando algo sofisticado é desejado. A maneira correta de se criar cenários e objetos 3d é modelando-os em programas como o 3D Studio Max. O problema é que o 3D Studio Max pode exportar em qualquer formato desejado (Se você implementar plugins). Logo precisamos definir um formato com o qual trabalharemos.

Arquivo .X (O modelo, não a série) Arquivos .X são interessantes porque a micro$oft já se deu o trabalho de implementar um carregador. Nós precisamos no entanto achar um plugin para o 3ds que exporte arquivos .X. (Procure no google por Panda DirectX Exporter). Nós iremos carregar o modelo usando a API do DirectX e depois extrairemos a informação geométrica, e com isso, iremos renderizar na tela modelos realmente interessantes...

Carregando Arquivos .X (Para pipeline fixo)

Renderizando Malhas DirectX (Usando pipeline fixo)

Exercício Exporte um modelo no 3D Studio Max e carregue-o e renderize-o usando pipeline fixo. (Para casa) Exporte um arquivo .ASE e tente escrever um parser dele.

Preparando malhas para uso em shaders

Preparando malhas para uso em shaders Após carregar a malha, nós criamos uma nova malha com um formato compatível com o nosso vertex shader. Nesse formato apenas um vertex buffer deve ser usado. (tentei com mais de 1 e não deu).

Renderizando a malha com shaders

Exercício Renderize a malha usando shaders.

Animação utilizando interpolação entre keyframes Nesse tipo de animação, um personagem é modelado em diferentes posições (como as imagens de desenhos animados) e para renderizar uma posição intermediária entre 2 key frames é feita uma interpolação linear entre 2 frames: Frame1.pos * (1-t) + Frame2.pos * t t define a posição intermediária da animação. 0 representa o frame 1 e 1 representa o frame 2. Um t = 0.5 representa uma posição no meio do caminho entre os frames 1 e 2.

Animação utilizando interpolação linear entre keyframes Para conseguirmos fazer a interpolação de maneira rápida (na GPU) precisamos enviar as posições dos dois keyframes para o vertex shader interpolar. Para isso usaremos coordenadas de texturas.

Animação usando interpolação entre keyframes

Animação usando interpolação entre keyframes

Animação usando interpolação entre keyframes

Animação usando interpolação entre keyframes

Exercício Implemente um exemplo que anima um triângulo.

Alpha Blending Usado para renderizar objetos com transparência. Quando o alpha blending está habilitado, a cor final de um pixel após renderizar uma primitiva é calculada da seguinte maneira: Final Color = (New Color * SourceBlendFactor) + (Pixel Color * DestBlendFactor)

Alpha Blending O resultado do pixel shader é uma cor. Essa cor possui os componentes rgba. Nós trabalhos com os componentes rgb até agora para dar a cor aos objetos. O componente alpha permite definir um nível de transparência quando alpha blending está habilitado. Geralmente 1 representará um pixel totalmente opaco, e 0 um pixel totalmente transparente. Para definir o componente alpha da cor gerada pelo pixel shader use:

Alpha Blending Para habilitar alpha blending:

Alpha Blending Para definir os fatores de blending:

Exercício Renderize o triângulo da cena de forma transparente (enigma) Se o triângulo transparente for renderizado antes do chão e do objeto .x ele terá sua cor calculada com o fundo preto, como fazer a cena aparecer transparente atrás do triângulo? (dica) Utilize um único float para definir o alpha do triângulo.

Volumes de sombras Sombras são muito importantes para posicionar objetos em um ambiente 3d. Através das sombras é possível saber a relação espacial entre os objetos. A técnica de volume de sombras gera sombras através da criação de um volume de sombras gerado através da malha do objeto e o stencil buffer.

Volume de sombras O volume de sombra é um objeto 3d que representa a região em sombra de outro objeto.

Stencil Buffer É necessário usar o stencil buffer para a técnica de volumes de sombra. O stencil buffer é um buffer que permite um controle à nível de pixel sobre o que é renderizado na tela. Quando o stencil buffer está habilitado, um pixel só é renderizado quando ele passa na seguinte operação: (StencilRef & StencilMask) CompFunc (StencilBufferValue & StencilMask) Todos esses elementos são customizáveis e o StencilBufferValue é o valor do stencil buffer.

O Algoritmo Limpar o Stencil Buffer para 0 Gerar o volume de sombras através da malha do objeto e da posição da luz. Renderizar o volume de sombra sem back face culling e sem z-writing. Por toda a região de todas as faces de frente do volume de sombra, adicionar 1 ao stencil buffer. Por toda a região de todas as faces de trás do volume de sombras, remover 1 do stencil buffer. Se um determinado pixel falhar em z, não alterar o stencil buffer. Renderizar um Quad ocupando toda a tela, naquelas regiões do stencil que forem diferentes de 0, escurecer.

O Algoritmo Limpar o Stencil Buffer para 0

O Algoritmo Gerar o volume de sombras através da malha e da posição da luz. Para isso, usaremos uma malha especial.

O Algoritmo Durante a criação do vertex buffer do volume de sombras para cada aresta do objeto definir triângulos da seguinte forma: (triangulos da aresta v1v2) = {v1,~v2, ~v1, v1, v2, ~v2} . ~v1 é igual a v1 só que com o w = 0. ~v2 tem a mesma relação com v2.

O Algoritmo Durante a renderização do volume de sombras, todos os vértices com w = 0 são afastados da luz uma certa distância e depois transformados. Os vértices com w != 0 são transformados normalmente.

O Algoritmo Para renderizar o volume sem back face culling e sem z-writing.

O Algoritmo Para permitir que o stencil buffer faça uma operação com as faces da frente e outra operação com as faces de trás.

O Algoritmo Para definir a adição no stencil buffer para todas as faces de frente e subtração para todas as faces de trás.

O Algoritmo Use alpha blending para renderizar volumes transparentes!

O Algoritmo Nesse momento o stencil buffer só possui valores diferentes de zero nas regiões de sombra. Para renderizar apenas nessas regiões:

Exercício Coloque o triângulo renderizando com um volume de sombras.

Bump Mapping Bump mapping é uma técnica que aumenta o realismo das cenas ao dar a aparência de mais detalhe em malhas geométricas. Para implementar a técnica, deve ser usada uma textura especial que define como é a superfície de um determinado triângulo de uma malha. Essa informação de superfície é usada na hora de calcular iluminação, e com isso o objeto parece ter muito mais detalhe do que ele possui.

Bump Mapping

Bump Mapping A textura de bump mapping que nós utilizaremos armazenará as normais da superfície dos triângulos. Quando nós calcularmos a iluminação por pixel, nós usaremos a normal adquirida pelo bump map e com isso daremos riqueza de detalhe à superfície.

Um problemão no entanto As normais da textura estão num espaço próprio delas. Chamado de espaço tangente. Ou nós convertemos a normal para o espaço de mundo, ou nós convertemos os outros vetores para o espaço tangente. Vamos converter todos os vetores para o espaço tangente.

Matriz de mudança de base A matriz de mudança de base é quem vai converter do espaço do mundo (onde a gente tem calculado a iluminação até agora) para o espaço tangente. Ela é composta por 3 componentes: Vetor normal: A normal do triângulo. Vetor tangente: É um vetor novo, que deve vir em cada vértice, e deve ser tangente à face do triângulo. O vetor tangente é perpendicular à normal. Vetor Binormal: É calculado usando um produto vetorial entre a normal e a tangente. Todos esses vetores são normalizados.

Matriz de mudança de base A cara da matriz é a seguinte: (tx, nx, bx) (ty, ny, by) (tz, nz, bz) Os vetores (da luz, do olho etc) são multiplicados por essa matriz da seguinte forma: (lx, ly, lz) * (tz, nz, bz) O exemplo acima multiplicou o vetor da luz no espaço de mundo pela matriz. O resultado é o vetor da luz no espaço tangente que nós podemos usar para calcular a iluminação normalmente. Mas porque essa matriz faz isso?

Matriz de mudança de base O vetor resulta da multiplicação anterior é composto por 3 operações: x = dot(L,T) y = dot(L,N) z = dot(L,B) Lembra que ao fazer o dot de 2 vetores sendo que 1 é normalizado, você acha a projeção do outro vetor no vetor normalizado? (T, N e B são normalizados...) dot(L,T) calcula a projeção de L no vetor tangente. dot(L,N) calcula a projeção de L no vetor normal. dot(L,B) calcula a projeção de L no vetor binormal. Essas projeções geram um vetor nos eixos x, y e z se a tangente representar o eixo x, a normal representar o eixo y e a binormal representar o eixo z.

Matriz de mudança de base Digamos que as normais são feitas da seguinte maneira: O mapa está deitado no eixo x,z As normais sem inclinação apontam para o eixo y. Esse é o espaço tangente. Os vetores Tangente, Normal e Binormal representam os vetores x,y e z porém no espaço de mundo. Ao projetar L no vetor tangente, você acha um valor que representa o tamanho de L no vetor tangente. Esse tamanho é o mesmo no eixo x. Logo ao projetar L em T, você acha L em x. Repetindo, T,N e B TEM QUE ESTAR NORMALIZADOS.

Vértices Além da posição, da normal, e da coordenada de textura, você agora precisa adicionar a tangente e, caso desejado, mais uma coordenada de textura para o bump map.

Vertex Shader

Pixel Shader

Exercício Implementar um triângulo com bump mapping. Experimente gerar proceduralmente um mapa normal com algum formato.

Grafos de Cena Grafos de cena são estruturas de dados muito importantes que são utilizadas para diferentes propósitos. O propósito mais importantes do grafo de cena é hierarquia de transformações. Um grafo de cena geralmente tem o formato de uma árvore n-ária.

Grafo de Cena Por exemplo, digamos que você queira colocar uma arma no braço de um guerreiro. Para colocar o guerreiro no ambiente 3d, ele é transformado por sua matriz de mundo. Para colocar a arma no mundo, primeiro a arma é tranformada para a posição do guerreiro, e depois é transformada para a posição da mão do guerreiro.

Grafo de Cena

Grafo de Cena

Grafo de Cena Node: é a classe que permite implementar a hierarquia de transformações. Possui uma transformação como membro e uma lista de filhos. Mesh: é uma classe que implementa a renderização de algum objeto.

Grafo de Cena No exemplo da arma, poderíamos ter 2 malhas, uma representando o torso do guerreiro e outra representando a arma. A transformação da arma poderia ser uma translação +5 em x. Após definir a transformação de mundo do guerreiro, é renderizada a malha do guerreiro. Depois essa transformação é usada para calcular a transformação de mundo da arma.

Culling em cenários usando octrees Os cenários dos jogos de hoje são complexos e detalhados. Logo, são pesados para renderizar. Porque enviar todo o cenário para a placa gráfica a cada frame, se na maioria das vezes você só consegue ver parte do cenário? A octree é uma estrutura de dados que permite reduzir o custo de renderização pois com ela é possível cálcular as regiões do cenário que são visíveis. A octree é uma árvore onde cada nó possui 8 filhos.

Culling usando octrees

Equação do plano A equação de um plano é definida geralmente de duas formas: (P – P0) * n = 0. Nesse caso P é um ponto qualquer, P0 é um ponto no plano e n é a normal normalizada do plano. Se P – P0 (que gera um vetor) dot n resultar em 0 é porque o ponto está no plano. Essa equação após distribuir os dots vira: P * n – P0 * n = 0 Ax + By + Cz + D = 0. Nesse caso A, B, C e D definem o plano. Se você colocar um ponto (x,y,z) nessa fórmula e ela der zero é porque o ponto está no plano. A,B,C correspondem aos componentes da normal e D = - P0 * n.

Calculando a distância de um ponto à um plano O que representa P * n e P0 * n na equação P * n – P0 * n ?

Calculando a distância de um ponto à um plano P0 * n = | P0 | * cos (theta) | P0 | * cos (theta) = tamanho de P0 projetado na normal = distância do plano à origem. P * n = | P | * cos (theta) | P | * cos (theta) = tamanho de P projetado na normal. P * n – P0 * n = Distância de P ao plano. Logo se essa distância for 0, o ponto está no plano. Se essa distância for positiva, o ponto está no lado do plano em que aponta a normal. Se essa distância for negativa, o ponto está no lado oposto ao qual a normal aponta.

Calculando a interseção de uma AABB com um plano A idéia do algoritmo é calcular na AABB um mínimo e um máximo baseado na posição do plano e a seguir verificar as distâncias dos pontos min e max com relação ao plano.

Calculando a interseção de uma AABB com um plano

Verificando se uma AABB está dentro de um Frustum

Calculando um frustum

Calculando um frustum

Criando a Octree

Criando a octree

Criando a Octree

Criando a octree

Renderizando com a octree

Adicionando objetos à octree

Exercício Implemente um código que calcula uma AABB em um objeto e só renderiza esse objeto se sua AABB estiver interceptando o frustum. Implemente uma octree e adicione esse objeto à octree. Renderize usando a octree.

Quaternions Quaternions são ferramentas importantes para realizar rotações. Com multiplicação de quaternions você pode realizar rotações. Realizar uma interpolação entre duas rotações é “relativamente” simples com quaternions.

Quaternions Para entender quaternions, precisamos primeiro aprender sobre números complexos. Com o conjunto dos números reais dá pra representar uma quantidade considerável de números. Nós no entanto não conseguimos representar a raiz de -4. Porque nenhum número vezes ele mesmo dá um número negativo.

Números complexos Números complexos possuem uma parte real e uma parte imaginária como escrito abaixo: a + bi a = parte real. bi = parte imaginária. Então os números abaixo são todos números complexos: 4 + 2i 1 + 10i 5 4i

Números complexos A parte imaginária possui uma peculiaridade: Ou seja: 2i * 2i = 4i^2 = -4 Com isso nós podemos encontrar a raiz de números negativos: raiz de -4 = raiz de 4 * i^2 = 2i raiz de -1 = raiz de i^2 = i

Quaternions Quaternions são números hiper-complexos (sério!). Eles possuem 3 partes imaginárias e são escritos como: a + bi + cj + dk Então o número abaixo é um quaternion: 2 + 3i + 4j + 5k

Quaternions Assim como números complexos: i^2 = j^2 = k^2 = -1 Porém números complexos possuem algumas definições adicionais (aqui começa a loucura). i * j = k j * i = -k j * k = i k * j = -i k * i = j i * k = -j i * j * k = (i * j) * k = i * (j * k) = -1

Multiplicando dois quaternions q0*q1 = (w0 + x0*i + y0*j + z0*k) * (w1 + x1*i + y1*j + z1*k). Vamos aplicar multiplicação distributiva: w0*w1 + w0*x1*i + w0*y1*j + w0*z1*k + x0*i*w1 + x0*i*x1*i + x0*i*y1*j + x0*i*z1*k + y0*j*w1 + y0*j*x1*i + y0*j*y1*j + y0*j*z1*k + z0*k*w1 + z0*k*x1*i + z0*k*y1*j + z0*k*z1*k (w0*w1 – x0*x1 – y0*y1 – z0*z1) + w0*(x1*i + y1*j + z1*k) + w1*(x0*i + y0*j + z0*k) + x0*y1*k - x0*z1*j – y0*x1*k + y0*z1*i + z0*x1*j - z0*y1*i

Definições para facilitar a vida: Um quaternion: q0 = (w0 + x0*i + y0*j + z0*k) = (w0 + v0) Ou seja: v0 = (x0*i + y0*j + z0*k) Outro quaternion: q1 = (w1 + x1*i + y1*j + z1*k) = (w1 + v1) Produto escalar: v0 * v1 = (x0*x1 + y0*y1 + z0*z1) Produto vetorial: v0 x v1 = (y0*z1*i – z0*y1*i + z0*x1*j – x0*z1*j + x0*y1*k – y0*x1*k) Escala: a * v0 = (a*x0*i + a*y0*j + a*z0*k)

Usando nossas definições q0*q1 = (w0*w1 – x0*x1 – y0*y1 – z0*z1) + w0*(x1*i + y1*j + z1*k) + w1*(x0*i + y0*j + z0*k) + x0*y1*k - x0*z1*j – y0*x1*k + y0*z1*i + z0*x1*j - z0*y1*i Torna-se: q0*q1 = w0*w1 – v0*v1 + w0*v1 + w1*v0 + v0 x v1

O conjugado de um quaternion O conjugado de um quaternion é definido como: q0 = (w0 + v0) – quaternion normal q0* = (w0 – v0) – conjugado do quaternion

Rotacionando vetores Crie 1 quaternion com w = 0 e com os componentes x,y e z do próprio vetor que você quer rotacionar: (x, y, z) -> (0, x, y, z) = v Crie outro quaternion onde w = cos(theta/2), o x,y,z é o eixo de rotação normalizado e escalado por sin(theta/2): q = (cos(theta/2) + sin(theta/2)d) A multiplicação qvq* gera um quaternion cujos elementos x,y,z são o vetor 3D rotacionado pelo eixo d do quaternion q por theta graus.

Rotacionando vetores Ou seja, digamos que queremos rotacionar o vetor (1, 0, 0) no sentido anti-horário do eixo (0, 0, 1). Queremos rotacionar 90 graus gerando o vetor (0, 1, 0). Vamos criar dois quaternions v e q: v = (0, 1, 0, 0) q = (cos(45) + sin(45)(0, 0, 1)) = (cos(45), 0, 0, sin(45)) Logo: qvq* = (0, 0, 1, 0) = Rotacionamos o vetor!

Porque isso funciona? Para entender porque quaternions funcionam precisamos primeiro aprender como criar uma matriz que rotaciona um vetor dado um eixo qualquer.

Rotacionando vetores pelo eixo z Para rotacionar um vetor em xy usando o eixo z usamos a matriz: (cos(theta) sin(theta) 0) (-sin(theta) cos(theta) 0) (vx vy vz) * (0 0 1) Como podemos fazer para rotacionar um vetor por um eixo qualquer?

Matriz de Mudança de Base Assim como no bump mapping vamos criar uma matriz que projeta o vetor em 3 outros vetores normalizados que formam uma base orthonormal. Um desses vetores base é o eixo de rotação. Os outros dois vetores podem ser dois vetores quaisquer ortogonais entre si.

O Algoritmo Criar a matriz de vetores base sendo que o vetor base que representa o eixo de rotação precisa ser a última coluna gerando o valor em z. A matriz abaixo mostra os vetores base a, b e rot, todos normalizados e rot é o eixo de rotação. (ax bx rotx) (ay by roty) (vx vy vz) * (az bz rotz) = (v*a v*b v*rot) Com essas projeções é possível achar o vetor em nosso espaço x,y,z padrão. Sendo que o eixo de rotação passa a representar o eixo z. Depois de multiplicar o vetor pela matriz acima, nós o multiplicamos pela matriz de rotação no eixo z mostrada anteriormente. Com isso o vetor rotaciona no eixo z que representa o eixo de rotação rot. Depois de rotacionar nosso vetor no eixos x,y,z padrão, devemos calcular como fica esse vetor rotacionado usando como vetores base os vetores a, b e rot. Para achar isso podemos calcular na mão, multiplicando os vetores a, b e rot pelos valores achados em x, y e z após a rotação: vfinal = vx * a + vy * b + vz * rot Ou podemos multiplicar o vetor rotacionado pela matriz inversa da matriz formada por a,b e rot.

O Algoritmo (Resumidamente) V = vetor que queremos rotacionar RV = vetor rotacionado MB = Matriz criada a partir de A, B e Rot RotZ = Matriz que rotaciona pelo eixo Z MB^t = Matriz transposta da MB. Matriz Transposta = Inversa para rotações. O Algoritmo aplica a seguinte fórmula: RV = V * MB * RotZ * MB^t

Código

Código sem problemas de cosseno

Analisando a matriz de rotação Vamos assumir que: cos(theta) = c sin(theta) = s R = (ax bx rotx) (c s 0) (ax ay az) (ay by roty) (-s c 0) (bx by bz) (az bz rotz) (0 0 1) (rotx roty rotz) R = (a^t b^t rot^t) (c s 0) (a) (-s c 0) (b) (0 0 1) (rot) R = (a^t b^t rot^t) (c*a+s*b) (-s*a+c*b) (rot) R = a^t(c*a+s*b) + b^t(-s*a+c*b) + rot^t*rot R = c*(a^t*a + b^t*b) + s*(a^t*b – b^t*a) + rot^t*rot

Algumas fórmulas v = (a*v)*a + (b*v)*b + (rot*v)*rot = x0*a + x1*b + x2*rot rot x v = rot x (x0*a + x1*b + x2*rot) = x0*(rot x a) + x1*(rot x b) + x2*(rot x rot) rot x v = x0*(rot x a) + x1*(rot x b) = x0*b – x1*a rot x v = x0*b – x1*a = (a*v)*b - (b*v)*a rot x v = (v*a^t)*b – (v*b^t)*a rot x v = v*(a^t*b – b^t*a)

Algumas Fórmulas v * I = v v * I = (a*v)a + (b*v)b + (rot*v)rot v * I = (v*a^t)a + (v*b^t)b + (v*rot^t)rot v * I = v*(a^t*a + b^t*b + rot^t*rot) I = a^t*a + b^t*b + rot^t*rot Voltando a nossa fórmula original: R = c*(a^t*a + b^t*b) + s*(a^t*b – b^t*a) + rot^t*rot R = c*(I – rot^t*rot) + s*(a^t*b – b^t*a) + rot^t*rot R = cI – c*rot^*rot + s*(a^t*b – b^t*a) + rot^t*rot R = cI + s*(a^t*b – b^t*a) + (1 – c)rot^t*rot

Algumas fórmulas rot x (rot x v) = rot x (x0*b – x1*a) = x0*(rot x b) – x1*(rot x a) = -x0*a –x1*b v = (a*v)*a + (b*v)*b + (rot*v)*rot = x0*a + x1*b + x2*rot -x*a – x1*b = x2*rot – v = (rot*v)*rot - v rot x (rot x v) = (rot*v)*rot – v = v*(rot^t*rot) – v rot x (rot x v) + v = v*(rot^t*rot) Voltando à formula: R = cI + s*(a^t*b – b^t*a) + (1 – c)rot^t*rot Se a gente multiplicar um vetor por essa matriz: vR = cv*I + sv*(a^t*b – b^t*a) + (1 – c)*v*(rot^t*rot) vR = cv*I + s*(rot x v) + (1 – c)*(rot x (rot x v) + v) vR = cv + s*(rot x v) + (1 – c)*(rot x (rot x v)) + (1 – c)v vR = cv + s*(rot x v) + (1 – c)*(rot x (rot x v)) + v – cv vR = v + sin(theta)*(rot x v) + (1 – cos(theta))*(rot x (rot x v))

Ou Seja Quando você multiplica um vetor pela matriz RV abaixo: RV = V * MB * RotZ * MB^t Você está fazendo na realidade o seguinte cálculo: vR = v + sin(theta)*(rot x v) + (1 – cos(theta))*(rot x (rot x v))

Código de rotação mais rápido

Voltando aos quaternions... Foi dito que: dado um vetor v = (x, y, z) armazenado em um quaternion q0 = (0 + v) Um eixo de rotação normalizado r = (rx, ry, rz) armazenado em um quaternion da seguinte forma: q1 = (cos(theta/2) + sin(theta/2)*r) A multiplicação: q1q0q1* gera um quaternion cujos componente x, y, z consistem no vetor v rotacionado pelo eixo r por theta graus.

Vamos analisar essa multiplicação Vamos considerar: s = sin(theta/2) c = cos(theta/2) Lembrando: q0*q1 = w0*w1 – v0*v1 + w0*v1 + w1*v0 + v0 x v1 q1q0q1* = (c + s*d)(0 + v)(c – s*d) q1q0q1* = (-s*d*v + c*v + s*d x v)(c – s*d) q1q0q1* = [-s*c*d*v – (c*v + s*d x v)*(-s*d)] + (-s*d*v)*(-s*d) + c*(c*v + s*d x v) + (c*v + s*d x v) x (-s*d) -s*c*d*v + s*c*d*v + s^2*(d x v)*d + s^2*(d*v)*d + c^2*v + s*c*(d x v) - s*c*(v x d) - s^2*(d x v) x d s^2*(d*v)*d + c^2*v + s*c*(d x v) + s*c*(d x v) - s^2*(d x v) x d s^2*(d*v)*d + c^2*v + 2*s*c*(d x v) - s^2*(d x v) x d s^2*(d*v)*d + c^2*v + 2*s*c*(d x v) + s^2*d x (d x v)

Continuando a análise s^2*(d*v)*d + c^2*v + 2*s*c*(d x v) + s^2*d x (d x v) (1 – s^2)*v + 2*s*c*(d x v) + s^2*[(d*v)*d + d x (d x v)] v – s^2*v + 2*s*c*(d x v) + s^2[(d*v)*d + d x (d x v)] v + 2*s*c*(d x v) + s^2[(d*v)*d – v + d x (d x v)] Porém: d x (d x v) = (d*v)*d – (d*d)*v = (d*v)*d – v. Logo: q1q0q1* = v + 2*s*c*(d x v) + 2*s^2*d x (d x v)

Continuando 2*s*c = 2*sen(theta/2)*cos(theta/2) = ? Para resolver essa fórmula vamos provar a seguinte fórmula: sin(a + b) = sin(a)cos(b) + sin(b)cos(a)

Prova da identidade trigonométrica RPQ = pi/2 – RQP = pi/2 – (pi/2 – RQO) = RQO = a OP = 1 PQ = sin(b) OQ = cos(b) AQ/OQ = sin(a). AQ = sin(a)cos(b). AQ = RB. PR/PQ = cos(a). PR = cos(a)sin(b) sin(a + b) = PB = RB + PR = sin(a)cos(b) + cos(a)sin(b) Logo: sin(theta) = sin(theta/2)cos(theta/2) + cos(theta/2)sin(theta/2) sin(theta) = 2sin(theta/2)cos(theta/2)

Outra Prova OP = 1 PQ = sin(b) OQ = cos(b) OA/OQ = cos(a). OA = cos(a)cos(b). RQ/PQ = sin(a). RQ = sin(a)sin(b). RQ = BA. cos(a + b) = OB = OA – BA = cos(a)cos(b) – sin(a)sin(b) Logo cos(theta) = cos(theta/2)cos(theta/2) – sin(theta/2)sin(theta/2) cos(theta) = cos^2(theta/2) – sin^2(theta/2) E Ainda: cos(theta) = (1 – sin^2(theta/2)) – sin^2(theta/2) cos(theta) = 1 – 2*sin^2(theta/2) 1 – cos(theta) = 2*sin^2(theta/2)

Voltando à fórmula do quaternion q1q0q1* = v + 2*s*c*(d x v) + 2*s^2*d x (d x v) q1q0q1*= v + sin(theta)*(d x v) + (1 – cos(theta))*d x (d x v) Comparando à fórmula de rotação por um eixo qualquer: vR = v + sin(theta)*(rot x v) + (1 – cos(theta))*(rot x (rot x v)) Podemos ver então que a multiplicação de quaternions q1q0q1* resulta na mesma equação da rotação de vetores por um eixo qualquer mostrada anteriormente.

Exercício Rotacione um vetor usando multiplicação de quaternions.

Tópicos para Programação Distribuída Fundamentos de Distribuição Técnicas de Distribuição para Jogos

Modelos de Distribuição Cliente / Servidor

Modelos de Distribuição Peer to Peer

Cliente / Servidor Capaz de manter milhares de jogadores no mesmo jogo (Utilizado em Massively Multiplayer Games). Há necessidade de manter um servidor (Custos podem ser elevados).

Peer to Peer (Ponto a Ponto) Armazena (geralmente) menos jogadores por instância de jogo. Não requer servidor de grande poder computacional.

TCP – Transmission Control Protocol Protocolo complexo que implementa a comunicação como um fluxo de bytes que trafega entre dois pontos (Circuito Virtual). Garante a chegada das mensagens As mensagens chegam de forma ordenada Algoritmos para redução do número de mensagens Comunicação 1 x 1

TCP - Conexão Há necessidade de estabelecer conexão Função connect() da API dos sockets.

TCP – Envio de Mensagens Garantia de chegada

TCP – Término da conexão Ocorre quando a função closesocket() é chamada.

UDP – User Datagram Protocol Protocolo simples que implementa comunicação como um envio de mensagens de forma atômica. Não há garantia de chegada Mensagens podem chegar desordenada ou duplicadas Permite realização de broadcasts ou multicast (comunicação 1 x N) Não há necessidade de se conectar.

Sockets API criada para abstrair a conexão entre computadores em diferentes sistemas operacionais. Um socket é formado pela união IP + Porta, que define unicamente uma parte de uma conexão.

Função socket() Função que cria um socket. SOCKET socket(int family, int type, int protocol) family: Define a família de protocolos utilizados. AF_INET = Protocolos convencionais da internet. type: Define o tipo de socket. SOCK_STREAM ou SOCK_DGRAM. protocol: Define o tipo de protocolo dentro da família. IPPROTO_TCP = TCP, IPPROTO_UDP = UDP.

Função socket() Exemplo: SOCKET s = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP).

Função connect() Estabelece conexão int connect(SOCKET socket, const struct sockaddr *serveraddr, int addrlen) socket: O socket do cliente serveraddr: O endereço (IP+Porta) do servidor. addrlen: O tamanho da estrutura serveraddr.

Estrutura sockaddr_in Estrutura passada para a função connect. struct sockaddr_in {    short sin_family;    //AF_INET unsigned short sin_port;    struct   in_addr sin_addr;    char sin_zero[8]; }; Define o endereço de um socket remoto

Estrutura in_addr

Função connect() SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(SERVER_PORT); addr.sin_addr.s_addr = inet_addr(“127.0.0.1”); connect(s, (const sockaddr*)&addr, sizeof(addr));

Função bind() Função usada pelo servidor para definir um socket local int bind(SOCKET s, const sockaddr *localaddr, int addrlen); s: o socket do servidor localaddr: o endereço local addrlen: o tamanho do endereço

Função bind() SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(LIST_PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); bind(server, (const serveraddr*)&addr, sizeof(addr));

Função listen() Define um socket como passivo. Informa o S.O. que deve aceitar conexões direcionadas à esse socket. int listen(SOCKET s, int backlog); s: o socket do servidor backlog: O número de conexões que podem ficar pendentes para esse socket.

Função listen() //continuação do código da bind() listen(server, 8);

Função accept() Chamada por um servidor TCP para retornar a próxima conexão completa do ínicio da fila de conexões completas. SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen); s: o socket do servidor addr: o endereço do novo cliente addrlen: o tamanho do endereço Essa função retorna o socket do novo cliente e bloqueia quando não há conexões terminadas.

Função accept() while(true) { addrlen = sizeof(addr); cli_socket = accept(server, (...)&addr, &addrlen); ProcessClientSocket(cli_socket); }

Função closesocket() Libera os recursos alocados ao socket. int closesocket(SOCKET s);

Função send() função que envia bytes usando um socket conectado. int send(SOCKET s, const char * buffer, int len, int flags); s: o socket conectado por onde enviar os dados buffer: os bytes a serem enviados len: o número de bytes a serem enviados flags: Flags

Função send() //continuando o código da connect char msg[]= “coéeee!”; send(s, msg, strlen(msg)+1, 0);

Função recv() Função usada para receber dados de uma conexão. int recv(SOCKET s, char * buffer, int len, int flags);

Função recv() //continuando o codigo da send() char reply[64]; recv(s, reply, 64, 0); cout << reply;

Função sendto() Usada com sockets UDP não conectados int sendto(SOCKET s, const char *buf, int len, int flags, const struct *destaddr, int addrlen);

Função recvfrom() Função usada em servidores UDP sem conexões. int recvfrom(SOCKET s, char * buffer, int len, int flags, sockaddr *addr, int *addrlen);

Para usar a API de sockets no windows Para inicializar a DLL do sockets use: Para finalizar a DLL: Inclua a lib: ws2_32.lib Header file: winsock2.h

Exercício Implementar uma aplicação cliente / servidor usando TCP onde o cliente envia uma string para o servidor e o servidor escreve na tela aquilo que recebeu. O servidor só aceita 1 cliente de cada vez.

Exercício Implementar uma aplicação cliente / servidor semelhante ao exercício 1 porém usando UDP.

Livros Importantes TCP/IP Illustrated, Vol 1 Unix Network Programming, Vol 1 (Ambos do W. Richard Stevens!)

Técnicas de Distribuição para Jogos

Multithreading (Revisão) Conhecimento fundamental (Xbox 360). Thread = Parte de um programa que executa concorrentemente ou paralelamente a outras partes do programa. Comunicação entre partes é feita através de variáveis compartilhadas. Um game = Thread Principal (Main) + Outras Threads

Multithreading Múltiplas threads parecem executar paralelamente mesmo em um único processador devido ao time slicing.

Multithreading (Exemplo)

Criando uma thread uintptr_t _beginthread( void( *start_address )( void * ), unsigned stack_size, void *arglist ); start_address: endereço de uma função que inicia a execução da thread. stack_size: tamanho da pilha da nova thread. arglist: ponteiro para parâmetro passado para thread.

Criando uma thread int A = 0; void Add(void * data) { A++; } void main() _beginthread(Add, 0, NULL); //tente comentar essa linha Sleep(1000); cout << A; char c; cin >> c;

Case Study: Project Gotham Racing Core Thread Software threads Update, physics, rendering, UI 1 Audio update, networking Crowd update, texture decompression Texture decompression 2 XAudio This title is also on Xbox 360. Things to notice: rendering on same thread as update. Two decompression threads. One unused thread, to leave all cycles to audio. Audio was a problem in this title. The update thread and crowd update threads both need to trigger sounds, which required grabbing a critical section that the Audio update thread was often holding.

Exercício Evolua o exercício 1 de forma que o servidor se torne capaz de processar múltiplos clientes simultaneamente. Utilize 1 thread por cliente.

Técnicas usando TCP, UDP e Sockets

Entendendo TCP O Algoritmo de Naggle e o Delayed Ack. Desligando o algoritmo BOOL b = TRUE; setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char*)&b, sizeof(bool));

Usando sockets UDP conectados sendto() em alguns sistemas operacionais realiza conexão, envio e desconexão. Permite receber mensagens ICMP.

Enviando múltiplos buffers em Sockets WSABUF MsgBuf[2]; MsgBuf[0].buf = &Cabecalho; MsgBuf[0].len = sizeof(Cabecalho); MsgBuf[1].buf = MsgChat; MsgBuf[1].len = strlen(MsgChat); WSASend(Socket, MsgBuf, 2, &BytesSent, Flags, NULL, NULL);

Implementando Garantia de Chegada Como calcular o RTO (Retransmission Timeout Timer) ? Levar em consideração o tempo estimado de round trip time (SRTT) e a variação estimada dos tempos de round trip time (VARRTT).

Implementando Garantia de Chegada Calculando RTT estimado (SRTT): delta = RTT – SRTT SRTT = SRTT + (delta / RTTINF) Calculando variação de RTT (VARRTT): VARRTT = VARRTT + ((| delta | - VARRTT) / VARRTTINF) Calculando RTO: RTO = SRTT + VARRTT * VARINC

Exercício (Para casa) Implemente um protocol de reliable UDP.

Técnicas para Ambientes Virtuais Distribuídos

Dead-Reckoning O conceito de Dead-Reckoning Forma normalmente usada: pos = pos0 + (vel * dir) * deltat Com Dead-Reckoning baseado a objetivos há a possibilidade de redução ainda maior no número de mensagens trocadas. Ex: “Desejo ir para o destino x,y”.

Sincronização baseada em tempo de comando Alterando velocidades é possível fazer uma sincronização visual. T 0s T 1s T 2s

Eliminando Informações Desnecessárias Geralmente, não há necessidade de enviar informações sobre objetos não perceptíveis.

Balanceamento Para conseguir processar um MMORPG é necessário quebrar o processamento entre diversos servidores.

Outras técnicas Nível de detalhe Agregação de Mensagens

Exemplo de Servidor MMORPG

Construindo a Rede de servidores A ferramenta permite dividir responsabilidades entre os servidores

Diagrama de Classes

Outras características IOCP Culling usando Grid Balanceamento usando retângulos Reliable UDP + Last Update Sincronização por Tempo de Comando Dead Reckoning baseado em objetivos

Resultados

Exercício Implemente uma demo onde existem 2 retângulos e cada retângulo sobre movimentação de um cliente diferente. Ao mover um retângulo, o outro cliente deve ver a movimentação. Implemente dead reckoning para reduzir o número de mensagens trocadas.

Tópicos para Física Teste de Interseção Raio – Plano Teste de Interseção Raio - Triângulo Teste de Interseção Esfera – Esfera Teste de Interseção Elipsóide - Elipsóide Teste de Interseção AABB – AABB Detecção de Colisão em cenário estático usando elipsóides Resposta à Colisão Picking Otimizando a colisão com Binary Space Partition Trees. Dinâmica Linear Dinâmica Rotacional

Interseção Raio - Plano Um plano pode ser expressado através da fórmula: N dot (P – P0) = 0 Onde N é a normal do plano, P é um ponto qualquer e P0 é um ponto no plano. Qualquer P colocado na fórmula que dê zero como resultado está no plano.

Interseção Raio - Plano Um raio pode ser expressado através da seguinte fórmula: O + tD Onde O é a origem do raio, D é a direção normalizada e t é um valor que permite achar qualquer ponto na extensão do raio.

Interseção Raio - Plano Devemos achar um t que gere um ponto que quando utilizado na fórmula do plano resulte em zero. Ou seja, devemos achar um t que resulte em um ponto no plano. t vai indicar a distância percorrida da origem ao local de interseção.

Interseção Raio - Plano O + tD = P N dot (P – P0) = 0 N dot (O + tD – P0) = 0 N*O + tN*D – N*P0 = 0 tN*D = N*P0 – N*O t = (N*P0 – N*O) / N*D t = N*(P0 – O) / N*D Quando N*D for igual a zero, o raio é paralelo ao plano.

Interseção Raio - Plano

Interseção Raio - Triângulo Ache a interseção do raio com o plano do triângulo. Ache uma componente na normal diferente de zero, projete em um plano do eixo alinhado de acordo com a componente isso transforma nosso problema em 2D. Use os coeficientes angulares entre o ponto e os vértices para verificar se o ponto está dentro do triângulo.

Interseção Raio - Triângulo Para achar a relação entre o coeficiente angular do ponto e uma aresta, testa-se a seguinte condição: (v2y–v1y) / (v2x–v1x) < (py–v1y) / (px–v1x) (v2y-v1y)*(px-v1x) < (py-v1y)*(v2x-v1x) 0 < (py-v1y)*(v2x-v1x) - (v2y-v1y)*(px-v1x)

Interseção Raio - Triângulo

Interseção Raio - Triângulo

Interseção Esfera - Esfera Um esfera é representada computacionalmente através de uma posição C que define o centro da esfera e um tamanho r que define o raio da esfera. Duas esferas estão colidindo se a distância entre os centros for menor que a soma dos raios das esferas, ou seja: Está colidindo se: | C1 – C2 | < r1 + r2 Onde C1 e C2 são os centros das esferas 1 e 2 e r1 e r2 são os raios das esferas 1 e 2.

Interseção Esfera - Esfera

Interseção Elipsóide - Elipsóide O que diabos é um elipsoíde? Elipsóide é como uma esfera que possui um raio de tamanho diferente para cada eixo.

Interseção Elipsóide - Elipsóide A interseção de elipsóides é muito parecida com a de esferas. Dois elipsóides estão colidindo se: | C1 – C2 | < | R1 | + | R2 | Onde C1 e C2 são os centros dos elipsóides. R1 é o vetor que vai de C1 até a superfície da elipsóide 1 na direção de C2. R2 é similar porém indo na direção de C2 para C1.

Interseção Elipsóide - Elipsóide O tamanho do raio de uma elipsóide varia dependendo da direção, logo como achar o tamanho de R1 e R2?

Interseção Elipsóide - Elipsóide Ou seja, após achar a direção, normalizar e depois multiplicar pelos tamanhos dos raios nos eixos x, y e z.

Interseção Elipsóide - Elipsóide

Interseção AABB - AABB AABB significa Axis Aligned Bounding Box, ou seja, representa uma caixa com os alinhada aos eixos x,y e z.

Interseção AABB - AABB AABBs são representadas por um ponto mínimo e um ponto máximo. Duas AABBs estão colidindo se em nenhum momento algum dos testes triviais de rejeição retornam sucesso.

Interseção AABB - AABB

Exercício Implemente uma aplicação que renderiza dois cubos. Coloque duas AABBs envolvendo cada um dos cubos. Faça um dos cubos se moverem com o uso de setas. Implemente a detecção de colisão entre os cubos e impeça que os cubos entrem um no outro.

Detecção de Colisão em cenário estático usando Elipsóides Como podemos colidir elipsóides com triângulos?

Colisão com cenários estáticos Para detectar colisão: Lance um raio do centro da elipsóide na direção do triângulo usando a normal negativa do triângulo como direção.

Colisão com cenário estáticos Calcule o tamanho do raio nessa direção usada. Se o raio acerta o triângulo e sua distância para o plano é menor que o raio da elipsóide na direção usada, a elipsóide colide com o triângulo.

Colisão com cenários estáticos

Resposta à colisão Após detectar a colisão temos que tratar a resposta à colisão. Uma resposta normalmente dada é empurrar o objeto para fora da geometria em que ele colidiu. Como vamos empurrar a elipsóide para fora do triângulo?

Resposta a colisão Nós empurramos o centro da elipsóide para fora na direção da normal do plano. A distância empurrada = Tamanho raio – Distância centro-plano.

Resposta à colisão

Exercício Implemente uma aplicação onde no cenário há um triângulo. Coloque um elipsóide como BV da câmera. Faça a câmera se mover e colidir com o triângulo.

Picking Algo bastante utilizado em games é a seleção de algum objeto 3d com o uso do mouse. Para conseguirmos descobrir o objeto selecionado, precisamos lançar um raio no espaço de mundo. Para descobrirmos esse raio precisamos saber onde no mundo se encontra a posição selecionada na tela. Com essa informação podemos lançar o raio partindo da câmera e passando por essa posição 3D no mundo.

Picking Para achar a posição no espaço 3D usaremos as informações para a criação do frustum de visão. Para achar a metade da altura da tela em espaço de mundo você calcula(usando os valores da imagem como exemplo): tg(15) = x / near tg(15) * near = x; Para achar a metade da largura basta fazer: half_width = x * aspect; aspect é a divisão da largura da tela pela altura. Na prática, o valor 15 da figura será FOVy / 2 (FOVy é o mesmo valor passado na função de criação de matriz de perspectiva).

Picking

Binary Space Partition Trees BSPs são árvores binárias muito parecidas com as árvores binárias de busca. (Na realidade, a árvore binária de busca pode ser vista como uma BSP de 1 dimensão). BSPs são úteis para acelerar a detecção de colisão.

Binary Space Partition Trees Uma BSP é uma árvore onde cada nó representa um plano. Esse plano corta o espaço em 2. Os planos dos filhos a direita e esquerda cortam os espaços resultantes da direita e da esquerda.

Binary Space Partition Trees Para detectar a colisão de um objeto com outros: Adicione todos os objetos à BSP. Os objetos terminam em listas nas folhas da árvore. Detectar colisão de cada objeto com todos os outros objetos da mesma folha.

Exercício (Para Casa) Implementar uma BSP. Adicionar objetos usando AABBs. Implemente a detecção de colisão de cada objeto com os outros da mesma folha.

Dinâmica Linear Como podemos mover objetos linearmente de maneira fisicamente realista? Aplicamos a segunda lei de Newton: F = M * A Dessa forma: F / M = A Onde F é força, M é massa e A é aceleração. F e A são vetores, M é um escalar.

O tempo e a posição Aceleração = variação de velocidade / variação de tempo Velocidade = variação de posição / variação de tempo Posição = área da função da velocidade em uma faixa de tempo Velocidade = área da função da aceleração em uma faixa de tempo

Um problema complicado Dada uma aceleração, temos que achar a variação da velocidade e depois a variação da posição. O problema é que isso envolve uma integração de uma função (e nós não sabemos a função previamente ).

Método de Euler Consiste no método mais simples para achar as áreas. Nele você adquire o valor no início do intervalo e considera ele constante durante todo o intervalo.

Método de Euler Para adquirir a posição: Aceleração = força / massa Posição = posição + velocidade * variação de tempo Velocidade = velocidade + aceleração * variação de tempo

Implementando o método de Euler

Dinâmica Rotacional Nós aprendemos a mexer linearmente um objeto. Mas e quanto a rotação? Torque é o equivalente da força para o movimento rotacional. Para achar o torque de um corpo rígido, ache o produto vetorial entre o vetor que vai do centro de massa do objeto até o local onde está sendo aplicada uma força e o vetor da força.

Descobrindo a rotação Torque = Cross(R, F) Aceleração Angular = Torque / Inércia Rotação = Rotação + Velocidade Angular * variação de tempo. Velocidade Angular = Velocidade Angular + Aceleração Angular * variação de tempo.

Descobrindo a rotação

Exercício Implemente um demo que renderiza uma caixa. Implemente física linear aplicando uma força constante na caixa. Implemente física rotacional causando a caixa rotacional dependendo do lugar onde se aplica a força.

Tópicos para Inteligência Artificial Máquinas de Estados Embedding Lua na sua aplicação Path Finding

Máquinas de Estado Máquinas de Estado são uma das principais técnicas de inteligência artificial usadas desde o primórdio dos tempos (~1980). Máquinas de Estado são máquinas abstratas compostas por estados e eventos que geram transições nesses estados. Elas são úteis pois permitem a criação de seres que possuem comportamento dependendo do estado e que variam esse comportamento dependendo de eventos que ocorrem no jogo.

Máquinas de Estado Um soldado em um jogo pode ter a seguinte máquina de estado:

Maquinas de estado

Maquina de estado

Máquinas de Estado Paralelas Quando uma determinada entidade exige um comportamento sofisticado sua máquina de estado tende a ficar complexa demais. A solução pra isso é fazer uma mesma entidade usar múltiplas máquinas paralelas para resolver diferentes problemas. Por exemplo: Digamos que aquele inimigo do exemplo anterior passe a usar uma metralhadora e 2 máquinas de estado, uma para movimentação e outra para atacar.

Maquina de estado de movimentação

Maquina de estado de combate

Máquinas de estado paralelas Implemente da mesma forma que a máquina de estado normal, porém use 1 variável de estado por máquina e 1 switch por máquina.

Sincronizando máquinas de estado É interessante às vezes fazer com que máquinas de estado façam interações. Half-life fez isso com seus soldados que se coordenavam ao combater o player. Para fazer essa comunicação basta usar variáveis compartilhadas como variáveis estáticas.

Sincronizando máquinas de estado Digamos que nós queremos que um inimigo ao avistar o player avise aos outros inimigos da localização do player e todos os outros inimigos a seguir se movem para a região avisada.

Máquina de estado de movimentação

Exercício Implemente um demo com 2 caixas. Uma caixa é o inimigo e se move pela tela seguindo por 4 posições pré-definidas. A outra é controlada pelo jogador. Quando a caixa do jogador se aproxima à uma certa distância. O inimigo começa a se aproximar até que ele chega à uma distância e pára. Se o jogador se afastar depois do inimigo, o inimigo volta a caminhar no seu caminho pré-definido. Implemente esse demo usando uma máquina de estados.

Embedding Lua na sua aplicação Scripting é uma adição fundamental à qualquer engine. Scripting fornece uma ambiente sandbox para programadores de gameplay e de inteligência artificial onde eles podem criar lógica e conteúdo sem se preocupar em quebrar nada interno da engine. Ou seja, Scripting fornece permite que um trabalho puramente criativo seja realizado. Scripting também é necessário se você deseja que seus jogos sejam “modable”.

Embedding Lua

Embedding Lua A primeira linha cria o objeto de estado lua que é usado em todas as funções. A função dofile é quem executa scripts. No final lua_close deve ser chamado para liberar recursos.

Escrevendo e lendo valores

Escrevendo e lendo valores Script em lua: Resultado: x = 7 e y = 9

Escrevendo e lendo valores Lua utiliza de uma pilha para trocar valores com o código em C++. A função setglobal define o nome de uma variável e a pushnumber seu valor. A função getglobal coloca uma variável no topo da pilha que pode ser capturada com a função tonumber que recebe o estado lua e o índice que se deseja usar para pegar um valor.

Adicionando funções à Lua Scripts são úteis para customização de comportamentos, implementação de lógica de jogo e AI. Porém scripts são mais lentos que código compilado em C++, portanto para certas computações custosas é valido implementá-las em C++ e adicionar a função à Lua.

Adicionando funções a Lua

Adicionando funções a Lua

Adicionando funções a lua A função register registra uma função em lua. Dentro da função faz-se uso da pilha normalmente para acessar valores. Para retornar valores usa-se push e o valor retornado define o numero de valores a serem retornados (É possível retornar mais de um valor).

Exercício Reimplemente o exemplo anterior executando a máquina de estados em Lua ao invés de executá-la em C++. Adicione à lua a função de distância implementada em C++.

Path Finding Um problema muito comum em diversos jogos é a necessidade de calcular o caminho para se mover de um ponto a outro do mapa. Para resolver esse problema nós usaremos o algoritmo A*.

A* O algoritmo A* é um algoritmo que implementa uma busca heurística. Uma busca heurística leva em consideração alguma informação antes de tomar uma decisão. No nosso caso, o A* levará em consideração informações geométricas para tomar decisões.

Min-Heap O algoritmo do A* precisa de uma estrutura de dados que implemente uma fila de prioridade. Uma fila de prioridade implementa uma função para retornar o elemento mais prioritário de forma eficiente. Uma forma de implementar uma fila de prioridade é usar a estrutura Min-Heap.

Min-Heap Uma Min-Heap é semelhante à uma árvore binária que possui a seguinte regra: Cada nó é menor que seus filhos. A Min-Heap pode ser implementada em um vetor.

PriorityQueue

PriorityQueue

PriorityQueue

PriorityQueue

PriorityQueue

A* A prioridade de um nó é calculada usando 2 informações. A distância percorrida até o nó = g. A distância do nó atual até o nó destino = h. A prioridade é então calculada: f = g + h Essas prioridades fazem o algoritmo selecionar os nós que se aproximam mais rapidamente do nó destino.

A*

A*

Exercício Implemente um PathFinder usando A* que recebe uma matriz de inteiros onde 1 é lugar passável e 0 é parede, e a partir dessa matriz é criado uma estrutura de dados onde cada nó possui norte, sul, leste e oeste. Faça o PathFinder calcular o caminho entre duas posições. Imprima as posições numa console application.

Fundamentos de Programação de Jogos Arquitetura de Tecnologias de Jogos

Arquitetura de Tecnologia de Jogos Jogos 3D possuem a vantagem de poderem compartilhar muita tecnologia. Isso aumenta a recompensa em implementar uma engine de jogos. Engines de jogos podem possuir designs completamente diferentes, logo o design mostrado aqui não deve ser considerado o design supremo e fundamental de engines.

Implementando um Jogo Uma forma interessante de dividir os diferentes momentos de um jogo é implementando-o como uma máquina de estados. Cada estado é implementado em uma classe diferente.

Implementando um Jogo

Loop Principal Uma forma, para jogos single player é a seguinte: Renderiza a cena (Sem dar Present) Adquire e Processa Input do Player Processa AI Processamento física, testa colisão e resposta Processamentos extras, específicos do estado atual Present

Game Engine Todo List (Single Player) 1 Level Loader que carrega o arquivo com a configuração do level. Esse arquivo aponta para outros arquivos que deve ser carregados para a execução do level. 1 engine 3d para carregar e renderizar o cenário com efeitos especiais de maneira rápida. 1 engine de física/colisão com estruturas otimizadas para detecção de colisão. Esse engine move objetos e detecta colisão e repassa o tratamento da resposta para o game. 1 engine de input para aquisição do input. 1 engine de AI que carrega dados em um cenário específicos para AI e processa as entidades AI do game. Todos são Singletons.

Level Loader Tem como missão carregar o arquivo que representa o level. Esse arquivo possui os nomes de todos os outros arquivos que serão usados no level. Ex: O arquivo da octree do cenário. O arquivo que armazena os waypoints do cenário. O arquivo que armazena a estrutura usada para processar a colisão. Os modelos, os scripts. O level loader repassa todos esses nomes para as respectivas engines carregarem.

Engine 3D Responsável pela parte gráfica do jogo. Carrega o cenário e os modelos 3D. Pode carregar uma estrutura especial como uma Octree. Carrega também a parte gráfica dos elementos do cenário. Renderiza o cenário 3D e os modelos usando técnicas de aceleração e efeitos especiais.

Engine 3D

Engine 3D (Extras) É interessante criar plugins para 3DS Max para exportar cenário e modelos. É importantíssimo implementar um level editor onde as fases são feitas a la wysiwyg onde você define locais onde ficam as entidades, triggers e scripts especiais. Você também configura características gráficas específicas da engine e vê em tempo real as alterações.

Engine de Física / Colisão Carrega a estrutura de colisão e a parte física dos elementos da cena. Pode carregar por exemplo uma BSP. Processa a física das entidades e usa técnicas de aceleração para os testes de colisão. Delega a resposta de colisão ao game. Isso pode ser feito com ponteiros para função ou funções virtuais.

Engine de Física / Colisão

Engine de Input Wrapper de funções que fazem polling de dispositivos.

Engine de AI Carrega a estrutura de dados necessária para executar a AI. Como exemplo, carregar os caminhos possíveis pela AI. Carrega a parte de AI das entidades da cena. Processa movimentação e inteligência usando C++ e delegando para Lua partes do processamento.

Engine de AI

Problema O AI Engine decide mover um NPC para alguma região. Ele então requisita a movimentação para o Engine de Física. O Engine de física o move normalmente. Como o engine gráfico recebe essa alteração? Uma solução é fazer uso de IDs. Um elemento é identificado nas 3 engines através do uso de um mesmo ID. Logo se o engine de AI decidir mover um personagem, ele identifica o personagem através de seu ID. O engine gráfico antes da renderização requisita todos os elementos que se moveram para atualizar suas posições.

Elementos e IDs

Exercício Final Implementar um Jogo 3D