sábado, 22 de outubro de 2011

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!

Nenhum comentário:

Postar um comentário