65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 OpenTK 在 C# 中设计的模拟时钟

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (20投票s)

2010年12月23日

CPOL

5分钟阅读

viewsIcon

82491

downloadIcon

4573

一个使用 C# 中的 OpenTK 和 WinForms 设计的 2D 模拟时钟。

引言

Open Toolkit 是一个高级的、底层的 C# 库,它封装了 OpenGL、OpenCL 和 OpenAL。它适用于游戏、科学应用程序以及任何需要 3D 图形、音频或计算功能的项目。简而言之,它被称为 OpenTK。

背景

它不是 OpenGL 的快速 C# 实现,但它是最好的。以前有一些,比如 CSGLTAO framework 等,但它们不完整并受到一些限制。而现在,它们已经完全无法跟上 .NET Framework 的步伐了。

我们可以通过 GL 类调用每一个 OpenGL 函数,就像 OpenGL 中使用的 glVertex2f(-2.0,-2.0) 在 OpenTk 中被用作 GL.Vertex2(-2.0,-2.0)。它拥有一个非常漂亮且灵活的 GUI 选项,跨平台的 GLControl (Windows.Forms) 可以轻松添加到 Visual Studio 工具箱中,GLWidget 是另一个丰富且有用的组件 (用于 GTK#)以及 WPFControl 类。还有一个原生、高性能的 GameWindow,它是专门为游戏设计的。你可以比你想象的更快地开发游戏。

它还拥有一个非常有用的 API 集合,例如 3D Math Toolkit,它提供了 Vector、Matrix、Quaternion 和 Bezier 结构。 Input API 提供了键盘、鼠标和操纵杆接口。 Display API 帮助处理多显示器。OpenTK.Compatibility 支持 TAO framework 应用程序。

如果你想考虑平台独立性,它支持 Windows、Linux 和 Mac OS X 的 32 位和 64 位版本。无需混乱的库 - 编译一次,随处运行。最棒的是,你可以自由使用、修改和分发 源代码。它适用于开源和闭源项目。

使用代码

你不需要任何 OpenGL 或 C# 图形方面的先验知识来学习 OpenTK。只需要一些 Windows Form 设计的基础知识就足够了。

要使用 OpenTK,你需要在 Visual Studio 中添加两个 DLL 作为引用。它们是 OpenTK.dllOpenTK.Graphics.OpenGL.dll。两者都可以在这里轻松找到。

我们不打算使用众所周知的游戏窗口作为 OpenTK 的窗口,因为我猜你对 Windows Form 设计很熟悉。我们只是使用一个普通的 Windows Form。OpenTK 提供了一个非常好的控件/工具。为此,你需要将它添加到 Visual Studio 工具箱中,它叫做 Glcontrol,你可以在 这里找到。为此,首先点击工具箱并选择“项”,然后浏览并添加这个控件的 DLL。现在你就可以使用它了。

首先,创建一个 Form,你将在上面放置你的 GLControl。在工具箱的空白处右键单击,选择“选择项…”,然后浏览 OpenTK.GLControl.dll。确保你可以在 .NET Framework 组件中找到 GLControl,如下图所示。

Click to enlarge image

然后,你可以像添加任何 .NET 控件一样将 GLControl 添加到你的 Form 中。一个名为 glControl1GLControl 将被添加到你的 Form 中。

所以,首先将这个命名为 glControl1 的控件添加到你的端口,并编写下面的代码。还要将此方法添加到此控件的加载事件中。有关更多详细信息,你可以 点击这里

private void glControl1_Resize(object sender, EventArgs e)
{
   int w = glControl1.Width;
   int h = glControl1.Height;
   glControl1.MakeCurrent();
   GL.MatrixMode(MatrixMode.Projection);
   GL.LoadIdentity();
   GL.ClearColor(Color.SkyBlue);
   GL.Ortho(-w / 2, w / 2, -h / 2, h / 2, -1, 1);
   GL.Viewport(0, 0, w, h);
   GL.End();
   glControl1.SwapBuffers();
}

如果我们运行它,我们会发现一个如下所示的窗口

undefined

那么这是怎么实现的呢?很简单。首先,我们看到

glControl1.MakeCurrent();

它使接下来所有针对此控件的 GL 命令都生效。

GL.MatrixMode(MatrixMode.Projection);
GL.LoadIdentity();

我们将矩阵模式设置为投影,然后加载单位矩阵。对于 2D 投影,有四种模式,如果你想了解更多关于这些模式的信息,你可以 点击这里查看。

下一个命令非常重要,需要理解。为此,我们首先需要成为好的 OpenGL 开发者,并使用 GL.Ortho() 设置一个正射投影矩阵。我们还需要调用 GL.Viewport()

GL.Ortho(-w / 2, w / 2, -h / 2, h / 2, -1, 1);

这使得 GL 框的中心在 x、y 轴上为 0,0。因为 w 是 GL 框的宽度,h 是高度。如果你想让左下角像素点为 0,0,你可以这样写

GL.Ortho(0, w, 0, h, -1, 1)
GL.Viewport(0, 0, w, h);

Viewport 用于选择控件中的绘图区域。

GL.ClearColor(Color.SkyBlue);

它将其设置为蓝色。为了命令的清理和窗口缓冲区,我们必须写

GL.End();
glControl1.SwapBuffers();

现在,首先,我们必须在我们的 glcontrol 中绘制一个圆。对于 draw 方法,我们必须使用 glcontrolpaint 事件的 paint 方法,如下所示

private void glControl1_Paint(object sender, PaintEventArgs e)
{
  GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit)
  drawclock();
  glControl1.SwapBuffers();
}

void Draw_clock()
{
   drawCircle(80);//80 is radius of the circle
   Draw_digit();
}

对于 drawcircle,我使用了以下代码

void drawCircle(float radius)
{
    GL.Color3(Color.White);
    GL.Begin(BeginMode.TriangleFan);

    for (int i = 0; i < 360; i++)
    {
        double degInRad = i * 3.1416/180;
        GL.Vertex2(Math.Cos(degInRad) * radius, Math.Sin(degInRad) * radius);
    }
    GL.End;
}

现在,看起来一切都如此轻松。

GL.Begin(BeginMode.TriangleFan);

此模式使用提供的顶点绘制一个三角形并用白色填充。所有这些三角形构成了圆,就像三角形披萨块组成了一个圆形的披萨 :)

如果你想了解 GL.Begin,请参阅 这里。你必须记住在 GL.Begin(); 之后写 GL.End(); 否则什么也不会发生,编译器也不会给你任何错误。现在我们需要绘制一个数字以及分钟和小时的两条线。

编写简单的代码来进行普通的顶点操作。记住圆的半径是 80。

void Draw_digit()
{
    GL.MatrixMode(MatrixMode.Projection);
    GL.LoadIdentity();
    GL.Color3(Color.Red);
   //for hour
    GL.Begin(BeginMode.TriangleFan);

    GL.Vertex2(0, +5);
    GL.Vertex2(0, -5);
    GL.Vertex2(70, 0);
    GL.Vertex2(70, 0);
    GL.Color3(Color.Red);

    GL.End();

    //for minute
    GL.Begin(BeginMode.TriangleFan);
    GL.Vertex2(+5, 0);
    GL.Vertex2(-5, 0);
    GL.Vertex2(-65, 40);
    GL.Vertex2(-65, 40);
    GL.End();
    GL.Color3(Color.Black);

    //for draw digit III
    GL.Begin(BeginMode.Lines);
    GL.Vertex2(5, 60);
    GL.Vertex2(5, 70);
    GL.Vertex2(0, 60);
    GL.Vertex2(0, 70);

    GL.Vertex2(-5, 70);
    GL.Vertex2(-15, 60);
    GL.Vertex2(-15, 70);
    GL.Vertex2(-5, 60);

    GL.End();

    GL.Color3(Color.Black);
    //for draw digit XII
    GL.Begin(BeginMode.Lines);

    GL.Vertex2(60,0);
    GL.Vertex2(60,8);
    GL.Vertex2(70,0);
    GL.Vertex2(70,8);
    GL.Vertex2(65, 0);
    GL.Vertex2(65, 8);

    GL.End();
    GL.Color3(Color.Black);
    //for draw digit IV
    GL.Begin(BeginMode.Lines);

    GL.Vertex2(10, -60);
    GL.Vertex2(10, -70);
    GL.Vertex2(0, -60);
    GL.Vertex2(0, -70);
    GL.Vertex2(5, -60);
    GL.Vertex2(0, -70);
    GL.Vertex2(5, -60);
    GL.Vertex2(0, -70);

    GL.End();
    GL.Color3(Color.Black);
    //for draw digit IX
    GL.Begin(BeginMode.Lines);
    GL.Vertex2(-75,-5);
    GL.Vertex2(-75,-15);

    GL.Vertex2( -70,-5);
    GL.Vertex2(-60,-15);
    GL.Vertex2(- 70,-15);
    GL.Vertex2(-60,-5);
}

这是连接一个顶点到另一个顶点的代码。有关详细信息,你可以 这里查看类似的 OpenGL 函数。

绘制完成后,我们看到输出窗口如下所示

现在我们需要为此创建一个计时器事件,所以首先添加一个计时器,将其间隔设置为 1000(1秒),启用它,然后将此事件添加到 timer tick 中

private void timer1_Tick(object sender, EventArgs e)
{
   glControl2.MakeCurrent();
   PaintEventArgs p = null;
   glControl2_Paint(sender,p);
   GL.End();
}

现在 paint 方法将在 1 秒后被调用。你需要在那里绘制秒针,所以 paint 事件将是

private void glControl2_Paint(object sender, PaintEventArgs e)
{
    glControl2.MakeCurrent();

    GL.End();
    glControl2.SwapBuffers();
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    Draw_clock();
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    GL.End();
    glControl2.SwapBuffers();
    drawsecond();
    glControl2.SwapBuffers();
}

这里绘制秒针需要一个 static 变量。为了改变秒针顶点位置的坐标,我们取

static int i = 0;

现在是 drawsecond() 函数

void  drawsecond()
{
    GL.Color3(Color.Red);
    GL.Begin(BeginMode.Quads);

    GL.Vertex2(5, 0);
    GL.Vertex2(-5, 5);
    double degInRad = i * 3.1416 / 180;
    GL.Vertex2(Math.Cos(degInRad) * 80, Math.Sin(degInRad) * 80);
    GL.Vertex2(Math.Cos(degInRad) * 85, Math.Sin(degInRad) * 85);

    i = i - 6;
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

    GL.End();
}

这里 static 变量被减去是为了顺时针旋转,并减去 6,因为 360/6 等于 60,我们需要这个来圆整圆。

所以秒针正在旋转

关注点

对于 C#,我确信你找不到比 OpenTK 更好的 OpenGL 替代品了。但是,如果你想开发一个真正专业的游戏,我必须告诉你,你走的不是正确的方向。C++ 比 C# 更适合。如果你想直接用 C# 进行开发,请使用 DirectXXNA

历史

  • 2010 年 12 月 23 日:初次发布。
© . All rights reserved.