GLUT 子窗口模板






4.29/5 (9投票s)
本文详细介绍了如何使用多个子窗口设置您的第一个 OpenGL FreeGLUT 窗口,并为您提供了 OpenGL 应用程序的模板。
Visual C++ 2015 文件
Visual C++ 6 文件(旧)
目录
引言
本文详细介绍了如何设置您的第一个 OpenGL GLUT 窗口与多个子窗口,并为您提供了 OpenGL 应用程序的模板。它可以通过以下方式使用:
- 了解 GLUT 子窗口
- 学习新的 GLUT 事件处理程序
- 如果您需要一个带有多个子窗口的图形程序,请将此代码用作模板。
有关 OpenGL 是什么、GLUT 是什么以及如何设置 GLUT 窗口的信息,请查看GLUT 窗口模板文章。
为什么要有 GLUT 子窗口模板?
在编写图形应用程序时,程序员有时需要多个子窗口作为在窗口上组织图形内容的方式。子窗口允许我们将主窗口划分为多个区域,每个区域都有自己的 OpenGL 上下文和回调函数。这意味着我们可以在每个区域进行不同的绘图和事件处理。
为了避免每次创建需要多个子窗口的 OpenGL 图形应用程序时都编写相同的代码,此程序代码可以用作模板,让您直接开始。
OpenGL GLUT 子窗口模板具有以下属性:
- 窗口标题:“GLUT 子窗口模板”
- 窗口背景颜色:黑色 (R = 0, G = 0, B = 0)
- 子窗口 1 背景颜色:蓝色
- 子窗口 2 背景颜色:黄色
- 子窗口 1 前景颜色:黄色
- 子窗口 2 前景颜色:蓝色
- 窗口尺寸:width = 256 + GAP * 2, height = 256 + 64 + GAP * 3,其中 GAP 是主窗口中两个子窗口之间的间隙。256 是两个子窗口的宽度。256 是第一个子窗口的高度,64 是第二个子窗口的高度。子窗口在主窗口上垂直放置。
- 子窗口 1 尺寸:width = 256, height = 256
- 子窗口 2 尺寸:width = 256, height = 64
- 窗口位置:x = (屏幕宽度 - 窗口宽度) / 2, y = (屏幕高度 - 窗口高度) / 2。这意味着窗口在屏幕上居中。
- 子窗口 1 位置(相对于主窗口):x = GAP, y = GAP
- 子窗口 2 位置(相对于主窗口):x = GAP, y = GAP + 子窗口 1 高度 + GAP
- 处理窗口、子窗口 1 和子窗口 2 的键盘、鼠标和显示事件。
- 通过命令提示符显示事件发生的时间、地点及其含义。
下图展示了子窗口在主窗口上的布局方式:
用法
编译与运行程序
有关如何编译和运行程序的信息,请查看 GLUT 窗口模板文章中的用法部分。
Using the Code
源代码旨在用作您的 OpenGL 应用程序的模板。要在新应用程序中使用它,您只需重命名 C 文件并将其添加到您的 Visual Studio 项目中。
代码解释
创建窗口
以下是与窗口 ID、位置、尺寸和标题相关的变量。GAP
常量表示放置在此窗口中的子窗口之间的间隙
。
// gap between subwindows
#define GAP 25
// define the window position on screen
float main_window_x;
float main_window_y;
// variables representing the window size
float main_window_w = 256 + GAP * 2;
float main_window_h = 256 + 64 + GAP * 3;
// variable representing the window title
char *window_title = "SubWindow Template";
// Represents the window id
int main_window;
设置变量后,窗口的创建方式如下所示。请注意,窗口 ID 是从 glutCreateWindow
函数返回的。此窗口 ID main_window
将用于引用窗口并在其中放置多个子窗口。
// Set the main window x and y coordinates such that the
// window becomes centered
centerOnScreen ();
// Set Window size
glutInitWindowSize (main_window_w, main_window_h);
// Set window position
glutInitWindowPosition (main_window_x, main_window_y);
// Set window display mode
glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE);
// Create window
main_window = glutCreateWindow (window_title);
创建子窗口
初始化并创建窗口后,我们需要初始化子窗口并将它们放置在窗口上。我们可以放置任意数量的子窗口。窗口成为其子窗口的父窗口。子窗口也可以是其他子窗口的父窗口,因此子窗口可以任意深度嵌套。
以下是与子窗口 1 相对于主窗口的位置、子窗口 1 尺寸及其 ID 相关的变量。
// define the window position on screen
float subwindow1_x = GAP;
float subwindow1_y = GAP;
// variables representing the window size
float subwindow1_w = 256;
float subwindow1_h = 256;
// Represents the subwindow id
int subwindow_1;
为了创建子窗口,使用了 glutCreateSubwindow
函数,其签名如下:
int glutCreateSubWindow(int win, int x, int y, int width, int height);
参数 | 描述 |
win |
子窗口父窗口的标识符。 |
x |
窗口 X 位置(像素),相对于父窗口的原点。 |
是 |
窗口 Y 位置(像素),相对于父窗口的原点。 |
width |
宽度(像素)。 |
height |
高度(像素)。 |
第一个子窗口以指定的y位置和尺寸创建,如下所示。请注意,必须传递 main_window
ID 作为参数,以便子窗口可以知道其父窗口。调用 glutCreateSubwindow
函数后,子窗口 1 将成为当前窗口。
// Create subwindow 1
subwindow_1 = glutCreateSubWindow (main_window, GAP, GAP, subwindow1_w,
subwindow1_h);
销毁子窗口
当不再需要子窗口时,可以按如下方式销毁它:
void glutDestroyWindow(int win);
其中 win
是要销毁的 GLUT 窗口的标识符。如果 win
是当前窗口,则当前窗口将变为无效。
定位和调整子窗口大小
在指定如何创建和销毁子窗口之后,我们接下来将看到如何动态定位和调整子窗口大小。但是,在我们定位或调整子窗口大小之前,我们需要将其设置为当前窗口。为此,我们必须拥有其 ID,并且必须使用 glutSetWindow
函数将其设置为当前窗口。
以下是我们如何定位和调整子窗口 1 大小的方法:
glutSetWindow (subwindow_1);
glutPositionWindow (subwindow1_x, subwindow1_y);
glutReshapeWindow (subwindow1_w, subwindow1_h);
下面是这些函数的描述。
glutSetWindow
glutSetWindow
设置当前窗口。
void glutSetWindow(int win);
其中 win
是要设置为当前窗口的 GLUT 窗口(或子窗口)的标识符。
glutGetWindow
如果我们想知道哪个是当前窗口,我们可以使用此函数。
int glutGetWindow(void);
如果不存在任何窗口或之前存在的当前窗口被销毁,则 glutGetWindow
返回零。
glutPositionWindow
glutPositionWindow
请求更改当前窗口的位置。对于顶级窗口,x
和 y
参数是相对于屏幕原点的像素偏移量。对于子窗口,x
和 y
参数是相对于窗口父窗口原点的像素偏移量。
void glutPositionWindow(int x, int y);
其中 x
是窗口(或子窗口)在像素中的新 X 位置,y
是窗口在像素中的新 Y 位置。
如果之前启用了全屏状态,glutPositionWindow
将禁用窗口的全屏状态。
glutReshapeWindow
glutReshapeWindow
请求更改当前窗口的大小。
void glutReshapeWindow(int width, int height);
其中 width
是窗口的新宽度(以像素为单位),height
是窗口的新高度(以像素为单位)。
当窗口被重塑时,重塑的尺寸将通过重塑回调报告给程序。如果之前启用了全屏状态,glutReshapeWindow
将禁用窗口的全屏状态。
回调函数
主窗口和两个子窗口都将分配给以下回调函数。
- 显示函数:每当 OpenGL 上下文需要重新显示时调用。
- 重塑函数:每当子窗口被调整大小时调用。
- 鼠标函数:每当鼠标被点击、按下或释放时调用。
- 移动函数:每当鼠标被拖动时调用。
- 被动移动函数:每当鼠标移动时调用。
- 键盘函数:每当按下 ASCII 码键时调用。
- 特殊函数:每当按下非 ASCII 码键时调用。
例如,子窗口 2 回调函数将按如下方式分配:
glutDisplayFunc (subwindow2_display);
glutReshapeFunc (subwindow2_reshape);
glutMouseFunc (subwindow2_mouse);
glutMotionFunc (subwindow2_motion);
glutPassiveMotionFunc (subwindow2_pmotion);
glutKeyboardFunc (subwindow2_keyboard);
glutSpecialFunc (subwindow2_special);
除了上述回调函数,主窗口还将分配给入口函数,该函数在鼠标进入或离开窗口时调用。
有关回调函数及其在我的 OpenGL 程序中如何使用的完整规范,请查看 GLUT 窗口模板文章的回调函数部分。此处将介绍重塑和入口函数。
glutReshapeFunc
当主窗口被重塑时,将调用以下函数。
//-------------------------------------------------------------------------
// Main Window Reshape Function.
//
// Reset the position and dimensions of subwindows when main window size
// changes.
//-------------------------------------------------------------------------
void main_reshape (int width, int height)
{
// Notify that we are reshaping the main window
printf ("Main Window: ");
// Just take the case when the user tries
// to make the size of the window very small...
if (width < GAP * 4 || height < GAP * 6)
{
glutSetWindow (main_window);
glutReshapeWindow (main_window_w, main_window_h);
return;
}
// Change the subwindow 1 dimensions as window dimensions change
// main_window_w ---> subwindow1_w
// main_window_w' (width) ---> ??
// ==>
subwindow1_w = (subwindow1_w * (width-GAP*2.0))/(main_window_w-GAP*2.0);
subwindow1_h = (subwindow1_h * (height-GAP*3.0))/(main_window_h-GAP*3.0);
// Set subwindow 1 as current window and then reposition and resize it
glutSetWindow (subwindow_1);
glutPositionWindow (GAP, GAP);
glutReshapeWindow (subwindow1_w, subwindow1_h);
// Change the subwindow 2 dimensions as window dimensions change
subwindow2_w = (subwindow2_w * (width-GAP*2.0))/(main_window_w-GAP*2.0);
subwindow2_h = (subwindow2_h * (height-GAP*3.0))/(main_window_h-GAP*3.0);
// Set subwindow 2 as current window and then reposition and resize it
glutSetWindow (subwindow_2);
glutPositionWindow (GAP, GAP+subwindow1_h+GAP);
glutReshapeWindow (subwindow2_w, subwindow2_h);
// Stay updated with the window width and height
main_window_w = width;
main_window_h = height;
// Print current width and height on the screen
printf ("Width: %d, Height: %d.\n", width, height);
}
当主窗口的大小改变时,子窗口相对于主窗口的大小和相对位置也将相应改变。
已知 GAP
是常量,子窗口将按比例调整大小,但不会考虑 GAP
。新子窗口尺寸的计算将按如下方式进行:
width
是主窗口的新宽度,main_window_w
是主窗口的旧宽度。- 忽略水平间隙。已知水平方向有两个间隙,我们需要从旧主窗口和新主窗口的宽度中减去
GAP
*2。 - 新子窗口宽度 / 旧子窗口宽度 = (新窗口宽度 - 总水平间隙) / (旧窗口宽度 - 总水平间隙)。
已知旧子窗口宽度 =subwindow_w
,旧窗口宽度 =main_window_w
,新窗口宽度 =width
,则新子窗口宽度将等于
(width
-GAP
*2) / (main_window_w
-GAP
*2) *subwindow_w
。
高度也将进行相同的计算。
由于我们在计算中扣除了水平和垂直间隙,因此我们需要确保主窗口的大小不会变得太小,以避免将子窗口大小更改为零。
if (width < GAP * 4 || height < GAP * 6)
{
glutSetWindow (main_window);
glutReshapeWindow (main_window_w, main_window_h);
return;
}
主窗口重塑后,内部的子窗口会自动重塑(如 main_reshape
函数所示),并调用其重塑回调函数。已知我们在每个子窗口内绘制一个茶壶,我们关心保持其宽高比恒定(保持纵横比恒定),以免茶壶形状失真。这将通过确保视口宽度和高度保持相等来完成。视口是窗口(或子窗口)中绘制图像的矩形区域。
下面是子窗口 1 重塑函数的代码:
//-------------------------------------------------------------------------
// SubWindow 1 Reshape Function.
//
// Preserve aspect ratio of viewport when subwindow is resized.
//-------------------------------------------------------------------------
void subwindow1_reshape (int width, int height)
{
// Represents a side of the viewport. A viewport is intended to
// to take a square shape so that the aspect ratio is reserved
int viewport_side = 0;
// Viewport x and y positions
int viewport_x = 0, viewport_y = 0;
// Calculate viewport side
viewport_side = (width > height) ? height : width;
// Calculate viewport position (Center viewport)
viewport_x = (width - viewport_side) / 2;
viewport_y = (height - viewport_side) / 2;
// Preserve aspect ratio
glViewport (viewport_x, viewport_y, viewport_side, viewport_side);
// Set subwindow width and height
subwindow1_w = width;
subwindow1_h = height;
// Notify that we are reshaping subwindow 1
printf ("Subwindow 1: ");
// Print current width and height
printf ("Width: %d, Height: %d, Viewport Side: %d.\n", width,
height, viewport_side);
}
请注意,视口在子窗口中居中,其宽度和高度设置为等于子窗口宽度和高度之间的最小值。
glViewport
void glViewport (int x, int y, int width, int height);
x
和 y
参数指定视口的左下角,width
和 height
是视口矩形在像素中的大小。默认情况下,初始视口值为 (0, 0, winWidth
, winHeight
),其中 winWidth
和 winHeight
是窗口的尺寸。
如果我们不在子窗口 2 的重塑函数中调用 glViewport
函数,视口将占用整个窗口的大小,因此茶壶将失真,如下图所示:
glutEntryFunc
当鼠标进入或离开窗口时,会调用 entry
函数。entry 函数的 state
参数为 GLUT_LEFT
或 GLUT_ENTERED
,具体取决于鼠标指针是进入还是离开了窗口。将 NULL
传递给 glutEntryFunc
会禁用鼠标进入/离开回调的生成。
//-------------------------------------------------------------------------
// Main Window Entry Function.
//
// This function is called whenever the mouse pointer enters or leaves
// the main window.
//-------------------------------------------------------------------------
void main_entry (int state)
{
if (state == GLUT_ENTERED)
printf ("Mouse entered main window...\n");
else if (state == GLUT_LEFT)
printf ("Mouse left main window...\n");
}
尝试为子窗口注册 entry 函数不会导致编译、链接或运行时错误。但是,在 Windows 操作系统上,当进入或离开子窗口时不会生成事件。
注意
鼠标被动运动事件将不会被记录,以便我们可以在命令提示符日志中清楚地看到其他事件。
结论
本文的目的是教您如何设置带有多个子窗口的 GLUT 窗口,以及事件处理。同时,该模板可用于节省大量从旧项目或互联网复制代码和粘贴的时间。
如果您发现此模板有用或有任何建议,请告诉我。
参考文献
修订历史
10/13/2015
- 为 Free GLUT 子窗口模板添加了 Visual Studio 2015 项目。
08/20/2007
- 应作者要求删除并重新发布。
02/07/2007
- 修改文章以符合 Marc Clifton 在《Code Project 文章撰写指南》中指定的标准。
- 为主窗口和两个子窗口添加了大部分回调函数。
- 将窗口和子窗口事件记录到命令提示符。
21/07/2005
- 原始文章发布