1

我正在使用 Winforms for GUI 在 C# 中编写应用程序。我想创建一个使用 OpenGL 显示 3D 图形的控件。

我用 OpenTK 做了一些实验,发现使用它的 GLControl 来显示 OpenGL 图形相当简单;我只需要实现一个处理 GLControl 的 Paint 事件的方法,并在此方法中调用 OpenTK 包装器方法,例如,而不是

glVertex2d(0.0, 0.0);

我会打电话给:

GL.Vertex2(0.0, 0.0);

我还发现可以将 OpenTK 与使用 P/Invoke 直接调用 OpenGL 混合:

using System;
using System.Runtime.InteropServices;
public class gl
{
    [DllImport("opengl32")]
    public static extern void glVertex2d(double x, double y);
}; 
...
GL.Vertex2(0.0, 0.0);    // OpenTK
gl.glVertex2d(1.0, 1.0); // OpenGL directly using Pinvoke

但是,我希望完全消除对 OpenTK 的依赖。我不想使用任何外部库,而是直接调用 OpenGL。

如何让 OpenGL 在 Winform 上“绘制”?换句话说; 我如何自己实现 GLControl?

4

1 回答 1

3

我如何自己实现 GLControl?

OpenTK 或多或少是如何做的;因为它已经开源了,你可以学习它!

我警告你:这并不容易。您必须将窗口/控件创建与 OpenGL 上下文创建(覆盖CreateHandleOnHandleCreatedOnHandleDestroyed)同步,并覆盖OnPaint例程以使 OpenGL 上下文处于当前状态(实际上允许在绘制阶段进行绘图)。

只是给你一个想法:这是我的 UserControl(部分)实现:

protected override void CreateHandle()
{
    // Create the render window
    mRenderWindow = new RenderWindow(this);
    mRenderWindow.Width = (uint)base.ClientSize.Width;
    mRenderWindow.Height = (uint)base.ClientSize.Height;

    // "Select" device pixel format before creating control handle
    switch (Environment.OSVersion.Platform) {
        case PlatformID.Win32Windows:
        case PlatformID.Win32NT:
            mRenderWindow.PreCreateObjectWgl(SurfaceFormat);
            break;
        case PlatformID.Unix:
            mRenderWindow.PreCreateObjectX11(SurfaceFormat);
            break;
    }

    // OVerride default swap interval
    mRenderWindow.SwapInterval = SwapInterval;

    // Base implementation
    base.CreateHandle();
}

/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Control.HandleCreated"/> event.
/// </summary>
/// <param name="e">
/// An <see cref="T:System.EventArgs"/> that contains the event data.
/// </param>
protected override void OnHandleCreated(EventArgs e)
{
    if (DesignMode == false) {
        // Finalize control handle creation
        // - WGL: SetPixelFormat
        // - GLX: store FBConfig and XVisualInfo selected in CreateHandle()
        // ...
        // - Setup swap interval, if supported
        mRenderWindow.Create((RenderContext)null);
        // Create the render context (before event handling)
        mRenderContext = new RenderContext(mRenderWindow.GetDeviceContext(), mRenderContextFlags);
    }
    // Base implementation
    base.OnHandleCreated(e);
    // Raise CreateContext event
    if (DesignMode == false) {
        mRenderContext.MakeCurrent(true);
        RaiseCreateContextEvent(new RenderEventArgs(mRenderContext, mRenderWindow));
        mRenderContext.MakeCurrent(false);
    }
}

/// <summary>
/// 
/// </summary>
/// <param name="e"></param>
protected override void OnHandleDestroyed(EventArgs e)
{
    if (DesignMode == false) {
        if (mRenderContext != null) {
            // Raise DestroyContext event
            mRenderContext.MakeCurrent(true);
            RaiseDestroyContextEvent(new RenderEventArgs(mRenderContext, mRenderWindow));
            mRenderContext.MakeCurrent(false);
            // Dispose the renderer context
            mRenderContext.Dispose();
            mRenderContext = null;
        }
        // Dispose the renderer window
        if (mRenderWindow != null) {
            mRenderWindow.Dispose();
            mRenderWindow = null;
        }
    }
    // Base implementation
    base.OnHandleDestroyed(e);
}

/// <summary>
/// 
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
    if (DesignMode == false) {
        if (mRenderContext != null) {
            // Render the UserControl
            mRenderContext.MakeCurrent(true);
            // Define viewport
            OpenGL.Gl.Viewport(0, 0, ClientSize.Width, ClientSize.Height);

            // Derived class implementation
            try {
                RenderThis(mRenderContext);
            } catch (Exception exception) {

            }

            // Render event
            RaiseRenderEvent(new RenderEventArgs(mRenderContext, mRenderWindow));

            // Swap buffers if double-buffering
            Surface.SwapSurface();

            // Base implementation
            base.OnPaint(e);

            mRenderContext.MakeCurrent(false);
        } else {
            e.Graphics.DrawLines(mFailurePen, new Point[] {
                new Point(e.ClipRectangle.Left, e.ClipRectangle.Bottom), new Point(e.ClipRectangle.Right, e.ClipRectangle.Top),
                new Point(e.ClipRectangle.Left, e.ClipRectangle.Top), new Point(e.ClipRectangle.Right, e.ClipRectangle.Bottom),
            });

            // Base implementation
            base.OnPaint(e);
        }
    } else {
        e.Graphics.Clear(Color.Black);
        e.Graphics.DrawLines(mDesignPen, new Point[] {
                new Point(e.ClipRectangle.Left, e.ClipRectangle.Bottom), new Point(e.ClipRectangle.Right, e.ClipRectangle.Top),
                new Point(e.ClipRectangle.Left, e.ClipRectangle.Top), new Point(e.ClipRectangle.Right, e.ClipRectangle.Bottom),
            });

        // Base implementation
        base.OnPaint(e);
    }
}

protected override void OnClientSizeChanged(EventArgs e)
{
    if (mRenderWindow != null) {
        mRenderWindow.Width = (uint)base.ClientSize.Width;
        mRenderWindow.Height = (uint)base.ClientSize.Height;
    }

    // Base implementation
    base.OnClientSizeChanged(e);
}

private static readonly Pen mFailurePen = new Pen(Color.Red, 1.5f);

private static readonly Pen mDesignPen = new Pen(Color.Green, 1.0f);

#endregion

(RenderWindow 是一个用于选择设备像素格式的实用程序类)。实现的关键是您需要模仿经典 C++ 程序所做的相同调用,并与实际的 System.Windows.Forms 实现(.NET 和 Mono)集成。

但最大的任务是使用 P/Invokes 定义 OpenGL API:OpenGL 定义了很多枚举数(常量)和入口点(数千个......);作为人类,您不能手动编写所有这些声明(主要是在有限的时间内,但也容易出错)。如果你不喜欢 OpenTK,你可以从 TAO 框架开始。如果您有时间和耐心,您可以自己生成 C# 绑定,使用OpenGL 注册表

但是……等等,好消息(你很幸运):XML OpenGL API 定义是公开的!(!)

(!)对于那些不明白的人热情的回答:我等了很多很多很多年!

于 2013-07-31T19:26:40.843 回答