Gadgets com OpenGL e WindowsForms
3D nos Gadgets da Sidebar
No post anterior foi falado sobre um método para usar 3D acelerado por hardware em um Gadget da sidebar do vista usando Direct3D. A principal intenção desse post era mostrar um pequeno exemplo como prova de conceito, com a intenção de mostrar que é sim possível fazer um gadget que exiba conteúdo 3D de maneira eficiente. O Direct3D foi usado por ser uma tecnologia da Microsoft e portanto provavelmente mais fácil de integrar com as outras tecnologias envolvidas na produção do gadget.
O vídeo abaixo mostra o gadget Direct3D em ação. Note que ele é muito simples, servindo apenas mesmo como uma prova de conceito.
Integrando o OpenGL
Para os propósitos da criação do gadget com V-ART o ideal seria usar OpenGL para os gráficos ao invés de Direct3D. A justificativa para essa preferência é pela praticidade que vem do fato de o V-ART já ter um backend em OpenGL implementado. Então o próximo passo realizado foi fazer a mesma integração de 3D nos gadgets, porém, dessa vez, usando OpenGL para renderizar 3D.
Realizar essa tarefa envolveu a pesquisa de bibliotecas que provessem um port de OpenGL para .NET. Buscas iniciais apontavam para o projeto
CSGL como a solução ideal, no entanto sua página diz claramente que o projeto não é mais mantido. Felizmente a própria página do projeto aponta para a solução mais atual: o
Tao Framework. O Tao é um conjunto de wrappers para diversas bibliotecas relacionadas a desenvolvimento de jogos, incluindo a OpenGL e a GLUT.
Depois de encontrar o conjunto de ferramentas adequado o próximo passo foi replicar a funcionalidade existente no gadget com Direct3D em uma versão com OpenGL. O gadget em Direct3D foi construído a partir de um componente WindowsForm derivado da classe UserControl. O código resumido dessa classe está descrito abaixo com a intenção de mostrar sua interface.
public abstract partial class DirectXBase : UserControl
{
protected Device device = null;
protected PresentParameters pp = new PresentParameters();
protected bool initialized = false;
public bool Initialized {
get { return initialized; }
}
public virtual void InitializeGraphics();
public virtual void OnDeviceReset(object sender, EventArgs e);
protected abstract void Render();
protected override void OnPaint(PaintEventArgs e)
{
device.BeginScene();
Render(); //call abstract Render which derived types will define
device.EndScene();
device.Present();
}
protected override void OnPaintBackground(PaintEventArgs e);
protected override void OnSizeChanged(EventArgs e);
}
Para implementar a versão OpenGL houveram problemas devido à pouca informação disponível sobre a integração de OpenGL e WindowsForms. No início eu tentei escrever do zero a classe gerenciando a abertura do contexto do OpenGL no braço, baseado em exemplos que acompanham o Tao. Isso se mostrou uma tarefa bastante complicada. Ao ver que perderia muito tempo procurei um caminho mais simples e encontrei. O Tao tem uma classe chamada SimpleOpenGlControl e foi herdando dessa classe que eu implementei a OpenGLBase:
public abstract partial class OpenGLBase : SimpleOpenGlControl
{
private bool openGLInitialized = false;
public OpenGLBase():
base()
{
this.InitializeGraphics();
}
protected bool initialized = false;
public bool Initialized
{
get { return initialized; }
}
public virtual void InitializeGraphics()
{
this.AccumBits = ((System.Byte)(0));
this.AutoCheckErrors = false;
this.AutoFinish = false;
this.AutoMakeCurrent = true;
this.AutoSwapBuffers = true;
this.BackColor = System.Drawing.Color.Black;
this.ColorBits = ((System.Byte)(32));
this.DepthBits = ((System.Byte)(16));
this.StencilBits = ((System.Byte)(0));
this.TabIndex = 1;
}
protected abstract void Render();
protected override void OnPaint(PaintEventArgs e)
{
if (!openGLInitialized)
{
Graphics graphics = e.Graphics;
graphics.FillRectangle(Brushes.Black, new Rectangle(0, 0, Width, Height));
return;
}
Gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
Gl.glLoadIdentity();
Render();
base.OnPaint(e);
}
protected override void OnLoad(EventArgs e)
{
this.InitializeContexts();
ReSizeGLScene(this.Width, this.Height);
this.openGLInitialized = true;
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//do nothing to eliminate flicker
}
protected override void OnSizeChanged(EventArgs e)
{
ReSizeGLScene(this.Width, this.Height);
Invalidate();
}
private void ReSizeGLScene(int width, int height)
{
if (height == 0) { height = 1; }
Gl.glViewport(0, 0, width, height);
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glLoadIdentity();
Glu.gluPerspective(45, width / (double)height, 0.1, 100);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glLoadIdentity();
}
}
Até chegar nesse código vários bugs apareceram devido a pequenas complicações. Como o OpenGL não tem uma versão Managed OpenGL e sim apenas um wrapper que expõe a API original de C para o C# as coisas são um pouco mais complicadas do que no Direct3D. A classe concreta que faz algo em OpenGL aparecer na tela segue abaixo:
[ComVisible(true), Guid("8A24CBEC-3E5E-4f7e-A7E4-4FA6844EB8D8")]
public partial class GadgetSceneGL : OpenGLBase
{
int lastMouseX;
float angleY = 0.0f;
public GadgetSceneGL():
base()
{
InitializeComponent();
}
protected override void Render()
{
Glu.gluLookAt(
0.0, 0.0, 20.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0
);
Gl.glRotatef(angleY, 0, 1, 0);
Gl.glColor3f(1.0f, 1.0f, 1.0f);
Gl.glBegin(Gl.GL_TRIANGLES);
Gl.glVertex3d(0.0, 5.0, 0.0);
Gl.glVertex3d(5.0, -5.0, 0.0);
Gl.glVertex3d(-5.0, -5.0, 0.0);
Gl.glEnd();
}
protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
lastMouseX = e.X;
}
}
protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
angleY += (lastMouseX - e.X) * ((float)Math.PI / 180.0f);
lastMouseX = e.X;
}
base.OnMouseMove(e);
Invalidate();
}
}
O objeto desenhado pelo gadget é ainda mais simples - apenas um triângulo - que no Direct3D pois a glut ainda não está funcionando e por isso não posso usar a função para desenhar teapots. Isso provavelmente estará resolvido em breve. Amanhã postarei um vídeo do gadget OpenGL funcionando.