• Nenhum resultado encontrado

Renderização 3D usando árvores esparsas de voxels

N/A
N/A
Protected

Academic year: 2021

Share "Renderização 3D usando árvores esparsas de voxels"

Copied!
64
0
0

Texto

(1)

UNIVERSIDADE FEDERAL DE SANTA CATARINA DEPARTAMENTO DE INFORMÁTICA E ESTATÍSTICA

Gabriel Müller

RENDERIZAÇÃO 3D USANDO ÁRVORES ESPARSAS DE VOXELS

Florianópolis 2019

(2)
(3)

Gabriel Müller

RENDERIZAÇÃO 3D USANDO ÁRVORES ESPARSAS DE VOXELS

Trabalho de conclusão de curso submetido ao Curso de Gradu-ação em Ciências da ComputGradu-ação para a obtençăo do Grau de Bacharel em Ciências da Computação.

Orientador: Prof. Dr. Alexandre Gonçalves Silva

Florianópolis 2019

(4)
(5)

Gabriel Müller

RENDERIZAÇÃO 3D USANDO ÁRVORES ESPARSAS DE VOXELS

Este trabalho de conclusão de curso foi julgado aprovado para a obtenção do Título de “Bacharel em Ciências da Computação”, e aprovado em sua forma final pelo Curso de Graduação em Ciências da Computação.

Florianópolis, 01 de julho 2019.

Prof. Dr. Alexandre Gonçalves Silva Orientador

Banca Examinadora:

Prof. Dr. Rafael de Santiago

Prof. Dr. Aldo Von Wangenheim

(6)
(7)

If you want to set off and go develop some grand new thing, you don’t need millions of dollars of capitalization. You need enough pizza and Diet Coke to stick in your refrigerator, a cheap PC to work on, and the dedication to go through with it.

(8)
(9)

RESUMO

A representação e renderização de modelos 3D em computador é feita tradicionalmente empregando polígonos, que representam uma superfície. Este trabalho apresenta uma forma de construir e visualizar modelos tridimensionais com dados volumétricos em vez de superficiais. A estrutura de dados desenvolvida armazena unidades chamadas voxels, – equivalentes 3D a pixels – dispostas em uma árvore de subdivisão espacial conhecida como octree. O armazenamento particionado poupa espaço em memória ao reduzir informações redundantes e permite executar a renderização de forma eficiente com a técnica de ray tracing. Também é mostrada uma interface intuitiva para a criação e visualização de volumes, com exemplos práticos em alguns domínios. Apesar de representar um novo método de criação e representação de objetos 3D de forma mais intuitiva, o modelo de voxels ainda tem poucas aplicações, salvo em meio acadêmico. Este trabalho tem como objetivo desenvolver um renderizador eficiente, baseado em voxels, acelerado em GPU, e fácil de usar em diferentes áreas e plataformas. Para esse fim, uma aplicação visualizadora de modelos volumétricos em tempo real usando OpenGL foi desenvolvida.

(10)
(11)

SUMÁRIO 1 INTRODUÇÃO . . . 11 1.1 METODOLOGIA . . . 12 1.2 OBJETIVOS . . . 12 1.2.1 Objetivo geral . . . 12 1.2.2 Objetivos específicos. . . 12 2 FUNDAMENTAÇÃO TEÓRICA . . . 13 2.1 RASTERIZAÇÃO. . . 13 2.2 RAY TRACING . . . 13

2.3 ÁRVORE ESPARSA DE VOXELS . . . 15

2.4 PARALELIZAÇÃO EM CPU COM OPENMP . . . 16

2.5 COMPUTAÇÃO DE PROPÓSITO GERAL EM GPU (GPGPU) . . . 16

2.6 TECNOLOGIAS DE PROGRAMAÇÃO EM GPU . . . 17

2.6.1 CUDA . . . 17

2.6.2 OpenCL . . . 17

2.6.3 OpenGL . . . 18

3 TRABALHOS CORRELATOS . . . 21

3.1 GIGAVOXELS, REAL-TIME VOXEL-BASED LIBRARY TO RENDER LARGE AND DETAILED OBJECTS . . . 21

3.2 EFFICIENT SPARSE VOXEL OCTREES . . . 22

3.3 HIGH RESOLUTION SPARSE VOXEL DAGS . . . 22

3.4 OUT-OF-CORE CONSTRUCTION OF SPARSE VOXEL OCTREES . . . 23

3.5 POTREE: RENDERING LARGE POINT CLOUDS IN WEB BROWSERS . . 23

3.6 CONSIDERAÇÕES SOBRE TRABALHOS RELACIONADOS . . . 24

4 REPRESENTAÇÃO E ALGORITMO DE CONSTRUÇÃO DO MO-DELO . . . 27

4.1 REPRESENTAÇÃO EM MEMÓRIA . . . 27

4.2 ALGORITMO DE CONSTRUÇÃO . . . 27

4.3 DESEMPENHO . . . 29

5 ALGORITMO E IMPLEMENTAÇÃO DA RENDERIZAÇÃO . . . 31

5.1 ALGORITMO DE RAY TRACING EM OCTREE . . . 31

5.2 DETALHES DE IMPLEMENTAÇÃO . . . 33

5.3 APLICAÇÃO . . . 35

5.4 DESEMPENHO . . . 38

6 CONCLUSÕES . . . 41

REFERÊNCIAS . . . 43

ANEXO A -- Código da implementação do algoritmo de renderização . . . . 47

ANEXO B -- Artigo para Conferência da Sociedade Brasileira de Com-putação . . . 53

(12)
(13)

11

1 INTRODUÇÃO

A representação e renderização de modelos 3D em computador é feita tradicional-mente com uso de polígonos. Por meio de várias transformações da pipeline gráfica, vérti-ces no espaço 3D são projetados para um sistema de coordenadas 2D e rasterizados para as unidades discretas da tela chamadas pixels. Para mitigar o alto custo computacional de renderização de uma cena complexa a cada quadro, foram popularizados circuitos dedi-cados para o processamento gráfico em uma placa chamada de unidade de processamento gráfico (GPU).

No entanto, objetos do mundo real apresentam volume, enquanto polígonos repre-sentam apenas superfícies. Uma representação volumétrica pode ser feita subdividindo um objeto 3D em pequenas unidades chamadas voxels, análogos tridimensionais aos pi-xels. Voxels e outras representações baseadas em amostragem discreta são usadas prin-cipalmente em tecnologias de escaneamento como tomografia computadorizada (LAINE; KARRAS, 2010). No entanto, o uso de voxels traz uma série de desafios: como armazenar

uma grande quantidade deles de forma eficiente? Como renderizar um modelo com grande quantidade de detalhes rapidamente? (CHEN; KAUFMAN; YAGEL, 2000).

A renderização baseada em árvores esparsas de voxel traz uma possível solução a estas questões. A estrutura de dados armazena voxels em uma árvore de subdivisão espacial conhecida como octree. Um voxel pode ter até oito filhos, cada qual é oito vezes menores que o pai. Pode-se pensar em um voxel conceitualmente como um cubo, e os filhos são o resultado da subdivisão do cubo pai em oito cubos. Se todos filhos são idênticos, os mesmos não precisam ser armazenados – o voxel pai é suficiente. Como consequência disso, o espaço em memória é reduzido pela eliminação de informações redundantes.

Além disso, é possível executar a renderização de forma eficiente com a técnica de ray tracing, já que a árvore permite uma busca baseada na posição no espaço cartesiano. Um raio que surge da câmera e passa em cada pixel da tela é simulado. Quando o raio atinge um voxel pai, calcula-se qual voxel filho foi atingido até chegar-se num voxel folha. Quando isso acontece, a cor do pixel é definida pelas informações do voxel e possivelmente pela informação de iluminação da cena.

A representação em voxels tem a vantagem de ter uma estrutura regular fácil de manipular, o que permite representar modelos com diferentes níveis de detalhe de forma natural (CRASSIN et al., 2009). A abordagem volumétrica também facilita a criação e

edição de modelos. Enquanto na criação de modelos poligonais é necessário especificar a superfície e considerar a topologia, a representação de voxel permite criar volumes de forma livre e topologia complexa. (WANG; KAUFMAN, 1995)

No entanto, esse método de renderização apresenta algumas limitações. Vértices de uma malha poligonal podem ser transformados a fim de provocar o efeito de movimento, mas a animação de voxels não tem uma solução ideal. Armazenar uma árvore de voxels para cada quadro é custoso, enquanto separar a cena em árvores independentes para cada objeto implica na travessia de várias árvores, o que reduz o desempenho. Além disso, a projeção de polígonos conta com o circuito especializado das GPUs, enquanto o ray tracing de voxels tem um acesso à memória não amigável à cache do hardware gráfico.

Por isso, há espaço para resolver duas questões: como contornar as limitações descri-tas, e quais aplicações práticas podem usufruir das vantagens desse modelo. Este trabalho tem como objetivo desenvolver um motor gráfico eficiente baseado em voxels com acele-ração em GPU, e demonstrar a sua aplicabilidade em diferentes áreas e plataformas.

(14)

12

1.1 METODOLOGIA

O tema deste trabalho é explorado com ênfase no desenvolvimento do renderiza-dor. Para embasar tal implementação, são selecionados trabalhos do estado da arte. Os conceitos da fundamentação teórica são sintetizados a partir de trabalhos e páginas instrucionais.

O trabalho é realizado com um foco prático para que seja possível adquirir um conhecimento profundo e especializado sobre o assunto. Ao contrário da estratégia mais comum de realizar uma pesquisa extensiva antes de começar qualquer implementação, neste projeto a pesquisa e o desenvolvimento são realizados em paralelo. Observa-se que é mais fácil entender um trabalho correlato e adotar uma postura crítica quando já houve tentativa de implementação própria.

Para avaliar o desempenho da aplicação, também são realizados experimentos con-siderando as variáveis mais importantes: resolução da imagem, do modelo, e hardware.

1.2 OBJETIVOS

1.2.1 Objetivo geral

Desenvolver um motor gráfico de árvores esparsas de voxel com aceleração em GPU baseado no estado da arte e estudar potenciais usos da aplicação.

1.2.2 Objetivos específicos.

• Desenvolver um protótipo do motor gráfico em CPU. • Implementar multi-threading em CPU.

• Desenvolver aceleração gráfica para o motor.

• Portar a aplicação para sistemas e arquiteturas distintas. • Demonstrar uma aplicação prática para o motor gráfico.

(15)

13

2 FUNDAMENTAÇÃO TEÓRICA

Neste capítulo são apresentados conceitos e técnicas necessários para o desenvolvi-mento do trabalho.

2.1 RASTERIZAÇÃO

Em computação gráfica, renderização é o processo de sintetizar uma imagem a partir de uma cena tridimensional. Tal processo exige cálculos para cada pixel da tela a cada quadro. Com dezenas de quadros por segundo e milhões de pixels por quadro, a renderi-zação em tempo real é computacionalmente custosa. Para mitigar essa dificuldade, foram desenvolvidos algoritmos eficientes e hardware especializado para essa tarefa. A técnica mais comum para renderização em tempo real ainda é a rasterização de polígonos.

A superfície de um modelo 3D é tipicamente representada por vértices (coordenadas no espaço 3D) e triângulos (associações de três vértices). Esses dados passam por uma sequência de etapas chamada de pipeline gráfica. Os dados em cada etapa são processados paralelamente, mas a próxima etapa só começa quando a anterior chega ao fim. Na etapa do vertex shader, os vértices são projetados do espaço 3D de mundo para o espaço 2D da tela, enquanto a etapa de rasterização transforma os triângulos, agora projetados, em pixels, conforme ilustra a Figura 1. Na renderização de superfícies opacas, é necessário determinar para cada pixel qual parte da geometria é visível e quais partes são ocluídas. Para esse fim, mantém-se a informação de distância da superfície até a câmera numa imagem interna chamada de z-buffer. Outras etapas da pipeline podem ser usadas para adicionar ou detalhar a geometria (GRUPO KHRONOS, 2019c).

Para o cálculo da iluminação, vetores normais são associados aos polígonos. Esses vetores representam a direção perpendicular à superfície. Quando a luz incide paralela-mente ao vetor, a intensidade da luz refletida é máxima. Quando a luz é perpendicular ao vetor (portanto paralela à superfície), a intensidade é mínima.

A transformação de um vértice não depende de outros vértices. Da mesma forma, a rasterização de cada pixel é uma tarefa independente. As fabricantes de placa de vídeo aproveitaram essa propriedade para produzir circuitos altamente paralelizados, com hard-ware específico para a execução da pipeline baseada em rasterização. Esses dispositivos, conhecidos como GPUs, tornaram viável a renderização 3D em tempo real.

O processo de rasterização, por tratar as primitivas de forma separada, não é capaz de representar fielmente fenômenos óticos como sombras, reflexões, refrações e iluminação indireta. Técnicas para aproximar esses efeitos na pipeline foram desenvolvidas, mas a simulação real do comportamento da luz requer outras técnicas, como o ray tracing (DENG et al., 2017).

2.2 RAY TRACING

O algoritmo de ray tracing usa o princípio básico da visão, em que raios de luz são emitidos, refletem ou refratam, e chegam na abertura da câmera. Usualmente, é representada uma câmera no mundo, com a posição de origem, direção, e uma tela virtual. Para fins de renderização, só são relevantes os raios que acabam chegando no observador.

(16)
(17)
(18)
(19)

17

ou oito cores e acesso à memória RAM, o dispositivo conta com milhares de unidades aritméticas concorrentes e tipos de memória distintos na VRAM. A transferência de in-formações entre RAM e VRAM é feita por um barramento (por exemplo, PCIe). Tal operação é mais custosa que uma leitura em memória e deve ser evitada para fins de otimização. A programação na CPU tem um paralelismo limitado, e é a solução ideal para algoritmos sequenciais. Já problemas que podem ser fragmentados em milhares de tarefas independentes são uma boa escolha para programação na GPU.

As ferramentas GPGPU têm chamadas especias para trabalhar com o dispositivo. Funções que executam no dispositivo são chamadas de kernels (núcleos). Na chamada de um kernel, são especificados o número de blocos e threads em cada bloco. O kernel pode ser executado com milhões de threads (uma para cada pixel, por exemplo), e o escalonador do dispositivo se encarrega em distribuir as tarefas apropriadamente.

Também é possível alocar os diversos tipos de memória da placa. Quando não espe-cificado, as variáveis são armazenadas nos registradores ou na memória local das threads. As threads de um mesmo bloco podem usar a mesma memória compartilhada, enquanto blocos distintos acessam a mesma memória global, memória constante ou memória de tex-tura. A memória constante não permite escritas, o que garante que condições de corrida não acontecerão. A memória de textura, por sua vez, leva em consideração a disposição bidimensional da informação na localidade espacial da cache (SANDERS; KANDROT, 2011).

2.6 TECNOLOGIAS DE PROGRAMAÇÃO EM GPU

A seguir são descritas as principais ferramentas para computação de propósito geral em GPU.

2.6.1 CUDA

Disponível desde 2007 nas placas de vídeo da NVIDIA, a API CUDA é a primeira na área de GPGPU. A linguagem CUDA C++, uma extensão de C++, permite desenvolver código no mesmo arquivo para tanto o hospedeiro como o dispositivo.

A NVIDIA não publica o conjunto de instruções da arquitetura de suas placas. O código do dispositivo é compilado para PTX (Parallel Thread Execution), uma linguagem intermediária similar a linguagem de montagem. Um driver proprietário, então, compila o PTX para a arquitetura do dispositivo. Programas CUDA não são compatíveis com dispositivos de outras fabricantes.

2.6.2 OpenCL

OpenCL, lançado em 2009 pelo grupo Khronos, tem como objetivo oferecer um padrão aberto para execução de código em várias plataformas. O OpenCL suporta diver-sos fabricantes, placas de gráfico integrado, CPUs multicore e até mesmo smartphones. No entanto, benchmarks mostram que essa API não alcança o mesmo desempenho que CUDA, com execução de 13% a 63% mais devagar (KARIMI; DICKSON; HAMZE, 2010).

(20)

18

2.6.3 OpenGL

Inicialmente, a API OpenGL disponibilizava uma pipeline fixa, mas configurável. Com o surgimento de placas gráficas programáveis, foi desenvolvida na versão 1.4 a lingua-gem de programação de shaders GLSL (GRUPO KHRONOS, 2019b). Shader (sombreador,

em português), é um programa executado na GPU que pode tanto calcular o sombrea-mento de objetos como fazer outras tarefas, por exemplo, projeção de vértices e efeitos pós-processamento. Também há os compute shaders, que usam o processamento da GPU para uma tarefa não relacionada a gráficos (GRUPO KHRONOS, 2019a).

Este trabalho utiliza shaders de fragmento em GLSL para realizar a renderização na GPU. GLSL é uma linguagem estilo C, com tipos para vetor e matriz e várias funções aceleradas em hardware. A linguagem não suporta recursão, já que existem placas que não possuem uma pilha de execução de fato para cada thread. Um uso simples de shader de fragmento é receber como entrada as coordenadas de um pixel e ter como saída a cor do pixel. Segue um exemplo:

1 in vec4 g l _ F r a g C o o r d ; 2 out vec4 o u t C o l o r ; 3 uniform uvec2 v i e w p o r t S i z e ; 4 5 void main () { 6 vec2 uv = g l _ F r a g C o o r d / v i e w p o r t S i z e . xy ;

7 o u t C o l o r = vec4 ( fract ( uv *2.) , abs ( sin ( uv . x * uv . y *100.) ) , 1.0) ; 8 }

Esse programa define uma entrada padrão do OpenGL, gl_FragCoord, que define as coordenadas do window space (espaço da janela). Se usarmos apenas as componentes x, y, temos as coordenadas dos pixels da tela. Também é definida uma entrada uniforme, assim chamada pois é a mesma para todos os pixels. A saída outColor é a cor resultante do pixel.

Algumas funções matemáticas são demonstradas: fract (parte fracionária), abs (valor absoluto), e sin (seno). Essas funções podem ser aplicadas a um tipo escalar como float ou a um tipo vetorial, como vec4. Além de todas operações matemáticas usuais, tipos vetoriais suportam swizzling, o reordenamento ou repetição das componentes. Por exemplo, a expressão vec2(3.0, 2.0).xyyx é equivalente a vec4(3.0, 2.0, 2.0, 3.0).

Tradicionalmente, shaders são compilados e ligados durante a execução do programa. Mais recentemente na versão 4.6, é possível compilar para a linguagem intermediária de sombreamento chamada SPIR-V.

(21)
(22)
(23)
(24)

22

Figura 7 – Exemplo de mipmap de uma textura. Esse esquema produz uma representação mais fiel quando a textura é reduzida a um tamanho pequeno. Fonte: (GEYTER, 2015)

3.2 EFFICIENT SPARSE VOXEL OCTREES

Este trabalho (LAINE; KARRAS, 2010) apresenta uma estrutura de dados compacta

para representar cenas ricas em detalhes, junto com um algoritmo de renderização imple-mentado em CUDA.

A árvore é dividida no nível mais alto em blocos, porções contíguas de memória. Cada bloco armazena voxels com informação de contorno, uma propriedade dos voxels usada para representar a superfície em termos de poliedros em vez de cubos. Essa informação de contorno é armazenada nos voxels não-folha, cada qual define um truncamento do cubo por dois planos paralelos. Três inteiros de 6 bit descrevem o vetor normal compartilhado pelos dois planos, enquanto dois inteiros de 7 bit especificam a posição de cada plano, totalizando 32 bits por nodo. O poliedro resultante é a intersecção do cubo representado pelo nodo folha e o contorno de todos seus ancestrais.

As propriedades de iluminação são comprimidas em blocos de 16 valores. Cada bloco de compressão usa 64 bits para cor. Os primeiros 32 bits armazenam duas cores RGB-565 (5, 6, 5 bits para as componentes vermelho, verde e azul, respectivamente). Os 32 bits restantes descrevem cada uma das 16 cores como uma de quatro possíveis interpolações entre as duas cores. Também é apresentada uma técnica original para compressão de vetores normais. Os vetores normais do grupo são escolhido entre 16 candidatos, que ocupam 4 × 16 = 64 bits. Esses candidatos encontram-se numa grade 4 × 4 definida por três vetores, que ocupam mais 64 bits.

Também é destacada a otimização de feixe, uma técnica relativamente simples para reduzir o tempo de travessia dos raios. A otimização consiste em lançar um subconjunto dos raios até certo ponto e deixar os raios restantes completarem a travessia com precisão maior. A tela é dividida em quadrados de 4 × 4 ou 8 × 8 pixels e um raio é lançado em cada canto do quadrado. A travessia para esses raios termina quando encontra-se um voxel pequeno demais para garantir que o mesmo cubra ao menos um raio do subconjunto. Para os raios restantes, a travessia começa na distância mínima dos quatro cantos, menos uma constante apropriada.

3.3 HIGH RESOLUTION SPARSE VOXEL DAGS

Esta implementação (KäMPE; SINTORN; ASSARSSON, 2013) generaliza a estrutura de

árvore para a estrutura de grafo direcionado acíclico (directed acyclic graph, ou DAG). Enquanto a SVO codifica espaços vazios e volumes homogêneos utilizando poucos nodos,

(25)
(26)

24

Figura 9 – Ilustração da ordem Morton numa quadtree. Fonte: (BAERT; LAGAE; DUTRÉ, 2013)

A resolução é definida não por uma quantidade específica de pontos, e sim pelo espaçamento mínimo entre pontos, o qual é definido como 1/128 do tamanho do cubo limitante. Por exemplo, considere uma cena que é limitada por um cubo de 128 metros em todas suas dimensões. Na raiz, é armazenada uma subamostragem, de forma que todos pontos tenham ao menos um metro de distância do vizinho mais próximo. Os filhos da raiz representam cubos de 64 metros em cada dimensão, portanto esses nodos permitem pontos com ao menos 0,5 metros de espaçamento, e assim sucessivamente.

Na renderização, é feita uma busca profunda na árvore no volume mais próximo à câmera, enquanto as partes distantes são buscadas na parte rasa da árvore. Isso faz com que a resolução volumétrica seja maior perto da câmera, o que efetivamente implementa diferentes níveis de detalhes (LODs, do inglês, level of detail).

O Potree usa WebGL, a versão web da API OpenGL. A implementação usa three.js, uma biblioteca JavaScript para gráficos 3D, e também GLSL e chamadas WebGL diretas onde necessário.

3.6 CONSIDERAÇÕES SOBRE TRABALHOS RELACIONADOS

Esse trabalho usa como base algumas técnicas dos trabalhos correlatos. O texto (LAINE; KARRAS, 2010), por ser um texto claro e com código acessível, serviu de base para a

es-trutura de dados inicial e o algoritmo de renderização. Já o algoritmo de construção usou a técnica descrita em (BAERT; LAGAE; DUTRÉ, 2013), considerando a eficiência do uso da

memória.

A linguagem CUDA é restrita à hardware NVIDIA. Já a linguagem OpenCL mostrou-se difícil de configurar para o usuário final. Como uma placa AMD também foi usada nos experimentos, optou-se por implementar a travessia em GLSL, assim como nos tra-balhos (CRASSIN et al., 2009) e (SCHUETZ, 2016).

(27)
(28)
(29)

27

4 REPRESENTAÇÃO E ALGORITMO DE CONSTRUÇÃO DO MODELO

Neste capítulo, será detalhada a estrutura de dados usada para representar o modelo 3D. Também será descrito o algoritmo de construção do arquivo.

4.1 REPRESENTAÇÃO EM MEMÓRIA

Como visto na Seção 2.3, cada nodo em uma octree guarda até oito filhos. Uma implementação trivial de árvore consiste em representar os filhos como um vetor de oito ponteiros. Numa máquina 64 bit, cada ponteiro ocupa 8 bytes, o que implica em um custo de 8 × 8 = 64 bytes por nodo.

Entretanto, a redução do espaço é uma das prioridades dessa implementação. Para fins de otimização, algumas observações levaram a uma otimização da estrutura:

• Implementar a árvore com vetor de ponteiros permite que os nodos estejam distri-buídos em posições da memória distantes, conforme o sistema operacional consiga alocar. Contudo, para o arquivamento em disco e transferência da octree para a GPU, é melhor manter a estrutura em uma região contígua da memória (SANDERS; KANDROT, 2011).

• O espaço de endereçamento em 64 bit é maior do que o necessário. Por isso, escolheu-se um inteiro de 32 bits repreescolheu-sentar o deslocamento relativo ao começo da octree. • Os nodos folha (que não têm filhos) não mantêm ponteiros, e sim a cor do voxel. • Os filhos inválidos (ou seja, que representam espaço vazio e são completamente

transparentes) não figuram nodos.

Considerando a alocação contígua da árvore, é possível alocar os filhos de um nodo na mesma parte da memória. Isso faz com que apenas o endereço do primeiro filho precise ser de fato guardado. Em vez de armazenar oito ponteiros que apontam para oito filhos, cada nodo guarda um ponteiro, e acessa os oito filhos somando um deslocamento ao ponteiro.

O formato de cor dos nodos folha é truecolor de 4 bytes. O canal alfa está reservado para uso futuro. Já os nodos internos possuem 6 bytes de informação, mas ocupam efetivamente 8 devido ao alinhamento 32 bit, como mostra a Figura 11.

No caso em que há menos de oito filhos, é necessário saber quais são os filhos válidos. Também é preciso identificar quais filhos são folha, a fim de interpretar os dados em memória corretamente. Para isso, são usados dois bytes de sinalização: um byte válido, e um folha. Os oito bits em cada byte representam os oito possíveis filhos. A forma como um nodo interno referencia seus filhos está ilustrada na Figura 12.

4.2 ALGORITMO DE CONSTRUÇÃO

A construção foi baseada no trabalho (BAERT; LAGAE; DUTRÉ, 2013), descrito em

linhas gerais na Seção 3.4. A construção implementada envolve principalmente dois ob-jetos, Builder e ZStream.

(30)
(31)
(32)
(33)

31

5 ALGORITMO E IMPLEMENTAÇÃO DA RENDERIZAÇÃO

Este capítulo descreve o algoritmo de renderização de uma árvore esparsa de voxels. Detalhes da implementação e análise de desempenho também são apresentados.

5.1 ALGORITMO DE RAY TRACING EM OCTREE

Um algoritmo de ray tracing convencional, na sua forma mais simples, calcularia a intersecção do raio com cada primitiva disposta na cena. A topologia da octree viabiliza uma busca mais adequada, baseada no percurso do raio pelo espaço. Para cada pixel, é lançado um raio de mesma origem e direções diferentes. A busca é finalizada quando o raio atinge um nodo folha ou quando o raio sai do escopo da árvore. Em contexto local, o algoritmo mantém o nodo atual e o octante do filho relevante. Um octante é uma das oito subdivisões do espaço cartesiano tridimensional de acordo com o sinal de cada um dos três eixos (Figura 15). No contexto desse trabalho, pode-se pensar em octantes como os cubos resultantes da divisão de um cubo maior em oito cubos idênticos.

Começando pelo nodo raiz, que representa toda a cena, determina-se em qual octante a origem do raio se encontra, processo que se repete até atingir um voxel vazio. Então, computa-se em qual face interna do octante o raio atinge, e a origem do raio é atualizada para a posição da intersecção. Se o espaço vizinho à face interseccionada é outro octante do mesmo pai (nodos irmãos), atualiza-se o octante atual e repete-se o processo. Senão, busca-se no nodo pai até que sejam achados nodos irmãos. Se esse processo chegar até a raiz, não haverá mais nodo pai, e considera-se que o raio está fora de cena. Em outro caso, o raio intersecciona um nodo folha, e a cor do nodo é reproduzida no pixel em questão.

Para realizar tal busca espacial, é mantida uma pilha de nodos. Quando o octante da origem do raio é atualizado, o octante anterior é empilhado. Quando buscam-se nodos irmãos no nodo pai, o nodo atual é desempilhado. São mantidos nas entradas de pilha o ponteiro para o nodo, o octante do filho e as coordenadas do canto inferior do cubo representado. O tamanho do cubo pode ser deduzido pela profundidade atual na árvore, sem necessidade de registrá-lo na pilha.

(34)
(35)

33

Algoritmo 1: Ray tracing em octree Fonte: Própria (2019).

Entrada: raio(origem, direção) Saída: Pixel

1 início

2 Empilhe(raiz) 3 para sempre faça

4 octante ← Ache octante(raio, topo) 5 se octante é folha então

6 retorna topo[octante].cor

7 fim

8 se octante é válido então 9 Empilhe(topo[octante])

10 fim

11 se octante é inválido então

12 face, posição ← Intersecção(topo, octante, raio) 13 enquanto próxima face está fora do topo faça

14 Desempilhe(topo)

15 se pilha vazia então

16 retorna cor de fundo

17 fim

18 fim

19 octante ← Inverta no eixo(face, octante)

20 fim

21 fim

22 fim

Todos cubos representados são alinhados aos eixos, conhecidos como axis aligned bounding boxes, o que torna a intersecção simples de computar. Ao implementar, nota-se que é mais fácil calcular tal intersecção quando as componentes do vetor direção têm o mesmo sinal (por exemplo, x, y, z < 0). Nesse caso, só há três possíveis faces de cruzamento, uma para cada eixo. Para usufruir dessa propriedade em todos os casos, o espaço vetorial é espelhado conforme necessário a fim de garantir que todas componentes sejam negativas.

5.2 DETALHES DE IMPLEMENTAÇÃO

Inicialmente, a renderização executava em CPU, e fazia proveito dos múltiplos cores com uma diretiva OpenMP, como mostra o trecho:

1 # pragma omp p a r a l l e l for s c h e d u l e ( dynamic , 1024) c o l l a p s e (2) 2 for (u n s i g n e d int i = 0; i < WIDTH ; i ++)

3 for (u n s i g n e d int j = 0; j < HEIGHT ; j ++) 4 render ( pixels [ i ][ j ] , j , i ) ;

O escalonamento dinâmico é adequado, visto que cada raio executa uma quantidade de iterações variável. A opção collapse é usada para paralelizar o loop aninhado, e não só a repetição mais externa. É definido um tamanho de chunk de 1024 a fim de reduzir o overhead.

(36)

34

na Figura 16. O modelo foi construído a partir de dados de arquivo com um gradiente adicionado a fim de mostrar o suporte a cores. O número de iterações em cada pixel torna claro o particionamento espacial da estrutura.

O próximo passo foi decidir qual ferramenta usar para executar o código em GPU. Um dos objetivos do projeto é obter uma solução portável. Como CUDA só é suportada em placas NVIDIA, e considerando que a aplicação teria de acessar um contexto gráfico a fim de desenhar a saída na tela, optou-se por utilizar OpenGL.

O shader de vértice da aplicação tem como saída apenas um triângulo que cobre toda tela, visto que o motor a ser implementado não desenha de fato triângulos. A aplicação tem dois shaders de fragmento: o primeiro realiza o ray tracing, o segundo faz o cálculo de iluminação. A janela e o tratamento de entrada são gerenciados pela biblioteca SDL.

O modelo chega a centenas de MB, por isso é enviado usando um shader storage buffer object (SSBO), disponível desde OpenGL 4.3. Um SSBO é uma forma de enviar e receber dados do dispositivo gráfico em OpenGL. A especificação da API garante que um SSBO tenha tamanho máximo de ao menos 128MB. O primeiro shader de fragmento tem como entrada o modelo já construído e algumas informações adicionais como a posição da câmera, e tem duas saídas: a cor do voxel atingido pelo raio e a posição da intersecção. As saídas são armazenadas em formato de ponto flutuante em duas texturas intermediárias (Figura 17), relacionadas a um frame buffer object.

O segundo shader de fragmento gera a imagem final na tela a partir da informação das duas texturas. O cálculo de iluminação de voxels é um problema interessante, mas não foi o foco deste trabalho. Mesmo assim, desenvolveu-se uma solução aproximada.

Um grande desafio no sombreamento de voxels é o cálculo do vetor normal. O vetor normal representa a direção perpendicular a um ponto da superfície. Se o ângulo entre a direção da luz e a normal da superfície é pequeno, a luz atinge a superfície perpendicularmente, portanto a região é bem iluminada. Se o ângulo é grande, menos luz é refletida.

Uma estratégia comum entre os trabalhos é calcular o vetor normal de cada voxel durante a construção. O trabalho (LAINE; KARRAS, 2010) calcula contornos, superfícies

associadas aos voxels. O artigo (MUNIZ, 2007) apresenta uma forma de calcular o vetor

normal. Primeiro estima-se uma função f(x, y, z) que representa a superfície local. A partir das derivações parciais, acha-se o plano tangente a partir do qual calcula-se a normal.

Como descrito anteriormente, a estrutura de árvore desenvolvida não armazena in-formação de superfície ou normais. Em vez de calcular a superfície durante a construção, as normais são aproximadas durante a renderização. Em cada pixel, o shader de ilumi-nação consulta a textura de posição em sua coordenada e na coordenada de dois pixels adjacentes. Esses três pontos formam um triângulo. O produto cruzado entre duas arestas do triângulo resultam no vetor normal do pixel. O nível de iluminação é calculado como sendo o produto escalar entre a direção da luz e a normal. Quando ambos esses vetores são unitários, isso equivale ao cosseno do ângulo entre os dois.

O cálculo dinâmico dos vetores normais tem algumas desvantagens quando compa-rado com a pré-computação dos mesmos durante a construção. Na forma atual, o cálculo leva em consideração apenas três pontos muito próximos, o que pode causar uma esti-mativa específica demais. Por exemplo, se a resolução do modelo não é alta suficiente, o mesmo voxel ocupará vários pixels, e a iluminação deixará evidente que o modelo é composto por pequenos cubos. Nos casos em que é realmente desejável exibir os voxels como cubos, o vetor poderia ser escolhido de seis possíveis normais, um para cada face de

(37)
(38)
(39)
(40)

38

Tabela 1 – Medição de desempenho de renderização em quadros por segundo. Imagem Modelo GTX 960 R7 240 i5-4590

640 × 480 2563 176,0 23,6 36,7 640 × 480 10243 116,0 13,2 38,9 1280 × 720 2563 77,8 11,3 12,8 1280 × 720 10243 51,4 6,25 14,9 5.4 DESEMPENHO

As variáveis consideradas foram: hardware, resolução da imagem em número de pixels, e resolução do modelo em número de voxels. Em cada teste foram renderizados os mesmos 500 frames. O resultado é a média de quadros por segundo, listados na Tabela 1. As placas de vídeo GTX 960 e AMD R7 240 ambas foram testadas no mesmo renderizador. Já a CPU Intel i5-4590 foi testada no renderizador de CPU.

Os equipamentos testados foram: • NVIDIA GeForce GTX 960 (GPU) • AMD R7 240 (GPU)

• Intel Core i5-4590 (CPU, quatro cores)

A placa GTX observou uma melhora de três a cinco vezes em relação à CPU. Já a placa R7 sequer alcançou o desempenho do processador Intel. O website UserBench-marks aponta o desempenho da GTX como sendo 465% melhor que a R7, o que explica parcialmente a discrepância.

O formato 720p tem 3 vezes mais pixels que o formato 480p. No entanto, a razão média entre o desempenho das duas resoluções foi de 2,25 para a GTX e 2,7 para a CPU. Uma explicação para tal fato é que os pixels na resolução mais alta estão mais próximos no espaço 3D, portanto realizam acessos à regiões de memória próximas, o que melhora o uso da cache.

Usando a ferramenta Nsight Graphics, é possível checar quais chamadas são mais custosas e quais unidades da GPU estão mais ocupadas. Como mostra a Figura 23, a primeira chamada, a qual realiza a travessia na árvore, consome o maior tempo; enquanto a segunda, que apenas calcula a iluminação, tem tempo ínfimo. A Figura 22 mostra a análise de desempenho de um quadro. Podemos observar que nenhuma das unidades chegou perto de utilizar 100% do SOL (speed of light, ou o desempenho máximo do circuito). Nota-se também que as unidades mais utilizadas são os subsistemas de memória (L2, TEX, VRAM). Isso se deve ao acesso à memória em um padrão não adequado para GPUs, ou seja, threads adjacentes acessam partes distantes da memória. Também observa-se um grande tempo de espera no long scoreboard, o que indica alta latência na leitura de memória na cache de textura (TEX) (NVIDIA RESEARCH, 2019).

O tamanho do modelo apresentou menor influência nos resultados, apesar do modelo 10243

representar 64 vezes mais volume que o modelo 2563

. Isso se deve ao fato de que a profundidade máxima do modelo menor é log2256 = 8, enquanto a do maior é de log21024 = 10. Curiosamente, as GPUs apresentaram mais variação de desempenho neste critério que a CPU.

(41)
(42)

40

O plano de projeto deste trabalho especificou o critério de aceite como “desempenho mínimo de 30 quadros por segundos para renderizar em HD (1280 × 720) em hardware comum de mercado (GTX 960)”. A taxa de quadros alcançada nessas condições foi de 51,4, portanto, a solução alcançou o objetivo planejado.

(43)

41

6 CONCLUSÕES

Esse trabalho teve como objetivo a criação de um renderizador de voxels eficiente. A aplicação desenvolvida desempenhou velocidade razoável, porém um tanto limitada. A renderização alcança taxa de quadros razoável até a resolução de 720p. Observou-se o ganho esperado da aceleração em GPU em relação à renderização na CPU em uma das placas testadas.

O algoritmo de construção permite a geração arbitrária de modelos. O cálculo de cor é feito a partir das coordenadas (x, y, z). Comparado com a renderização poligonal, foi apresentada uma maneira fácil de construir um modelo definido matematicamente, sem se preocupar com a topologia ou a superfície do mesmo. A iluminação calculada durante a renderização possibilita a manipulação do modelo em tempo real, demonstrada na aplicação pela visualização de um corte transversal.

O escopo deste trabalho cobriu apenas parte das técnicas do estado da arte em renderização de árvores esparsas de voxel. Os seguintes pontos requerem mais estudo:

• Implementação em CUDA e comparação com a solução GLSL; • Animação de voxels;

• Renderização de várias árvores independentes na mesma cena; • Colisão entre árvores, simulação física de corpo rígido;

• Cálculo de vetores normais durante a construção, ou cálculo mais preciso durante a renderização e eliminação de artefatos;

• Edição em tempo real da estrutura, ferramenta de esculpir.

Na construção, foi generalizada a otimização do trabalho (BAERT; LAGAE; DUTRÉ,

2013). Ao contar quantos voxel são idênticos ao invés de inválidos, modelos com grande quantidade de nodos folha repetidos são sintetizados em menos tempo. Também foi mostrado um esquema de iluminação que, apesar de simples e com alguns artefatos, é dinâmico e talvez contribua para o estado da arte.

Mesmo com modelos modernos de programação em GPU, a renderização de volu-mes ainda apresenta um grande desafio. A representação poligonal é adequada para a maior parte das aplicações práticas, considerando que, em geral, desenhar a superfície de um objeto tridimensional é suficiente. Para aplicações que necessitam da visualização e interação com o interior dos objetos, a representação de voxels pode ser mais adequada. No contexto de jogos virtuais, por exemplo, um cenário maciço, destrutível ou de outra forma interativo deve representar um novo paradigma de imersão para o jogador.

(44)
(45)

43

REFERÊNCIAS

BAERT, J.; LAGAE, A.; DUTRÉ, P. Out-of-core construction of sparse voxel octrees. In: Proceedings of the 5th High-Performance Graphics Conference. New York, NY, USA: ACM, 2013. (HPG ’13), p. 27–32. ISBN 978-1-4503-2135-8. <http://doi.acm.org/10.1145/2492045.2492048>. Acessado em 25 jun. 2019.

CHEN, M.; KAUFMAN, A. E.; YAGEL, R. Volume Graphics. [S.l.]: Springer-Verlag London Limited, 2000. 15-23 p.

CRASSIN, C. et al. Gigavoxels : Ray-guided streaming for efficient and detailed voxel rendering. In: ACM. ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games (I3D). Boston, MA, Etats-Unis: ACM Press, 2009. To appear. <http://maverick.inria.fr/Publications/2009/CNLE09>. Acessado em 25 jun. 2019. DENG, Y. et al. Toward real-time ray tracing: A survey on hardware acceleration and microarchitecture techniques. ACM Computing Surveys, v. 50, p. 1–41, 08 2017.

GEYTER, D. D. Texture filtering & MIP mapping. 2015.

<https://daviddegeyter.wordpress.com/2015/11/06/example-02-02-texture-filtering-mip-mapping/>. Acessado em 3 dez. 2019.

GRUPO KHRONOS. Compute Shader – OpenGL Wiki. 2019.

<https://www.khronos.org/opengl/wiki/Compute_Shader>. Acessado em 6 jun. 2019.

GRUPO KHRONOS. Fixed Function Pipeline – OpenGL Wiki. 2019. <https://www.khronos.org/opengl/wiki/Fixed_Function_Pipeline>. Acessado em 30 out. 2019.

GRUPO KHRONOS. Rendering Pipeline Overview – OpenGL Wiki. 2019. <https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview>. Aces-sado em 6 jun. 2019.

KäMPE, V.; SINTORN, E.; ASSARSSON, U. High resolution sparse voxel dags. ACM Trans. Graph., ACM, New York, NY, USA, v. 32, n. 4, p. 101:1–101:13, jul. 2013. ISSN 0730-0301. <http://doi.acm.org/10.1145/2461912.2462024>.

KARIMI, K.; DICKSON, N. G.; HAMZE, F. A performance comparison of CUDA and opencl. CoRR, abs/1005.2581, p. 1–10, 2010. <http://arxiv.org/abs/1005.2581>. Acessado em 8 jul. 2019.

LAINE, S.; KARRAS, T. Efficient Sparse Voxel Octrees – Analysis, Extensions, and Implementation. NVIDIA Research, ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games (I3D), 2010. <https://research.nvidia.com/publication/efficient-sparse-voxel-octrees>. Acessado em 25 jun. 2019.

LEVOY, M. The Stanford volume data archive. 2001.

(46)

44

MUNIZ, C. E. V. Finding surface normals from voxels. Workshop of Undergraduate Works, p. 49–52, 2007.

NVIDIA Corporation. nVidia Turing GPU Architecture. Set.

2018. <https://www.nvidia.com/content/dam/en-zz/Solutions/design- visualization/technologies/turing-architecture/NVIDIA-Turing-Architecture-Whitepaper.pdf>. Acessado em 25 jun. 2019.

NVIDIA RESEARCH. The Peak-Performance-Percentage Analysis Method for Optimizing Any GPU Workload | NVIDIA Developer Blog. 2019.

<https://devblogs.nvidia.com/the-peak-performance-analysis-method-for-optimizing-any-gpu-workload/#5.3.1>. Acessado em 31 out. 2019.

OPENMP ARB. OpenMP FAQ - OpenMP. 2019.

<https://www.openmp.org/about/openmp-faq/#WhatIs>. Acessado em 4 dez. 2019.

SANDERS, J.; KANDROT, E. CUDA by Example. Addison-Wesley, 2011. 1-11 p. ISBN 978-0-13-138768-3. <https://developer.nvidia.com/cuda-example>. Acessado em 25 jun. 2019.

SCHUETZ, M. Potree: Rendering Large Point Clouds in Web Browsers. Dissertaçăo (Mestrado) — Institute of Computer Graphics and Algorithms, Vienna University of Technology, Favoritenstrasse 9-11/186, A-1040 Vienna, Austria, Set 2016. <https://www.cg.tuwien.ac.at/research/publications/2016/SCHUETZ-2016-POT/>. Acessado em 8 jul. 2019.

WANG, S. W.; KAUFMAN, A. E. Volume sculpting. In: Proceedings of the 1995 Symposium on Interactive 3D Graphics. New York, NY, USA: ACM, 1995. (I3D ’95), p. 151–ff. ISBN 0-89791-736-7. <http://doi.acm.org/10.1145/199404.199430>. Acessado em 25 jun. 2019.

WEISSTEIN, E. Octant. From MathWorld–A Wolfram Web Resource. 2019. <http://mathworld.wolfram.com/Octant.html>. Acessado em 4 dez. 2019.

(47)
(48)
(49)

47

Listagem A.1 – "Implementação do algoritmo de ray tracing em GLSL."

1 #v e r s i o n 430 core 2 3 u n i f o r m uvec2 v i e w p o r t S i z e ; 4 u n i f o r m vec3 camPos ; 5 u n i f o r m float time ; 6 u n i f o r m uint m o d e l S i z e ; 7 u n i f o r m mat3 camRot ; 8 u n i f o r m float x S e c t i o n ; 9 in vec4 g l _ F r a g C o o r d ; 10

11 layout ( l o c a t i o n = 0) out vec4 o u t C o l o r ; 12 layout ( l o c a t i o n = 3) out vec4 o u t P o s i t i o n ; 13

14 layout ( b i n d i n g = 2 , std430 ) buffer svo { 15 uint [] data ;

16 }; 17

18 uint w h i c h O c t a n t ( in vec3 pos , in vec3 corner , in float size ) {

19 /* Which of the eight o c t a n t s does pos reside in the box ( corner , size ) ? */ 20 uint oct = 0;

21 float o c t S i z e = size / 2.; 22

23 if ( pos . x > corner . x + o c t S i z e ) oct ^= 4; 24 if ( pos . y > corner . y + o c t S i z e ) oct ^= 2; 25 if ( pos . z > corner . z + o c t S i z e ) oct ^= 1; 26 return oct ;

27 } 28

29 vec3 octVec ( in uint octant ) {

30 /* Each r e s u l t i n g vector c o m p o n e n t is either one or zero d e p e n d i n g on 31 * octant 's bits . */

32 vec3 vec = vec3 (0.) ;

33 if ( bool ( octant & 4) ) vec . x = 1.; 34 if ( bool ( octant & 2) ) vec . y = 1.; 35 if ( bool ( octant & 1) ) vec . z = 1.; 36 return vec ;

37 } 38

39 void voxel ( uint block_i , out uint child , out uint leaf , out uint v alid ) { 40 child = data [ b l o c k _ i ];

41 leaf = data [ b l o c k _ i + 1] >> 8; 42 valid = data [ b l o c k _ i + 1] & 0 xff ; 43 }

44

45 vec4 l e a f C o l o r ( in uint b l o c k _ i ) { 46 uint rgba = data [ b l o c k _ i ]; 47 return vec4 (

48 float( rgba & 0 xff ) / 256. ,

49 float(( rgba >> 8) & 0 xff ) / 256. , 50 float(( rgba >> 16) & 0 xff ) / 256. , 51 float(( rgba >> 24) & 0 xff ) / 256. 52 ) ;

53 } 54

55 uint a d d r e s s O f ( in uint child , in uint leaf , in uint valid , in uint octant ) { 56 /* Get a d d r e s s of a s p e c i f i c child inside a voxel . */

57 uint mask = ~(~0 << octant ) ;

58 return child + b i t C o u n t ( mask & valid ) * 2 - b i t C o u n t ( mask & leaf ) ; 59 } 60 61 void main () { 62 /* I n i t i a l i z e ray . */ 63 float fov = 0.6; 64 vec2 uv = ( g l _ F r a g C o o r d . xy * 2. / v i e w p o r t S i z e . y ) - vec2 (1.) ; 65 vec3 d i r e c t i o n = camRot * vec3 ( uv * fov , 1.) ;

66 vec3 p o s i t i o n = camPos ; 67

68 /* Set up stack . */

69 uint size = 1; // size of stack

70 float b o x S i z e = 2.0; // size of box ray is c u r r e n t l y in 71 72 /* Stack a t t r i b u t e s */ 73 uint child [10]; 74 uint leaf [10]; 75 uint valid [10]; 76 vec3 corner [10]; 77 uint octant [10]; 78

79 /* Set root stack entry */

80 voxel ( m o d e l S i z e - 2 , child [0] , leaf [0] , valid [0]) ; 81 corner [0] = vec3 ( -1) ;

82

83 /* Assume ray d i r e c t i o n does not change during e x e c u t i o n 84 * ( no r e f r a c t i o n / r e f l e c t i o n )

(50)

48 86 uint mask = 0; 87 vec3 invdir = 1. / d i r e c t i o n ; 88 if ( invdir . x >= 0) mask ^= 4; 89 if ( invdir . y >= 0) mask ^= 2; 90 if ( invdir . z >= 0) mask ^= 1; 91 vec3 m a s k V e c = octVec ( mask ) ;

92 vec3 mirror = vec3 (1.) - m a s k V e c * 2.; 93

94 float c u t S i z e = b o x S i z e * x S e c t i o n ; 95 if ( any ( l e s s T h a n ( position , corner [0]) )

96 || any ( g r e a t e r T h a n ( position , corner [0] + vec3 ( c u t S i z e ) ) ) ) {

97 /* Before the first iteration , if the camera is o u t s i d e the sc enes 's 98 * b o u n d i n g box , the t r a v e r s a l starts at the i n t e r s e c t i o n b e t w e e n the 99 * ray and the box .

100 */

101 vec3 l o w C o r n e r = corner [0] + c u t S i z e * ( vec3 (1.) - m a s k Ve c ) ; 102 vec3 h i g h C o r n e r = corner [0] + c u t S i z e * m a s k V ec ;

103 vec3 tMin = ( l o w C o r n e r - p o s i t i o n ) * invdir ; 104 vec3 tMax = ( h i g h C o r n e r - p o s i t i o n ) * invdir ; 105

106 if ( any ( g r e a t e r T h a n ( tMin , tMax . zxy ) ) 107 || any ( g r e a t e r T h a n ( tMin , tMax . yzx ) ) ) { 108 /* No i n t e r s e c t i o n */ 109 o u t C o l o r = vec4 (0.) ; 110 o u t P o s i t i o n = vec4 (0.) ; 111 return; 112 } 113

114 float t = max ( max ( tMin .x , tMin . y ) , tMin . z ) ; 115 p o s i t i o n += d i r e c t i o n * t ; 116 117 if ( t < 0.) { 118 o u t C o l o r = vec4 (0.) ; 119 o u t P o s i t i o n = vec4 (0.) ; 120 return; 121 } 122 } 123

124 octant [0] = w h i c h O c t a n t ( position , vec3 ( -1) , b o x S i z e ) ; 125

126 while ( true ) {

127 uint oct = octant [ size -1];

128 if ( bool (( leaf [ size -1] >> oct ) & 1) ) {

129 /* Ray origin is inside leaf voxel , render leaf . */ 130 o u t C o l o r = l e a f C o l o r ( 131 a d d r e s s O f ( 132 child [ size -1] , 133 leaf [ size -1] , 134 valid [ size -1] , 135 oct 136 ) 137 ) ; 138 o u t P o s i t i o n = vec4 ( position , 1.0) ; 139 return; 140 } 141

142 if ( bool (( valid [ size -1] >> oct ) & 1) ) { 143 /* Go a level deeper . */ 144 145 // PUSH 146 voxel ( 147 a d d r e s s O f ( 148 child [ size -1] , 149 leaf [ size -1] , 150 valid [ size -1] , 151 oct 152 ) , 153 child [ size ] , 154 leaf [ size ] , 155 valid [ size ] 156 ) ; 157 158 b o x S i z e *= 0.5;

159 corner [ size ] = corner [ size -1] + b o x S i z e * octVec ( oct ) ; 160 octant [ size ] = w h i c h O c t a n t ( 161 position , 162 corner [ size ] , 163 b o x S i z e 164 ) ; 165 166 size ++; 167 168 } else {

169 /* Ray origin is in i n v a l id voxel , cast ray until it hits the next

170 * voxel .

171 */

(51)

49

173 float c h i l d S i z e = b o x S i z e * 0.5;

174 vec3 c h i l d C o r n e r = corner [ size -1] + c h i l d S i z e * octVec ( oct ) ; 175

176 vec3 a d j u s t e d C o r n e r = c h i l d C o r n e r - m a s k V e c * c h i l d S i z e * mi rror ; 177

178 vec3 t = ( a d j u s t e d C o r n e r - p o s i t i o n ) * invdir ;

179 float amount = 9 9 9 9 9 9 9 9 9 9 9 . ; // D i s t a n c e ray will t r a v e r s e 180

181 /* Detect which face hit . */ 182 uint h i t F a c e = 0;

183

184 /* amount will be the m i n i m u m p o s i t i v e c o m p o n e n t of t . */ 185 186 if ( t . x > 0.) { 187 amount = t . x ; 188 h i t F a c e = 4; 189 } 190 if ( t . y > 0. && t . y < amount ) { 191 amount = t . y ; 192 h i t F a c e = 2; 193 } 194 if ( t . z > 0. && t . z < amount ) { 195 amount = t . z ; 196 h i t F a c e = 1; 197 } 198

199 /* Ray will start next step at the point of this i n t e r s e c t i o n . */ 200 p o s i t i o n += d i r e c t i o n * amount ;

201

202 while (

203 bool ( h i t F a c e & ~( octant [ size -1] ^ mask ) ) &&

204 size > 0

205 ) {

206 /* Hit face is at this voxel 's boundary , search parent */ 207 208 // POP 209 size - -; 210 b o x S i z e *= 2.; 211 } 212 213 if ( size == 0) {

214 /* Ray is o u t s i d e root octree . */ 215 o u t C o l o r = vec4 (0.0) ;

216 o u t P o s i t i o n = vec4 ( direction , 0.) ;

217 return;

218 }

219 /* Loop end : found a n c e s t r a l voxel with space on the hit axis . 220 * T r a n s f e r to s i b l i n g voxel , c h a n g i n g on the axis of the face

221 * that was hit .

222 */

223 octant [ size -1] ^= h i tF a c e ;

224 }

225 } 226 }

(52)
(53)

ANEXO B -- Artigo para Conferência da Sociedade Brasileira de Computação

(54)
(55)

Renderizac¸˜ao 3D Usando ´

Arvores Esparsas de Voxels

Gabriel M ¨uller 1

Departamento de Inform´atica e Estat´ıstica – Universidade Federal de Santa Catarina (UFSC) Abstract. 3D objects are usually represented and rendered as a polygon mesh

surface. In this paper we present a way to construct and display 3D models as data that is volumetric instead of superficial. Our data structure stores units called voxels, a 3D equivalent to pixels, in a space partitioning tree known as an octree. We present a real-time GPU voxel renderer, along with several examples of models.

Resumo. Objetos 3D costumam ser representados e renderizados como uma malha superficial de pol´ıgonos. Este trabalho apresenta uma forma de con-struir e visualizar modelos tridimensionais como dados volum´etricos em vez de superficiais. A estrutura de dados desenvolvida armazena unidades chamadas voxels – equivalentes 3D a pixels – dispostas em uma ´arvore de subdivis˜ao es-pacial conhecida como octree. Um renderizador de voxels de tempo real na GPU ´e apresentado junto a v´arios exemplos de modelos.

1. Introduc¸˜ao

A representac¸˜ao e renderizac¸˜ao de modelos 3D em computador ´e feita tradicionalmente com uso de pol´ıgonos. Por meio de v´arias transformac¸˜oes da pipeline gr´afica, v´ertices no espac¸o 3D s˜ao projetados para um sistema de coordenadas 2D e rasterizados para as unidades discretas da tela chamadas pixels. Para mitigar o alto custo computacional de renderizac¸˜ao de uma cena complexa a cada quadro, foram popularizados circuitos dedi-cados para o processamento gr´afico em uma placa chamada de unidade de processamento gr´afico (GPU).

A renderizac¸˜ao baseada em ´arvores esparsas de voxel ´e uma forma de represen-tar objetos em termos de volume em vez de superf´ıcie. A estrutura de dados armazena voxels em uma ´arvore de subdivis˜ao espacial conhecida como octree, que diminui a quan-tidade de informac¸˜oes repetidas em uma estrutura espacialmente regular, que possibilita a renderizac¸˜ao eficiente com a t´ecnica de ray tracing. A abordagem volum´etrica tamb´em facilita a criac¸˜ao e edic¸˜ao de modelos. Enquanto na criac¸˜ao de modelos poligonais ´e necess´ario especificar a superf´ıcie e considerar a topologia, a representac¸˜ao de voxel per-mite criar volumes de forma livre e topologia complexa. [Wang and Kaufman 1995]

No entanto, esse m´etodo de renderizac¸˜ao apresenta algumas limitac¸˜oes. V´ertices de uma malha poligonal podem ser transformados a fim de provocar o efeito de movi-mento, mas a animac¸˜ao de voxels n˜ao tem uma soluc¸˜ao ideal. Al´em disso, a projec¸˜ao de pol´ıgonos conta com o circuito especializado das GPUs, enquanto o ray tracing de voxels tem um acesso `a mem´oria n˜ao amig´avel `a cache do hardware gr´afico. Este trabalho tem como objetivo desenvolver um motor gr´afico eficiente baseado em voxels com acelerac¸˜ao em GPU, e demonstrar a sua aplicabilidade em diferentes ´areas e plataformas.

(56)
(57)
(58)
(59)
(60)
(61)
(62)
(63)
(64)

renderizac¸˜ao alcanc¸a taxa de quadros razo´avel at´e a resoluc¸˜ao de 720p. Observou-se o ganho esperado da acelerac¸˜ao em GPU em relac¸˜ao `a renderizac¸˜ao na CPU em uma das placas testadas.

O algoritmo de construc¸˜ao permite a gerac¸˜ao arbitr´aria de modelos. Comparado com a renderizac¸˜ao poligonal, foi apresentada uma maneira f´acil de construir um modelo definido matematicamente, sem se preocupar com a topologia ou a superf´ıcie do mesmo. A iluminac¸˜ao calculada durante a renderizac¸˜ao possibilita a manipulac¸˜ao do modelo em tempo real, demonstrada na aplicac¸˜ao pela visualizac¸˜ao de um corte transversal.

O escopo deste trabalho cobriu apenas parte das t´ecnicas do estado da arte em renderizac¸˜ao de ´arvores esparsas de voxel. Na construc¸˜ao, foi generalizada a otimizac¸˜ao do trabalho [Baert et al. 2013]. Ao contar quantos voxel s˜ao idˆenticos ao inv´es de inv´alidos, modelos com grande quantidade de nodos folha repetidos s˜ao sintetizados em menos tempo. Tamb´em foi mostrado um esquema de iluminac¸˜ao que, apesar de simples e com alguns artefatos, ´e dinˆamico e talvez contribua para o estado da arte.

Mesmo com modelos modernos de programac¸˜ao em GPU, a renderizac¸˜ao de vol-umes ainda apresenta um grande desafio. A representac¸˜ao poligonal ´e adequada para a maior parte das aplicac¸˜oes pr´aticas, considerando que, em geral, desenhar a superf´ıcie de um objeto tridimensional ´e suficiente. Para aplicac¸˜oes que necessitam da visualizac¸˜ao e interac¸˜ao com o interior dos objetos, a representac¸˜ao de voxels pode ser mais adequada. No contexto de jogos virtuais, por exemplo, um cen´ario macic¸o, destrut´ıvel ou de outra forma interativo deve representar um novo paradigma de imers˜ao para o jogador.

Referˆencias

Baert, J., Lagae, A., and Dutr´e, P. (2013). Out-of-core construction of sparse voxel oc-trees. In Proceedings of the 5th High-Performance Graphics Conference, HPG ’13, pages 27–32, New York, NY, USA. ACM.

Grupo Khronos (2019a). Compute shader – OpenGL wiki.

Grupo Khronos (2019b). Fixed function pipeline – OpenGL wiki. Levoy, M. (2001). The stanford volume data archive.

NVIDIA Research (2019). The peak-performance-percentage analysis method for opti-mizing any GPU workload — NVIDIA developer blog.

Sanders, J. and Kandrot, E. (2011). CUDA by Example. Addison-Wesley.

Wang, S. W. and Kaufman, A. E. (1995). Volume sculpting. In Proceedings of the 1995 Symposium on Interactive 3D Graphics, I3D ’95, pages 151–ff., New York, NY, USA. ACM.

Weisstein, E. (2019). Octant. From MathWorld–A Wolfram Web Resource.

Referências

Documentos relacionados

Analisando pergunta a pergunta, as grávidas sentem-se menos infelizes que as mulheres não grávidas em relação à infelicidade por ter acne facial, apesar da

A baixa taxa de desconto ao longo dos anos de produção do campo, para o cálculo da função objetivo, aliada a baixa produção de água que a locação de

Para verificar essas hipóteses, objetivou-se analisar se os dados de produção de biogás medidos em escala real são semelhantes aos dados obtidos por meio de

Changes in the gut microbiota appears to be a key element in the pathogenesis of hepatic and gastrointestinal disorders, including non-alcoholic fatty liver disease, alcoholic

Apesar dos esforços para reduzir os níveis de emissão de poluentes ao longo das últimas décadas na região da cidade de Cubatão, as concentrações dos poluentes

O mesmo par não aparece duas vezes na lista, seja na mesma ordem, seja invertido?. Existe na lista alguma linha de ônibus conectando uma cidade de número menor que 4 a outra

Neste estudo foram estipulados os seguintes objec- tivos: (a) identifi car as dimensões do desenvolvimento vocacional (convicção vocacional, cooperação vocacio- nal,

da quem praticasse tais assaltos às igrejas e mosteiros ou outros bens da Igreja, 29 medida que foi igualmente ineficaz, como decorre das deliberações tomadas por D. João I, quan-