Ray Casting de Volumes Não-Estruturados com Dados Dinâmicos usando Hardware Gráfico



Documentos relacionados
Capacidade = 512 x 300 x x 2 x 5 = ,72 GB

Disciplina: Introdução à Informática Profª Érica Barcelos

O hardware é a parte física do computador, como o processador, memória, placamãe, entre outras. Figura 2.1 Sistema Computacional Hardware

Orientação a Objetos

BARRAMENTO DO SISTEMA

Na medida em que se cria um produto, o sistema de software, que será usado e mantido, nos aproximamos da engenharia.

SISTEMAS OPERACIONAIS CAPÍTULO 3 CONCORRÊNCIA

Processos e Threads (partes I e II)

Tabela de Símbolos. Análise Semântica A Tabela de Símbolos. Principais Operações. Estrutura da Tabela de Símbolos. Declarações 11/6/2008

6. Geometria, Primitivas e Transformações 3D

Bruno Pereira Evangelista.

Notas da Aula 17 - Fundamentos de Sistemas Operacionais

TRABALHO COM GRANDES MONTAGENS

4 Segmentação Algoritmo proposto

Capítulo 3. Avaliação de Desempenho. 3.1 Definição de Desempenho

UNIVERSIDADE FEDERAL DE SANTA CATARINA GRADUAÇÃO EM SISTEMAS DE INFORMAÇÃO DEPARTAMENTO DE INFORMÁTICA E ESTATÍSTICA DATA MINING EM VÍDEOS

Módulo 4. Construindo uma solução OLAP

5 Mecanismo de seleção de componentes

A memória é um recurso fundamental e de extrema importância para a operação de qualquer Sistema Computacional; A memória trata-se de uma grande

3 SCS: Sistema de Componentes de Software

Manual Sistema MLBC. Manual do Sistema do Módulo Administrativo

Dadas a base e a altura de um triangulo, determinar sua área.

Sistemas Operacionais

ORGANIZAÇÃO DE COMPUTADORES MÓDULO 10

Um Driver NDIS Para Interceptação de Datagramas IP

1

AMBIENTE PARA AUXILIAR O DESENVOLVIMENTO DE PROGRAMAS MONOLÍTICOS

Processamento de Imagem. Prof. MSc. André Yoshimi Kusumoto

Arquitetura de Rede de Computadores

Sistemas Operacionais Gerência de Dispositivos

MÓDULO 7 Modelo OSI. 7.1 Serviços Versus Protocolos

AULA 1. Informática Básica. Gustavo Leitão. Disciplina: Professor:

Programação de Sistemas

CAPÍTULO 3 - TIPOS DE DADOS E IDENTIFICADORES

OpenGL. Uma Abordagem Prática e Objetiva. Marcelo Cohen Isabel Harb Manssour. Novatec Editora

Multiplexador. Permitem que vários equipamentos compartilhem um único canal de comunicação

1.1. Organização de um Sistema Computacional

Tais operações podem utilizar um (operações unárias) ou dois (operações binárias) valores.

ARQUITETURA DE COMPUTADORES

Sistemas Operacionais

ISO/IEC 12207: Gerência de Configuração

Hardware (Nível 0) Organização. Interface de Máquina (IM) Interface Interna de Microprogramação (IIMP)

Organização e Arquitetura de Computadores I. de Computadores

Manual SAGe Versão 1.2 (a partir da versão )

7 Processamento Paralelo

ORGANIZAÇÃO DE COMPUTADORES MÓDULO 8

FACULDADE PITÁGORAS DISCIPLINA: ARQUITETURA DE COMPUTADORES

Tecnologia PCI express. Introdução. Tecnologia PCI Express

Noções de. Microsoft SQL Server. Microsoft SQL Server

Memórias Prof. Galvez Gonçalves

Segundo Pré-teste. Data de realização. 18 de Novembro de Local.

Guia de utilização da notação BPMN

ArpPrintServer. Sistema de Gerenciamento de Impressão By Netsource Rev: 02

O que é RAID? Tipos de RAID:

Conceitos de Banco de Dados

MRP II. Planejamento e Controle da Produção 3 professor Muris Lage Junior

Admistração de Redes de Computadores (ARC)

VIRTUALIZAÇÃO CONVENCIONAL

APOSTILA DE EXEMPLO. (Esta é só uma reprodução parcial do conteúdo)

Unidade 13: Paralelismo:

Algoritmos e Programação (Prática) Profa. Andreza Leite andreza.leite@univasf.edu.br

AULA 5 Sistemas Operacionais

Paralelismo. Computadores de alto-desempenho são utilizados em diversas áreas:

Sistemas Computacionais II Professor Frederico Sauer

CorelDRAW UM PROGRAMA DE DESIGN

Engenharia de Software III

Itinerários de Ônibus Relatório Final

UNIVERSIDADE FEDERAL DE SANTA CATARINA UFSC DEPARTAMENTO DE INFORMÁTICA E ESTATÍSTICA INE BACHARELADO EM CIÊNCIAS DA COMPUTAÇÃO.

PROCESSO DE DESENVOLVIMENTO DE SOFTWARE. Modelos de Processo de Desenvolvimento de Software

AULA 6 - Operações Espaciais

Bacharelado em Ciência e Tecnologia Bacharelado em Ciências e Humanidades. Representação Gráfica de Funções

Diagrama de transição de Estados (DTE)

Novidades no Q-flow 3.02

Guia de início rápido do Powersuite

CONHEÇA MELHOR SEU COMPUTADOR

Sua indústria. Seu show. Seu Futuro

IFPE. Disciplina: Sistemas Operacionais. Prof. Anderson Luiz Moreira

7.Conclusão e Trabalhos Futuros

ARQUITETURA DE COMPUTADORES

Prof. Marcos Ribeiro Quinet de Andrade Universidade Federal Fluminense - UFF Pólo Universitário de Rio das Ostras - PURO

CONTROLE DE QUALIDADE e VALIDAÇÃO DE PRODUTO CARTOGRÁFICO

2. Representação Numérica

COMPUTAÇÃO GRÁFICA REPRESENTAÇÃO DE IMAGENS

AULA 1 Iniciando o uso do TerraView

Introdução a Informática. Prof.: Roberto Franciscatto

Organização de Computadores Hardware

Departamento de Matemática - UEL Ulysses Sodré. Arquivo: minimaxi.tex - Londrina-PR, 29 de Junho de 2010.

Análise e Desenvolvimento de Sistemas ADS Programação Orientada a Obejeto POO 3º Semestre AULA 03 - INTRODUÇÃO À PROGRAMAÇÃO ORIENTADA A OBJETO (POO)

Sistemas Operacionais. Prof. André Y. Kusumoto

Formatos de Imagem PNG. Universidade Federal de Minas Gerais. Bruno Xavier da Silva. Guilherme de Castro Leite. Leonel Fonseca Ivo

Barra de ferramentas padrão. Barra de formatação. Barra de desenho Painel de Tarefas

O ESPAÇO NULO DE A: RESOLVENDO AX = 0 3.2

DESENVOLVIMENTO DE PROGRAMA MULTIMIDIA PARA O ENSINO DEDINÂMICA DE MÚLTIPLOS CORPOS

Sistemas de Informação I

A4 Projeto Integrador e Lista de Jogos

Google Drive. Passos. Configurando o Google Drive

Universidade Federal de Goiás Instituto de Informática Processamento Digital de Imagens

Prefixo a ser comparado Interface Senão 3

EA960 Redundância e Confiabilidade: RAID

Transcrição:

UNIVERSIDADE FEDERAL DO RIO GRANDE DO SUL INSTITUTO DE INFORMÁTICA BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO FÁBIO FEDRIZZI BERNARDON Ray Casting de Volumes Não-Estruturados com Dados Dinâmicos usando Hardware Gráfico Projeto de Diplomação Prof. Dr. João Luiz Dihl Comba Orientador Porto Alegre, julho de 2005

UNIVERSIDADE FEDERAL DO RIO GRANDE DO SUL Reitor: Prof. José Carlos Ferraz Hennemann Pró-Reitor de Graduação: Prof. Pedro Cezar Dutra da Fonseca Pró-Reitora Adjunta de Pós-Graduação: Profa. Valquiria Link Bassani Diretor do Instituto de Informática: Prof. Philippe Olivier Alexandre Navaux Bibliotecária-chefe do Instituto de Informática: Beatriz Regina Bastos Haro

Não tenha medo de crescer lentamente. Tenha medo, apenas, de ficar parado. PROVÉRBIO CHINÊS

AGRADECIMENTOS Primeiramente gostaria de agradecer aos meus pais, Lurdes Fedrizzi Bernardon e Clemir Cérgio Bernardon, por todo o apoio e dedicação ao longo de minha vida. Ao meu orientador João Comba, pela paciência e interesse em dividir seu conhecimento. Aos membros do grupo de Computação Gráfica da UFRGS, em especial a Carlos Dietrich, Christian Pagot, Leandro Fernandes e Átila Bohlke pelo apoio e excelentes discussões durante o desenvolvimento deste trabalho. À Jens Schneider, por ter disponibilizado seu código de compressão. À Steve Callahan, pelo código do HAVS para a extensão deste trabalho. À equipe do SCI da Universidade de Utah, pelas malhas e dados aqui utilizados. Aos meus colegas, que junto comigo enfrentaram e superaram as dificuldades do curso de Ciência da Computação da UFRGS. Muito obrigado.

SUMÁRIO LISTA DE ABREVIATURAS E SIGLAS.................... 7 LISTA DE FIGURAS............................... 8 LISTA DE TABELAS.............................. 9 RESUMO..................................... 10 ABSTRACT................................... 11 1 INTRODUÇÃO................................ 12 2 HARDWARE GRÁFICO.......................... 14 2.1 Pipeline Gráfico................................ 14 2.2 Modelo Computacional........................... 16 2.2.1 Acesso a Memória.............................. 17 2.2.2 Processamento Iterativo........................... 18 2.3 Depuração................................... 18 3 VISUALIZAÇÃO VOLUMÉTRICA..................... 20 3.1 Visualização de Volumes Não-Estruturados................ 21 3.1.1 Projeção de Células............................. 21 3.1.2 Visualização de Volumes Com Dados Dinâmicos............. 21 3.2 Ray Casting.................................. 22 3.2.1 Intersecção dos Raios com a Malha..................... 23 3.2.2 Integração da Cor.............................. 24 3.3 Funções de Transferência.......................... 24 4 HARDWARE RAY CASTING (HRC).................... 26 4.1 Estruturas de Dados............................. 26 4.2 Intersecção dos Raios com a Malha..................... 28 4.3 Depth Peeling................................. 29 4.4 Subdivisão da Tela.............................. 30 4.5 Resultados................................... 31 5 COMPRESSÃO DE CAMPOS ESCALARES DINÂMICOS....... 33 5.1 Decomposição Hierárquica de Dados.................... 33 5.2 Quantização Vetorial............................. 34 5.3 Código da Compressão............................ 35

6 HARDWARE RAY CASTING COM DADOS DINÂMICOS........ 37 6.1 Estrutura de Dados.............................. 37 6.2 Compressão e Descompressão dos Dados.................. 38 6.3 Gerenciamento de Instâncias de Tempo.................. 39 6.4 Visão Geral do Programa.......................... 40 6.5 Resultados................................... 42 7 EXTENSÕES................................ 46 7.1 Implementação Usando HAVS....................... 46 7.1.1 Dados Dinâmicos.............................. 46 7.1.2 Resultados.................................. 47 7.2 Outras Extensões............................... 48 8 CONCLUSÃO................................ 50 REFERÊNCIAS................................. 52

LISTA DE ABREVIATURAS E SIGLAS AGP Advanced Graphics Port API Application Program Interface CPU Central Processor Unit GB Giga Bytes GHz Giga Hertz GPU Graphics Processor Unit HAVS Hardware-Assisted Visibility Sorting HLSL High Level Shading Language HRC Hardware Ray Casting HT Hyper Threading I/O Input / Output KB Kilo Bytes MB Mega Bytes OQ Occlusion Query pixel picture element PS Pixel Shader RAM Random Access Memory RSR Relação Sinal-Ruído RT Render Target SIMD Single Instruction Multiple Data SPMD Single Program Multiple Data voxel volume element VS Vertex Shader

LISTA DE FIGURAS Figura 2.1: Esquema do pipeline gráfico atual.................... 15 Figura 3.1: Exemplo de uma ordenação de triângulos................ 21 Figura 3.2: Exemplo de um Ray Casting em uma malha bidimensional...... 23 Figura 3.3: Possíveis pontos de entrada e saída de um raio............. 24 Figura 4.1: Disposição dos dados da malha dentro das texturas........... 27 Figura 4.2: Disposição dos valores nas texturas intermediárias........... 28 Figura 4.3: Sistemas de coordenadas usados no algoritmo de ray casting..... 29 Figura 4.4: Geração dos níveis de depth peeling................... 30 Figura 4.5: Diferentes níveis de depth peeling com seus respectivos resultados.. 30 Figura 4.6: Exemplos de diferentes subdivisões possíveis.............. 31 Figura 5.1: Reconstrução de um vetor decomposto em três níveis......... 34 Figura 5.2: Subdivisão de um conjunto em seus componentes principais...... 36 Figura 6.1: Estrutura de armazenamento da malha para dados dinâmicos...... 38 Figura 6.2: Acesso às tabelas de códigos...................... 39 Figura 6.3: Gerenciamento dos conjuntos das instâncias de tempo (CT)...... 40 Figura 6.4: Verificação do estado de troca dos conjuntos de tempos........ 41 Figura 6.5: Procedimento de geração das diversas camadas de depth peeling.... 42 Figura 6.6: Iterações do ray casting......................... 43 Figura 6.7: Verificação do término do programa................... 44 Figura 6.8: Malha SPX visualizada com ray casting................ 45 Figura 7.1: Divisão do processamento entre a CPU e a GPU no HAVS....... 47 Figura 7.2: Malha BLUNT visualizada com HAVS................. 48 Figura 7.3: Malha TORSO visualizada com HAVS................. 49

LISTA DE TABELAS Tabela 4.1: Resultados da visualização utilizando Ray Casting........... 31 Tabela 4.2: Tempos de visualização usando uma função de transferência 3D (ms). 32 Tabela 6.1: Malhas utilizadas na visualização................... 43 Tabela 6.2: Resultados da Compressão....................... 44 Tabela 6.3: Resultados da visualização do Ray Casting com dados dinâmicos... 45 Tabela 7.1: Resultados da visualização usando HAVS................ 47

RESUMO A visualização volumétrica de conjuntos de dados com variação temporal é essencial em diversas aplicações científicas. Devido a enorme quantidade de dados envolvida, para conjuntos de dados com uma região de amostragem estática é comum a utilização de apenas campos escalares com variação temporal. O uso de Quantização Vetorial para comprimir campos escalares tem se mostrado bastante efetiva quando combinada com algoritmos de visualização de volumes baseados em textura para dados estruturados. Este trabalho apresenta uma abordagem que utiliza a quantização Vetorial para visualizar malhas não-estruturadas (compostas por tetraedros). É explicada em detalhes a extensão da técnica de ray casting para visualizar os dados dinâmicos, implementada em uma GPU (Graphics Processor Unit). Também é apresentada uma extensão a outro algoritmo de visualização, mostrando a capacidade da técnica de compressão ser utilizada em malhas não-estruturadas. Os resultados são capazes de manipular centenas de instâncias de tempo. Palavras-chave: Visualização volumétrica, ray casting, GPU.

Ray Casting of Unstructured Grids with Dynamic Data using Graphics Hardware ABSTRACT The volumetric rendering of time-varying datasets is essential in several scientific applications. Due to the enormous amount of data involved, in datasets with static sampling regions is common to consider time-varying scalar fields. The use of Vector Quantization to compress scalar fields has been shown to be quite effective when combined with texture-based volume rendering algorithms for structured grids. This work presents an approach to use vector quantization to render unstructured grids (meshes of tetrahedra). It explains in detail the extension of a ray casting algorithm to handle dynamic data, using a GPU (Graphics Processor Unit). It also presents an extension to another volume rendering algorithm, showing the ability of the compression scheme to be used on unstructured grids. The results can handle several hundred time instances. Keywords: Ray Casting, Volume Visualization, GPU.

12 1 INTRODUÇÃO O advento da computação oferece aos cientistas novas alternativas para estudar e compreender os problemas. Novas abordagens para testar as hipóteses de forma mais econômica e rápida são desenvolvidas, utilizando as facilidades dos sistemas computacionais. As mais diversas áreas do conhecimento humano estão tirando proveito do uso de computadores para realizar simulações. Isso está sendo utilizado desde a engenharia, com projetos de prédios, veículos e simulações de fluidos, até a medicina, com a preparação e simulação de cirurgias. O crescente aumento no poder computacional está permitindo a criação de sofisticadas simulações que resultam em uma grande quantidade de dados. Porém analisar de forma eficaz estes grandes conjuntos de dados é um desafio constante para os cientistas que devem validar o fato de seus códigos representarem fielmente a realidade. A exploração de dados através da visualização oferece uma poderosa forma de avaliar a credibilidade e as limitações de resultados de simulações, e estimulam o uso efetivo dos resultados por outras pessoas, pois trata-se de uma abordagem intuitiva para a classe de problemas a que se destina. Com uma maior quantidade de dados, médicos podem simular uma cirurgia complicada, prevendo e contornando erros que poderiam colocar a vida do paciente em risco. O mesmo acontece com engenheiros, que conseguem simular novos sistemas antes de serem construídos, economizando tempo e dinheiro. Todavia, neste momento, existe uma confusão entre a capacidade de simulação dos sistemas existentes, os quais freqüentemente são baseados em malhas não-estruturadas tridimensionais de alta resolução, e a disponibilidade de técnicas de visualização. Em um recente artigo sobre esse tópico (MA, 2003), o autor diz: "Até agora a pesquisa em visualização de dados volumétricos com variação temporal tem se direcionado primariamente para os problemas de codificação e geração de imagens de uma única variável escalar em uma malha regular.... Malhas não-estruturadas com variação temporal tem sido visualizadas com uma abordagem de força bruta ou simplesmente re-amostradas e reduzidas a um volume estruturado para futuros cálculos de visualização...." Um dos problemas chaves em manipular dados com variação temporal é a quantidade de informação que deve ser processada. Para a visualização, eles precisam ser armazenados (e/ou parcialmente carregados) na memória principal da CPU ou da GPU. As taxas de transferência dos dados criam um gargalo para uma efetiva análise dessas informações. Uma grande quantidade de técnicas bem sucedidas para a visualização de volumes estruturados dinâmicos tem utilizado compressão para atenuar este problema, e permitem um melhor uso dos recursos.

Este trabalho apresenta uma abordagem para comprimir os dados temporais em malhas não-estruturadas, permitindo uma rápida reconstrução e visualização das informações, e apresenta resultados utilizando dois dos mais rápidos algoritmos de visualização de dados estáticos. No capítulo 2 será apresentado o que existe atualmente em termos de hardware gráfico, descrevendo as etapas que os objetos passam, desde sua descrição e representação geométrica até a imagem exibida ao usuário. Também são apresentadas restrições e soluções que permitiram o desenvolvimento deste trabalho. O capítulo 3 aborda as técnicas utilizadas em visualização volumétrica, focando em dados não-estruturados. Aqui são explicados diversos conceitos relacionados a este tipo de visualização. Uma implementação de ray casting na GPU é apresentada em detalhes no capítulo 4. Ela apresenta algumas alterações a abordagens existentes, o que possibilitou uma redução dos dados armazenados. A técnica de compressão utilizada para reduzir a quantidade de dados trafegando pelo sistema é descrita no capítulo 5. A técnica de visualização de volumes com variação temporal utilizando ray casting é apresentada no capítulo 6. Também é explicado como o programa gerencia uma quantidade arbitrária de instâncias de tempo. Uma visão geral do algoritmo é abordada na seção 6.4. O capítulo 7 explica como podem ser feitas extensões no trabalho, mostrando como a técnica de compressão pode ser utilizada em outros algoritmos de visualização. Outras extensões são sugeridas. Considerações finais, limitações e a conclusão são abordadas no capítulo 8. 13

14 2 HARDWARE GRÁFICO O avanço da computação gráfica nos computadores pessoais possibilitou o surgimento e a evolução de empresas que desenvolvem o que hoje é chamado de GPU (Graphics Processor Unit, Unidade de Processamento Gráfico). Inicialmente esses processadores possuíam apenas um conjunto de rotinas implementadas em hardware, que podiam ser configuradas, porém não podiam ser programadas. Nos últimos anos esses processadores tiveram algumas partes de seu pipeline convertidas em unidades de processamento mais genéricas, recebendo a capacidade de executar um programa composto por um conjunto de instruções mais limitado que o presente em uma CPU, porém específico ao seu propósito, o que lhe permite executar de forma muito rápida as suas operações. O objetivo que guia o desenvolvimento desse processador é o processamento de gráficos e imagens. Seu conjunto de instruções é fortemente direcionado ao processamento de vetores e matrizes, sendo esse um processador vetorial. Além disso, ele possui diversas unidades de processamento paralelas, garantindo-lhe um grande poder computacional. Apesar de diversas aplicações que utilizam um processamento vetorial massivo estar aproveitando a GPU para processamento de som e simulações físicas, sua principal aplicação ainda é o processamento gráfico, largamente utilizado em jogos de computador e videogame para aumentar o realismo das imagens. Aplicações de visualização científica também estão aproveitando esse processador para aumentar a velocidade de geração de imagens, permitindo interações entre o usuário e seu objeto de estudo de uma forma que antes era computacionalmente muito cara. 2.1 Pipeline Gráfico O modelo de processamento gráfico tradicional está cedendo lugar a um modelo mais geral. Onde antes haviam operações fixas que apenas podiam ser configuradas, agora existem processadores programáveis. A programação possibilita uma quantidade praticamente infinita efeitos e é favorável ao surgimento de novas técnicas, ao contrário da configuração, que permite apenas um conjunto limitado de opções. Isso abre novas possibilidades para os desenvolvedores. No atual modelo pode-se dividir o pipeline gráfico em quatro estágios, como pode ser visto na figura 2.1. Uma explicação detalhada sobre o pipeline e seus estágios pode ser encontrada em livros (MöLLER; HAINES, 2002) (WATT, 2000). Aqui será feita apenas uma breve revisão. A primeira parte do pipeline é programável, e recebe o nome de processador de vértices. Esse processador executa um programa informado pelo usuário para cada vértice desenhado. Ele geralmente apresenta uma pequena quantidade de unidades em paralelos (o chip NV40 apresenta 6 unidades), executando em uma freqüência inferior ao processa-

15 Figura 2.1: Esquema do pipeline gráfico atual. dor de fragmentos, pois a quantidade de vértices presente em um programa é, usualmente, muito inferior a quantidade de fragmentos que será gerada. A segunda parte do pipeline é responsável pela montagem das primitivas geométricas (basicamente os triângulos) e sua rasterização. Aqui são gerados os fragmentos, que são candidatos a pixel na imagem final. Essa parte é fixa, não podendo ser programada, apenas configurada. Neste ponto são realizados: back/front face culling: remoção de faces ocultas (back) ou visíveis (front) dos modelos; clipping: "recorte"dos triângulos contra planos definidos pelo usuário e pelo volume de visualização (frustum); divisão por W: divisão dos elementos do vetor pela sua coordenada W, trazendo todos os fragmentos para o mesmo plano de projeção; geração das coordenadas de tela. A seguir os dados passam para o processador de fragmentos. Esse nível pode ser programado, sendo responsável pelo cálculo da cor dos fragmentos que serão enviados ao render target. Essa é, possivelmente, a etapa do pipeline gráfico mais crítica atualmente. A iluminação, anteriormente realizada por vértice utilizando Gouraud Shading, agora é calculada para cada fragmento utilizando Phong Shading, combinando texturas e permitindo a realização de uma série de efeitos especiais. Devido a quantidade de fragmentos presentes em uma cena ser muito superior a quantidade de vértices, esta etapa apresenta uma grande quantidade de unidades de processamento em paralelo (o chip NV40 apresenta 16 unidades), executando em altas freqüências, geralmente superiores às do processador de vértices.

16 A etapa de processamento de fragmentos é a mais crítica no contexto deste trabalho, pois nela são realizadas a maioria das operações do algoritmo de visualização A penúltima parte do pipeline corresponde as operações de escrita. Essa parte só pode ser configurada, possuindo as seguintes operações: scissor test: é um tipo especial de stencil test, otimizada; alpha test: aceita ou rejeita um fragmento baseado no valor de seu canal alpha; stencil test: utiliza uma máscara para filtrar os fragmentos; depth test: compara o valor da coordenada Z do fragmento com o previamente gravado, permitindo sua escrita ou não. É usado para fins de ordenamento dos fragmentos, mediante um determinado critério (menor valor, por exemplo); blending: composição de transparência; dithering: melhora a resolução da cor em sistemas com um pequeno número de planos de cores; operações lógicas: permite a composição do novo fragmento com o previamente gravado usando informações lógicas (AND, OR, XOR...). A última etapa corresponde a escrita do resultado no render target, que é a área de memória que armazena os valores de cor, compondo a imagem que será exibida como resultado do processamento do pipeline. 2.2 Modelo Computacional O modelo computacional de uma GPU é diferente do modelo de uma CPU. Enquanto uma CPU possui classicamente um único fluxo de execução, uma GPU apresenta vários fluxos paralelos. O nível de paralelismo varia de acordo com a etapa o processamento, seguindo o pipeline gráfico, apresentado na seção 2.1. A GPU é baseada num modelo de SIMD (Single Instruction Multiple Data - Uma Instrução e Vários Dados), utilizando ainda SPMD (Single Program Multiple Data - Um Programa e Vários Dados). Muitos elementos são processados em paralelo, sendo que em uma determinada etapa e instante de tempo, diversos dados utilizam a mesma instrução. A entrada de dados para esse processamento é uma descrição de um objeto geométrico. São informadas as posições de vértices de um modelo, onde cada vértice é uma coordenada 2D, 3D ou 4D. Além da posição, outros atributos podem ser informados, como a cor, coordenadas de texturas, orientação da direção normal da superfície sendo descrita, entre outros. Além disso, é configurada a forma como será feita a conectividade desses vértices, geralmente agrupando-os em triângulos, que é a primitiva geométrica usualmente utilizada. O processamento é realizado em vários fluxos paralelos. A quantidade de fluxos depende de quantas unidades de processamento estão presentes na GPU e do estágio de processamento. Os vértices, por exemplo, são processados independentemente, pois o resultado de um não influencia os demais. O mesmo ocorre com os fragmentos, que são resultantes da interpolação das primitivas geométricas e processados de forma independente.

17 Para controlar o pipeline, algumas regiões sincronizam os dados. Após o processador de vértices, quando as primitivas devem ser montadas, existe a necessidade de trabalhar com os vértices que a compõe. Assim é realizada uma sincronia no processamento. O mesmo ocorre após o processador de fragmentos, quando seu resultado realiza as operações de escrita e vai ser composto no render target (RT). O resultado do processamento são os fragmentos, que possuem uma posição fixa de escrita no RT. Essa posição é determinada quando os fragmentos são gerados, e atualmente não pode ser alterada pelo processador de fragmentos. Este consegue apenas controlar em quais render targets serão gravados resultados. Isso limita como o resultado pode ser composto, pois não existem escritas aleatórias a memória. O uso de Multiple Render Targets permite a escrita de mais de quatro valores como resultado do pipeline. Para cada render target extra utilizado o programa pode escrever mais quatro valores como resultado. Para cada RT ativado, existe uma perda de performance, causada pela gravação de mais valores no final do programa. Atualmente existe um limite de quatro RT s simultaneamente ativos, sendo que todos precisam ter as mesmas dimensões. Uma restrição presente atualmente nas GPU s não permite a leitura e escrita simultânea numa textura (i.e., ler e escrever numa mesma textura durante uma iteração do programa). Por isso foi utilizada a técnica de Ping-Pong Rendering com duas texturas para trocar informação entre duas iterações do programa. Enquanto o programa está escrevendo em uma textura, ele lê as informações da segunda. Na próxima iteração, o programa escreverá o resultado na segunda textura e lerá as informações da primeira. 2.2.1 Acesso a Memória Diferente de trabalhar com a CPU, uma GPU não possui um sistema de gerenciamento de memória que permita a manipulação de estruturas de dados tradicionais. Como esse processador foi concebido para trabalhar exclusivamente com gráficos, seu sistema de memória está fortemente direcionado à manipulação de texturas. A única forma presente para recuperar dados da memória numa GPU durante seu processamento é o acesso a textura. Este acesso até pouco tempo estava restrito ao processador de fragmentos, porém já existem GPU s capazes de acessar a memória de texturas no processador de vértices. Originalmente as texturas utilizadas nos programas gráficos possuíam uma precisão de 8 bits em ponto-fixo por canal com até quatro canais por texel, pois isso era considerado suficiente para a maioria das aplicações. Com o desenvolvimento de novas técnicas e efeitos de visualização, viu-se a necessidade da utilização de texturas com uma maior precisão e em ponto-flutuante. As GPU s atuais podem trabalhar com valores de textura de até 128 bits (32 bits por canal) em ponto-flutuante. A existência desse tipo de textura foi fundamental para o trabalho, pois alguns dados utilizados estarão armazenados e codificados dentro dessas texturas. Uma menor precisão poderia gerar artefatos nas imagens, e impediria a visualização de grandes conjuntos de dados. O processador de fragmentos utiliza uma memória cache para armazenar os valores das texturas que estarão sendo utilizadas. Para realizar a carga dos dados nessa memória, utilizam os valores das coordenadas de textura que são calculados no processador de vértices. Quando o programa acessa um valor diferente do recebido como entrada ele está realizando um acesso dependente. Esse tipo de acesso afeta negativamente a performance do programa, pois aumenta muito a chance de ocorrer uma cache miss durante este tipo de acesso. Como em nossos cálculos existe a necessidade de realizar operações sobre

18 dados recuperados de texturas baseados em um índice armazenado em outra textura, todos os acessos para recuperar as informações do cálculo são caracterizados como acessos dependentes. 2.2.2 Processamento Iterativo Como o modelo de processamento de uma GPU baseia-se em um pipeline que existe a bastante tempo, sua implementação não preocupou-se a princípio de acrescentar certas opções, como o controle dinâmico do fluxo de execução. Assim, laços dependentes de variáveis controladas dentro dos programas não foram implementadas num primeiro momento. Hoje, já existem GPU s capazes de realizar esse controle de fluxo (GeForce 6800 da NVidia). Para compensar a falta de um controle de iterações dentro do processador de fragmentos especificamente, foram utilizadas duas características disponíveis na GPU para controlar as iterações na CPU, chamadas de occlusion queries (OQ) e early-z test, respectivamente. O uso de OQ permite a contagem dos fragmentos que são efetivamente escritos como resultado do pipeline. O programa deve iniciar uma OQ, desenhar os objetos de que deseja a informação de escrita e finalizar a OQ. O resultado das OQ não esta disponível quando o programa pede seu término, pois é necessário esperar todos os fragmentos gerados terminarem seu processamento. Para evitar bloquear o programa até o resultado estar pronto, a OQ possui uma implementação assíncrona e não-bloqueante. O programa pode perguntar para a OQ se ela já possui o resultado do processamento. Em caso afirmativo, o programa pode recuperar esse valor e utilizá-lo. Caso negativo, o programa deve continuar seu processamento e, depois de algum tempo, perguntar novamente se o resultado está pronto. Usualmente o teste de Z para manter a ordem de visibilidade dos objetos é realizada na última etapa do pipeline. Porém, para acelerar o processamento total do programa, as novas GPU s utilizam um teste prévio, logo antes dos fragmentos entrarem no processador de fragmentos. Este novo procedimento é chamado de Early-Z Test. Somente os dados que passarem no teste prosseguirão pelo pipeline, e os que forem rejeitados serão descartados. Este teste só está habilitado quando o programa não irá alterar o valor da coordenada Z dos fragmentos dentro do processador de fragmentos. 2.3 Depuração Grande parte do processamento da GPU é realizado em fluxos de dados, processando uma série de informações simultaneamente em cada uma das unidades de processamento. Além disso, toda a arquitetura da GPU está voltada para o recebimento de informações da CPU. A transferência de dados da memória da GPU para a CPU possui uma implementação muito lenta, dificultado a leitura de informações que estão armazenadas na GPU. Além das restrições e dificuldades encontradas no hardware, o software também pode atrapalhar. Todos os recursos presentes nas placas de vídeo são controlados através de seu driver 1. Apesar do programa ter controle sobre alguns desses recursos, o driver pode forçar o uso de características indesejadas para a aplicação. Essas características devem ser manualmente desativadas, o que torna difícil o gerenciamento do programa e a detecção de erros. 1 programa que gerencia a comunicação entre um dispositivo de hardware e os aplicativos.

19 Para realizar a remoção de erros dos programas de fragmento foi utilizada a seguinte abordagem: o valor de uma variável é escrito para o render target. O programa da CPU lê esse resultado da placa e escreve seu valor num arquivo. Finalmente, o arquivo pode ser analisado para se extrair informações. Esse modelo, apesar de ser pouco prático, mostrou-se útil para detecção e remoção de erros. Atualmente, a API DirectX oferece uma ferramenta de depuração integrada ao editor Visual Studio 2. 2 DirectX e Visual Studio são marcas registradas da Microsoft Corporation.

20 3 VISUALIZAÇÃO VOLUMÉTRICA Depois de explicado como funciona o pipeline gráfico, deve-se entender como e porquê é utilizada a visualização volumétrica antes de apresentar a técnica desenvolvida neste trabalho. Usualmente, objetos visualizados em computação gráfica possuem uma forma, porém não possuem informações sobre seu interior. O modelo de uma pessoa, por exemplo, quando utilizada em um jogo de computador, não apresenta os órgãos internos, nem esqueleto, pois essas regiões não serão visualizadas pelo usuário. Quando o objetivo da visualização é mostrar a parte interior de objetos, como em diversas aplicações científicas, os modelos tradicionais de visualização devem ser substituídos por técnicas desenvolvidas com essa finalidade. Os dados utilizados para permitir esse tipo de visualização devem conter informações espaciais sobre seu interior. No caso da pessoa citado anteriormente, é necessário que o modelo armazene informações sobre os ossos, órgãos e demais atributos que sejam considerados relevantes. Dados que armazenam informações resultantes da discretização do espaço são chamados dados volumétricos. Basicamente, existem dois tipos de representação utilizados em dados volumétricos: Dados Estruturados: esse tipo de dado possui uma subdivisão do domínio em uma estrutura regular. Sua informação é geralmente guardada em uma textura tridimensional, sendo cada porção do espaço discretizado denominada voxel (volume element). Também pode ser armazenado na forma de uma malha regular. Dados Não-Estruturados: esse tipo de dado também apresenta uma discretização do espaço, porém utilizando uma estrutura irregular. Essa estrutura é construída utilizando geometria, sendo comumente utilizadas malhas de tetraedros, por representarem o simplexo do espaço 3D. Essas malhas podem aparecer em diversas resoluções, sempre dependendo do tamanho dos detalhes que deseja-se visualizar. Uma boa descrição das técnicas e métodos de visualização para dados estruturados pode ser encontrada em diversos trabalhos (BINOTTO, 2003) (WATT, 2000) (DIE- TRICH et al., 2004), não sendo aqui abordados por não pertencerem ao escopo deste trabalho. O trabalho aborda o uso de dados não-estruturados para armazenar as informações. Na próxima seção, serão revistos trabalhos que estão diretamente relacionados a visualização desse tipo de dado.

21 3.1 Visualização de Volumes Não-Estruturados Atualmente utilizam-se duas técnicas para a visualização de volumes não-estruturados. A primeira delas, chamada de Projeção de Células, é brevemente discutida na seção 3.1.1. A segunda forma utilizada para este tipo de visualização é a técnica chamada de ray casting. Ela será explicada na seção 3.2, sendo fundamental para esse trabalho. 3.1.1 Projeção de Células Uma técnica comumente utilizada para visualização de volumes não-estruturados é a Projeção de Células. Nessa técnica, a geometria do objeto é ordenada para cada quadro a ser gerado e desenhada de trás para frente. Assim é garantida uma correta composição da transparência dos dados (WILLIAMS, 1992) (figura 3.1). Figura 3.1: Exemplo de uma ordenação de triângulos. Apesar de simples, a classificação dos dados para cada quadro da animação exige uma grande quantidade de processamento, comprometendo o tempo de resposta de sistemas que usam essa abordagem. Sua vantagem é tratar tanto malhas convexas quanto nãoconvexas da mesma forma, sem introduzir processamento extra para uma classe desses objetos (STEIN; BECKER; MAX, 1994). Diversos trabalhos buscam aperfeiçoar os mecanismos de classificação dos dados utilizando diferentes abordagens (FARIAS; MITCHELL; SILVA, 2000) (FARIAS; SILVA, 2001). Técnicas que utilizam a GPU para auxiliar a ordenação também têm sido propostas (KRISHNAN; SILVA; WEI, 2001). Recentemente uma alternativa a completa classificação foi proposta (CALLAHAN et al., 2005), onde a CPU do computador realiza uma pré-classificação das primitivas da malha, ordenando-as pelo centróide das faces. Isso resulta em uma ordenação parcial, que será terminada utilizando um k-buffer, descrito no trabalho, completando a ordenação dos dados dentro da GPU. 3.1.2 Visualização de Volumes Com Dados Dinâmicos É interessante notar que a maioria dos trabalhos relacionados a visualização de volumes com dados dinâmicos utilizam dados estruturados. Alguns trabalhos têm explorado o uso de estruturas de dados espaciais para otimizar a geração de imagens de informações

22 com variação temporal (ELLSWORTH; CHIANG; SHEN, 2000) (MA; SHEN, 2000). A Árvore de Particionamento de Tempo-Espaço (TSP-Tree: Time-Space Partitioning) usada nestes trabalhos é baseada em uma octree 1 que é estendida para armazenar uma dimensão extra (SHEN; CHIANG; MA, 1999), mantendo para isso uma árvore binária em cada nodo, que representa a evolução da sub-árvore através do tempo. A árvore TSP também pode armazenar sub-imagens parciais para acelerar a geração das imagens em algoritmos de ray casting. Por não ser utilizada neste trabalho, esta estrutura não será explicada em detalhes. Relacionado diretamente com este trabalho é uma técnica de compressão de dados volumétricos com variação temporal ou não, utilizando uma transformada de wavelet, permitindo uma rápida reconstrução e visualização (WESTERMANN, 1995). Um trabalho seguinte a este (SCHNEIDER; WESTERMANN, 2003) descreve uma implementação na GPU utilizando quantização vetorial, vista com mais detalhes na seção 5.2. Outra técnica utiliza visualização acelerada por hardware, na qual a idéia básica é baseada numa codificação temporal de dados volumétricos indexados, permitindo uma rápida decodificação em hardware (LUM; MA; CLYNE, 2001) (LUM; MA; CLYNE, 2002). Seu mecanismo de visualização é baseado em texturas volumétricas. A compressão da informação temporalmente variável é feita agrupando os dados em pequenos conjuntos e usando a Transformada Discreta do Cosseno (DCT - Discrete Cosine Transform). Cada amostra dentro de um conjunto é codificada como um único índice. Armazena-se, assim, o volume como um conjunto de texturas bidimensionais que são decodificadas usando uma paleta com variação temporal. Devido ao fato do modelo estar inteiramente armazenado na memória principal, são obtidas taxas de geração de imagens muito superiores àquelas dos modelos não comprimidos, pois não necessitam ficar constantemente carregando dados do disco. Por possuírem um grande tamanho, tarefas de I/O são características muito importantes quando se está trabalhando com dados dinâmicos (YU; MA; WELLING, 2004). 3.2 Ray Casting Ray Casting é uma técnica de visualização de objetos que consiste em lançar um raio para cada ponto da imagem a ser gerada. Diferente da técnica de Ray Tracing, ela não está preocupada em capturar as reflexões do ambiente ou as refrações causadas pelos objetos. Por isso, ela é mais utilizada para a visualização de objetos volumétricos, preocupando-se em obter a integração de um raio dentro de um volume. A técnica consiste em lançar um raio para cada pixel da imagem que será gerada, partindo de um ponto de observação (observador ou olho). Para cada objeto presente na cena, é computado se ocorreu uma colisão com o raio. Nesse caso, é calculada a contribuição de cor correspondente ao ponto de intersecção (Figura 3.2). Caso o algoritmo seja implementado na CPU, o objeto que está sendo visualizado pode estar armazenado dentro de uma estrutura de dados que facilite a obtenção do ponto de colisão, acelerando a obtenção do resultado. Todavia esse processo pode ser melhorado em outros modelos computacionais. A utilização das GPU s para a realização do processo de integração dos raios traz um grande ganho em velocidade de processamento. Vários trabalhos têm buscado a implementação de algoritmos de ray casting na GPU, para diferentes tipos de dados (KARLSSON; LJUNGSTEDT, 2004). 1 estrutura de dados utilizada para subdividir dados em três dimensões, muito utilizada em computação gráfica para subdividir o espaço 3D.

23 Figura 3.2: Exemplo de um Ray Casting em uma malha bidimensional. A primeira abordagem de um ray casting para malhas de tetraedros na GPU apresentou uma forma de armazenar os dados necessários na memória de vídeo e como utilizar o processador de fragmentos para realizar a computação dos raios e da integração da cor (WEILER et al., 2003). Bons resultados foram obtidos, porém a escolha do uso de texturas tridimensionais para o armazenamento os dados, assim como a explícita codificação de dados que poderiam ser implicitamente derivados impediram a obtenção de tempos ainda melhores. Uma posterior implementação da técnica utilizando estruturas mais otimizadas e uma menor quantidade de informações armazenadas, além de técnicas de aceleração de visualização, foi desenvolvida e apresentou resultados até duas vezes melhores (em termos de tempo de processamento) que o primeiro trabalho (BERNARDON et al., a aparecer). Essa técnica será explicada em detalhes no capítulo 4. 3.2.1 Intersecção dos Raios com a Malha Para calcular a contribuição de um tetraedro para a cor final ao longo de um raio é necessário calcular o ponto de entrada e o ponto de saída do raio em cada tetraedro. Isso é feito através do cálculo dos pontos de intersecção do raio contra os planos que descrevem as faces do tetraedro. Esses pontos são obtidos pela equação 3.1, onde v t,i representa o vértice i do tetraedro t, e é a posição do observador, r é a direção de visualização e n t,i é a normal i do tetraedro t. λ i = (v t,4 i e) n t,i r n t,i,i {1..4} (3.1) Apenas valores positivos de λ devem ser considerados. O ponto de entrada será o maior valor em que o denominador da equação 3.1 seja negativo. O ponto de saída será o de menor valor dentre os que possuírem o denominador positivo. A figura 3.3 mostra um exemplo 2D para o cálculo dos pontos: λ 2 corresponde ao ponto de entrada do raio e λ 3 corresponde ao ponto de saída do raio. O cálculo do ponto de entrada do raio em um tetraedro só precisa ser realizado quando

24 Figura 3.3: Possíveis pontos de entrada e saída de um raio. estão sendo processadas as faces de fronteira. Uma vez que os raios estejam dentro da malha, apenas precisa-se calcular os pontos de saída, pois o ponto de entrada é transmitido de uma iteração do algoritmo para outra através das texturas auxiliares. 3.2.2 Integração da Cor Para acelerar a integração da cor, uma função de interpolação deve ser calculada para cada tetraedro da malha (LÜRIG; GROSSO; ERTL, 1997). Quando se está pronto a fazer a integração da cor, é utilizada a equação 3.2 para calcular o valor interpolado do escalar no ponto de saída do raio. Os valores de x, y e z correspondem as coordenadas dos vértices dos tetraedros, e f(x, y, z) ao escalar associado ao vértice. f (x,y,z) = a + bx + cy + dz (3.2) Para obter os coeficientes a, b, c e d da equação 3.2, basta resolver o sistema 3.3 em uma etapa de pré-processamento. O vetor (b, c, d) é chamado de Vetor Gradiente, e resulta da derivação da equação 3.3. 1.0 x 1 y 1 z 1 1.0 x 2 y 2 z 2 1.0 x 3 y 3 z 3 1.0 x 4 y 4 z 4 a b c d = f 1 f 2 f 3 f 4 (3.3) Uma vez obtidos os valores escalares dos pontos de entrada e saída do tetraedro podese acessar uma função de transferência pré-calculada para recuperar a contribuição de cor do segmento de raio. As funções de transferência serão abordadas em 3.3 3.3 Funções de Transferência Funções de Transferência são equações matemáticas multidimensionais cujo domínio geralmente se encontra no espaço 2D, 3D ou 4D e o contra-domínio no espaço 4D (equação 3.5). Elas objetivam atribuir características visuais, geralmente cor e opacidade, a elementos de volumes de dados sendo processados. 2D : s,t (R,G,B,A) (3.4)

25 3D : s,t,d (R,G,B,A) (3.5) Existem diversos meios para se especificar funções de transferência, porém a obtenção de uma que seja apropriada para um dado volume não é uma tarefa trivial (MANSOUR, 2002). Neste trabalho são utilizadas funções de transferência 2D e 3D, que utilizam o valor escalar associado ao ponto de entrada e ao ponto de saída de um raio, mais o comprimento do segmento do raio no caso de uma função 3D, para acessar o valor correspondente a uma etapa do processo de integração. Essas funções são geradas pela interpolação linear de valores de escalares que possuem uma cor associada, e uma posição dentro do volume 3D da função. Funções bidimensionais utilizam uma fatia desse volume, e o comprimento do segmento de raio é utilizado para modular seu valor.

26 4 HARDWARE RAY CASTING (HRC) Este capítulo descreve um método de visualizar malhas não-estruturadas utilizando ray casting. Este método é similar a outros que utilizam a GPU para o processamento dos raios em malhas sem variação temporal (WEILER et al., 2003) (BERNARDON et al., a aparecer), apresentando algumas modificações. Ele serve como base para a implementação que visualiza dados dinâmicos. Como mencionado na seção 3.2, a GPU oferece um modelo computacional adequado para a implementação do algoritmo de ray casting. Os raios em um ray casting são independentes, podendo ser processados em paralelo. A GPU oferece uma grande quantidade de pipelines para o processamento paralelo de fragmentos, e o objetivo de cada raio é justamente calcular a cor que deve ser atribuída a um pixel. A utilização dos diversos fluxos de execução presentes na GPU favorecem a implementação de ray casting nessa plataforma. 4.1 Estruturas de Dados Utilizar a GPU para processar os raios implica em criar uma estrutura de dados que utilize-se de texturas para armazenar as informações necessárias à realização do ray casting. Os dados da malha necessários em uma iteração do algoritmo são os vértices (v) do tetraedro (t), a normal (n) de cada face e o índice do tetraedro adjacente (a) de cada face para calcular qual o próximo tetraedro no qual o raio irá entrar, além de um vetor gradiente (ĝ) e um valor escalar (s) que informarão a contribuição do segmento de raio entre o ponto inicial e o final para a cor sendo avaliada. O índice de um tetraedro é codificado em duas posições na textura para possibilitar uma grande quantidade de valores. Como cada uma dessas posições corresponde a um valor em ponto-flutuante de 32 bits, a precisão do canal acaba sendo insuficiente para grandes malhas, pois são aproveitados cerca de 23 bits da mantissa (aproximadamente 1 milhão de tetraedros), e esses 20 bits ainda estão sujeitos a erros de precisão. Utilizando 2 canais de 32 bits, pode-se codificar malhas grandes sem os efeitos negativos causados pela baixa precisão. A normal de cada face não precisa ser explicitamente armazenada nas texturas, pois elas são facilmente reconstruídas a partir dos vértices do tetraedro, que são armazenados de forma a permitir a reconstrução descrita pelas fórmulas 4.1 a 4.4. N 0 = (V 3 V 1 ) (V 2 V 1 ) (4.1) N 1 = (V 2 V 0 ) (V 3 V 0 ) (4.2)

27 N 2 = (V 3 V 0 ) (V 1 V 0 ) (4.3) N 3 = (V 1 V 0 ) (V 2 V 0 ) (4.4) A codificação da malha é apresentada na figura 4.1. Os vértices são dispostos em três posições consecutivas na textura, cada uma armazenando quatro valores. Uma segunda textura é utilizada para armazenar os índices dos tetraedros adjacentes junto ao vetor gradiente e ao escalar do tetraedro. As duas texturas são quadradas e armazenam valores em ponto-flutuante. O lado da textura é calculado através da equação 4.5. Caso o resultado seja divisível por três, será adicionado 1 (um) ao valor em cada dimensão. Isso é necessário devido a forma como o algoritmo detecta que um raio não precisa mais ser computado. Quando um raio sai da malha, o programa grava o valor 0 na primeira parte do índice do tetraedro (coordenada X da textura intermediária). Dessa forma uma referência à primeira coluna das texturas de armazenamento dos dados é considerada critério de parada para o processamento de um raio. Por esse motivo, a primeira coluna das texturas deve permanecer sem uso. Além de manter a textura quadrada, quando desenvolvendo para determinados processadores gráficos, a textura precisa ter seu lado como uma potência de 2 (chips da ATI Technologies Inc.). l = quantidadedetetraedros 3 (4.5) Esses dados (posições dos vértices e índices dos tetraedros adjacentss) são estáticos, sendo carregados em uma etapa de inicialização do programa e apenas acessados nas suas diversas iterações. Figura 4.1: Disposição dos dados da malha dentro das duas texturas. As primeiras três linhas armazenam as posições dos vértices e correspondem a primeira textura (T1). As linhas 4 e 5 possuem os índices dos tetraedros adjacentes e estão numa segunda textura (T2), junto com os dados do vetor gradiente do tetraedro e do valor escalar. t u e t v correspondem as coordenadas de textura. Além dos dados da malha, é preciso armazenar dados sobre a atual situação dos raios sendo avaliados e suas respectivas contribuições de cor. Para isso, existem dois conjuntos compostos por duas texturas cada que são utilizados como buffers intermediários. A primeira textura de cada conjunto armazena o índice, o valor do ponto de entrada e o campo escalar do tetraedro atual. A segunda textura armazena as cores resultantes do processo de integração do raio até o presente tetraedro (figura 4.2). A utilização de dois conjuntos de textura é a solução encontrada para não ler e escrever em uma mesma textura, uma limitação presente nas GPU s atuais. A dimensão de cada uma dessas texturas corresponde diretamente ao tamanho da janela onde o resultado será exibido (equação 4.6).

28 Figura 4.2: Disposição dos valores nas texturas intermediárias. t u e t v possuem o índice do tetraedro corrente, λ corresponde a distância percorrida e S(e+λr) é o valor do campo escalar no ponto de entrada do tetraedro. RGBA é a cor e opacidade acumuladas até a presente iteração. size = largura altura 4 canais 4B (4.6) 4.2 Intersecção dos Raios com a Malha Para permitir o avanço dos raios dentro da malha e o acúmulo de cor e opacidade, é preciso calcular os pontos de entrada e saída de cada raio no tetraedro em que se encontra. O cálculo de intersecção dos raios com a malha dentro do processador de fragmentos é o mesmo descrito na seção 3.2.1. A forma como os dados estão armazenados na memória da placa de vídeo (como explicado na seção anterior) impede o descarte a priori da face de entrada do raio. Por isso todos os possíveis pontos de intersecção do raio com o tetraedro devem ser calculados e apenas o ponto de saída considerado. Outro problema resultante do armazenamento dos dados na memória da placa de vídeo são as transformações geométricas. Como esses dados são carregados na inicialização do programa, as alterações sofridas pela geometria da malha (i.e., rotações, translações e escalas) não são propagadas para o objeto já codificado. Assim, não é possível provocar alterações no Sistema de Coordenadas do Objeto (OCS - Object Coordinate System). Para possibilitar a interação do objeto sendo visualizado com o usuário, é feita a alteração da câmera sintética no Sistema de Coordenadas do Universo (WCS - World Coordinate System). A alteração da câmera nesse sistema permite ao observador adquirir imagens da malha sob qualquer ângulo. Todavia, a liberdade de movimento da câmera afeta a forma como os raios devem ser propagados, pois estes são criados usando o Sistema de Coordenadas da Câmera (CCS - Camera Coordinate System). A primeira parte do algoritmo utiliza os raios gerados no CCS, pois as faces de fronteira da malha podem sofrer as transformações geométricas necessárias. O mesmo acontece na geração dos diferentes níveis de depth peeling (explicados na seção 4.3). Porém, ao iniciarem as diversas iterações do ray casting, esses raios seriam processados junto com uma estrutura que não foi afetada pelas transformações, gerando um resultado incorreto. Para resolver esse problema, aplica-se transformações aos raios que correspondem às inversas das transformações aplicadas inicialmente nas faces de fronteira do modelo (figura 4.3). Agora os raios podem ser corretamente computados contra as faces armazenadas na memória pertencente a GPU, pois encontram-se no OCS.

29 Figura 4.3: "A"representa o sistema de coordenadas no qual os vértices da malha estão definidos. "B"mostra uma rotação do objeto, colocando-o na posição desejada do mundo. "C"apresenta as correções que devem ser aplicadas aos raios para o correto cálculo de intersecção. 4.3 Depth Peeling A condição de término no processamento de um raio é que este saia da malha. Se o objeto, porém, for não-convexo (como exemplificado na figura 4.4), diversos raios deveriam reentrar na malha após terminar uma primeira camada de processamento. Esse problema tem sido classicamente resolvido através de uma convexificação da malha. Todavia, tal abordagem gera uma sobrecarga de processamento e espaço de armazenamento, além de não existir uma solução simples para realizar esse procedimento (COMBA; MITCHELL; SILVA, 2003). Geralmente a convexificação ocorre manualmente, sendo um processo lento e dispendioso. Como o processamento do HRC ocorre inteiramente dentro da placa de vídeo utilizando imagens como ponto inicial de execução, pode-se utilizar a abordagem de depth peeling (EVERITT, 2001) para capturar as reentrâncias da malha. Em uma etapa de pré-processamento quando os dados estão sendo carregados, as faces de fronteira da malha são separadas. No início da geração de cada quadro, essas faces são rasterizadas para inicializar os pontos onde serão gerados os raios e para gerar diversos níveis de reentradas (figura 4.4). A primeira camada do depth peeling corresponde a fronteira visível da malha. As demais são geradas desconsiderando os níveis até o recém desenhado, criando uma seqüência de imagens que capturam todas as áreas onde os raios devem prosseguir seu processamento após uma detecção de término (figura 4.5). Quando não houver mais níveis de depth peeling para serem processados e o raio detectar que saiu da malha, ele terminará sua execução. Essa abordagem permite um melhor aproveitamento do espaço de armazenamento e do processamento, pois não precisa-se armazenar tetraedros vazios nem os processar.

30 Figura 4.4: Geração dos níveis de depth peeling. Além disso, o tempo gasto para gerar os níveis necessários é menor que o tempo que seria gasto para percorrer os tetraedros nulos. Figura 4.5: Diferentes níveis de depth peeling na primeira linha (níveis 0, 1 e 2, respectivamente) com seus respectivos resultados na segunda linha. 4.4 Subdivisão da Tela Para reduzir a quantidade de fragmentos que são gerados durante as diversas iterações do algoritmo é feita uma subdivisão da tela. Essa subdivisão objetiva tirar proveito da coerência espacial que existe no espaço da imagem sendo processada. Quando um determinado raio termina seu processamento e sai da malha, existe uma grande probabilidade de seus vizinhos também terminarem seus processos junto ou em um espaço de tempo curto. Ao detectar que em uma determinada região não ocorre mais processamento, o programa pára de desenhar naquela área, reduzindo assim a quantidade de fragmentos sendo gerada. Para detectar o fim do processamento dentro de uma área, o programa utiliza occlusion queries para realizar a contagem dos fragmentos que estão escrevendo seu resultado no render target. Toda vez que um determinado número de passadas do ray casting é

31 executado, o programa inicia o uso dessas OQ s, e verifica se o resultado está disponível durante cada iteração. Pela implementação assíncrona e não-bloqueante das OQ s, o programa não fica esperando o retorno do resultado, ele apenas o utiliza quando estiver disponível. A espera pelo resultado bloquearia a execução do programa e reduziria sua performance. Figura 4.6: Exemplos de diferentes subdivisões de tela possíveis. A quantidade ótima de subdivisões da tela varia de acordo com a topologia da malha a ser visualizada. Por isso, diversas configurações podem ser utilizadas, e mesmo alteradas, durante a execução. Algumas dessas configurações podem ser vistas na figura 4.6. 4.5 Resultados O programa foi testado em um computador Intel Pentium 4 de 2.8 GHz HT 1 com 1 GB de memória RAM e uma placa de vídeo NVidia GeForce 6800 GT com 256 MB de memória RAM e barramento AGP 8X. O programa foi desenvolvido usando a linguagem C++ e a API gráfica Microsoft DirectX 9.0c Update February 2005, onde os programas de vértices e de fragmentos foram escritos na linguagem HLSL (High Level Shading Language), presente nessa API. O driver de vídeo utilizado foi o ForceWare 71.89. O programa foi testado com três diferentes malhas, cada uma com diferentes quantidades de tetraedros. Elas foram testadas com diversas combinações de subdivisão da tela, e utilizando funções de transferência 3D. Os resultados podem ser vistos nas tabelas 4.1. A tabela 4.2 mostra os resultados obtidos com diversas configurações de subdivisão da tela. Malha Tetraedros FPS Min. FPS Max. Tetra/s Min. Tetra/s Max. SPX1 103 mil 1,64 3,37 169 mil 350 mil BLUNT 187 mil 0,91 10,63 173 mil 1,99 milhão F117 240 mil 1,20 6,41 288 mil 1,54 milhão Tabela 4.1: Resultados da visualização utilizando Ray Casting. A remoção do armazenamento das normais causou uma reação interessante no programa. Para malhas pequenas, a geração de um quadro passou a levar alguns milisegundos a mais, enquanto que para malhas grandes o tempo total diminuiu alguns milisegundos. Isso é creditado ao armazenamento dos dados na memória cache do chip. A reconstrução precisa de operações extras no processador de fragmentos, e esse fato sozinho deveria gerar um aumento no tempo total de execução, como acontece nas malhas pequenas. Porém, 1 Hyper Threading, tecnologia que permite a uma CPU tratar threads a nível de hardware. Apesar de presente, essa tecnologia não foi utilizada neste trabalho.

32 Malha 1x1 2x2 3x3 4x4 5x5 6x6 Spx1 297-516 312-516 312-515 297-500 313-547 329-609 Blunt 94-1203 94-1047 94-1062 94-1031 109-1032 109-1047 F117 156-484 157-453 157-453 156-437 172-438 172-469 Tabela 4.2: Tempos de visualização usando uma função de transferência 3D (ms). em malhas grandes, existe uma maior probabilidade de um acesso para recuperar a normal não encontrar a informação necessária na cache, precisando acessar a memória principal da placa. Esse acesso mostrou-se mais lento que o tempo gasto para reconstruir a normal. No caso de malhas pequenas, a chance dos dados encontrarem-se na cache é maior, e um acesso a cache mostrou-se mais rápido que a reconstrução. Devido a isso, a reconstrução da normal mostrou-se mais eficiente quando trabalhando com malhas grandes e menos eficiente para malhas pequenas.

33 5 COMPRESSÃO DE CAMPOS ESCALARES DINÂMICOS Este trabalho já mostrou como é possível realizar a visualização volumétrica de malhas de tetraedros estáticas utilizando a GPU. Adicionar dados com variação temporal implica num grande aumento na quantidade de informação. Por isso, este capítulo descreve como podemos utilizar uma técnica de compressão para reduzir a quantidade de dados que está trafegando no sistema. Para um correto entendimento do processo de visualização dos dados dinâmicos é necessário entender o problema inerente a seu uso e o método escolhido para tratá-lo. Trabalhar com dados que guardam informação temporal exige um enorme espaço de armazenamento, altas taxas de transferência e poder de processamento. A utilização de técnicas de compressão pode reduzir significativamente o espaço de armazenamento e a taxa de transferência dos dados. Porém a descompressão da técnica deve ser rápida o bastante para lidar com a grande quantidade de dados sem sobrecarregar o processamento do algoritmo de visualização. Em uma malha de tetraedros, tanto a geometria da malha quanto atributos dos vértices podem variar de acordo com o tempo. Este trabalho considera malhas cuja informação com variação temporal é o campo escalar dos vértices, sendo essa a forma mais comum de variação temporal em malhas não-estruturadas. Neste caso um dado vértice v possui n valores escalares associados, correspondendo a n instâncias de tempo. A técnica escolhida para comprimir os dados foi a quantização vetorial, aplicada sobre vetores decompostos hierarquicamente, como será explicado a seguir. 5.1 Decomposição Hierárquica de Dados Para fins de explicação, será considerada uma malha não-estruturada composta por m vértices e n instâncias de tempo, sendo n uma potência de b por simplicidade (b h 1 = n). Cada vértice da malha possui um vetor V R n com os valores escalares dos diferentes tempos, que será decomposto em h níveis de hierarquia. I é um vetor de dimensão h-1 que contém os índices para acessar as tabelas de códigos geradas pela técnica de quantização (que será explicada na próxima seção), e cada vetor correspondente a uma linha dessa tabela é denotado por Y. Divide-se V em b h 2 subvetores compostos de b elementos cada. A média dos b elementos de cada subvetor é utilizada para gerar um novo vetor de dimensão b h 2. Este processo é repetido iterativamente até a obtenção de um vetor unidimensional com a média de todos os valores do vetor inicial V. Para todos os h vetores gerados, com exceção do vetor unitário, é gerado um vetor D que armazena a diferença entre o valor calculado para o seu nível e a soma de todos os níveis inferiores, sendo que estes já foram processados. Esse vetor é calculado segunda a

34 Figura 5.1: Reconstrução de um vetor decomposto em três níveis. O nível 0 possui a Média (m) dos tempos, e os demais níveis possuem vetores de diferenças (D). fórmula i 1 D i = V i (V 0 + j=1 onde i {1..h 1}. Cada elemento de D será representado por i 1 d k,i = v k,i (v 0 + j=1 D j ) (5.1) d k b j,i 1) (5.2) com k { 0..b i}. Esse processo descrito acima é realizado para cada um dos m vetores associados aos vértices da malha. Os m vetores de diferenças em um dado nível serão quantizados, gerando h-1 tabelas de códigos com os vetores representativos deste conjunto (Y). Este processo será explicado na próxima seção. Para aproximar o valor original de cada componente dos vetores, deve-se somar os diferentes níveis da hierarquia, h 1 ˆV = V 0 + e cada componente de V é expresso por i=0 h 1 ˆv i = v 0 + j=0 A figura 5.1 exemplifica a reconstrução de um vetor. 5.2 Quantização Vetorial Y i,i[i] (5.3) y j, i b j (5.4) A quantização vetorial é uma técnica de compressão de dados que utiliza o fato das variações nos dados serem, muitas vezes, similares para aproximar seus valores originais. Através da análise de um conjunto de valores semelhantes, são selecionados aqueles que possuem a maior representatividade no seu grupo.

35 Considera-se novamente a notação descrita na seção anterior. Com a aplicação da decomposição hierárquica nos dados, agora existem h-1 conjuntos de m vetores de diferenças. Para cada um desses conjuntos, será aplicada a técnica de quantização vetorial a fim de obter um conjunto de c elementos mais representativos. A métrica usada para a medida da distorção δ entre dois elementos é a distância quadrada entre eles ( δ(x,y) = (x y) 2 ). Para calcular o valor inicial da tabela de códigos é utilizada uma técnica de subdivisão dos grupos de vetores baseada numa Análise de Componentes Principais (PCA - Principal Component Analysis). Inicialmente, todos os vetores de um conjunto são dispostos dentro de uma única célula de quantização (I). Calcula-se o centróide e a distorção δ do grupo. A seguir aplica-se o seguinte algoritmo: 1. escolha o grupo com a maior distorção (grupo j); 2. calcule a matriz de auto-covariância M = i I j ( Vi Y j ) ( Xi Y j ) t; 3. calcule o autovetor e max correspondente ao maior autovalor λ max de M; 4. divida o grupo j em 2 subgrupos, segundo a convenção: I j,1 = { i I j, ( Y j X i ),emax < 0 } I j,2 = { i I j, ( Y j X i ),emax 0 } 5. calcule os centróides Y j,1 e Y j,2 junto de novas distorções δ j,1 e δ j,2 ; 6. insira os dois grupos na lista junto aos grupos já existentes; 7. se a quantidade de entradas na tabela ainda não foi alcançada, repita a partir do ítem 1, senão pare. Quando o processo de escolha dos valores iniciais da tabela de código termina, iniciase a etapa de refinamento dos valores através de uma versão modificada do algoritmo de LBG 1 (LINDE; BUZO; GRAY, 1980). A principal modificação restringe a área de busca aos vizinhos da célula que contém o vetor representativo, pois se ocorrer uma migração da valor possivelmente será para um dos vizinhos. Esse processo é repetido até a obtenção de uma das seguintes condições: não ocorre ganho na distorção; o valor cruza um raio de busca definido; a distorção gerada é menor que um valor estabelecido pelo usuário. 5.3 Código da Compressão O código da compressão utilizado foi escrito originalmente por Jens Schneider para um trabalho sobre a compressão de dados estruturados (SCHNEIDER; WESTERMANN, 2003). A utilização de vetores com 64 elementos resulta da compressão dos volumes estruturados, onde uma textura tridimensional era dividida em blocos de dimensões 4 4 4, ou 1 iniciais dos autores do algoritmo (Linde, Buzo, Gray).

36 Figura 5.2: Subdivisão de um conjunto em seus componentes principais, utilizada para gerar os grupos da quantização. seja, com 64 valores. Esses blocos aproveitavam a coerência espacial para não perderem muita qualidade durante a compressão, permitindo bons resultados de descompressão. Este trabalho utiliza a coerência temporal dos dados não-estruturados, e o uso de vetores de 64 elementos vêm da utilização quase direta do código de compressão para volumes estruturados (algumas alterações foram realizadas para o código poder ser utilizado, porém nenhuma alterou a forma como os dados são processados).

37 6 HARDWARE RAY CASTING COM DADOS DINÂMICOS O algoritmo de visualização de campos escalares dinâmicos usando ray casting é uma extensão à visualização de dados estáticos (capítulo 4). A técnica realiza a descompressão dos dados (capítulo 5) no processador de fragmentos, possuindo uma etapa extra de reconstrução da informação dos escalares e cálculo do gradiente. A estrutura de armazenamento dos dados da malha foi estendida para comportar as novas necessidades do algoritmo. Também foi implementado um sistema de gerenciamento de conjuntos de tempos, para possibilitar ao programa trabalhar com uma quantidade arbitrária de instâncias de tempo. 6.1 Estrutura de Dados A adição das estruturas necessárias para reconstruir a informação dos escalares provocou alterações na forma como a malha é armazenada nas texturas. O vetor gradiente do campo escalar não pode mais ser diretamente armazenado (seção 3.2.2), pois essa informação está variando com o tempo. Para permitir o cálculo desse vetor, a representação passou a armazenar a inversa da matriz de geração do campo escalar. Tal decisão deve-se ao fato de ser extremamente custoso do ponto de vista computacional inverter a matriz dentro do processador de fragmentos. Por isso optou-se por armazenar essa informação nas texturas e apenas recuperá-la quando ela fosse necessária. Todos esses dados são estáticos, e necessitam ser carregados uma única vez na inicialização do programa. Outra informação que também precisa ser armazenada é a média dos valores escalares ao longo do tempo, juntamente com os índices de acesso às tabelas de códigos, resultantes do processo de compressão. Cada vértice de um tetraedro possui um conjunto de dois índices e uma média. Esses valores precisam ser alterados quando o programa troca um conjunto de tempos, como será explicado na próxima seção. Além dos dados que compõe a informação da malha, deve-se armazenar as tabelas de códigos utilizadas para reconstruir a informação dos escalares. Essas tabelas resultam do processo de quantização, e suas dimensões estão diretamente relacionadas ao processo de compressão. No estudo de caso que será detalhado a seguir, foram utilizadas uma tabela de dimensões 64 x 256 e uma tabela de dimensões 8 x 256 para cada conjunto de 64 instâncias de tempo.

38 Figura 6.1: Estrutura de armazenamento da malha para dados dinâmicos. Cada três posições consecutivas de uma textura armazenam dados de um mesmo tetraedro. Na primeira textura (T1), encontram-se os quatro vértices, na segunda textura (T2) estão os índices dos tetraedros adjacentes e a primeira linha da matriz de cálculo do vetor gradiente. Na terceira textura (T3) estão as demais linhas da matriz. Uma quarta textura (T4) armazena a média e os índices para a reconstrução do valor escalar. Esta última textura precisa ser alterada sempre que muda o conjunto de instâncias de tempo. 6.2 Compressão e Descompressão dos Dados A etapa de compressão dos dados temporais é realizada na CPU do computador, pois corresponde a uma fase de pré-processamento, não sendo crítica a compressão em temporeal. Para fins de quantização, as diversas instâncias de tempo foram divididas em conjuntos de 64 elementos, como explicado na seção 5.3. Dessa forma, as 64 primeiras instâncias de tempo compõe um grupo a ser quantizado, as próximas 64 instâncias formam outro grupo e assim sucessivamente. Caso o total de instâncias de tempo não seja um múltiplo de 64, o último conjunto terá replicada a última instância até completar o conjunto. Cada um desses conjuntos foi individualmente submetido ao processo de decomposição e quantização, resultando em um conjunto com a média para cada vetor de entrada e duas tabelas de diferenças, uma com vetores em R 64 e outra com vetores em R 8. A descompressão dos dados é realizada no processador de fragmentos. Para cada um dos vértices da malha é recuperada a média e as duas diferenças que serão usadas para reconstruir o valor escalar do vértice. Esses valores foram relacionados durante o processo de quantização, e para cada conjunto de 64 instâncias de tempo, tem-se um grupo de médias e índices e duas tabelas de diferenças representativas. A soma da média com uma entrada de cada tabela resulta em uma aproximação ao valor original. O índice i 8 é utilizado como valor para a coordenada v da textura que armazena a tabela de 8 valores por linha. O índice i 64 corresponde ao valor da coordenada v para a

39 textura com a tabela de 64 valores. Ambos os valores devem estar normalizados entre 0 e 1, e na implementação, assumem um dentre 256 valores possíveis. A coordenada u de ambas as texturas corresponde a instância de tempo que deseja-se visualizar. O valor é o mesmo para referenciar as duas tabelas, pois este valor também estará normalizado entre 0 e 1 assumindo um de 64 valores possíveis. A figura 6.2 mostra como os índices são combinados com o valor do tempo para acessar as tabelas de códigos. Figura 6.2: Acesso às tabelas de códigos, cada uma correspondendo a uma textura. 6.3 Gerenciamento de Instâncias de Tempo A utilização da técnica de compressão proposta neste trabalho permite a utilização de 64 instantes de tempo diferentes. Porém, para uma malha real, a quantidade de tempos que fazem parte de uma animação é arbitrária, e na maioria das vezes superior a esse valor. Por isso foi desenvolvido um sistema de gerenciamento que permite trabalhar com uma quantidade arbitrária de instâncias de tempo. O problema com uma simples troca do conjunto de dados ao terminar de visualizar 64 diferentes instâncias é o tempo necessário para carregar os novos dados, geralmente provocando uma diminuição de performance no processo de visualização. Isso pode causar desconforto ao usuário, especialmente se essa perda de performance ocorrer constantemente. Como solução a esse problema, guardam-se dois conjuntos de tempos simultaneamente na placa de vídeo. Eles são inicialmente carregados quando o programa inicia. Ao terminar o primeiro conjunto, o programa passa a consultar o segundo durante o processo

40 de reconstrução. O próximo conjunto de dados é carregado enquanto não é necessário, utilizando o tempo ocioso para realizar a transferência para a memória da placa vídeo (figura 6.3). Oculta-se assim o tempo de troca, pois o processo de visualização não precisa esperar a carga dos novos dados. Figura 6.3: Gerenciamento dos conjuntos das instâncias de tempo (CT). É assumido que o usuário nunca irá retroceder a visualização. Para permitir controle total da animação, três conjuntos deveriam ser armazenados e atualizados, ao invés de apenas dois: o conjunto atual, o imediatamente anterior e o posterior. 6.4 Visão Geral do Programa Nesta seção serão explicadas em ordem as etapas realizadas pelo programa para gerar um quadro da animação. Primeiramente o programa carrega de arquivos no disco os dados da malha, da função de transferência e dos dados resultantes da quantização. Isso é feito uma única vez na inicialização do programa, mantendo os dados necessários em memória. Os dados estáticos da malha já são carregados na memória de vídeo, assim como os dados dinâmicos para iniciar a visualização. A seguir, o programa entra no processo de geração dos quadros. A primeira parte do algoritmo atualiza o índice do tempo, e verifica se existe a necessidade de iniciar o processo de carga de um novo conjunto de tempos na textura que não está mais sendo utilizada (figura 6.4). A seguir o programa gera a primeira intersecção dos raios com a malha. Para isso são desenhadas as faces de fronteira da malha, possuindo como coordenada de textura o índice do seu tetraedro. Nesta parte do algoritmo, o teste de profundidade está habilitado (usando o Z-buffer com a função "menor que"). Assim, apenas as faces visíveis ao observador tem seu resultado propagado. Para uma correta inicialização das estruturas transversais (texturas intermediárias), é necessário o cálculo do parâmetro de intersecção λ e o valor do campo escalar no ponto de entrada do raio. Ainda nesta etapa são gerados os níveis de depth peeling, que são utilizados para capturar as não-convexividades da malha. Para cada um dos níveis que serão gerados, desenha-se novamente as faces de fronteira da malha. Para eliminar os níveis já gerados,

41 Figura 6.4: Verificação do estado de troca dos conjuntos de tempos. utiliza-se o último nível criado como limiar para a aceitação de um fragmento sendo processado. O fragmento apenas será escrito caso seu λ seja maior que o λ correspondente gerado no último nível. Esse processo de geração dos níveis de depth peeling pode ser automatizado, utilizando occlusion queries para determinar quando todas as re-entradas necessárias para os raios já foram processadas pelos níveis. Atualmente o código utiliza uma quantidade pré-estabelecida de níveis a gerar, suficientes para tratar o pior casa da malha mais complexa que é utilizada (figura 6.5). O processo de identificar o pior caso é manual. A próxima etapa do algoritmo é o ray casting propriamente dito. Como a pouco tempo atrás as GPU s não possuíam instruções de controle de fluxo, o programa realiza as diversas iterações de travessia dos raios em passadas. Cada passada corresponde ao desenho de retângulos alinhados ao plano de imagem, cobrindo todos os pontos da tela. Esse procedimento gera os raios utilizados para adentrar a malha, e cada raio avança um tetraedro por passada. Apenas recentemente as GPU s começaram a permitir o uso de instruções de controle de fluxo em seus programas, e essas instruções ainda executam de forma pouco otimizada e eficiente. Se um bom conjunto de operações de controle de fluxo estivesse presente, as diversas iterações de um nível de depth peeling poderiam ser realizadas dentro de um simples laço no processamento. Para monitorar quando todos os raios terminaram sua execução, o programa dispara uma contagem de quantos fragmentos irão escrever seu resultado na imagem final, utilizando para isso as occlusion queries. Esse procedimento é realizado a uma taxa fixa de k passos, pois precisa de um programa especial que controla a escrita dos valores no Z-buffer. Como essas OQ s possuem o retorno de seu resultado de forma assimétrica, o programa não fica esperando o retorno do resultado, mas apenas verifica a cada iteração se o resultado está disponível. Caso afirmativo, seu valor é utilizado. Em caso negativo, o programa segue seu processamento e na passada seguinte verifica a OQ novamente. Existe uma OQ para cada subdivisão da tela, e caso o programa detecte que em uma determinada área nenhum fragmento está escrevendo seu resultado, ele pára de desenhar o retângulo correspondente àquela área, economizando a geração dos fragmentos. Além disso, é utilizada uma funcionalidade presente nas recentes GPU s chamada de early-z

42 Figura 6.5: Procedimento de geração das diversas camadas de depth peeling. test, que descarta o processamento dos fragmentos que estão atrás dos já escritos. Para isso, após uma determinada quantidade de passadas, o programa realiza uma atualização do z-buffer para alterar a informação de profundidade dos fragmentos que já terminaram seu processamento, movendo para seu valor de profundidade para o near plane. A troca para um próximo nível de depth peeling ocorre apenas quando todos os fragmentos de todas as subdivisões terminaram seu processamento (figura 6.6). Por fim, depois que todos os níveis de depth peeling foram processados, o programa está pronto para exibir o resultado. É desenhado um único retângulo com a mesma dimensão da janela de visualização, e a cor final acumulada durante o ray casting é copiada para o frame buffer (figura 6.7). Neste ponto as ações do usuário são interpretadas, como uma mudança na posição da câmera ou a saída do programa, por exemplo. A maior parte do processamento do algoritmo é realizada dentro da GPU, e seu desempenho está diretamente relacionado a velocidade com que o processador de fragmentos consegue trabalhar (isto é, processar os dados e recuperar a informação necessária da memória). Esse ponto do pipeline gráfico que concentra o gargalo do algoritmo possui a vantagem de ser altamente paralelizável. Os processadores gráficos atuais contam com 16 unidades de processamento paralelo para fragmentos (GeForce 6800 Ultra), e na próxima geração deverão suportar 24 ou mais fluxos simultâneos (GeForce 7800 GTX). 6.5 Resultados O programa foi desenvolvido e testado em um computador com a mesma configuração descrita na seção 4.5. Diversas malhas de tetraedros foram utilizadas para validar a técnica de compressão e visualização, cada uma com diferentes quantidades de tetraedros. A tabela 6.1 enumera as malhas utilizadas junto da respectiva quantidade de vértices, tetraedros e instâncias de tempo.

43 Figura 6.6: Iterações do ray casting. As quatro primeiras malhas (SPX, SPX1, SPX2 e BLUNT) possuem dados escalares gerados proceduralmente, através de uma interpolação linear do valor escalar presente em uma instância de tempo qualquer até atingir zero em 64 iterações. As demais malhas (TORSO e BRAIN) apresentam dados reais. O processo de decomposição e quantização foi aplicado sobre os vértices de cada malha, gerando duas tabelas de códigos para cada seqüência de 64 instâncias de tempo. A tabela 6.2 apresenta os resultados da compressão. O código calcula a Relação Sinal- Ruído (RSR) que a técnica gera. Além disso, foi medido a maior discrepância entre um valor original e um reconstruído, resultando em um erro máximo apresentado pela malha. Nota-se que as malhas geradas proceduralmente apresentam os melhores resultados, pois a variação nos dados é uniforme ao longo do tempo. Mas mesmo uma malha com Malha Vértices Tetraedros Instâncias de Tempo SPX 19K 12K 64 SPX1 36K 101K 64 SPX2 162K 808K 64 BLUNT 40K 183K 64 TORSO 8K 50K 360 BRAIN 68K 387K 120 Tabela 6.1: Malhas utilizadas na visualização

44 Figura 6.7: Verificação do término do programa. Malha Tamanho Tamanho Taxa de RSR RSR Erro Original Comprimido Compressão Mínimo Máximo Máximo SPX 4.75M 300K 16.21 39.44dB 42.08dB 0.0041 SPX1 9.00M 504K 18.29 39.45dB 41.96dB 0.0045 SPX2 40.50M 1.97M 20.57 39.24dB 41.88dB 0.0091 BLUNT 10.00M 552K 18.55 41.70dB 44.36dB 0.0046 TORSO 11.25M 1008K 11.43 20.53dB 28.12dB 0.0017 BRAIN 31.87M 1.73M 18.38 2.96dB 10.24dB 1.0632 Tabela 6.2: Resultados da Compressão dados reais (TORSO) apresentou um erro final muito baixo, demonstrando a capacidade da técnica em ser aplicada na prática. A malha BRAIN poderia ter seu erro reduzido caso fossem utilizadas tabelas de códigos com dimensões maiores que as adotadas, possibilitando o armazenamento de uma maior quantidade de valores representativos. Todas as tabelas usadas possuíam dimensões 64 X 256 e 8 X 256 (256 valores representativos), facilitando a comparação dos resultados. O cálculo do espaço necessário para armazenar os dados temporais sem compressão é size m = v t 4B (6.1) onde v representa a quantidade de vértices da malha e t a quantidade de tetraedros, e cada valor escalar ocupa 4 bytes. O espaço necessário para armazenar os dados comprimidos é size qv = v c 3 4B + c 72KB (6.2) onde c representa a quantidade de conjuntos de tabelas de códigos utilizadas (uma tabela de 8 e uma de 64 correspondem a um conjunto) e é calculada por c = t/64, cada vértice utiliza 3 valores por conjunto de tabelas (a média do vetor e dois índices, um para cada tabela), e cada conjunto de tabelas utiliza 72KB (256 64 4B + 256 8 4B). O tempo total necessário para gerar um quadro da animação reconstruindo os valores é apresentado na tabela 6.3. A visualizaçao dos dados dinâmicos é mais lenta que a simples visualização da malha, pois o programa deve reconstruir os valores escalares do tetraedro e calcular o gradiente dentro do processador de fragmentos, procedimentos

45 realizados numa etapa de pré-processamento para os dados estáticos. A malha SPX é uma exceção a essa regra, provavelmente devido ao retorno assíncrono das occlusion queries que determinam a finalização da geração da imagem. Malha Tempo Min. Tempo Max. Tempo Min. Tempo Max. Estático(ms) Estático(ms) Dinâmico(ms) Dinâmico(ms) SPX 156 265 203 235 SPX1 297 500 406 672 BLUNT 94 1062 125 1125 Tabela 6.3: Resultados da visualização do Ray Casting com dados dinâmicos. Uma abordagem diferente foi considerada para remover a necessidade de calcular o gradiente no processador de fragmentos. Foram realizados testes de quantização do vetor gradiente pré-calculado. Porém isso restringe o uso da coerência temporal, utilizada para justificar a quantização dos dados ao longo do tempo, pois os componentes do vetor gradiente não conseguem usufruir dessa coerência nem da coerência espacial. Os resultados dos testes foram muito inferiores aos obtidos quantizando os valores escalares diretamente, e essa abordagem acabou sendo descartada. Figura 6.8: Variação do campo escalar da malha SPX visualizada com ray casting em 9 diferentes instantes de tempo.

46 7 EXTENSÕES A decomposição hierárquica seguida da quantização vetorial pode ser utilizada em conjunto com outras técnicas de visualização de malhas não-estruturadas, proporcionando ganho no espaço de armazenamento desses dados. A seguir, será explicado como essa técnica foi estendida para trabalhar com um algoritmo de visualização baseado em projeção de células. 7.1 Implementação Usando HAVS HAVS é a sigla para Hardware-Assisted Visibility Sorting (Ordenação de Visibilidade Auxiliada por Hardware). Este algoritmo realiza uma ordenação das faces dos tetraedros baseado no centróide de cada face, com respeito a distância do observador. Isso não garante uma completa ordenação no espaço do objeto se a malha não for uma malha de Delaunay, pois ela pode conter faces de tamanhos variáveis e mesmo ciclos de visualização. Após a rasterização, os fragmentos realizam uma etapa de ordenação no espaço de imagem dentro do processador de fragmentos, numa abordagem chamada de k-buffer, como pode ser visto na figura 7.1 (CALLAHAN et al., 2005). Se o k-buffer possuir uma resolução suficiente para capturar o maior erro na ordenação parcial feita na CPU, o resultado estará completamente correto. Caso contrário, pequenos artefatos podem aparecer na imagem. O k-buffer é implementado usando o processador de fragmentos e mantém um número fixo de fragmentos (k) em cada pixel do render target. Quando um novo fragmento é gerado, ele é comparado com os outros armazenados no k-buffer. As duas entradas mais próximas ao ponto de visão (considerando um desenho de frente para trás) são usadas para encontrar a cor e a opacidade utilizando uma função de transferência. A cor é composta no render target e os fragmentos restantes são escritos novamente no k-buffer. 7.1.1 Dados Dinâmicos Para adicionar os dados com variação temporal no HAVS foi necessário desenvolver três diferentes abordagens, pois as duas primeiras não foram bem sucedidas. A primeira tentativa de incorporar a visualização de dados dinâmicos nesse algoritmo foi armazenar as tabelas de código em texturas e reconstruir os valores escalares no processador de vértices, utilizando para isso o Shader Model 1 3.0 presente na placa de vídeo GeForce 6800, que permite o acesso a texturas dentro do processador de vértices. Esta abordagem não apresentou bons resultados e mostrou-se consideravelmente lenta, pois a quantidade de operações realizadas neste estágio são insuficientes para compensar a 1 especificação de operações que devem ser suportadas nos processadores de vértice e fragmento.

47 Figura 7.1: Divisão do processamento entre a CPU e a GPU no HAVS. latência necessária para carregar os dados das texturas. Uma segunda abordagem considerava o uso do processador de fragmentos para reconstruir a informação dos escalares. Como o programa desenha as faces da malha, seria preciso recuperar a média e os dois índices para cada vértice da face (utilizando ao menos 9 acessos adicionais a texturas). Essa informação varia por face, implicando no envio constante de informação para o processador de fragmentos, difícil de ser feito senão como um argumento do programa de fragmentos, o que pode ser lento se alterado para cada face. Por fim foi optado por realizar a descompressão dos dados na CPU, e informar os valores reconstruídos como coordenadas de texturas dos vértices. As tabelas de código são armazenadas na memória principal do sistema, e um simples mecanismo de paginação permite a visualização de múltiplas instâncias de tempo. 7.1.2 Resultados malha Tempo Min. Tempo Max. Tempo Min. Tempo Max. Estático (ms) Estático (ms) Dinâmico (ms) Dinâmico (ms) SPX 31 47 31 47 SPX1 109 125 110 125 SPX2 703 813 1016 1157 BLUNT 156 312 218 266 TORSO 62 79 62 79 BRAIN 438 500 578 625 Tabela 7.1: Resultados da visualização usando HAVS. Resultados foram obtidos executando o programa em uma quantidade fixa de orientações de visualização. Tempos de geração das imagens foram próximos aos necessários

48 para visualizar dados estáticos em volumes pequenos, enquanto que em volumes grandes o tempo exigido chegou a aumentar em até 40%, como pode ser visto na tabela 7.1. O programa foi desenvolvido e testado em um computador com a mesma configuração descrita na seção 4.5, porém utilizando a API gráfica OpenGL ao invés de DirectX, pois a implementação original desse método utilizava essa API. Figura 7.2: Malha BLUNT visualizada com HAVS. 7.2 Outras Extensões Futuramente podem ser implementadas extensões para permitir a geração automatizada dos parâmetros da quantização vetorial baseadas em propriedades da malha, como a homogeneidade dos campos escalares para determinar as dimensões das tabelas de códigos. Um tipo diferente de dado com variação temporal que também pode ser explorado são as malhas com geometria dinâmica. Neste caso, seria útil uma técnica que aproveitasse o uso de coerência espacial e temporal para a compressão dos dados.

49 Figura 7.3: Malha TORSO visualizada com HAVS.