Mostrando postagens com marcador Programação. Mostrar todas as postagens
Mostrando postagens com marcador Programação. Mostrar todas as postagens

quinta-feira, 26 de novembro de 2015

Criando uma aplicação CLR/CLI no Visual Studio 2015

Aqueles que acompanham este blog já devem ter percebido que prefiro utilizar o Visual Studio para elaborar meus programas para Windows. Ali eu tenho usado mais o CLR/CLI, que é uma versão do C++ que se aproxima bastante do C#.
Usei por um bom tempo o Visual Studio 2010, e ali existem opções pré-definidas para se iniciar um projeto CLR/CLI que facilitam bastante a vida do programador. No entanto, descobri com uma certa surpresa que a versão 2015 já não possui estas opções, obrigando o programador a criar todo o projeto manualmente. E isto me levou a buscar como eu poderia fazer isto.
Já que esta é uma informação bem específica, e que alguém talvez queira encontrar, resolvi compartilhar aqui no blog. Espero facilitar a vida de alguém com isto. Vamos ao código?

Criando o projeto

Primeiro, vamos abrir o Visual Studio e selecionar o Menu FILE, New, Project.


A janela New Project vai abrir, e nela vamos selecionar Templates, Visual C++, CLR. Nesta opção, escolheremos CLR Empty Project. Então nas opções abaixo, vamos dar um nome para nosso projeto e escolher o diretório onde ele será criado (aqui em nosso exemplo, adotamos o nome Teste).


Clicando em OK, o Visual Studio vai criar seu projeto. No entanto, o projeto não possui nenhum arquivo ainda. Por isto, vamos criar o arquivo principal do projeto. Clicamos então com o botão direito em Source Files, depois selecionamos Add, New Item.


Escolhemos agora a opção C++ File. Para seguirmos uma padronização, vamos dar o mesmo nome do projeto para este arquivo: Teste.cpp.


Clicando em Add, nosso arquivo principal é criado. No entanto, ele ainda está vazio. Precisamos agora inserir o código necessário para a inicialização de nossa aplicação:

#include "MainForm.h"

using namespace System;
using namespace System::Windows::Forms;


[STAThread]
void Main(array<String^>^ args)
{
 Application::EnableVisualStyles();
 Application::SetCompatibleTextRenderingDefault(false);

 Teste::MainForm form;
 Application::Run(%form);
}

Este é um código bem simples. A primeira linha contém um include, que vai adicionar o código para o nosso Form (vamos adicioná-lo adiante). As duas próximas linhas dizem ao compilador quais namespaces vamos usar neste arquivo, no caso, System e System::Windows::Forms. Só precisamos destes dois para dar início à nossa aplicação.
A cláusula [STAThread] basicamente habilita a comunicação com componentes COM. Muitos componentes do sistema utilizam esta tecnologia, como por exemplo as caixas de diálogo.
Em seguida temos nossa tradicional função Main.
O método EnableVisualStyles habilita cores, fontes e outros elementos visuais que formam um tema do sistema operacional. Já o método SetCompatibleTextRenderingDefault define o valor padrão para a propriedade UseCompatibleTextRendering que existe em vários componentes, de forma global.
Logo em seguida, criamos uma instância de nosso form, como é definido pelo cabeçalho MainForm.h. Depois de defini-lo, inicializamos a aplicação, passando-o como argumento.
Feito tudo isto, vamos adicionar nosso Form. Clicando com o botão direito em cima do nome do projeto (Teste) na Solution Explorer, selecionamos Add, New Item.


Na janela Add New Item, vamos escolher UI, Windows Form. Para refletir o que fizemos no arquivo Teste.cpp, vamos chamar este arquivo de MainForm.h.


Com isto, o Visual Studio vai criar nosso Form. Mas espere, ainda não acabou! Temos ainda que fazer algumas últimas configurações no nosso projeto, para informar ao Visual Studio qual será o ponto de entrada de nosso projeto. Para isto, vamos clicar novamente no nome do projeto (Teste) na Solution Explorer, e escolher Properties (última opção). Ou então o menu PROJECT, Properties.
A primeira opção que devemos alterar é a opção Subsystem, que se encontra em Configuration Properties, Linker, System. Esta opção afeta o símbolo do ponto de entrada que é escolhido pelo Linker. Devemos defini-lo como Windows.


Agora vamos selecionar a aba Advanced. Aqui vamos alterar a opção Entry Point, definindo-a para Main, que é o nome da nossa função principal, definida no arquivo Teste.cpp.


Com isto, nosso programa está pronto para ser compilado!
Espero que este texto possa ajudar quem estiver com a mesma necessidade.

sexta-feira, 11 de abril de 2014

Luzes!!!

Depois de um bom tempo sem postagens, resolvi retomar a nossa série sobre OpenGL no Visual Studio. No último post, nós aplicamos uma textura a um cubo que criamos. Se você observar naquele exemplo, todas as faces apareciam com a mesma coloração. Isto acontece por que não habilitamos ainda o sistema de luzes do OpenGL, e é o que faremos agora.

Mudando a função Load

Os primeiros passos para se habilitar a iluminação serão feitos na função Load de nosso Form. Vamos expandir nossa função para que o resultado fique como está apresentado abaixo:

float light_ambient[4] = { 0.5f, 0.5f, 0.5f, 1.0f };

     GL::Enable(EnableCap::Texture2D);
     GL::ShadeModel(ShadingModel::Smooth);
     GL::ClearColor(0.0f, 0.0f, 0.0f, 0.5f);
     GL::ClearDepth(1.0f);
     GL::Enable(EnableCap::DepthTest);
     GL::DepthFunc(DepthFunction::Lequal);
     GL::Light(LightName::Light0, LightParameter::Ambient, light_ambient);
     GL::Enable(EnableCap::Lighting);
     GL::Enable(EnableCap::Light0);
     textureID = loadTexture("Crate.bmp");
     SetupViewport();
     angle = 0.0f;
     tmrAnimate->Enabled = true;
Como pode-se ver, adicionamos algumas funções novas aqui. Em primeiro lugar, definimos um vetor de 4 posições, que vão indicar as cores de nossa luz ambiente.
Depois de habilitar as texturas, adicionamos a função ShadeModel, que definirá os modelos de sombra que vamos empregar. Adicionamos ainda depois da função ClearColor a função ClearDepth, que define o Buffer de profundidade. Sendo tudo isto feito, habilitamos o teste de profundidade e definimos a função para o teste de profundidade como sendo DepthFunction::Lequal.
É neste momento que iniciamos a parte de iluminação. A função GL::Light cria para nós um tipo de luz. O primeiro parâmetro é o nome da luz criada, e é através deste nome que poderemos depois alterá-la. O segundo parâmetro é o tipo de luz que estamos criando, e por isto aqui definimos uma luz ambiente. O terceiro parâmetro é a cor da luz, que definimos com o vetor light_ambient.
Criada nossa luz, vamos então habilitar a iluminação, depois a nossa luz.

Criando nosso desenho

Algumas mudanças também são necessárias na criação de nosso desenho. Agora que temos iluminação, devemos definir a Normal de cada face de nosso cubo. É baseando-se nesta face que a iluminação de cada face será calculada. Adicionando-se portanto a Normal de cada uma, o código para o desenho ficará:
      // Face frontal
      GL::Normal3(0.0f, 0.0f, -1.0f);
      GL::TexCoord2(1.0f, 1.0f);
      GL::Vertex3(0.5f, 0.5f, -0.5f);
      GL::TexCoord2(0.0f, 1.0f);
      GL::Vertex3(-0.5f, 0.5f, -0.5f);
      GL::TexCoord2(0.0f, 0.0f);
      GL::Vertex3(-0.5f, -0.5f, -0.5f);
      GL::TexCoord2(1.0f, 0.0f);
      GL::Vertex3(0.5f, -0.5f, -0.5f);
      // Face traseira
      GL::Normal3(0.0f, 0.0f, 1.0f);
      GL::TexCoord2(1.0f, 1.0f);
      GL::Vertex3(0.5f, -0.5f, 0.5f);
      GL::TexCoord2(0.0f, 1.0f);
      GL::Vertex3(-0.5f, -0.5f, 0.5f);
      GL::TexCoord2(0.0f, 0.0f);
      GL::Vertex3(-0.5f, 0.5f, 0.5f);
      GL::TexCoord2(1.0f, 0.0f);
      GL::Vertex3(0.5f, 0.5f, 0.5f);
      // Face Direita
      GL::Normal3(1.0f, 0.0f, 0.0f);
      GL::TexCoord2(1.0f, 1.0f);
      GL::Vertex3(0.5f, 0.5f, 0.5f);
      GL::TexCoord2(0.0f, 1.0f);
      GL::Vertex3(0.5f, -0.5f, 0.5f);
      GL::TexCoord2(0.0f, 0.0f);
      GL::Vertex3(0.5f, -0.5f, -0.5f);
      GL::TexCoord2(1.0f, 0.0f);
      GL::Vertex3(0.5f, 0.5f, -0.5f);
      // Face esquerda
      GL::Normal3(-1.0f, 0.0f, 0.0f);
      GL::TexCoord2(1.0f, 1.0f);
      GL::Vertex3(-0.5f, 0.5f, 0.5f);
      GL::TexCoord2(0.0f, 1.0f);
      GL::Vertex3(-0.5f, -0.5f, 0.5f);
      GL::TexCoord2(0.0f, 0.0f);
      GL::Vertex3(-0.5f, -0.5f, -0.5f);
      GL::TexCoord2(1.0f, 0.0f);
      GL::Vertex3(-0.5f, 0.5f, -0.5f);
      // Face superior
      GL::Normal3(0.0f, 1.0f, 0.0f);
      GL::TexCoord2(1.0f, 1.0f);
      GL::Vertex3(0.5f, 0.5f, 0.5f);
      GL::TexCoord2(0.0f, 1.0f);
      GL::Vertex3(-0.5f, 0.5f, 0.5f);
      GL::TexCoord2(0.0f, 0.0f);
      GL::Vertex3(-0.5f, 0.5f, -0.5f);
      GL::TexCoord2(1.0f, 0.0f);
      GL::Vertex3(0.5f, 0.5f, -0.5f);
      // Face inferior
      GL::Normal3(0.0f, -1.0f, 0.0f);
      GL::TexCoord2(1.0f, 1.0f);
      GL::Vertex3(0.5f, -0.5f, 0.5f);
      GL::TexCoord2(0.0f, 1.0f);
      GL::Vertex3(-0.5f, -0.5f, 0.5f);
      GL::TexCoord2(0.0f, 0.0f);
      GL::Vertex3(-0.5f, -0.5f, -0.5f);
      GL::TexCoord2(1.0f, 0.0f);
      GL::Vertex3(0.5f, -0.5f, -0.5f);
Depois de aplicar estas alterações, nosso cubo ficará assim:


As faces já não possuem o mesmo nível de iluminação, como na imagem anterior:


segunda-feira, 11 de novembro de 2013

Texturas em OpenGL

Em nossos últimos posts, estávamos tratando de como usar o OpenGL no Visual Studio. No último post nós criamos um cubo e fizemos ele girar em torno de seu eixo. Neste post vamos aplicar uma textura às faces deste cubo.

Criando texturas

Como estamos usando um cubo, nada melhor do que uma textura de uma caixa para ilustrar nosso exemplo. Estamos portanto pegando a textura de exemplo dos tutoriais da Nehe Productions.
Como aplicar esta textura às faces de nosso cubo?
Bem, o primeiro passo é habilitar as texturas, através do comando:
GL::Enable(EnableCap::Texture2D);
Assim, habilitamos as texturas. Depois disto, precisamos criar uma textura e atribuir a ela uma imagem. Quando criarmos nosso cubo, devemos então indicar qual o ponto da textura que estamos empregando.
Para criar e carregar a textura, vamos criar uma função, assim como no tutorial da Widget-101, que pode ser lido em espanhol para aqueles que querem fazer o mesmo em C#. A nossa função ficará como está descrito abaixo:
public: int loadTexture(String^ filename)
        {
                int id = GL::GenTexture();

                GL::BindTexture(TextureTarget::Texture2D, id);
                //LOAD FILE
                Bitmap^ image;
                try 
                { 
                        image = gcnew Bitmap(filename);
                }
                catch (...) 
                { 
                        MessageBox::Show("Imagem não pode ser carregada", "Erro", MessageBoxButtons::OK, MessageBoxIcon::Exclamation);
                        return -1; 
                };

                BitmapData^ imageData = image->LockBits(Rectangle(0, 0, image->Width, image->Height), ImageLockMode::ReadOnly, System::Drawing::Imaging::PixelFormat::Format32bppRgb);
                GL::TexImage2D(TextureTarget::Texture2D, 0, PixelInternalFormat::Rgb, imageData->Width, imageData->Height, 0, OpenTK::Graphics::OpenGL::PixelFormat::Bgra, PixelType::UnsignedByte, imageData->Scan0);
                image->UnlockBits(imageData);
                //TEXTURE PROPERTY
                GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMinFilter, (int)TextureMinFilter::Linear);
                GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMagFilter, (int)TextureMagFilter::Linear);
                return id;
        }
A primeira função nova é GenTexture, que informa ao OpenGL que queremos gerar um nome de textura. Depois disto usamos a função BindTexture, para vincular o nome que criamos a dados de textura. Isto na prática informa ao OpenGL que estamos usando a textura fornecida no segundo argumento. O próximo passo é carregar a imagem. Isto é feito através do objeto Bitmap, que pega o caminho da imagem passado como argumento para a função.
Depois disto, usamos o objeto BitmapData. Este objeto é o que nos dá acesso aos dados do Bitmap, a seus pixels. A propriedade Scan0 deste objeto é o que nos permite fazer isto, e ele será usado como argumento para a função TexImage2D do OpenGL. É esta a função que carrega os dados da imagem para o OpenGL. O que esta função recebe como parâmetros?
O primeiro parâmetro é qual o tipo de textura que estamos carregando, aqui, uma textura 2D. O próximo parâmetro é o nível de detalhe da imagem, geralmente deixado em 0. O próximo parâmetro é a quantidade de componentes de dados. Como nossa textura é colorida, teremos 3 componentes, vermelho, azul, verde. Os dois próximos parâmetros são largura e altura da textura. O sexto parâmetro é a borda, que geralmente é deixada em 0. O sétimo parâmetro é o formato da imagem, que em nosso caso é Azul, Verde, Vermelho, Alpha Blending, nesta ordem. O próximo parâmetro, PixelType::UnsignedByte, informa que os dados são compostos por unsigned bytes. Por fim, fornecemos a propriedade Scan0 de nossa imagem.
As duas funções TexParameter definem algumas propriedades para quando a textura for maior que a imagem usada (a imagem terá que ser "esticada") ou quando a textura for menor que a imagem usada (a imagem terá que ser "compactada"). Os dois usam o filtro linear.

Usando as texturas

Agora que temos nossa textura, chegou a hora de usá-la. Nós escolhemos a textura com a mesma função que escolhemos uma textura ao criá-la: BindTexture. Como criamos só uma textura, nem precisamos chamá-la aqui. Mas se você criar mais de uma textura, é necessário selecionar qual você deseja através desta função. Um detalhe importante desta função é que ela não pode ser chamada entre GL::Begin e GL:End. Você sempre terá que chamá-la antes destas duas funções.
Dito isto, vamos agora reescrever nosso evento Paint:
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);
                  {
                           GL::Color3(Color::White);
                           // Face frontal
                           GL::TexCoord2(1.0f, 1.0f);
                           GL::Vertex3(0.5f, 0.5f, -0.5f);
                           GL::TexCoord2(0.0f, 1.0f);
                           GL::Vertex3(-0.5f, 0.5f, -0.5f);
                           GL::TexCoord2(0.0f, 0.0f);
                           GL::Vertex3(-0.5f, -0.5f, -0.5f);
                           GL::TexCoord2(1.0f, 0.0f);
                           GL::Vertex3(0.5f, -0.5f, -0.5f);
                           // Face traseira
                           GL::TexCoord2(1.0f, 1.0f);
                           GL::Vertex3(0.5f, -0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 1.0f);
                           GL::Vertex3(-0.5f, -0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 0.0f);
                           GL::Vertex3(-0.5f, 0.5f, 0.5f);
                           GL::TexCoord2(1.0f, 0.0f);
                           GL::Vertex3(0.5f, 0.5f, 0.5f);
                           // Face Direita
                           GL::TexCoord2(1.0f, 1.0f);
                           GL::Vertex3(0.5f, 0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 1.0f);
                           GL::Vertex3(0.5f, -0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 0.0f);
                           GL::Vertex3(0.5f, -0.5f, -0.5f);
                           GL::TexCoord2(1.0f, 0.0f);
                           GL::Vertex3(0.5f, 0.5f, -0.5f);
                           // Face esquerda
                           GL::TexCoord2(1.0f, 1.0f);
                           GL::Vertex3(-0.5f, 0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 1.0f);
                           GL::Vertex3(-0.5f, -0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 0.0f);
                           GL::Vertex3(-0.5f, -0.5f, -0.5f);
                           GL::TexCoord2(1.0f, 0.0f);
                           GL::Vertex3(-0.5f, 0.5f, -0.5f);
                           // Face superior
                           GL::TexCoord2(1.0f, 1.0f);
                           GL::Vertex3(0.5f, 0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 1.0f);
                           GL::Vertex3(-0.5f, 0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 0.0f);
                           GL::Vertex3(-0.5f, 0.5f, -0.5f);
                           GL::TexCoord2(1.0f, 0.0f);
                           GL::Vertex3(0.5f, 0.5f, -0.5f);
                           // Face inferior
                           GL::TexCoord2(1.0f, 1.0f);
                           GL::Vertex3(0.5f, -0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 1.0f);
                           GL::Vertex3(-0.5f, -0.5f, 0.5f);
                           GL::TexCoord2(0.0f, 0.0f);
                           GL::Vertex3(-0.5f, -0.5f, -0.5f);
                           GL::TexCoord2(1.0f, 0.0f);
                           GL::Vertex3(0.5f, -0.5f, -0.5f);
                  }
                  GL::End();
                  glGraphicWindow->SwapBuffers();
         }
Fizemos algumas modificações na função do último post. A primeira foi retirar todas as seleções de cores. Deixamos apenas a seleção da cor branca, no início do processo. Isto acontece por que o OpenGL mistura a cor da textura com a cor que você seleciona. Assim, se você usar vermelho, sua textura irá se misturar com o vermelho, dando uma textura avermelhada. Deixamos em branco, pois assim o OpenGL usará apenas a cor da textura.
A textura é aplicada através do comando TexCoord2, usada acima. Ela usa dois argumentos, que são as coordenadas X e Y da textura. Algo precisa ser dito sobre estas coordenadas. Elas variam entre 0 e 1, sendo que 0 é a origem do eixo e 1 é a coordenada máxima daquele eixo (largura ou altura da textura). Agora que nosso evento Paint está modificado, vamos modificar o evento Load para carregar nossa textura:
private: System::Void glGraphicWindow_Load(System::Object^  sender, System::EventArgs^  e) 
         {
                  GL::ClearColor(Color::Black);
                  GL::Enable(EnableCap::DepthTest);
                  GL::Enable(EnableCap::Texture2D);
                  textureID = loadTexture("Crate.bmp");
                  SetupViewport();
                  angle = 0.0f;
                  tmrAnimate->Enabled = true;
         }
Chamamos a função que criamos com o nome da imagem como parâmetro (a imagem está salva no mesmo diretório do executável). A variável textureID é uma variável global que usamos para salvar o ID de nossa textura. Agora que habilitamos as texturas e carregamos a textura do exemplo, podemos executar o programa, que nos dará o seguinte resultado:


O que acontece se mudarmos a cor de Branco para Azul?


E assim, terminamos a aplicação de texturas.

Conclusão

O programa de hoje ensina a aplicar vários tipos de texturas aos seus polígonos. Há ainda muitos outros efeitos que podemos usar em nossos programas, como transparência e névoa. Estes dois serão tratados no próximo post.

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.

quarta-feira, 6 de novembro de 2013

Desenhando figuras geométricas com o OpenGL e o OpenTK

Neste segundo post da nossa série sobre o OpenGL no Visual Studio, vamos tratar um pouco sobre o desenho de figuras geométricas. Para aqueles que desejam executar os passos aqui em C#, recomendo o post de Christiam Mena, que está em espanhol, mas é de fácil compreensão (e é uma das fontes de nosso texto, ao lado dos tutoriais da NeHe Productions).

Inicializando o ViewPort

Antes de desenharmos qualquer coisa em nosso GLControl, devemos primeiro definir algumas das propriedades do ViewPort, que é a janela de visualização de desenhos. No artigo de Christiam Mena, ele cria uma função separada para fazer esta inicialização, o que nos permite usar o mesmo código em mais de um local. A função fica 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();
            GL::Ortho(-1, 1, -1, 1, -1, 1);
            GL::MatrixMode(MatrixMode::Modelview);
            GL::LoadIdentity();
        }
O comando Viewport vai definir as dimensões da cena OpenGL, e por isto usamos as dimensões do próprio controle GLControl que criamos.
Depois disto, usamos o comando MatrixMode, com o parâmetro Projection. Isto vai instruir ao OpenGL que os próximos comandos irão afetar a matriz de projeção. LoadIdentity é similar a um reset. Ele vai reinicializar a matriz de projeção para seus valores padrão.
Já o comando Ortho cria o volume de visualização ortográfica.
Usamos novamente o comando MatrixMode para trabalharmos agora com a matriz Modelview. É nesta matriz que as informações de nosso modelo serão armazenadas. Como fizemos com a matriz de projeção, resetamos a matriz ModelView com o comando LoadIdentity.
Agora vamos empregar esta função no evento Load do controle GLControl:
private: System::Void glGraphicWindow_Load(System::Object^  sender, System::EventArgs^  e) 
         {
            GL::ClearColor(Color::Black);
            SetupViewport();
         }
O comando ClearColor já foi apresentado no último post da série. Chamando aqui nossa função SetupViewport, nosso ViewPort está inicializado.

Desenhando um polígono

O nosso polígono será desenhado na função Paint do controle GLControl. O código para desenhá-lo esta a seguir:
private: System::Void glGraphicWindow_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
         {
            GL::Clear(ClearBufferMask::ColorBufferBit | ClearBufferMask::DepthBufferBit);
            GL::Color3(Color::Red);
            GL::Begin(BeginMode::Quads);
            GL::Vertex2(-0.5f, 0.5f);
            GL::Vertex2(-0.5f, -0.5f);
            GL::Vertex2(0.5f, -0.5f);
            GL::Vertex2(0.5f, 0.5f);
            GL::End();
            glGraphicWindow->SwapBuffers();
         }
O código aqui foi baseado no código de Christiam. O comando Clear prepara o nosso buffer para o desenho. Color3 é o comando usado para escolher a cor que vamos usar para desenhar os vértices. É o comando Begin que informa que estamos iniciando o desenho de polígonos. Ele deve ser encerrado posteriormente por um comando End. Entre estes dois comandos, devemos informar os vértices de nosso desenho, e o parâmetro usado em Begin informará se estes vértices fazem parte de triângulos ou quadrados. Aqui informamos que vamos desenhar Quadrados.
Assim, o comando Vertex2 é o comando usado para criar um vértice 2D. Como são 4 vértices, criamos apenas um quadrado.
Por fim, depois do comando End, temos o SwapBuffers, que troca o buffer de trabalho com o buffer em exibição.
Agora, o Christiam observa aqui algo muito interessante. Se o seu GLControl for um retângulo (com uma largura diferente da altura), seus quadrados também ficarão assim. A solução para isto é calcular, na inicialização do ViewPort, a razão de aspecto de seu controle, e usar isto no comando Ortho. A função SetupViewport então ficará assim:
public: System::Void SetupViewport()
        {
            GL::Viewport(0, 0, glGraphicWindow->Width, glGraphicWindow->Height);
            GL::MatrixMode(MatrixMode::Projection);
            GL::LoadIdentity();
            GL::Ortho(-aspectRatio, aspectRatio, -1, 1, -1, 1);
            GL::MatrixMode(MatrixMode::Modelview);
            GL::LoadIdentity();
        }
Nosso quadrado agora poderá ficar assim:


Além disto, o controle GLControl possui uma propriedade Dock, que se definida para Fill, fará com que o controle ocupe toda a janela. Fazendo-se isto, poderemos redimensionar a janela, o que causará o mesmo efeito no controle. Observe que isto fará com que nosso retângulo mude de lugar ou até desapareça. Para mudar isto, crie o evento Resize do controle GLControl, e adicione o seguinte código:
private: System::Void glGraphicWindow_Resize(System::Object^  sender, System::EventArgs^  e) 
         {
            SetupViewport();
            glGraphicWindow->Invalidate();
         }
A função Invalidate invalida toda a cena. Isto na prática faz com que a cena seja redesenhada.

Trabalhando com cores

Como foi dito acima, o comando Color escolhe a cor que será usada. Quando desenhamos nosso quadrado, escolhemos a cor vermelha e mantivemos esta cor assim... No entanto, podemos escolher uma cor diferente para cada vértice do quadrado. Por exemplo no código abaixo:
private: System::Void glGraphicWindow_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
         {
            GL::Clear(ClearBufferMask::ColorBufferBit | ClearBufferMask::DepthBufferBit);
            GL::Begin(BeginMode::Quads);
            GL::Color3(Color::Red);
            GL::Vertex2(-0.5f, 0.5f);
            GL::Color3(Color::Blue);
            GL::Vertex2(-0.5f, -0.5f);
            GL::Color3(Color::Green);
            GL::Vertex2(0.5f, -0.5f);
            GL::Color3(Color::Yellow);
            GL::Vertex2(0.5f, 0.5f);
            GL::End();
            glGraphicWindow->SwapBuffers();
         }
Nós teremos o seguinte efeito:

Conclusão

Criamos aqui um quadrado a partir de 4 vetores e definimos cores diferentes para cada um deles. O próximo passo é seguir para o desenho 3D.

terça-feira, 5 de novembro de 2013

OpenGl com OpenTK no Visual Studio

Imagem gerada por Reza Ali no OpenGL.
A programação de gráficos é uma das partes mais importantes no desenvolvimento de jogos. E  é por isto que é importante saber como criar gráficos 3D em qualquer aplicação. Por isto, vamos iniciar uma pequena série sobre como usar o OpenGL em um aplicativo Windows. Escolhi o OpenGL por ser gratuito e ser bem disseminado. Eu vou também empregar o Visual Studio Express 2010, mais especificamente a sua versão C++/CLI. Esta versão é muito próxima do C#, então programadores nesta linguagem poderão facilmente adaptar seu código. Minha escolha por este ambiente é por mera preferência: eu já estou mais acostumado a programar ali. No momento que escrevo este post, há a versão 2013 para se fazer download. Escolhi não atualizar ainda, pois isto poderia levar um tempo que eu ainda não tenho, tanto para atualizar quanto para me acostumar com o novo ambiente. De qualquer forma, acredito que o que eu vou escrever aqui sirva para qualquer um dos dois.

Instalando o OpenGL

Antigamente quando se desejava usar o OpenGL em seus projetos, bastava incluir os cabeçalhos do OpenGL no seu programa e começar a usar suas funções. Isto é bem mais complicado em programas C++/CLI ou C#. Eles precisam importar cada função da DLL do OpenGL, antes de usá-las. Felizmente, algumas pessoas facilitaram nossa vida, criando wrappers para o OpenGL. Você instala o wrapper, e emprega as funções OpenGL, da mesma forma que fazia antes.
Um que me foi recomendado é o OpenTK. Não tive tempo de testar outros, mas provavelmente devem ter as mesmas funcionalidades. O OpenTK tem a vantagem de traduzir todas as variáveis de um ambiente não gerenciado para um ambiente gerenciado como o C# e o C++/CLI. O OpenTK tem um tutorial bem fácil de seguir, para aqueles que desejam se aventurar com ele.
Então, o primeiro passo é fazer o download do OpenTK e instalá-lo em qualquer diretório.
Depois disto, o tutorial informa, na parte de instalação, que devemos clicar em References na janela Solution Explorer do Visual Studio. Bem, no meu Visual Studio, esta opção não está na Solution Explorer diretamente, mas pode ser acessada clicando-se com o botão direito no nome do projeto, nesta janela. A janela de Referências será aberta...


Se você clicar no botão Add New Reference, vai aparecer uma lista de possíveis referências. O OpenTK se encontrará na aba .NET:


Adicione esta referência, clicando em OK. É importante observar que o projeto também deve ter uma referência a System.Drawing, então verifique se esta referência existe, e a adicione caso contrário.
Podemos agora adicionar um novo componente à nossa caixa de ferramentas. É interessante que o passo de se adicionar referências pode ser deixado de lado, caso você queira usar este componente. Trata-se do GLControl. Acontece que ao adicionar o GLControl à caixa de ferramentas já adiciona também as referências necessárias para se usá-lo. Caso não se opte por usar este componente (se você, por exemplo, quer usar toda a janela com o OpenGL), deverá adicionar as referências manualmente, como explicado acima.
Para adicionar este componente, basta clicar em algum lugar da caixa de ferramentas com o botão direito e escolher Choose Items. Uma lista de componentes disponíveis será exibida, e você deverá selecionar o componente GLControl nesta lista.


Ao clicarmos em OK, o controle aparecerá na caixa de ferramentas no grupo General. Como acontece com todos os componentes do Visual Studio, basta agora clicar no componente e criá-lo na nossa janela. Estarei criando nossa janela OpenGL com o nome de glGraphicWindow. Para empregarmos os comandos OpenGL em nosso código, devemos inserir as duas seguintes linhas abaixo, na área apropriada do código:
 using namespace OpenTK::Graphics;
 using namespace OpenTK::Graphics::OpenGL;
Depois de adicionar estas linhas, adicione o evento Paint de nosso glGraphicWindow. Depois de adicionar este evento, insira o seguinte código:
 private: System::Void glGraphicWindow_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
{
     GL::Clear(ClearBufferMask::ColorBufferBit | ClearBufferMask::DepthBufferBit);
     glGraphicWindow->SwapBuffers();
}
Usamos duas funções acima que são definidas pelo OpenGL. A função GL::Clear é o wrapper da função glClear do OpenGL, que limpa a tela do componente. Já o método SwapBuffers do nosso objeto GLControl faz a troca de buffers de imagem. Se neste momento mandarmos executar o programa, uma janela com uma parte preta será exibida:


Bem, uma janela assim não é muito significativa, não é? Mas por enquanto ela serve para ilustrar o uso do OpenGL. Vamos agora, para concluir, testar mais um último comando antes de inicializarmos o viewport. Para este comando, vamos adicionar também o evento Load do componente glGraphicWindow. O código adicionado será o seguinte:
 private: System::Void glGraphicWindow_Load(System::Object^  sender, System::EventArgs^  e) 
{
     GL::ClearColor(Color::Red);
}
O que fizemos agora é chamar a função glClearColor do OpenGL, que define a cor de fundo da janela que criamos. Observe que nesta função usamos uma definição do próprio C++/CLR para definir a cor de fundo. Pelo OpenGL teríamos que usar os valores RGB da cor desejada. Como resultado, temos a nova janela:

Conclusão

Esta é a primeira parte de nossa série sobre o OpenGL, que mostra alguns comandos básicos. Esperamos apresentar alguns recursos, suficientes para fazer uma pequena animação e controlá-la. No próximo post, esperamos tratar de algumas formas de desenho.

Você também poderá gostar de