【问题标题】:Switching Between windowed and full screen in OpenGL/GLFW 3.2在 OpenGL/GLFW 3.2 中切换窗口和全屏
【发布时间】:2018-05-04 07:21:54
【问题描述】:

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

窗口似乎进入全屏,但看起来不正确。要切换模式,将创建一个新窗口并销毁旧窗口。

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);
}

初始窗口

全屏(不正确)

返回窗口

问题更新

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

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;
    }
}

Rabbid:当我调整大小时:

Rabbid:当我进入全屏模式时:

【问题讨论】:

    标签: c++ opengl glfw


    【解决方案1】:

    在下文中,我将描述一个小而方便的类,它处理调整 GLFW 窗口的大小并处理打开和关闭全屏窗口。
    所有使用的 GLFW 函数都在GLFW documentation 中有详细记录。

    #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 );
    };
    

    在创建窗口时,用户函数指针(glfwSetWindowUserPointer)被设置为窗口管理类。调整大小回调由glfwSetWindowSizeCallback 设置。窗口创建后,其当前大小和位置可以通过glfwGetWindowPos 和glfwGetWindowSize 获取。

    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;
    }
    

    当resize通知发生时,则可以通过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 );
    }
    

    通知窗口大小的任何变化并存储新的窗口大小(glfwGetWindowSize):

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

    当窗口大小发生变化时,视口必须适合窗口大小 (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();
        }
    }  
    

    如果当前窗口处于全屏模式,可以通过询问该窗口用于全屏模式的监视器来实现(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;
    }
    

    【讨论】:

      【解决方案2】:

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

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

      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);
      

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


      当用户调整窗口大小时,您是否调整视口大小并更新相机?

      【讨论】:

      • 我刚刚注意到 Rabbid76 已经推荐了与我在此答案中所做的相同的方法。
      • 但是,他的方法有一个我无法弄清楚的缺陷 - 屏幕进入全屏,然后变成蓝色 :( 我看到 3 次调用 Resize() ...可以之一他们在破坏它吗?
      • 您是否在按下按钮时切换到全屏模式?尝试找出按下按钮的事件是否重复并导致循环。检查 GL 是否使用glGetError 抛出任何错误
      • 我正在使用按键,但可以防止多次按键。另外,我只看到一次“切换到 xxxx”。我打算尝试放置一个 bodge 来停止多个 Resize() 调用......它们来自对 glfwSetWindowSizeCallback(m_window, OpenGLWindow::CallbackResize); 的调用
      • 这很奇怪。如果宽度和高度在回调函数内部没有改变,不调用Resize怎么样。
      【解决方案3】:

      您的代码有几个问题:

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

        对于全屏窗口,指定大小成为窗口所需视频模式的分辨率。只要全屏窗口未图标化,就会为指定的监视器设置最接近匹配所需视频模式的支持视频模式。

      2. 假设以“像素”为单位指定窗口大小也不正确。再次引用relevant part of the documentation:

        虽然窗口的大小是用屏幕坐标来衡量的,但 OpenGL 使用的是像素。例如,您传递给glViewport 的大小应该以像素为单位。在某些机器上,屏幕坐标和像素是相同的,但在其他机器上则不同。还有第二组函数用于检索窗口帧缓冲区的大小(以像素为单位)。

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

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

      【讨论】:

      • 这是否意味着我需要做的就是在将上下文设为当前后移动视口调用?
      • 很可能不是。正如我所说,在你的上下文中不做glViewport,你应该已经得到了正确的。我确实在您的代码中列出了几个问题。但是您的问题不包含MCVE,所以我无法告诉您问题的根本原因是什么。
      • 我想过如何生成 MCVE,但要显示问题将涉及模型加载、相机和渲染
      • 如果 MCVE 涉及模型加载,那么模型加载就是问题的一部分。但是,我不明白为什么会这样。渲染一个简单的四边形就足够了,看看它会在哪里结束。
      • 我在视网膜 Mac 上运行,我想知道这是否会影响我的模式更改方式?
      猜你喜欢
      • 2012-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-28
      • 1970-01-01
      • 2012-06-09
      相关资源
      最近更新 更多