Raytracer OSL usando a estrutura BRL-CAD

Junho 26, 2011

Faz algum tempo que eu não posto sobre meu projeto do BRL-CAD, mas é porque eu não vinha tendo nenhum resultado interessante para mostrar. A maior parte do tempo eu gastei resolvendo aspectos mais técnicos de programação, sobre os quais escrevi nas últimas semanas.

Interface OSL BRL-CAD

Comecei escrevendo uma interface para o renderizador OSL. Essa interface recebe os dados necessários sobre o ponto sendo renderizado. Esses dados incluem: as coordenadas do ponto P sendo renderizado, o nome do shader da superfície a qual P pertence, a normal dessa superfície em P e a direção do raio de incidência em P.

Essa modularização permite que o modo como o renderizador OSL calcula a cor nesse ponto seja transparente à aplicação. Por outro lado, o renderizador não precisa saber como os objetos da cena são manipulados e como as interseções são calculadas.

Um problema é que o renderizador OSL pode decidir que o raio será refletido (por exemplo se o shader não for totalmente opaco). Como ele não sabe nada sobre a cena sendo renderizada, ele precisa devolver o trabalho para a aplicação. Por isso, ele retorna uma estrutura dizendo se houve reflexão e qual a direção do raio refletido.

Com esse novo raio, a aplicação fará seus cálculos e chamará a interface novamente.

Testando a interface

Antes de partir para a implementação do shader osl, decidi testar a nova interface com uma adaptação daquele raytracer sobre o qual falei em um post anterior.

Usando a mesma cena, consegui reproduzir as mesmas imagens.

Conversando com um dos meus mentores, me foi sugerido então tentar renderizar uma cena modelada no BRL-CAD usando apenas shaders osl.

Cornell box na interface do mged.

A cena é conhecida por caixa de cornell. Para determinar as interseções de um raio com um objeto, usei a função rt_shootray provida pelo BRL-CAD. Para ela, devemos passar a origem e direção do raio além de uma callback que será chamada sempre que um raio for atingido. Me baseei nesse exemplo.

Para testar, fiz as paredes e o teto serem um azul difuso, o chão ser vermelho difuso, a caixa alta um amarelo difuso e a caixa mais baixa um espelho. O resultado ficou o seguinte, com 400 amostragens:

Cena renderizada com 400 amostragens.

Apesar de a cena ter ficado meio escura, gostei do resultado.

Próximos passos

Minha ideia agora é adaptar esse código para o shader osl. Já andei fazendo alguns testes e a tarefa não parece simples. Um problema é que o aplicativo rt, que usará o shader osl, só atira um raio por pixel, enquanto o código acima usa vários.

Isso é necessário porque a direção do raio de saída de um shader osl é probabilística e é preciso uma grande amostragem de raios para ter a cor mais próxima do esperado.

Para se ter uma ideia, veja a cena da caixa de cornell renderizada com um número baixo de amostragens:

Cena renderizada com 4 amostragens.

Outro problema com o qual terei que lidar é a mistura de shaders do BRL-CAD com os shaders OSL. O mecanismo de funcionamento deles é meio diferente e terei que estudá-los mais a fundo para fazer uma eventual adaptação.

No mais, fiquei mais tranquilo de ter conseguido implementar um renderizador independente, pois isso se mostra um projeto mais concreto no qual eu posso continuar trabalhando e apresentar no final, caso a implementação do shader osl não dê resultados.

Meu medo era de ficar enroscado com algum problema e por isso o projeto não ser bem sucedido.

Nota finais

Ganhei acesso de commit ao código-fonte do BRL-CAD. É bastante satisfatório poder contribuir diretamente com um código grande, que é usado por muitas pessoas.

A regra da comunidade é fazer commits constantemente, sempre que o código estiver estável. Por enquanto só fiz atualizações do meu programa e uma correção de erros de digitação que encontrei perdidos no código.

O BRL-CAD possui uma página no ohloh, onde dá pra ver os contribuidores e os commits que foram feitos.

Anúncios

Seletiva Interna da Maratona Unicamp 2011

Junho 19, 2011

O prova da seletiva aconteceu ontem, 18 de Junho. Esse ano conseguimos realizar a seletiva da maratona mais cedo. Essa sempre foi a ideia, já que esperávamos que com times formados, os alunos estivessem mais motivados a treinar nas férias.

Apesar se não estar ajudando com o treinamento, ajudei com a organização da prova, escolhendo alguns dos problemas e ficando de fiscal.

Dos competidores

Foram 23 participantes, sendo que decidimos formar por enquanto apenas 3 times. O restante dos times (2 ou 3) serão formados com base na dedicação, mas ainda não decidimos como será a seleção.

A motivação para essa nova regra é que com base em anos passados, temos que os competidores dos primeiros times são já mais experientes e comprometidos com treinamentos, enquanto o restante era um pessoal mais novo na maratona e muitos desaparecem após a regional. No fim das contas, o intuito de formar os times adicionais é justamente manter os novos competidores motivados para se tornarem bons maratonistas nos anos seguintes.

Competidores

A classificação foi a seguinte (número de problemas entre parênteses):

  1. Marcelo Póvoa (9)
  2. Douglas Santos (8)
  3. Bruno Crepaldi (7)
  4. Igor Wolff (7)
  5. Ruan Silva (6)
  6. Thiago Cavalcante (6)
  7. Patrícia Hongo (5)
  8. Mauro Lopes (4)
  9. Gabriel Borges (4)
  10. Victor Pompêo (3)

Comentários gerais: O Mauro desistiu por não ter tempo para treinar e então o Victor Pompêo entrou para o terceiro time.

O Marcelo, que fechou a prova, é de longe o competidor mais experiente. Ao escolhermos os problemas da prova consideramos a possibilidade dele fechá-la, mas nosso objetivo mesmo era classificar os outros competidores e por isso os problemas da prova estavam relativamente fáceis.

A briga esse ano ficou entre o Douglas, Bruno, Igor, Ruan e Thiago. Eles estiveram empatados por um tempo, até que o Douglas, Bruno e Igor despontaram no final da prova.

A revelação foi o Gabriel Borges. Me disseram que ele é bixo, mas não sei se é da Ciência ou Engenharia.

Competidores 2

Dos problemas

Os problemas foram selecionados do SPOJ:

Os problemas mais fáceis eram para ser o “To and Fro” e “Army Strength”; Os médios-fáceis eram “Rectangles”, “Street Parade” e “Sorting Bank Accounts”; Os médios-difíceis eram “Distinct Subsequences”, “Cow Cars”, “Cleaning Robot” e “Subset sum”. No final a única diferença foi que o “Rectangles” era mais fácil do que imaginávamos e o “Army Strength” mais difícil.

Além disso, o problema City Game estava entre os problemas originais da prova mas tivemos que remover. A razão é que incluímo-no antes de implementá-lo. Nossa ideia era que uma programação dinâmica O(n^3), explicada em um treino, passasse, mas vimos que os limites exigem um algoritmo O(n^2). Não é ruim que o problema seja mais difícil do que imaginávamos, mas é péssimo o fato de que podíamos ter induzido quem foi no treino a perder tempo com uma solução que estouraria o tempo.

Do futuro

Até agora era o Igor quem estava conduzindo os treinos, mas em breve ele irá fazer um estágio no Facebook (o processo começou na seletiva do ano passado :) e defender antes do final do ano. Com isso, o Mário César, do LOCo, assumirá o cargo de técnico. Embora não tenha experiência na maratona, o Mário é doutorando em Teoria de Computação e conhece vários tópicos importantes vistos na maratona. Além do mais, ele tem acompanhado o Igor nos treinamentos para se familiarizar com a maratona, principalmente a parte de programação.

Mário, o novo coach

Vamos torcer para que a Unicamp se classifique para o mundial esse ano, que será em Varsóvia, Polônia!


Usando bibliotecas C++ em código C

Junho 12, 2011

O BRL-CAD é escrito majoritariamente em C, enquanto o OSL é escrito em C++. Para que o código C possa usar código C++ é preciso usar uns truques. No meu caso, eu queria disponibilizar uma classe C++ para ser acessada pelo C.

Para indicar que um código C irá usar uma função sua, o código C++ deve usar a diretiva extern "C". Por exemplo, se temos a função void foo(int x), ela ficará assim:

extern "C" void foo(int x);

ou alternativamente

extern "C" {
    void foo(int x);
}

Entretanto, o código C não entende essa diretiva. Por isso, o cabeçalho que ele deverá ver é

void foo(int x);

Um truque para o código C e C++ compartilharem o mesmo header, é usar a macro __cpluscplus que só é setada se o código for C++. Podemos usar o seguinte cabeçalho para ambos os códigos:

#ifdef __cplusplus
extern "C" {
#endif
    void foo(int x);
#ifdef __cplusplus
}
#endif

Exportando uma classe

Agora suponha que queremos exportar uma classe, como por exemplo a classe A, definida como:

class A {
    int x;
 public:
    A(int _x) : x(_x){};
    int getx() { return x; };
};

Precisamos definir funções estilo C para encapsular os métodos da class A, inclusive o construtor e o destrutor.

/* wrapper para construtor */
A* init(int x){
    A* objA = new A(x);
    return objA;
}
/* wrapper destrutor */
void destroy(A** objA){
    delete *objA;
    *objA = NULL;
}
/* wrapper para A::getx() */
int getx(A* objA){
    return objA->getx();
}

Para que o código C mantenha uma referência para o objeto da classe A, podemos usar um ponteiro opaco, já que a estrutura da classe A é desconhecida para o código C. Além disso, para poder compartilhar as funções acima com o código C++, devemos criar um apelido para nosso ponteiro opaco, de A.

typedef void A;

Agora A* representa um ponteiro opaco que pode apontar para um objeto da classe A, independente de sua estrutura. Aliás, ao invés de usarmos void, recomenda-se usar struct A para que haja uma diferenciação dos tipos dos pointeiros opacos [2]. Isso é interessante no caso em que há duas classes A e B, e erroneamente fazemos um ponteiro da classe B apontar para um objeto da classe A, como no código abaixo:

typedef void A;
typedef void B;
A* objA = init(10);
B* objB = objA; 

Se ao invés de void usarmos structs,

typedef struct A A;
typedef struct B B;
A* objA = init(10);
B* objB = objA; /* Gera um warning */

Temos uma garantia adicional contra erros não-intencionais.

Exemplo completo

lib.h:

 
#include <stdio.h>

#ifdef __cplusplus
class A {
    int x;
 public:
    A(int _x);
    int getx();
};

#else
typedef struct A A;
#endif

#ifdef __cplusplus
extern "C" {
#endif

    A* init(int x);
    void destroy(A** objA);
    int getx(A* objA);

#ifdef __cplusplus
}
#endif

lib.cpp:

#include "lib.h"
A::A(int _x) : x(_x) {}
int A::getx(){ return x; }

A* init(int x){
    A* objA = new A(x);
    return objA;
}
void destroy(A** objA){
    delete *objA;
    *objA = NULL;
}
int getx(A* objA){
    return objA->getx();
}

main.c:

#include <stdio.h>
#include "lib.h"

int main (){

    A *objA = init(3); 
    printf("%d\n", getx(objA));
    destroy(&objA);
    return 0;
}

Compilando

Podemos usar o cmake para simplificar o processo de compilação:

add_library(lib lib.cpp)
add_executable(main main.c)
target_link_libraries(main lib)

Referências

[1] http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html
[2] Stack overflow: A question about exporting a C++ class to C


Cmake

Junho 5, 2011

Conforme eu tinha comentado em um post anterior, minha tarefa dessa semana era desenvolver um shader para interfacear com o OSL. A primeira etapa dessa tarefa é permitir a chamada de funções do OSL. Apanhei bastante para fazer isso.

Recentemente o BRL-CAD migrou seu sistema de construção do autotools para o cmake. O pouco que sei sobre cmake, é que ele é um sistema multi-plataforma para compilar um projeto. Você descreve, em um nível mais alto, os CMakeLists.txt em cada diretório do seu projeto. No caso mais básico, executa o comando cmake no diretório raíz e serão gerados os Makefile‘s. Basta então executar make para compilar e make install para instalar.

Eu venho usando o cmake de maneira bem cut and paste no meu mestrado por causa do CGAL. Decidi estudar um pouco mais sobre esse sistema e escrever sobre as principais componentes.

Geração de executável

A principal função presente no cmake é a de gerar executáveis. Dados vários arquivos de código-fonte, por exemplo a.c, b.c e c.c, sendo que o main está em a.c, uma maneira de gerar um executável usando o gcc é o seguinte:

gcc -c b.c c.c
gcc a.c b.o c.o -o executavel

Com o cmake isso simplifica para

add_executable(executavel a.c b.c c.c)

Geração de biblioteca estática

Outra possibilidade é gerar bibliotecas estáticas. Se tivermos funções definidas nos arquivos a.c e b.c e quisermos disponibilizá-las para outros programas utilizarem, podemos fazer:

gcc -c a.c b.c
ar -cvq libab.a a.o b.o

Para que um programa c.c possa usá-la, fazemos a linkagem da seguinte maneira:

gcc c.c -o executavel -L/caminho/para/libab/ -lab

que é equivalente a:

gcc c.c -o executavel /caminho/para/libab/libab.a

No cmake essas tarefas podem ser realizadas pelos seguintes comandos

add_library(libab a.c b.c)
add_executable(executavel c.c)
target_link_libraries(executavel libab)

Uma excelente referência sobre bibliotecas no linux é a do yolinux.com

Dependências

Agora, suponha que queremos linkar o nosso código a uma biblioteca já compilada. Vamos supor que essa biblioteca, chamada libxyz.a, esteja em /home/jose/programa/lib e o cabeçalho com as funções, chamado xyz.h, esteja em /home/jose/programa/include.

Fazendo manualmente ficaríamos com algo do tipo

gcc -I/home/jose/programa/include -L/home/jose/programa/lib -lxyz c.c -o executavel

Uma das maneiras de se encontrar uma biblioteca usando o cmake é da seguinte maneira:

find_library(xyz_library NAMES xyz PATHS /home/jose/programa/lib)

No exemplo acima, xyz_library é um nome qualquer para nos referenciarmos à biblioteca. Em NAMES colocamos o nome da biblioteca sem o prefixo lib e a extensão (por exemplo, libxyz.a vira xyz) em PATHS o caminho para essa biblioteca (PS.: podemos colocar vários caminhos se a priori não sabemos onde a biblioteca libzyx.a está).

Para fazer a ligação basta fazer:

target_link_libraries(executavel ${xyz_library})

Para encontrar o cabeçalho com as funções, usamos

find_path(xyz_include NAMES xyz.h PATHS /home/jose/programa/include)

Para informar ao compilador o caminho de xyz.h, basta fazer

include_directories(${xyz_includes})

Geralmente a busca por bibliotecas externas é modularizada para haver re-uso de código. Para cada biblioteca externa, podemos definir um arquivo com nome descritivo e extensão cmake. Por exemplo, suponha que queremos encontrar a biblioteca Boost. Podemos definir um arquivo FindBOOST.cmake contendo código para encontrar as bibliotecas e os cabeçalhos.

Cada um desses arquivos são postos em um diretório separado, que deverá ser salvo na variável interna do cmake chamada CMAKE_MODULE_PATH. Um modo de fazer isso é:

set(CMAKE_MODULE_PATH "/caminho/para/os/arquivos/Find/")

Para executar os comandos desse arquivo, dentro de qualquer CMakeLists.txt dentro do projeto, basta fazer algo como:

include(FindBOOST)

Sub-diretórios

Em geral cria-se um arquivo CMakeLists.txt por diretório. Para que o cmake processe esses arquivos, os diretórios pais devem informá-lo com o comando add_subdirectories. Se por exemplo quisermos incluir os diretórios sub1 e sub2 filhos do diretório atual, basta incluir as seguintes linhas:

add_subdirectories(sub1)
add_subdirectories(sub2)

Compilação de código

É possível controlar o modo como a compilação é feita com o cmake. Um dos principais meios para se fazer isso é através das variáveis CMAKE_C_FLAGS (para C) e CMAKE_CXX_FLAGS (para C++).

Ao fazer:

set(CMAKE_C_FLAGS -Wall -Werror)

A próxima vez que o um programa C for compilado, essas flags serão adicionadas à compilação. Para verificar isso, uma flag muito interessante a ser setada no comando make, é o VERBOSE. Ao setar essa variável, toda vez que você executar o comando make, ele exibirá os comandos de compilação que estão sendo executados.

make VERBOSE=1

Ele é bastante útil também para “debugar” um arquivo do tipo CMakeLists.txt, para ver se ele está gerando comandos de compilação do jeito que esperamos.

Integração do cmake com o emacs

(Seção adicionada em 6 de Junho de 2011)

Nesse wiki do cmake há dicas para configuração do emacs para colorir corretamente arquivos do tipo CMakeLists.txt e .cmake. Geralmente o emacs vem configurado por padrão, mas em todo caso, basta baixar o arquivo cmake-mode.el e adicionar as seguintes linhas no arquivo de configuração:

; Add cmake listfile names to the mode list.
(setq auto-mode-alist
	  (append
	   '(("CMakeLists\\.txt\\'" . cmake-mode))
	   '(("\\.cmake\\'" . cmake-mode))
	   auto-mode-alist))

(autoload 'cmake-mode "~/Downloads/cmake-mode.el" t)

Troque Downloads pelo local onde você baixou o cmake-mode.el.

Se você usa abas no emacs, ao abrir vários arquivos CMakeLists.txt, eles serão chamados de CMakeLists.txt, CMakeLists.txt<2>, CMakeLists.txt<3> e assim por diante. Isso é bem incômodo, pois você sempre tem que olhar o conteúdo para ver de qual CMakeLists se trata.

Para resolver esse problema, adicione a seguinte rotina no seu arquivo de configurações. Ela adiciona um prefixo contendo o caminho de cada arquivo. Muito útil!

;; uniquify.el is a helper routine to help give buffer names a better unique name.
(when (load "uniquify" 'NOERROR)
  (require 'uniquify)
  (setq uniquify-buffer-name-style 'forward)
  ;(setq uniquify-buffer-name-style 'post-forward)
)

Leitura Complementar

Ao invés de reunir uma série de referências, deixo apenas esse post do stackoverflow para maior abrangência: cmake tutorial