Ferramentas do usuário

Ferramentas do site


prog2:visualizacao_de_modelos_3d

Diferenças

Aqui você vê as diferenças entre duas revisões dessa página.

Link para esta página de comparações

Ambos lados da revisão anterior Revisão anterior
Próxima revisão
Revisão anterior
prog2:visualizacao_de_modelos_3d [2019/08/01 16:07]
maziero
prog2:visualizacao_de_modelos_3d [2019/12/09 10:06] (atual)
maziero
Linha 1: Linha 1:
 +====== Visualização de Modelos 3D ======
 +
 +{{ :​prog2:​animation.gif |}}
 +
 +Este projeto consiste em construir um programa para ler uma descrição de objeto em 3 dimensões de um arquivo em disco e apresentar na tela a visualização desse objeto, considerando uma projeção em perspectiva.
 +
 +Os requisitos do projeto são:
 +
 +  * Ler a descrição de objeto 3D de um arquivo em disco. O objeto está descrito no formato //Wavefront OBJ//, usado em ferramentas de modelagem/​visualização 3D como //​Blender//,​ //​3Dstudio//​ e //Rhino//.
 +  * Calcular a projeção em perspectiva dos vértices e arestas do objeto.
 +  * Mostrar em uma janela gráfica a projeção obtida com representação //​[[https://​en.wikipedia.org/​wiki/​Wire-frame_model|wireframe]]//,​ usando uma biblioteca gráfica.
 +  * Usar as teclas de setas //​left/​right//​ e //up/down// para mudar a posição do observador nos eixos X e Y, respectivamente,​ recalculando a projeção a cada mudança de posição.
 +  * Usar a tecla "​ESC"​ ou "​q"​ para sair do programa.
 +
 +A seguir serão apresentados mais detalhes sobre cada um desses requisitos.
 +
 +===== Formato Wavefront OBJ =====
 +
 +O formato de dados [[https://​en.wikipedia.org/​wiki/​Wavefront_.obj_file|Wavefront OBJ]] é considerado um "​formato universal"​ para a representação de objetos em 3 dimensões, sendo reconhecido pela maioria dos softwares de modelagem/​visualização em 3D.
 +
 +Nesse tipo de arquivo, os dados do objeto são representados em formato ASCII. Os dados com representação mais frequente são os //​vértices//​ e as //faces//:
 +
 +  * um **vértice** é um ponto no espaço, com coordenadas (x, y, z) em ponto flutuante.
 +  * uma **face** é uma sequência de 3 ou mais vértices que define uma superfície.
 +
 +Um exemplo simples de objeto 3D no formato OBJ:
 +
 +<code obj cubo.obj>​
 +# OBJ - Wavefront object file
 +# Esta linha contém um comentário
 +
 +# definição dos vértices (coordenadas x y z)
 +# números podem ser inteiros ou reais
 +v 16 32 16        # definição do vértice v1
 +v 16 32 -16       # definição do vértice v2
 +v 16 0 16         # ...
 +v 16 0 -16
 +v -16 32 16
 +v -16 32 -16
 +v -16 0 16
 +v -16 0 -16
 +
 +# definição das faces
 +f 1 3 4 2         # face f1: v1 -> v3 -> v4 -> v2 -> v1
 +f 6 8 7 5
 +f 2 6 5 1
 +f 3 7 8 4
 +f 1 5 7 3
 +f 4 8 6 2
 +</​code>​
 +
 +Além dos vértices e faces, o formato OBJ também permite definir linhas, pontos, normais, texturas e outros elementos importantes em um modelo 3D.
 +
 +<note important>​
 +Neste projeto, somente precisam ser tratadas as declarações de vértices e faces. As demais declarações (vn, vt, l, etc) podem ser ignoradas.
 +</​note>​
 +
 +Mais exemplos de objetos 3D estão disponíveis a seguir:
 +
 +  * {{spike.obj|Pirâmide}} (5 vértices, 5 faces)
 +  * {{cube.obj|Cubo}} (8 vértices, 6 faces)
 +  * {{dodecahedron.obj|Dodecaedro}} (20 vértices, 36 faces)
 +  * {{teapot.obj|Bule}} (822 vértices, 1600 faces)
 +  * {{skeleton.obj|Esqueleto}} (8338 vértices, 16034 faces)
 +  * {{bunny.obj|Coelho}} (34834 vértices, 69451 faces)
 +
 +Esses arquivos podem ser facilmente visualizados no Linux através do programa ''​g3dviewer'',​ ou neste [[https://​a360.autodesk.com/​viewer |visualizador online]] (ou [[http://​3dviewer.net/​|neste]]).
 +
 +===== Projeção em perspectiva =====
 +
 +A técnica de [[https://​en.wikipedia.org/​wiki/​3D_projection|projeção 3D]] consiste em transformar uma representação 3D em 2D, ou seja, com todos os seus pontos em um mesmo plano, possibilitando então sua visualização na tela do computador.
 +
 +Existem vários tipos de projeção, como a projeção ortográfica e a projeção em perspectiva. Na projeção em perspectiva,​ a posição do observador é considerada nos cálculos da transformação 3D-2D. Uma boa explicação da projeção em perspectiva pode ser encontrada [[http://​www.scratchapixel.com/​lessons/​3d-basic-rendering/​computing-pixel-coordinates-of-3d-point|neste site]].
 +
 +==== Perspectiva fraca ====
 +
 +Os cálculos necessários à projeção podem ser complexos e demorados, mas algumas simplificações podem ser realizadas. Este projeto usa uma simplificação chamada //​perspectiva fraca// (//weak perspective//​),​ descrita a seguir:
 +
 +Considerando que as coordenadas da câmera são [x<​sub>​c</​sub>​ y<​sub>​c</​sub>​ z<​sub>​c</​sub>​] e que a câmera está olhando para a origem [0 0 0], a conversão das coordenadas 3D de cada vértice v = [x<​sub>​v</​sub>​ y<​sub>​v</​sub>​ z<​sub>​v</​sub>​] em sua projeção 2D p = [x<​sub>​p</​sub>​ y<​sub>​p</​sub>​] no plano z = 0 pode ser calculada desta forma:
 +
 +  * x<​sub>​p</​sub>​ = x<​sub>​c</​sub>​ + z<​sub>​c</​sub>​ × ( (x<​sub>​v</​sub>​ - x<​sub>​c</​sub>​) ÷ (z<​sub>​v</​sub>​ + z<​sub>​c</​sub>​) )
 +
 +  * y<​sub>​p</​sub>​ = y<​sub>​c</​sub>​ + z<​sub>​c</​sub>​ × ( (y<​sub>​v</​sub>​ - y<​sub>​c</​sub>​) ÷ (z<​sub>​v</​sub>​ + z<​sub>​c</​sub>​) )
 +
 +Assim é obtida uma coleção de pontos [x<​sub>​p</​sub>​ y<​sub>​p</​sub>​] que representa os vértices do objeto projetados no plano z=0.
 +
 +==== Conversão para coordenadas de tela ====
 +
 +Para que os pontos obtidos na etapa anterior possam ser plotados na tela, eles devem primeiro ser ajustados para o intervalo [(0,0) ... (//width//, //​height//​)],​ onde //width// e //height// são as dimensões da janela de visualização,​ em pixels.
 +
 +Esta etapa transforma o o conjunto de pontos projetados [x<​sub>​p</​sub>​ y<​sub>​p</​sub>​] em um conjunto de pontos de desenho [x<​sub>​d</​sub>​ y<​sub>​d</​sub>​],​ que podem ser usados para plotar as arestas dos objetos na tela.
 +
 +**Passo 1**: calcular mínimos, máximos, centros e diferenças das coordenadas em X e Y
 +
 +  * x<​sub>​min</​sub>​ = min (x<​sub>​p</​sub>​)
 +  * x<​sub>​max</​sub>​ = max (x<​sub>​p</​sub>​)
 +  * x<​sub>​cen</​sub>​ = (x<​sub>​max</​sub>​ + x<​sub>​min</​sub>​) / 2
 +  * x<​sub>​dif</​sub>​ = x<​sub>​max</​sub>​ - x<​sub>​min</​sub>​
 +
 +  * y<​sub>​min</​sub>​ = min (y<​sub>​p</​sub>​)
 +  * y<​sub>​max</​sub>​ = max (y<​sub>​p</​sub>​)
 +  * y<​sub>​cen</​sub>​ = (y<​sub>​max</​sub>​ + y<​sub>​min</​sub>​) / 2
 +  * y<​sub>​dif</​sub>​ = y<​sub>​max</​sub>​ - y<​sub>​min</​sub>​
 +
 +**Passo 2**: calcular fator de escala para o desenho na tela
 +
 +  * W: largura da janela de desenho (em pixels)
 +  * H: altura ​ da janela de desenho (em pixels)
 +  * esc<​sub>​x</​sub>​ = W / x<​sub>​dif</​sub>​
 +  * esc<​sub>​y</​sub>​ = H / y<​sub>​dif</​sub>​
 +  * escala = min (esc<​sub>​x</​sub>,​ esc<​sub>​y</​sub>​)
 +
 +**Passo 3**: centrar pontos da projeção em [0 0]
 +
 +  * ∀ (x y)
 +    * x'<​sub>​p</​sub>​ = x<​sub>​p</​sub>​ - x<​sub>​cen</​sub>​
 +    * y'<​sub>​p</​sub>​ = y<​sub>​p</​sub>​ - y<​sub>​cen</​sub>​
 +
 +**Passo 4**: ajustar escala dos pontos da projeção para a tela
 +  * ∀ (x y)
 +    * x"<​sub>​p</​sub>​ = x'<​sub>​p</​sub>​ * escala
 +    * y"<​sub>​p</​sub>​ = y'<​sub>​p</​sub>​ * escala
 +
 +**Passo 5**: ajustar pontos do desenho em relação ao centro da tela
 +
 +  * ∀ (x y)
 +    * x<​sub>​d</​sub>​ = x"<​sub>​p</​sub>​ + W / 2
 +    * y<​sub>​d</​sub>​ = y"<​sub>​p</​sub>​ + H / 2
 +
 +<note tip>
 +Os passos 3 a 5 podem ser condensados em um único passo:
 +
 +  * ∀ (x y)
 +    * x<​sub>​d</​sub>​ = ( (x<​sub>​p</​sub>​ - x<​sub>​cen</​sub>​) * escala) + W / 2
 +    * y<​sub>​d</​sub>​ = ( (y<​sub>​p</​sub>​ - y<​sub>​cen</​sub>​) * escala) + H / 2
 +</​note>​
 +
 +Com isso é obtido um conjunto de pontos no intervalo [(0, 0) .. (//width//, //​height//​)] que pode ser usado para plotar na janela gráfica as arestas que definem o objeto 3D.
 +
 +===== Atividade =====
 +
 +Para apresentar a visualização de um objeto 3D, o programa deve:
 +
 +  - Ler o conjunto de vértices e faces do arquivo de entrada
 +    - Modelos grandes podem conter milhões de vértices, portanto o uso de alocação dinâmica de memória é obrigatório.
 +    - Para tratar os dados do arquivo OBJ, sugere-se usar a função ''​strtok''​.
 +  - Para uma dada posição da câmera, calcular as projeções 2D dos vértices 3D.
 +  - Converter os pontos projetados para coordenadas de tela; sugere-se usar uma janela de 800×600 pixels.
 +  - Desenhar as faces na tela, produzindo uma representação //​wireframe//​ do objeto. Para desenhar uma face, desenhe separadamente as arestas que a compõem. Por exemplo, para desenhar a face (1 2 3 4), desenhe as arestas (1 2), (2 3), (3 4) e (4 1).
 +  - Ler as teclas de setas (← → ↑ ↓), ajustar as coordenadas da câmera, recalcular a projeção e desenhá-la novamente.
 +  - Tecla ESC (ou fechar a janela) para sair do programa.
 +
 +Formas de chamada do executável (**ambas** devem ser implementadas):​
 +
 +  # usando argc/argv
 +  wireframe arquivo.obj
 +  ​
 +  # usando stdin  ​
 +  wireframe < arquivo.obj
 +  # ou
 +  cat arquivo.obj | wireframe
 +
 +A implementação deve atender os seguintes requisitos:
 +
 +  * Usar as funções da biblioteca gráfica indicada pelo professor
 +  * Usar ''​struct''​ para representar os elementos do modelo (vértices, faces, arestas)
 +  * Alocar memória para os elementos do modelo de forma dinâmica (''​malloc''/''​free''​)
 +  * Funcionar para **todos** os exemplos disponíveis nesta página
 +  * Estrutura **sugerida** de arquivos do código-fonte:​
 +    * ''​wireframe.c'':​ arquivo principal
 +    * ''​datatypes.h'':​ tipos de dados usados no programa
 +    * ''​objread.c/​h'':​ funções de leitura do arquivo OBJ
 +    * ''​perspect.c/​h'':​ funções de cálculo de perspectiva
 +    * ''​graphics.c/​h'':​ funções de apresentação gráfica
 +    * ... (se necessário)
 +  * Usar ''​Makefile''​
 +    * cláusulas ''​all''​ (default), ''​clean''​ e ''​purge''​
 +    * usar flag de compilação ''​-Wall''​
 +    * separar as fases de compilação e de ligação
 +    * **usar regras implícitas**
 +
 +Bônus (+ 20/100 pontos cada):
 +  * Usar o mouse para girar o objeto na tela
 +  * Usar perspectiva completa ao invés da perspectiva fraca
 +
 +O que deve ser entregue ao professor:
 +  * arquivos ''​.c''​ e ''​.h''​
 +  * arquivo ''​Makefile''​
 +  * **não enviar** os arquivos OBJ de teste
 +
 +===== Estruturas de dados sugeridas =====
 +
 +As estruturas mais adequadas para este projeto são vetores de elementos (de pontos 3D, de pontos 2D, de retas, etc). Como o número de elementos não é fixo, esses vetores devem ser alocados dinamicamente. Entretanto, o número de elementos só é conhecido ao final da leitura do arquivo OBJ, por isso não é possível alocar cada vetor com seu tamanho final desde o início do programa.
 +
 +A estratégia recomendada para esse problema é fazer uma alocação inicial e aumentá-la conforme a necessidade (usando a chamada ''​realloc''​). Como a realocação de memória é uma operação demorada, sugere-se realocar os vetores em "​blocos"​ de centenas ou milhares de elementos.
 +
 +/* 
 +
 +O pseudocódigo a seguir ilustra a definição e alocação do vetor de pontos 3D:
 +
 +<code c>
 +// define um ponto 3D
 +typedef struct
 +{
 +  float x, y, z ;
 +} ponto3d_t ;
 +
 +// tamanho do bloco de alocação
 +#define TAM_BLOCO 1000
 +
 +// definição do vetor de pontos 3D
 +ponto3d_t p3d[]  = NULL;  // ponteiro para vetor de pontos 3D
 +int num_pontos3d = 0 ;    // número de pontos 3D
 +int max_pontos3d = 0 ;    // número máximo de pontos 3D
 +
 +...
 +
 +// após ler um ponto 3D do arquivo OBJ
 +
 +// se for necessário,​ realoca vetor de pontos
 +if (num_pontos3d >= max_pontos3d)
 +{
 +  // adiciona TAM_BLOCO elementos ao tamanho do vetor
 +  max_pontos3d += TAM_BLOCO ;
 +  p3d = realloc (p3d, max_pontos3d * sizeof (ponto3d_t)) ;
 +  if (!p3d)
 +  {
 +    // erro: realloc falhou
 +  }
 +}
 +
 +// guarda o ponto lido (primeiro ponto é p3d[1])
 +num_pontos3d++ ;
 +p3d[num_pontos3d].x = valor lido de x ;
 +p3d[num_pontos3d].y = valor lido de y ;
 +p3d[num_pontos3d].z = valor lido de z ;
 +</​code>​
 +
 +*/
 +
 +===== A biblioteca SDL =====
 +
 +Existe uma infinidade de bibliotecas gráficas; entre as mais populares está a biblioteca SDL (//​[[https://​www.libsdl.org/​|Simple DirectMedia Layer]]//). Além de gráficos, essa biblioteca permite a produção de sons e o gerenciamento de dispositivos de entrada/​saída (//mouse//, teclado, //​joystick//​) em várias plataformas.
 +
 +<note important>​
 +Recomenda-se usar SDL 2, a versão atual da biblioteca. Tome cuidado, pois existem muitos exemplos e tutoriais na Internet que usam versões mais antigas da SDL, incompatíveis com a versão atual.
 +</​note>​
 +
 +Documentação e tutoriais sobre SDL:
 +
 +  * [[https://​www.libsdl.org]]
 +  * [[https://​wiki.libsdl.org/​SDL_RenderDrawLine]] (exemplo de desenho de retas)
 +  * [[http://​lazyfoo.net/​SDL_tutorials/​index.php]]
 +
 +Instalação da biblioteca SDL 2 em Linux (Ubuntu, Mint, Debian):
 +
 +<​code>​
 +sudo apt-get install libsdl2-dev
 +</​code>​
 +
 +Arquivos de cabeçalho necessários:​
 +
 +<code c>
 +#include <​SDL2/​SDL.h>​
 +</​code>​
 +
 +Flags de compilação:​
 +  ​
 +  LDLIBS = -lSDL2
 +
 +===== A biblioteca Allegro =====
 +
 +A biblioteca gráfica [[http://​liballeg.org/​|Allegro]] permite a manipulação de gráficos simples e áudio, sendo bem adaptada para a construção de jogos 2D. É uma biblioteca mais simples e limitada que SDL, mas boa para projetos menores.
 +
 +Algumas de suas características:​
 +
 +  * multiplataforma:​ Linux, Windows, MacOS, Android, iOS
 +  * pode ser usada em C e outras linguagens
 +  * usa aceleração gráfica (através de OpenGL ou DirectX)
 +  * manipulação de áudio e vídeo
 +  * leitura de mouse, teclado e joystick
 +
 +Instalação da biblioteca Allegro 5 em Linux (Ubuntu, Mint, Debian):
 +
 +<​code>​
 +sudo apt-get install liballegro5-dev
 +</​code>​
 +
 +Arquivos de cabeçalho necessários (mínimo):
 +
 +<code c>
 +#include <​allegro5/​allegro.h>​
 +</​code>​
 +
 +Flags de compilação (mínimo):
 +
 +  LDLIBS = -lallegro