在 OpenGL/GLFW 3.2 中在窗口和全屏之间切换

Switching Between windowed and full screen in OpenGL/GLFW 3.2

我正在 Linux 上学习 OpenGL,但我无法进行模式切换(window 切换到全屏并返回)。

window 似乎进入了全屏模式,但看起来不正确。要切换模式,正在创建新的 window 并销毁旧的。

void OpenGLWindow::FullScreen(bool fullScreen, int width, int height)
{
    GLFWwindow *oldHandle = m_window;

    m_fullscreen = fullScreen;
    m_width = width;
    m_height = height;

    m_window = glfwCreateWindow(width, height, m_caption.c_str(),
        fullScreen ? m_monitor : NULL, m_window);

    if (m_window == NULL)
    {
        glfwTerminate();
        throw std::runtime_error("Failed to recreate window.");
    }

    glfwDestroyWindow(oldHandle);

    m_camera->Invalidate();

    // Use entire window for rendering.
    glViewport(0, 0, width, height);

    glfwMakeContextCurrent(m_window);
    glfwSwapInterval(1);

    if (m_keyboardHandler) SetKeyboardHandler(m_keyboardHandler);
}

初始Window

全屏(不正确)

Return 到 Windowed

问题更新

我已更新代码以使用您的代码并遇到同样的问题。根据您的建议,我现在正在更新相机,但仍然无济于事:(

void OpenGLCamera::Invalidate()
{
    RecalculateProjection(m_perspProjInfo->Width(), m_perspProjInfo->Height());
    m_recalculateViewMatrix = true;
    m_recalculatePerspectiveMatrix = true;
    m_recalculateProjectionMatrix = true;
}

void OpenGLCamera::RecalculateProjection(int width, int height)
{
    float aspectRatio = float(width) / height;
    float frustumYScale = cotangent(degreesToRadians(
        m_perspProjInfo->FieldOfView() / 2));

    float frustumXScale = frustumYScale;

    if (width > height) 
    {
        // Shrink the x scale in eye-coordinate space, so that when geometry is
        // projected to ndc-space, it is widened out to become square.
        m_projectionMatrix[0][0] = frustumXScale / aspectRatio;
        m_projectionMatrix[1][1] = frustumYScale;
    }
    else {
        // Shrink the y scale in eye-coordinate space, so that when geometry is
        // projected to ndc-space, it is widened out to become square.
        m_projectionMatrix[0][0] = frustumXScale;
        m_projectionMatrix[1][1] = frustumYScale * aspectRatio;
    }
}

兔子:当我调整大小时:

兔子:当我进入全屏时:

在下文中,我将描述一个小而方便的 class,它处理调整 GLFW window 的大小并处理开关全屏 window 打开和关闭。
GLFW documentation.

中详细记录了所有使用的 GLFW 函数
#include <GL/gl.h>
#include <GLFW/glfw3.h>
#include <array>
#include <stdexcept>

class OpenGLWindow
{
private:

    std::array< int, 2 > _wndPos         {0, 0};
    std::array< int, 2 > _wndSize        {0, 0};
    std::array< int, 2 > _vpSize         {0, 0};
    bool                 _updateViewport = true;
    GLFWwindow *         _wnd            = nullptr;
    GLFWmonitor *        _monitor        = nullptr;

    void Resize( int cx, int cy );

public:

    void Init( int width, int height );
    static void CallbackResize(GLFWwindow* window, int cx, int cy);
    void MainLoop ( void );
    bool IsFullscreen( void );
    void SetFullScreen( bool fullscreen );
};

创建window时,用户函数指针(glfwSetWindowUserPointer)被设置为window管理class。调整大小回调由 glfwSetWindowSizeCallback 设置。在 window 创建后,它的当前大小和位置可以通过 glfwGetWindowPosglfwGetWindowSize.

获得
void OpenGLWindow::Init( int width, int height )
{
    _wnd = glfwCreateWindow( width, height, "OGL window", nullptr, nullptr );
    if ( _wnd == nullptr )
    {
        glfwTerminate();
        throw std::runtime_error( "error initializing window" ); 
    }

    glfwMakeContextCurrent( _wnd );

    glfwSetWindowUserPointer( _wnd, this );
    glfwSetWindowSizeCallback( _wnd, OpenGLWindow::CallbackResize );

    _monitor =  glfwGetPrimaryMonitor();
    glfwGetWindowSize( _wnd, &_wndSize[0], &_wndSize[1] );
    glfwGetWindowPos( _wnd, &_wndPos[0], &_wndPos[1] );
    _updateViewport = true;
}

当调整大小通知发生时,那么window管理class的指针可以通过glfwGetWindowUserPointer:

得到
static void OpenGLWindow::CallbackResize(GLFWwindow* window, int cx, int cy)
{
    void *ptr = glfwGetWindowUserPointer( window );
    if ( OpenGLWindow *wndPtr = static_cast<OpenGLWindow*>( ptr ) )
        wndPtr->Resize( cx, cy );
}

window 大小的任何更改都会收到通知,并会存储新的 window 大小 (glfwGetWindowSize):

void OpenGLWindow::Resize( int cx, int cy )
{
    _updateViewport = true;
}

当 window 大小发生变化时,视口必须适合 window 大小 (glViewport)。这可以在应用程序的主循环中完成:

void OpenGLWindow::MainLoop ( void )
{
    while (!glfwWindowShouldClose(_wnd))
    {
        if ( _updateViewport )
        {
            glfwGetFramebufferSize( _wnd, &_vpSize[0], &_vpSize[1] );
            glViewport( 0, 0, _vpSize[0], _vpSize[1] );
            _updateViewport = false;
        }

        // ..... render the scene

        glfwSwapBuffers(_wnd);
        glfwPollEvents();
    }
}  

如果当前window处于全屏模式,可以通过请求window用于全屏模式(glfwGetWindowMonitor)的显示器来实现:

bool OpenGLWindow::IsFullscreen( void )
{
    return glfwGetWindowMonitor( _wnd ) != nullptr;
} 

要打开和关闭全屏模式,必须调用 glfwSetWindowMonitor,或者使用全屏模式的监视器,或者使用 nullptr:

void OpenGLWindow::SetFullScreen( bool fullscreen )
{
    if ( IsFullscreen() == fullscreen )
        return;

    if ( fullscreen )
    {
        // backup window position and window size
        glfwGetWindowPos( _wnd, &_wndPos[0], &_wndPos[1] );
        glfwGetWindowSize( _wnd, &_wndSize[0], &_wndSize[1] );
        
        // get resolution of monitor
        const GLFWvidmode * mode = glfwGetVideoMode(_monitor);

        // switch to full screen
        glfwSetWindowMonitor( _wnd, _monitor, 0, 0, mode->width, mode->height, 0 );
    }
    else
    {
        // restore last window size and position
        glfwSetWindowMonitor( _wnd, nullptr,  _wndPos[0], _wndPos[1], _wndSize[0], _wndSize[1], 0 );
    }

    _updateViewport = true;
}

您的代码有几个问题:

  1. 假设 glfwCreateWindow 会在全屏模式下将分辨率设置为 width * height 是不正确的。 GLFW documentation 状态(强调我的):

    For full screen windows, the specified size becomes the resolution of the window's desired video mode. As long as a full screen window is not iconified, the supported video mode most closely matching the desired video mode is set for the specified monitor.

  2. 假设在"pixels"中指定的window大小不正确either.Quoting再次relevant part of the documentation

    While the size of a window is measured in screen coordinates, OpenGL works with pixels. The size you pass into glViewport, for example, should be in pixels. On some machines screen coordinates and pixels are the same, but on others they will not be. There is a second set of functions to retrieve the size, in pixels, of the framebuffer of a window.

问题 1 和 2 可以通过在创建 window 之后简单地调用 glfwGetFramebufferSize 来解决。这给我们留下了问题 3:

  1. 您在没有当前 GL 上下文的情况下调用 glViewport - 导致未定义的行为,尤其是根本不设置视口。这实际上是一个有趣的问题,因为新上下文的初始视口将是全新的 window,因此您的错误 1 ​​和 2 没有直接影响。不过,如果您的代码依赖于包含有用值的 m_widthm_height,它们以后仍可能会产生一些影响。

当您只想在 windowed 和全屏之间切换时,我建议您不要使用 glfwCreateWindow 创建新的 Window。请改用 glfwSetWindowMonitor

当您创建启用全屏的 window 时,您必须传递与监视器上的视频模式兼容的参数。您可以像这样在主显示器上获得标准视频模式:

GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);

并切换到全屏:

glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);

只需传递一个 nullptr-mode 和您自己的值当然:

glfwSetWindowMonitor(window, nullptr, 0, 0, windowWidth, windowHeight, windowRefreshRate);

别忘了调整视口大小和更新相机。


您是否在用户调整 window 大小时调整视口大小并更新相机?