我如何自己实现 GLControl?
OpenTK 或多或少是如何做的;因为它已经开源了,你可以学习它!
我警告你:这并不容易。您必须将窗口/控件创建与 OpenGL 上下文创建(覆盖CreateHandle
、OnHandleCreated
、OnHandleDestroyed
)同步,并覆盖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 定义是公开的!(!)
(!)对于那些不明白的人热情的回答:我等了很多很多很多年!