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

GLUT 子窗口模板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (9投票s)

2007年8月20日

CPOL

10分钟阅读

viewsIcon

99651

downloadIcon

2269

本文详细介绍了如何使用多个子窗口设置您的第一个 OpenGL FreeGLUT 窗口,并为您提供了 OpenGL 应用程序的模板。

Visual C++ 2015 文件

Visual C++ 6 文件(旧)

Screenshot - GLUT_Subwindow_Template.jpg

目录

引言

本文详细介绍了如何设置您的第一个 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 的键盘、鼠标和显示事件。
  • 通过命令提示符显示事件发生的时间、地点及其含义。

下图展示了子窗口在主窗口上的布局方式:

Screenshot - GLUT_Subwindow_Template_1.jpg

用法

编译与运行程序

有关如何编译和运行程序的信息,请查看 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 请求更改当前窗口的位置。对于顶级窗口,xy 参数是相对于屏幕原点的像素偏移量。对于子窗口,xy 参数是相对于窗口父窗口原点的像素偏移量。

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。新子窗口尺寸的计算将按如下方式进行:

  1. width 是主窗口的新宽度,main_window_w 是主窗口的旧宽度。
  2. 忽略水平间隙。已知水平方向有两个间隙,我们需要从旧主窗口和新主窗口的宽度中减去 GAP*2。
  3. 新子窗口宽度 / 旧子窗口宽度 = (新窗口宽度 - 总水平间隙) / (旧窗口宽度 - 总水平间隙)。

    已知旧子窗口宽度 = 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);

xy 参数指定视口的左下角,widthheight 是视口矩形在像素中的大小。默认情况下,初始视口值为 (0, 0, winWidth, winHeight),其中 winWidthwinHeight 是窗口的尺寸。

如果我们不在子窗口 2 的重塑函数中调用 glViewport 函数,视口将占用整个窗口的大小,因此茶壶将失真,如下图所示:

glut_subwindow/glut_subwindow_template_2.jpg

glutEntryFunc

当鼠标进入或离开窗口时,会调用 entry 函数。entry 函数的 state 参数为 GLUT_LEFTGLUT_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

  • 原始文章发布
© . All rights reserved.