使用 OpenGL 控件进行 2D 绘图






4.87/5 (27投票s)
一篇关于如何创建用于使用 OpenGL 进行 2D 形状绘图的用户控件的文章

引言
我最近编写了一些代码来为有限元程序显示大量的简单 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
中的内存泄漏