quarta-feira, 26 de outubro de 2011

Materials Scripts

Materials Scripts fornecem a capacidade de definir materiais complexos que podem ser facilmente reutilizados. Ainda que se possa definir todas as configurações de materiais necessários para uma cena através do código - utilizando métodos das classes Material e TextureLayer -, essa é uma tarefa bastante árdua. Em vez disso, podemos armazenar definições de materiais em arquivos de texto que podem ser carregados sempre que necessário.

Scripts [Introdução]

Finalmente acabaram-se as explicações básicas sobre os principais objetos que trabalham para renderizar uma cena (qualquer que seja ela) no OGRE. Agora é hora de começar a estudar uma nova e empolgante funcionalidade: o uso de scripts!

Ainda não li muito sobre o assunto (no âmbito do OGRE) mas já vi, e escrevi nas postagens anteriores, que um diferencial no uso de scripts é que eles podem ser alterados enquanto a aplicação roda e as modificações são renderizadas em tempo real. Isto é, não é preciso ter todo aquele trabalho maçante e repetitivo toda vez que fizer uma alteraçãozinha de nada no código pra encerrar a aplicação, compilar e rodar tudo de novo! Isso torna o debug muito mais prático, rápido e eficiente.

Tutorial Básico 3: Terreno (parte 1)

Neste tutorial, aprenderemos a criar, renderizar e configurar o terreno de um ambiente dentro do Ogre 3D. Devido a extensão dessa parte do tutorial, dividi ele em duas partes, de forma que nessa primeira parte nós sejamos capazes de criar um terreno. O projeto feito nessa parte será necessário para a próxima parte do tutorial.

Este tutorial foi traduzido e adaptado do Tutorial Básico 3 da Wiki do Ogre 3D. Link: http://www.ogre3d.org/tikiwiki/Basic+Tutorial+3


terça-feira, 25 de outubro de 2011

Overlays

Overlays permitem que elementos 2D ou 3D sejam renderizados no topo do conteúdo normal da cena para criar efeitos como heads-up displays (HUD's), sistemas de menus, painéis de status, etc. O painel de estatísticas que é exibido no OGRE é um exemplo de overlay:


Como se vê na imagem, o painel de estatísticas "FPS" e a logo do OGRE se sobrepõem a qualquer objeto da cena através de overlay.

Materials

O objeto Material controla como objetos na cena são renderizados. Ele especifica propriedades básicas da superfície que os objetos têm (como reflexão de cores, brilho, etc), quantas camadas de textura são utilizadas, quais as imagens que estão nelas e como são mescladas, quais efeitos especiais são aplicados (mapeamento ambiental, por exemplo), qual modo de clipagem (recorte) é utilizado, como as texturas são filtradas, etc.

Materiais podem tanto ser configurados via código pela chamada ao método SceneManager::createMaterial ou via especificação em um script que é carregado em tempo de execução. Futuras postagens explanarão a respeito da utilização de scripts.

Praticamente todas as características de um objeto que dizem respeito à sua aparência (com exceção de seu formato) são controladas pela classe Material.

Entities

Uma entidade (em inglês, Entity) é uma instância de um objeto móvel na cena - que pode ser um carro, uma pessoa, um cachorro, uma shuriken, etc. A única assertiva sobre ela é que não possui uma posição fixa no mundo.

Entidades se baseiam em malhas discretas (Mesh) , isto é, coleções de geometria que são independentes, normalmente muito pequenas em relação à escala do mundo virtual em que estão inseridas e representadas por objetos Mesh. Várias entidades podem ser criadas a partir de um mesmo mesh, desde que se deseje realmente criar cópias de um mesmo objeto na cena. As entidades exibidas na figura abaixo, por exemplo, foram criadas a partir do mesmo mesh:


O Objeto Mesh

Um objeto Mesh representa um modelo discreto: um conjunto de geometria que é independente e normalmente bastante pequeno em relação ao mundo virtual em que se insere. Objetos Mesh têm o intuito de representar objetos móveis e não são utilizados para representação da geometria plana extensa, geralmente usada para criar backgrounds.

Objetos Mesh são um tipo de recurso gerenciado pelo MeshManager. São normalmente carregados a partir do formato de objetos OGRE, .mesh. Arquivos .mesh são geralmente criados pela exportação de alguma ferramenta de modelagem 3D.

É possível criar meshes manualmente através da chamada ao método MeshManager::createManual. Assim, o programador pode ele mesmo definir a geometria do objeto mas isso está fora do escopo desta postagem.

Objetos Mesh são a base para os objetos móveis individuais no mundo, que são chamados Entities (assunto da próxima postagem).

Objetos Mesh também podem ser animados através do uso de Skeletal Animation (que, provavelmente, também será um assunto abordado em postagens futuras).

O Objeto ResourceGroupManager

O objeto ResourceGroupManager é, na realidade, uma espécie de hub para o carregamento de recursos reutilizáveis como texturas e meshes. Através dele o programador pode definir grupos de recursos que podem ser descarregados ou recarregados em conjunto sempre que se precisar. Ao seu favor, estão vários ResourceManagers, que gerenciam os tipos de recursos individualmente - a exemplo dos TextureManager e MeshManager. Nesse contexto, recursos são conjuntos de dados que devem ser carregados para fornecer ao OGRE os dados que ele precisa.

ResourceManagers asseguram que recursos sejam carregados uma única vez e compartilhados através da engine OGRE. Eles também são responsáveis pelos requisitos de memória dos recursos que gerenciam e podem procurar em vários lugares pelos recursos que precisam - inclusive caminhos de busca múltiplos e arquivos compactados (ZIP).

A maior parte do tempo, o programador não lida com ResourceManagers diretamente. Eles são chamados, quando necessário, por outras partes do sistema OGRE. Por exemplo, quando se solicita que uma textura seja aplicada a um material, o TextureManager é chamado automaticamente.

Se preferir, o programador pode manipular diretamente o gerente de recurso apropriado para pré-carregamento de recursos (se, por exemplo, ele quiser evitar o acesso a disco mais tarde) mas não há problema algum em deixar que OGRE decida quando fazer essa manipulação.

Uma coisa que se deve fazer é informar aos gerentes de recursos onde procurar pelos recursos. Isso é feito através da instrução Root::getSingleton().addResourceLocation - que, na verdade, passa a informação para o ResourceGroupManager.

Como só existe sempre uma única instância para cada ResourceManager da engine, se você quiser obter referência a um gerente de recursos, pode fazer como no exemplo a seguir:

TextureManager::getSingleton().algumMetodo() MeshManager::getSingleton().algumMetodo()

O Objeto SceneManager

Sem contar com o objeto Root, o SceneManager é, provavelmente, o objeto mais importante de todos com que você vai lidar quando estiver desenvolvendo sua aplicação OGRE. Certamente, será o objeto mais utilizado também.

O SceneManager é responsável pelo conteúdo da cena que está sendo renderizada pela engine. Ele é responsável por organizar o conteúdo usando a técnica que achar mais adequada para realizar as tarefas de criação e gerenciamento de câmeras, objetos móveis (entidades), luzes e materiais (propriedades superficiais dos objetos), bem como por gerenciar a "geometria global" - que é a geometria estática mais ampla, geralmente utilizada para representar partes da cena que não se movem.

É ao SceneManager que você recorre quando quer criar uma câmera para a cena! Também é através dele que se pode adicionar ou remover uma fonte de luz. Ou seja, sua aplicação não precisa manter listas dos objetos que estão sendo dispostos: o SceneManager mantém um conjunto de todos os objetos na cena para que o programador possa referenciá-los quando precisar. Consulte na API os métodos getLight(), getCamera(), getEntity() e outros.

O SceneManager também envia a cena para RenderSystem quando é hora de renderizar a cena. No entanto, o programador nunca precisa chamar o método _renderScene diretamente. Ele é chamado automaticamente sempre que um objeto que está sendo renderizado é atualizado.

Portanto, a maior parte da interação do programador com o objeto SceneManager é durante a configuração da cena em que, possivelmente, é invocada uma grande quantidade de métodos (o que às vezes é guiado por algum arquivo de entrada que contém os dados da cena). Também é possível modificar o conteúdo da cena dinamicamente durante o ciclo de renderização, caso se crie um objeto FrameListener.

Diferentes tipos de cena exigem abordagens algorítmicas muito diferentes para decidir quais objetos são mandados pro RenderSystem da maneira mais eficiente possível. Por conta disso, a classe SceneManager foi projetada para ser estendida para diferentes tipos de cena.

O objeto SceneManager padrão irá renderizar uma cena mas ele faz muito pouca ou quase nenhuma organização de cena e não se pode esperar que trabalhe de maneira ótima (o mais eficientemente possível) para cenas muito amplas.

O intuito de criar especializações para a classe SceneManager é justamente que cada uma delas irá otimizar a organização da cena para ganhar desempenho, visto que assumem o que pode ser feito para cada tipo de cena. Um exemplo de especialização é a BspSceneManager, que otimiza a renderização para cenas grandes in-door com base em uma árvore BSP.

A aplicação que usa OGRE não precisa saber quais são as especializações disponíveis. Ela deve simplesmente chamar Root::createSceneManager(..) passando como parâmetro um dos vários tipos de cena (ST_GENERIC, ST_INTERIOR, etc.). A engine irá automaticamente usar a melhor especialização de SceneManager para o tipo de cena escolhido ou o padrão caso uma especialização não esteja disponível. Isso permite que os desenvolvedores de OGRE possam criar novas especializações para tipos de cena para os quais ainda não havia especialização alguma e as aplicações usufruam das novas especializações sem mudar nada do seu código.

segunda-feira, 24 de outubro de 2011

O Objeto RenderSystem

O objeto RenderSystem é, na realidade, uma classe abstrata que serve de interface para a API 3D subjacente. Ele é responsável pela chamada a operações de renderização da API e pela configuração das várias opções de renderização. A classe é abstrata por que toda a implementação é específica à renderização implementada por uma API: existem subclasses específicas para cada API de renderização (D3DRenderSystem para a Direct3D, por exemplo). Depois que o sistema é inicializado através da chamada a Root::initialise(), o objeto RenderSystem da API de renderização selecionada pode ser obtido pela chamada ao método Root::getRenderSystem().

Apesar disso, uma aplicação normalmente não precisa manipular o objeto RenderSystem diretamente - tudo que o programador precisa para renderizar objetos e customizar configurações está disponível em classes orientadas a cena como SceneManager, Material, etc. O objeto deve ser manipulado diretamente apenas quando se desejar criar múltiplas janelas de renderização (neste caso, janelas completamente separadas, isto é, não apenas diferentes janelas de exibição de uma cena - como num efeito split screen -, o que pode ser feito via funcionalidades da classe RenderWindow) ou acessar propriedades avançadas do objeto RenderSystem.

Pelas razões explicitadas no parágrafo anterior, o manual oficial do OGRE não aborda mais profundamente este objeto. Durante o estudo do manual, pode-se assumir que o SceneManager lida com todas as chamadas às funcionalidades de RenderSystem nos momentos apropriados.

O Objeto Root

O objeto Root é a porta de entrada para a programação no mundo OGRE e fornece funcionalidades para a configuração do sistema. Ele deve ser o primeiro a ser criado e o último a ser destruído. Uma instanciação básica do objeto, para fins de exemplo, pode ser vista a seguir:

Ogre::Root *mRoot;
mRoot = new Ogre::Root("plugins.cfg");


O parâmetro recebido no construtor indica o arquivo que contém as configurações de plugin. O padrão é "plugins.cfg". Consulte o construtor, na API, para mais detalhes.
Para exemplificar, através do método showConfigDialog() do Root, que é extremamente útil, podem ser detectadas todas as opções de renderização do sistema e exibida uma caixa de diálogo para que o usuário personalize as opções de resolução, profundidade de cor, tela cheia, etc. Ele também armazena as configurações do usuário para que o sistema possa ser inicializado diretamente depois da personalização. Um exemplo da utilização do método é exibido no código abaixo:

/*
 * Exibe o diálogo de configuração e inicializa o sistema.
 * Alternativamente, pode-se usar root.restoreConfig() para carregar 

 * a configuração se houver certeza de que há configurações 
 * válidas em ogre.cfg 
 */
if(mRoot->showConfigDialog())
    {
    /* 

     * Se showConfigDialog() retornar true, usuário clicou em OK,
     * então o sistema deve ser inicializado.
     * O parâmetro true indica a criação de uma janela padrão

     * de renderização 
     */
        mWindow = 
mRoot->initialise(true, "Nome da Janela");
    }

Para detalhes sobre o método initialise(), consulte a sua documentação, na API do OGRE.
O resultado da chamada ao método showConfigDialog() é a conhecida janela de configuração do OGRE.

O objeto Root também é a maneira como são obtidos ponteiros para outros objetos do sistema como  SceneManager, RenderSystem e vários outros gerentes de recursos. Para obter um gerente de cena (SceneManager) padrão, por exemplo, pode-se utilizar o seguinte código:

Ogre::SceneManager *mSceneManager;
mSceneManager = mRoot->createSceneManager(Ogre::ST_GENERIC);


Nas duas linhas de código acima, o objeto Root é utilizado para obter um gerente de cena com a chamada ao método createSceneManager(), que recebe como atributo uma flag que indica a criação de um Scene Manager genérico, isto é, não é específico para um cenário in-door, out-door ou qualquer outro.
Por fim, se você utilizar OGRE no modo contínuo de renderização, isto é, se você quer que todos os objetos que são renderizados sejam atualizados tão rápido quanto possível (o que acontece em games e demos mas não em utilitários de janela),  o objeto Root tem um método chamado startRendering() que, quando chamado, entra em um loop de renderização que só termina quando todas as janelas de renderização são fechadas ou qualquer objeto FrameListener indica que quer interromper o ciclo.
A chamada ao método é bastante simples - como se vê na API, ele não recebe qualquer parâmetro:

mRoot->startRendering();

domingo, 23 de outubro de 2011

Objetos Centrais do OGRE [Introdução]

Vamos, com base no manual disponível no site oficial do OGRE [http://goo.gl/fU0Ky] fazer um apanhado geral sobre os principais objetos que são utilizados para renderizar uma cena. É claro que, devido à enorme quantidade de classes que compõem a engine, não são explicados cada um dos objetos que podem ser instanciados - apenas aqueles que possuem papel fundamental para a criação de qualquer que seja o tipo de cenário que você deseja.
Um detalhe importante para o bom funcionamento do código é que OGRE utiliza namespaces do C++. Quem já programa na linguagem, possivelmente não encontrará qualquer novidade quanto a isso. De modo simplificado, namespaces são um recurso que possibilita a distinção e o uso de diferentes classes (pertencentes a diferentes domínios da aplicação) que possuem o mesmo nome.
Por exemplo, se na aplicação em que utilizaremos OGRE já houver uma classe chamada Camera (que também é definida na biblioteca da engine), não haverá qualquer tipo de conflito nos usos por que a classe Camera de OGRE é especificada com seu namespace, assim:

Ogre::Camera

Para evitar ter que utilizar o prefixo "Ogre::" sempre que for utilizar uma de suas classes, é possível declarar que se está utilizando o namespace desejado com o seguinte statement

using namespace Ogre;

O que não é nenhuma novidade para quem já está familiarizado com C++. No entanto, vale lembrar que, havendo alguma ambiguidade entre as classes utilizadas (isto é, duas classes com mesmo nome), é necessário o uso do prefixo.

Visão Global das Classes OGRE

Na imagem, vemos a representação UML de um diagrama de classes do OGRE - clique para ver em tamanho maior. 
No topo da imagem está o objeto raiz - Root. Ele é a porta de entrada para utilização da engine e é o objeto através do qual se instanciam objetos muito importantes como gerentes de cena (Scene Manager), sistemas e janelas de renderização, entre outros. Normalmente, o objeto Root só oferece outros objetos - que farão os trabalhos específicos -, funcionando como um organizador ou facilitador do processo.
Os principais módulos da engine, como se pode ver na imagem, são:

Gerenciamento de Cena (Scene Management)

Responsável pelo conteúdo da cena, sua estruturação, como ela é vista pela(s) câmera(s), etc. Objetos deste módulo são responsáveis por fornecer uma interface declarativa para o mundo que está sendo construído. Isto é, eles oferecem funcionalidades que permitem ao programador declarar onde colocar determinados objetos, qual o material de cada objeto, qual o ponto de observação pelo qual a cena está sendo visualizada e assim por diante.

Gerenciamento de Recursos (Resource Management)

Qualquer renderização precisa de recursos - que podem ser geometria, texturas, fontes, etc. É importante gerenciar cuidadosamente o carregamento, re-uso e descarregamento desses recursos e essa gerência é provida pelos objetos dos tipos definidos neste módulo (Resource Management).

Renderização (Rendering)

As classes deste módulo tratam do nível mais baixo do pipeline de renderização. São objetos específicos da API do sistema de renderização como buffers e estados de renderização (render states). Os objetos do módulo de gerenciamento de cena usam os objetos deste módulo para ter suas representações de alto-nível renderizadas na tela.

Plugins

Os módulos de plugin presentes na imagem são a maneira como OGRE pode ser facilmente estendido para solucionar os mais diversos tipos de problemas de computação gráfica - e não ficar atrelado a um único tipo de renderização como, por exemplo, renderização de cenários in-door, que são muito frequentes.

Abordaremos, em postagens futuras, cada um dos principais objetos que precisam ser instanciados e utilizados na renderização de uma cena básica. Também é possível ter uma noção da utilização e do papel de cada objeto dentro do contexto global (explicitado aqui) nos tutoriais básicos - que são menos maçantes e bastante motivadores.

sábado, 22 de outubro de 2011

Conceitos Básicos do OGRE

Orientação a Objetos

Como o nome já diz, OGRE é uma engine gráfica orientada a objetos. Vamos, agora, tentar entender o porquê da escolha dessa abordagem.
Atualmente, engines gráficas são como qualquer outro grande sistema de software. Elas começam pequenas mas logo se expandem a monstruosas e complexas bestas que não podem ser completamente compreendidas e aprendidas do dia pra noite. 
É muito difícil gerenciar sistemas desse tamanho e mais difícil ainda fazer modificações confiáveis, o que é muito importante num campo em que novas técnicas e abordagens aparecem quase toda semana. Projetar sistemas com arquivos enormes cheios de funções escritas em C não ameniza o problema - ainda que o código todo fosse escrito por uma pessoa só (o que é improvável), ela ainda teria dificuldade de se encontrar em meio a tantas linhas de código.
Orientação a objetos é uma abordagem muito popular para resolver o problema da complexidade. Está um passo além de decompor o programa em funções separadas: funções e dados de estado (atributos) são agrupados em classes que representam conceitos do mundo real. 
Isso permite abstrair a complexidade em pacotes facilmente reconhecíveis com uma interface conceitual simples que podem ser "montados" depois. Também é possível organizar esses pacotes de modo que diferentes deles possuam uma mesma interface mas sejam implementados distintamente. Assim, o programador só precisa trabalhar com uma interface bem conhecida.
Orientação a objetos (OO) é um tópico extenso que é tratado em muitos livros. Não é possível que seja contemplado de maneira suficientemente abrangente em uma única postagem. Mesmo assim, suas vantagens na utilização para desenvolvimento de engines gráficas são notórias e devem ser observadas - como mencionadas nos parágrafos anteriores.
Uma crítica ferrenha à abordagem orientada a objetos é sua suposta perca de desempenho em relação ao paradigma tradicional de programação (estruturada / procedural). Alguns acham que essa suposta desvantagem é motivo suficiente para que ela não seja utilizada no desenvolvimento de sistemas para renderização em tempo real. No entanto, o fundador e os colaboradores do OGRE acreditam que tiveram sucesso em desenvolver uma engine gráfica orientada a objetos de alto desempenho.
Em suma, os benefícios da utilização de OO são:
  • Abstração: interfaces comuns escondem as diferenças de implementação da API 3D e dos sistemas operacionais aos quais as aplicações se destinam;
  • Encapsulamento: há muito trabalho com o gerenciamento de variáveis de estado e ações sensíveis a contexto a ser feito em engines gráficas. Encapsulamento permite posicionar código e dados perto de onde são utilizados, o que torna o código mais legível, compreensível e confiável por que evita duplicatas;
  • Polimorfismo: o comportamento de métodos muda de acordo com o tipo de objeto que está sendo usado, mesmo que seja utilizada uma única interface. Por exemplo, uma classe especializada em gerenciar cenários in-door se comporta completamente diferente do scene manager padrão mas parece idêntica a outras classes do sistema e possui os mesmos nomes para os métodos.


Multi-Tudo

OGRE é desenvolvido com a proposta de estender qualquer tipo de cenário, plataforma ou API 3D. Apesar de implementar, debaixo dos panos, otimizações específicas para cada tipo de cenário, a engine não é restrita a rodar sobre uma única API gráfica, em uma determinada plataforma e para cenários específicos.
Assim sendo, todas as partes "vísiveis" de OGRE são independentes de plataforma, de API e do tipo de cenário. Não há dependências com tipos Windows nem suposições sobre o cenário que você quer criar e todos os princípios dos aspectos 3D são baseados mais em conceitos matemáticos que em uma API gráfica em particular.
É claro que em algum momento questões específicas sobre API, plataforma e cenário têm de ser definidas. No entanto, isso é feito em uma classe especializada que mantém uma interface comum com outras especializações para os mais diversos ambientes.
Por exemplo: existe a classe "Win32Window" que se responsabiliza por todos os detalhes da renderização de janelas na plataforma Win32. Contudo, o designer da aplicação só precisa manipular a sua interface "RenderWindow", que é a mesma para todas as plataformas.
Da mesma maneira, a classe "SceneManager" é responsável pela disposição dos objetos na cena e pela ordem em que esses objetos são renderizados. As aplicações só precisam utilizar essa interface mas existe uma classe especializada chamada "BspSceneManager" que otimiza a disposição de objetos para cenários in-door - o que garante ao usuário tanto um bom desempenho quanto uma interface fácil de aprender a usar.
Tudo que a aplicação precisa fazer é dar a entender qual o tipo de cenário que ela está renderizando e deixar que o próprio OGRE escolha qual a implementação mais adequada para aquele propósito.
A natureza orientada a objetos de OGRE possibilita todas essas vantagens. Atualmente, a engine roda em Windows, Linux e Mac OSX usando drivers plugins para as API's gráficas subjacentes (atualmente, Direct3D e OpenGL). Aplicações usam OGRE em um alto nível de abstração, o que assegura que elas irão rodar em qualquer plataforma e sistemas de renderização providos pelo OGRE sem a necessidade de uma plataforma ou API específica.

Tutorial Básico 2: Câmeras, Luzes e Sombras

Neste tutorial aprenderemos a definir e controlar câmeras, luzes e sombras. É importante que você tenha visto os tutoriais anteriores sobre Ogre 3D, mas é possível iniciar a partir desse ponto, criando um novo projeto. Este tutorial foi traduzido e adaptado a partir do Tutorial Básico 2, disponível na Wiki do Ogre 3D: http://www.ogre3d.org/tikiwiki/Basic+Tutorial+2&structure=Tutorials

Preparando o Projeto

Antes de tudo, crie um novo projeto (Arquivo > Novo > Projeto), selecione OGRE Applicattion, coloque o nome do Projeto como "TutorialBasico2" e clique em OK. Em seguida, escolha as opções Standard application e Postbuild Copy. Abra o arquivo TutorialBasico2.h e adicione as seguintes linhas de código, dentro da seção protected:


 virtual void createCamera(void);
 virtual void createViewports(void);

Em seguida, abra o arquivo TutorialBasico2.cpp e adicione no corpo do código as seguintes funções:


void TutorialBasico2::createCamera(void)
{
}

void TutorialBasico2::createViewports(void)
{
}
 

Outra coisa importante, limpe todo o conteúdo do método createScene, pois nós iremos colocar novos objetos nesse tutorial.

Câmeras

Uma Camera é o que nós usamos para mostrar a cena que nós criamos. É um objeto especial que funciona como um SceneNode. Ela possui as funções setPosition, yaw, roll e pitch Você pode associa-la a qualquer SceneNode

A posição de uma câmera é relativa aos seus pais, assim como um SceneNode. Para movimentação e rotação, você pode considerar uma Câmera como um SceneNode

Você só pode usar uma Câmera por vez (por enquanto). Nós não criamos uma câmera para mostrar uma porção da cena, depois criamos uma camera secundaria para mostrar outra porção da cena e então ativamos e desativamos cameras baseado em que parte da cena queremos ver.  Em vez disso, nós criamos SceneNodes que funcionam como "marcadores de Camera". Estes SceneNodes simplesmente ficam na cena e apontam para aonde a Câmera deve olhar .Quando for a hora de mostrar aquela porção da cena, a camera simplesmente se associa ao SceneNode apropriado.

Criando uma câmera

Nós substituiremos o método padrão que o arquivo BaseApplication usa para criar uma Camera. Vá até a função createCamera que criamos anteriormente e escreva a seguinte linha de código dentro do método create camera:


    // cria a camera
    mCamera = mSceneMgr->createCamera("PlayerCam");

Isto cria uma camera com o nome "PlayerCam". Observe que uma câmera está associada a um SceneManager o qual ela pertence.

Agora, vamos definir a posição da nossa camera. Como estaremos colocando objetos perto da origem, nós colocaremos a camera a uma boa distancia na direcao z, e a camera estara olhando para a origem. Adicione esse codigo dentro do metodo createCamera:


// define a posicao e a direcao da camera  
    mCamera->setPosition(Ogre::Vector3(0,10,500));
    mCamera->lookAt(Ogre::Vector3(0,0,0));

Note que setPosition define a posicao de nossa camera. E lookAt? Ela define para onde a camera olhará (facing) sem precisar rotacionar a camera posteriormente.

Então, vamos definir uma distância de recorte (clipping distance). Adicione essa linha de código:


// define a distancia de recorte proxima
    mCamera->setNearClipDistance(5);

A distância de recorte de uma camera define o quão distante alguma coisa deve estar para que seja ou não  visível. Isto permite que a engine pare de renderizar qualquer coisa mais distante ou mais perto que o valor especificado. setNearClipDistance(5) especifica que qualquer coisa mais próxima que 5 unidades não seja visível. Ou  seja, a camera não irá ver objetos que estejam muito perto dela (irá ver através do objeto). A função setFarClipDistance faz o inverso: ela não vê objetos que estejam mais longe do que a distância especificada. Mas não usaremos essa função aqui.

Por último, vamos construir um controlador de câmera, usando nossa câmera recém construída:


mCameraMan = new OgreBites::SdkCameraMan(mCamera);   // cria um controlador padrao de camera

A função createCamera deve ficar assim:


void TutorialBasico2::createCamera(void)
{
    // cria a camera
    mCamera = mSceneMgr->createCamera("PlayerCam");
    // define a posicao e a direcao da camera  
    mCamera->setPosition(Ogre::Vector3(0,10,500));
    mCamera->lookAt(Ogre::Vector3(0,0,0));
    // define a distancia de recorte proxima
    mCamera->setNearClipDistance(5);
 
    mCameraMan = new OgreBites::SdkCameraMan(mCamera);   // cria um controlador padrao de camera
}

Viewports

O Ogre pode ter vários SceneManagers executando ao mesmo tempo. Também é possível dividir a tela em múltiplas áreas, e ter câmeras separadas para renderizar emcada área da tela (como se fosse um jogo para dois jogadores). Nós só veremos esses exemplos em tutoriais mais avançados.

Para entender como o Ogre rederiza uma cena, considere três construtores do Ogre: a Camera, o SceneManager e o RenderWindow.

O RenderWindow é basicamente a janela aonde tudo é exibido. O objeto SceneManager cria cameras para exibir a cena. Você precisará dizer ao RenderWindow que Cameras mostrar na tela, e qual porção da janela renderizar. A área no qual você deve dizer ao RenderWindow mostrar a Câmera é o seu Viewport
O mais comum é criar somente uma Câmera, registrar a Camera para usar toda a RenderWindow e assim ter somente um objeto Viewport.

Nesse tutorial nós veremos como registrar a Camera para criar o Viewport. Nós podemos usar este objeto Viewport para definir a cor de fundo da cena que estaremos renderizando.

Criando um Viewport

Vá até a função Tutorial::createViewports. Para criar o Viewport, simplesmente chame a função addViewport do RenderWindow e forneça a Camera que estamos usando. A BaseApplication já estará cuidando da RenderWindow, então tudo que precisamos fazer é adicionar esse código ao createViewports:


// Cria um viewport, janela inteira
    Ogre::Viewport* vp = mWindow->addViewport(mCamera);

Agora, definiremos a cor do background. Como estaremos mexendo com luzes, então definimos a cor preta:


vp->setBackgroundColour(Ogre::ColourValue(0,0,0));

Note que ColorValue recebe valores de cores RGB, entre 0 e 1 cada. A última coisa que faremos (e a mais importante também) é definir a proporção (aspect ratio) de nossa Camera). Definiremos a proporção padrão:


// Altera a proporcao da camera para coincidir com o viewport
    mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));

No final, a função createViewports deve ser a seguinte:


void TutorialBasico2::createViewports(void)
{
    // Cria um viewport, janela inteira
    Ogre::Viewport* vp = mWindow->addViewport(mCamera);
    vp->setBackgroundColour(Ogre::ColourValue(0,0,0));
    // Altera a proporcao da camera para coincidir com o viewport
    mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));    
}

Até aqui, você deve ser capaz de compilar e executar a aplicação, mas nada aparecerá na tela. Certifique-se que a aplicação pode executar sem dar erros antes de continuar.

Sombras

O Ogre atualmente dá suporte a três tipos de sombras:

1- Sombras de texturas modulativas (Ogre::SHADOWTYPE_TEXTURE_MODULATIVE) - A menos cara computacionalmente das três. Ela cria lançadores de sombras preto e branco do tipo que renderiza na textura, que então é aplicada na cena

2- Sombras de estampas modulativas (Ogre::SHADOWTYPE_STENCIL_MODULATIVE) - essa técnica renderiza todos os volumes de sombras como uma modulação após todos os objetos não-transparentes terem sido renderizados na cena. Não é tão intensivo quanto as Sombras de estampas aditivas, mas também não é tão precisa.

3- Sombras de estampas aditivas (Ogre::SHADOWTYPE_STENCIL_ADDITIVE) - essa técnica renderiza cada luz como um passo aditivo em particular na cena. É bastante custoso à placa de vídeo, porque cada luz adicional requer um passo adicional na renderização da cena.

Usando Sombras no Ogre

A classe SceneManager possui uma função chamada setShadowTechnique que define o tipo de Sombra que nós queremos. Para cada Entity que você criar, chame a função setCastShadows para dizer se ela lança sombras ou não

Então vamos começar a definir a nossa cena. Vá até a função Tutorial:createScene (lembre-se que essa  função não deve ter nada até agora, onde colocaremos os elementos da cena). Primeiro definimos a luz ambiente, e o tipo de sombra que queremos nessa cena:


mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);

Criamos um objeto nessa cena e faremos ela lançar sombras:


Ogre::Entity* entNinja = mSceneMgr->createEntity("Ninja", "ninja.mesh");
entNinja->setCastShadows(true);
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entNinja);

Nós precisamos de um chão para o Ninja ficar. Para isso, nós criaremos um plano simples, através desse comando:

Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);

Registraremos esse plano para que possamos usar em nossa aplicação. Para isso, usaremos a classe MeshManager, que gerencia todas as malhas que foram carregadas. A função createPlane pega a definição de Plano acima e faz uma malha a partir dos parâmetros.

Ogre::MeshManager::getSingleton().createPlane("ground", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
    plane, 1500, 1500, 20, 20, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);

Para saber o que cada parâmetro da função getSingleton do MeshManager faz, consulte a API do Ogre. Basicamente, nós registramos nosso plano com um tamanho 1500 x 1500 em uma malha chamada "ground". Finalmente, criaremos uma entidade dessa malha e colocaremos na cena:

Ogre::Entity* entGround = mSceneMgr->createEntity("GroundEntity", "ground");
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entGround);

Finalizando o chão, nós colocaremos uma textura de pedras nele, e diremos que o chão não lançará sombras, através dos códigos:


entGround->setMaterialName("Examples/Rockwall");
entGround->setCastShadows(false);

Nossa função createScene deverá parecer com a seguinte:


void TutorialBasico2::createScene(void)
{
    mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
    mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);
 
    Ogre::Entity* entNinja = mSceneMgr->createEntity("Ninja", "ninja.mesh");
    entNinja->setCastShadows(true);
    mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entNinja);
 
    Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);
 
    Ogre::MeshManager::getSingleton().createPlane("ground", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
        plane, 1500, 1500, 20, 20, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);
 
    Ogre::Entity* entGround = mSceneMgr->createEntity("GroundEntity", "ground");
    mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entGround);
 
    entGround->setMaterialName("Examples/Rockwall");
    entGround->setCastShadows(false);
 
}

Se você executar o código, verá o ninja e o chão que nós criamos, mas não veremos nenhuma sombra que nós definimos. Isso porque nós não definimos nenhuma luz para essa cena, afinal para ter sombra, precisamos ter luz. Apesar de ser possível ver o ninja, isso acontece porque definimos uma luz ambiente (setAmbientLight), mas ela não gera nenhuma sombra pois ilumina todo o ambiente igualmente. Se você definir setAmbientLight(Ogre::ColorValue(0,0,0)) você não conseguirá ver nada; o que corresponderá a realidade, pois não definimos nenhuma luz.


Luzes

O Ogre dá suporte a três tipos de luzes:

1- Pontual (Ogre::Light::LT_POINT) - uma luz pontual emite luz em todas as direções igualmente.

2- Holofote (Ogre::Light::LT_SPOTLIGHT) - um holofote funciona exatamente como uma lanterna. Você define uma posição aonde a luz inicia, então a luz é direcionada àquela direção. Também é possível definir o ângulo dessa fonte de luz.

3- Direcional (Ogre::Light::LT_DIRECTIONAL) - uma luz direcional simula uma luz bem distante que atinge tudo em cena a partir de uma direção. Vamos dizer que você tem uma cena noturna e você quer simular a luz da lua. Você pode fazer isso definindo uma luz direcional e apontar para a direção que a lua deve iluminar.

As luzes tem uma diversidade de propriedades que descrevem como ela se comporta. Duas propriedades mais importantes da luz são as cores difusa e especular. Cada material define o quanto da luz difusa e especular irá refletir, o que nós aprenderemos a controlar em outro tutorial.

Criando Luzes

Para criar uma luz, chamaremos a função createLight do SceneManager e lhe daremos um nome. Nós podemos definir a posição dessa luz diretamente ou associa-la a um SceneNode para movimentação. As luzes tem somente as funções setPosition e setDirection. Para uma luz estacionária, você deve chamar a função setPosition. Caso seja uma luz em movimento, você deve anexa-la ao um SceneNode.

Vamos criar uma luz básica pontual. Definiremos o tipo e sua posição. Coloque o código a seguir dentro de createScene:


Ogre::Light* pointLight = mSceneMgr->createLight("pointLight");
    pointLight->setType(Ogre::Light::LT_POINT);
    pointLight->setPosition(Ogre::Vector3(0, 150, 250));


Agora que criamos a luz, vamos definir a cor difusa e especular como vermelha:


pointLight->setDiffuseColour(1.0, 0.0, 0.0);
    pointLight->setSpecularColour(1.0, 0.0, 0.0);

Compile o programa e você verá que agora o Ninja tem uma sombra:



Uma coisa que você pode notar é que não dá pra ver diretamente a fonte de luz. Você pode ver a luz que ela gera, mas não o objeto que gera a luz. É possível adiconar um objeto na fonte de luz para simular que que ele está gerando a luz, e ver diretamente de onde a luz está vindo.

Vamos colocar outra luz agora, sendo ela direcional. Fazemos o mesmo que fizemos com a luz anterior, apenas mudando o tipo de luz, e a cor que ela emite.


Ogre::Light* directionalLight = mSceneMgr->createLight("directionalLight");
    directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);
    directionalLight->setDiffuseColour(Ogre::ColourValue(.25, .25, 0));
    directionalLight->setSpecularColour(Ogre::ColourValue(.25, .25, 0));


Como uma luz direcional é uma luz distante, nós não definimos a sua posição, apenas a direção. Vamos dizer que ela está vindo de um ângulo de 45 graus em relação à frente e acima do ninja:


directionalLight->setDirection(Ogre::Vector3( 0, -1, 1 ));

Compile e execute novamente. Você verá que agora o ninja tem duas sombras:



Por último, vamos brincar com a luz do tipo holofote. Vamos criar uma do tipo azul:


Ogre::Light* spotLight = mSceneMgr->createLight("spotLight");
    spotLight->setType(Ogre::Light::LT_SPOTLIGHT);
    spotLight->setDiffuseColour(0, 0, 1.0);
    spotLight->setSpecularColour(0, 0, 1.0);


Para esse tipo de luz, precisaremos definir a posição e a direção dela. A luz estará vindo do lado direito no linja, e iluminará diretamente em cima dele:


spotLight->setDirection(-1, -1, 0);
    spotLight->setPosition(Ogre::Vector3(300, 300, 0));


A luz do tipo holofote também nos permite especificar a amplitude do raio de luz. Imagine um raio de luz vindo de uma lanterna. Ela tem um raio central que é mais forte do que a luz periférica. Nós podemos definir o tamanho desses dois raios chamando a função setSpotlightRange:


spotLight->setSpotlightRange(Ogre::Degree(35), Ogre::Degree(50));


Para dar uma percepção melhor das luzes e das sombras, defina a luz ambiente (setAmbientLight) para (0,0,0). Essa linha está logo no começo da função createScene.

Compile e veja o Perigoso Ninja Roxo!


Outras coisas que você pode tentar
  • Tente mudar o tipo de sombra para ver o que acontece. Aqui nós utilizamos a sombra de estampa aditiva, mas existem outros tipos de sombra (como aqueles que mostramos anteriormente, mas existem outras na classe SceneManager).
  • A classe luz tem uma função chamada setAttenuation que permite controlar como a luz se dissipa conforme c você se afasta dela. Veja na prática como essa função afeta a luz.
  • Dissemos que o Viewport define uma cor de fundo, mas definimos ela como preta. Tente por outrta cor para ver o que acontece.
Bem, agora que aprendemos a definir Câmeras, Luzes e Sombras, ficaremos por aqui No próximo tutorial, veremos: Terreno, Céu e Neblina!

Abraços!