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

使用 OpenGL 控件进行 2D 绘图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (27投票s)

2007年1月25日

CPOL

2分钟阅读

viewsIcon

216020

downloadIcon

7779

一篇关于如何创建用于使用 OpenGL 进行 2D 形状绘图的用户控件的文章

Sample Image - GLView.png

引言

我最近编写了一些代码来为有限元程序显示大量的简单 2D 形状。我最初的想法是使用 GDI。但是,由于我需要将数千个形状显示在屏幕上,GDI 对我来说太慢了。解决方案是使用 OpenGL 控件。 在本文中,我将尝试解释我是如何使用 OpenGL 创建一个用于 2D 形状绘制的控件的。

Using the Code

源代码包含一个名为 GLCanvas2D 的控件,该控件基于 System.Windows.Forms.UserControl。 OpenGL 特定的初始化代码如下:

GLCanvas2D::GLCanvas2D()
{
    /* User control initialization code goes here
    ....
    .... */

    // Get a handle to the device context
    mhDC = GetDC((HWND)this->Handle.ToPointer());

    // Choose a pixel format
    PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),     // size of the structure
        1,                                 // version
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
                        // flags
        PFD_TYPE_RGBA,                     // pixel type
        32,                                // color bits
        0, 0, 0, 0, 0, 0, 0, 0,            // RGBA bits and shifts
        0,                                 // accumulation buffer bits
        0, 0, 0, 0,                        // accumulation buffer RGBA bits
        32,                                // depth bits
        24,                                // stencil bits
        0,                                 // aux bits
        PFD_MAIN_PLANE,                    // layer type
        0,                                 // reserved
        0, 0, 0                            // layer masks
    };

    // Set the pixel format
    int iPixelFormat = ChoosePixelFormat(mhDC, &pfd);
    SetPixelFormat(mhDC, iPixelFormat, &pfd);

    // Create the render context
    mhGLRC = wglCreateContext(mhDC);
    wglMakeCurrent(mhDC, mhGLRC);
}

这段代码在 GLCanvas2D 的构造函数中调用。 析构函数会删除渲染上下文并释放设备上下文。

GLCanvas2D::~GLCanvas2D()
{
    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(mhGLRC);
    ReleaseDC((HWND)this->Handle.ToPointer(), mhDC);
}

我使用两个变量来平移和缩放视图:一个包含相机位置的点结构和一个缩放因子。 在每个绘制事件中,投影矩阵都使用这两个变量设置。

// Set an orthogonal projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(mCameraPosition.X -
        ((float)ClientRectangle.Width) * mZoomFactor / 2, // Left
    mCameraPosition.X +
        ((float)ClientRectangle.Width) * mZoomFactor / 2, // Right
    mCameraPosition.Y -
        ((float)ClientRectangle.Height) * mZoomFactor / 2, // Bottom
    mCameraPosition.Y +
        ((float)ClientRectangle.Height) * mZoomFactor / 2, // Top
    -1.0f,     // Near plane
    1.0f);     // Far plane

当用户平移(按住鼠标滚轮)或缩放(滚动鼠标滚轮)时,计算相机位置和缩放因子。 我还需要一些方法将坐标从屏幕坐标转换为模型坐标,反之亦然。

/// <summary>
/// Converts the given point from world coordinates to screen coordinates.
/// </summary>
Drawing::Point WorldToScreen(float x, float y)
{
    // Move the given point to the origin, divide by the zoom factor and
    // add the screen coordinates of the center point
    return Drawing::Point(
        (int)((x - mCameraPosition.X) / mZoomFactor) +
            ClientRectangle.Width / 2,
        -(int)((y - mCameraPosition.Y) / mZoomFactor) +
            ClientRectangle.Height / 2);
}

/// <summary>
/// Converts the given point from screen coordinates to world coordinates.
/// </summary>
Drawing::PointF ScreenToWorld(int x, int y)
{
    // Move the given point to the origin, multiply by the zoom factor and
    // add the model coordinates of the center point (camera position)
    return Drawing::PointF(
        (float)(x - ClientRectangle.Width / 2) * mZoomFactor +
            mCameraPosition.X,
        -(float)(y - ClientRectangle.Height / 2) * mZoomFactor +
            mCameraPosition.Y);
}

GLCanvas2D 暴露了一个 Render 事件,通常在实现中重写该事件。 该控件重写基类的 OnPaint 事件,并引发 Render 事件,将 GLGraphics 对象作为参数传递。 GLGraphics 类似于 System.Drawing.Graphics 类。 绘制是使用 GLGraphics 对象的方法完成的。

void MyCanvas2D::Render(System::Object ^ sender, GLView::GLGraphics ^ Graphics)
{
    // Draw a filled rectangle
    Graphics->FillRectangle(0.0f, 0.0f, 100.0f, 50.0f, Drawing::Color::Red);
}

实现还可以使用原生 OpenGL 调用在 Render 事件中。

void MyCanvas2D::Render(System::Object ^ sender, GLView::GLGraphics ^ Graphics)
{
    // Draw a filled rectangle
    glBegin(GL_QUADS);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex2f(0.0f, 0.0f);
    glVertex2f(100.0f, 0.0f);
    glVertex2f(100.0f, 50.0f);
    glVertex2f(0.0f, 50.0f);
    glEnd();
}

关注点

如果一个实现将多个 GLCanvas2D 实例放置在一个窗体上,我们必须确保 OpenGL 调用被定向到正确的渲染上下文。 我们通过每次触发 OnPaint 事件时使用渲染上下文的句柄调用 wglMakeCurrent 来实现这一点。

void GLCanvas2D::OnPaint(System::Windows::Forms::PaintEventArgs^ e)
{
    // Save previous context and make our context current
    HDC mhOldDC = wglGetCurrentDC();
    HGLRC mhOldGLRC = wglGetCurrentContext();
    wglMakeCurrent(mhDC, mhGLRC);

    /* Drawing code goes here
    ....
    .... */

    // Restore previous context
    wglMakeCurrent(mhOldDC, mhOldGLRC);
}

GLView 使用 OpenGL 顶点数组来加速绘制。 顶点数组由自定义的 GLVertexArray 类内部处理。 GLGraphics 类使用两个 GLVertexArray 收集顶点信息。 一个 GLVertexArray 用于填充形状。 每个填充形状都转换为三角形并存储在顶点数组中。 第二个 GLVertexArray 收集线条。 实际的绘制操作直到 Render 事件结束才执行。

System::Void Render()
{
    // Create the vertex arrays on the fly
    float * vp = new float[mVertices->Count * 3];
    float * cp = new float[mVertices->Count * 4];

    // Fill in the vertex arrays. mVertices is an internal list for collecting
    // vertex position and color.
    for (int j = 0; j < mVertices->Count; j++)
    {
        vp[j * 3] = mVertices[j].x;
        vp[j * 3 + 1] = mVertices[j].y;
        vp[j * 3 + 2] = mVertices[j].z;
        cp[j * 4] = mVertices[j].r;
        cp[j * 4 + 1] = mVertices[j].g;
        cp[j * 4 + 2] = mVertices[j].b;
        cp[j * 4 + 3] = mVertices[j].a;
    }

    // Set OpenGL vertex and color pointers to our vertex arrays
    glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), vp);
    glColorPointer(4, GL_FLOAT, 4 * sizeof(float), cp);

    // Draw the arrays
    glDrawArrays(mType, 0, mVertices->Count);

    // Clean up
    delete[] vp;
    vp = 0;
    delete[] cp;
    cp = 0;
}

历史

  • 2007 年 1 月 25 日 - 首次发布
  • 2007 年 4 月 26 日 - 各种错误修复(感谢 Jac 提供的错误报告和建议)
  • 2010 年 2 月 5 日 - 修复了 GLGraphics::Render 中的内存泄漏
© . All rights reserved.