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
- 原始文章发布


