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.

Last edited Oct 21, 2008 at 4:53 PM by kcfelix, version 11

Comments

No comments yet.