quinta-feira, 7 de novembro de 2013

Um mundo 3D com OpenGL

Já postamos dois textos sobre o uso do OpenGL no Visual Studio. Continuaremos aqui com a série, agora iniciando nosso primeiro desenho 3D. Uma explicação sobre sistemas de visão é importante agora. Novamente estamos baseando em um texto do site Widget-101, desta vez de autoria de Luiyit Hernández. Para aqueles que desejam aprender o mesmo em C#, recomendamos visitar o site, que está em espanhol mas é facilmente entendido.

Sistemas de visão

No último post, usamos a função Ortho, que criava o volume de visualização ortográfica. Esta definição ficou um tanto vaga, e é por isto que agora precisamos defini-la melhor. O que é o sistema de visão ortográfica? A figura abaixo foi tirada de um texto do CodeProject e ilustra bem o que é este sistema de visão:


As coordenadas que definimos nesta função são as coordenadas left, right, top, bottom, near, far. Elas definem este volume. O retângulo mais à esquerda, na coordenada near, é o nosso ViewPort. Observem que há dois quadrados dentro de nosso volume de visualização. Nós veremos somente as faces direcionadas para o ViewPort. Embora estejamos tratando de um volume de visualização, o resultado é apenas 2D, sem noção de profundidade. Na prática, os dois cubos aparecerão do mesmo tamanho, mesmo que um esteja mais distante do outro. E é por isto que usamos a função Ortho no último texto.
Por outro lado, existe também o sistema de visualização em perspectiva. A figura abaixo ilustra este sistema:


Aqui, há a perspectiva para dar a impressão de 3D. Se desejamos, portanto, empregar figuras 3D, devemos mudar nosso código para empregar o segundo sistema de visualização.

Empregando o sistema de perspectiva

Para empregarmos o sistema de perspectiva, devemos retirar de nosso código de inicialização do ViewPort a função Ortho e criar ali uma matriz de perspectiva, aplicando-a ao ViewPort. O código ficará assim:
public: System::Void SetupViewport()
        {
             float aspectRatio = safe_cast<float>(glGraphicWindow->Width)/safe_cast<float>(glGraphicWindow->Height);
             GL::Viewport(0, 0, glGraphicWindow->Width, glGraphicWindow->Height);
             GL::MatrixMode(MatrixMode::Projection);
             GL::LoadIdentity();
             OpenTK::Matrix4 perspective = OpenTK::Matrix4::CreatePerspectiveFieldOfView(safe_cast<float>(System::Math::PI / 4.0f), aspectRatio, 0.01f, 2000.0f);
             GL::MultMatrix(perspective);
             GL::MatrixMode(MatrixMode::Modelview);
             GL::LoadIdentity();
        }
Em primeiro lugar, criamos a matriz de perspectiva. O primeiro valor é o ângulo de visualização. O segundo valor é a razão de aspecto que já usávamos. Por fim, os dois pontos são o valor da coordenada de profundidade inicial e final (near e far).

Visualizando nosso cubo

Agora que estamos em um ambiente 3D, devemos modificar nossa rotina de criação de vértices. Se vocês se recordam de nosso último post, os vértices criados eram 2D (Vertex2). Devemos mudar todas estas funções para Vertex3, e adicionar a coordenada Z aos seus argumentos (adicionaremos -5.0f por enquanto).
Para finalizar, devemos adicionar ainda uma "câmera", como a do desenho acima. Ela funciona como nosso olho dentro do desenho, já que ela tem posição e direção . Assim, vamos alterar nossa função Paint para ficar assim:
private: System::Void glGraphicWindow_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
         {
             GL::Clear(ClearBufferMask::ColorBufferBit | ClearBufferMask::DepthBufferBit);
             OpenTK::Matrix4 lookat = OpenTK::Matrix4::LookAt(OpenTK::Vector3::UnitZ, OpenTK::Vector3::Zero, OpenTK::Vector3::UnitY);
             GL::MatrixMode(MatrixMode::Modelview);
             GL::LoadMatrix(lookat);
             GL::Begin(BeginMode::Quads);
             GL::Color3(Color::Red);
             GL::Vertex3(-0.5f, 0.5f, -5.0f);
             GL::Color3(Color::Blue);
             GL::Vertex3(-0.5f, -0.5f, -5.0f);
             GL::Color3(Color::Green);
             GL::Vertex3(0.5f, -0.5f, -5.0f);
             GL::Color3(Color::Yellow);
             GL::Vertex3(0.5f, 0.5f, -5.0f);
             GL::End();
             glGraphicWindow->SwapBuffers();
         }
A função LookAt vai gerar nossa matriz para a câmera, constituída de vetor de posição, vetor de direção de visualização e vetor de inclinação. Esta matriz será posteriormente adicionada com a função LoadMatrix ao ViewPort. Executando o programa teremos o seguinte resultado:


Está aí nosso quadrado. Quanto mais próximo de 0.0f for a coordenada Z, mais próximo ele estará da tela.
Bem, temos um quadrado. Mas seria mais legal desenharmos um cubo. Precisamos desenhar então as outras 5 faces. Para diferenciar cada face na visualização, vamos atribuir uma cor diferente para cada uma.
Mesmo tendo um cubo, ainda visualizaremos apenas sua face frontal. Por isto vamos também rotacioná-lo. A função que faz isto é a função Rotate. Ele recebe como parâmetros o ângulo de rotação e o vetor indicando em qual eixo o objeto será rotacionado. É importante notar que a função Rotate deverá vir antes dos vértices que serão rotacionados. Nossa nova função Paint ficará assim:
private: System::Void glGraphicWindow_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
         {
             GL::Clear(ClearBufferMask::ColorBufferBit | ClearBufferMask::DepthBufferBit);
             OpenTK::Matrix4 lookat = OpenTK::Matrix4::LookAt(OpenTK::Vector3::UnitZ * 3, OpenTK::Vector3::Zero, OpenTK::Vector3::UnitY);
             GL::MatrixMode(MatrixMode::Modelview);
             GL::LoadMatrix(lookat);
             GL::Rotate(angle, OpenTK::Vector3::UnitY);
             GL::Begin(BeginMode::Quads);
             {
                  // Face frontal
                  GL::Color3(Color::Cyan);
                  GL::Vertex3(0.5f, 0.5f, -0.5f);
                  GL::Vertex3(-0.5f, 0.5f, -0.5f);
                  GL::Vertex3(-0.5f, -0.5f, -0.5f);
                  GL::Vertex3(0.5f, -0.5f, -0.5f);
                  // Face traseira
                  GL::Color3(Color::Blue);
                  GL::Vertex3(0.5f, -0.5f, 0.5f);
                  GL::Vertex3(-0.5f, -0.5f, 0.5f);
                  GL::Vertex3(-0.5f, 0.5f, 0.5f);
                  GL::Vertex3(0.5f, 0.5f, 0.5f);
                  // Face Direita
                  GL::Color3(Color::Green);
                  GL::Vertex3(0.5f, 0.5f, 0.5f);
                  GL::Vertex3(0.5f, -0.5f, 0.5f);
                  GL::Vertex3(0.5f, -0.5f, -0.5f);
                  GL::Vertex3(0.5f, 0.5f, -0.5f);
                  // Face esquerda
                  GL::Color3(Color::Yellow);
                  GL::Vertex3(-0.5f, 0.5f, 0.5f);
                  GL::Vertex3(-0.5f, -0.5f, 0.5f);
                  GL::Vertex3(-0.5f, -0.5f, -0.5f);
                  GL::Vertex3(-0.5f, 0.5f, -0.5f);
                  // Face superior
                  GL::Color3(Color::Red);
                  GL::Vertex3(0.5f, 0.5f, 0.5f);
                  GL::Vertex3(-0.5f, 0.5f, 0.5f);
                  GL::Vertex3(-0.5f, 0.5f, -0.5f);
                  GL::Vertex3(0.5f, 0.5f, -0.5f);
                  // Face inferior
                  GL::Color3(Color::Orange);
                  GL::Vertex3(0.5f, -0.5f, 0.5f);
                  GL::Vertex3(-0.5f, -0.5f, 0.5f);
                  GL::Vertex3(-0.5f, -0.5f, -0.5f);
                  GL::Vertex3(0.5f, -0.5f, -0.5f);
             }
             GL::End();
             glGraphicWindow->SwapBuffers();
         }
Aqui podemos observar algumas coisas. Adicionamos uma variável angle, que indicará o ângulo que vamos girar nosso cubo. Usamos o Vector3::UnitY para indicar que vamos rotacionar no eixo Y. Observe também que multiplicamos o Vetor UnitZ por 3 ao usá-lo na criação de nossa câmera. Isto facilitará nossa visualização do cubo. Por fim, agrupamos todos os vértices dentro de um par de chaves, para melhor visibilidade do código.
Depois disto, devemos criar a variável angle, como membro de nossa janela:
public: float angle;
Ainda criaremos um objeto Timer, e definimos seu evento Tick como:
private: System::Void tmrAnimate_Tick(System::Object^  sender, System::EventArgs^  e) 
         {
             angle+= 5.0f;
             glGraphicWindow->Invalidate();
         }
Observamos acima que o ângulo é incrementado a cada evento. Depois disto, chamamos a função Invalidate, para que o controle GLControl redesenhe a cena. Por fim, temos que modificar a função Load do controle GLControl:
private: System::Void glGraphicWindow_Load(System::Object^  sender, System::EventArgs^  e) 
         {
             GL::ClearColor(Color::Black);
             GL::Enable(EnableCap::DepthTest);
             SetupViewport();
             angle = 0.0f;
             tmrAnimate->Enabled = true;
         }
Aqui, nós habilitamos o teste de profundidade. Ele é necessário para que o OpenGL verifique se existe algum retângulo ou triângulo na frente de outros, tomando as medidas necessárias. Além do mais, nós inicializamos aqui a variável angle além de nosso Timer.
Se tudo der certo, nosso cubo estará girando na tela...

Conclusão

No post de hoje trabalhamos com figuras 3D. No próximo post, tentaremos adicionar alguns efeitos mais interessantes em nossa cena.

Nenhum comentário:

Postar um comentário

Você também poderá gostar de