SFML2.6 窗口模块--在SFML窗口中使用OpenGL

介绍

本教程不是关于OpenGL本身,而是如何使用SFML作为OpenGL的环境,以及如何将它们组合在一起。

如你所知,OpenGL最重要的特性之一是可移植性。但是单靠OpenGL并不足以创建完整的程序:你需要一个窗口、一个渲染上下文、用户输入等等。你别无选择,只能自己编写特定于操作系统的代码来处理这些东西。这就是sfml-window模块发挥作用的地方。让我们看看它如何让你使用OpenGL。

包含并链接OpenGL到你的应用程序

OpenGL头文件在每个操作系统上并不相同。因此,SFML提供了一个“抽象”头文件,负责为您包含正确的文件。

#include <SFML/OpenGL.hpp>

这个头文件包含OpenGL函数,没有别的内容。有些人认为SFML会自动包含OpenGL扩展头文件,因为SFML会自动加载扩展,但这只是实现细节。从用户的角度来看,OpenGL扩展加载必须像任何其他外部库一样处理。

然后,您需要将程序链接到OpenGL库。与头文件不同,SFML不能提供统一的OpenGL链接方式。因此,您需要根据使用的操作系统知道要链接哪个库(在Windows上是“opengl32”,在Linux上是“GL”等)。

OpenGL函数以“gl”前缀开头。当您遇到链接器错误时,请记住这一点,它将帮助您找到您忘记链接的库。

创建一个OpenGL窗口

由于SFML是基于OpenGL的,所以它的窗口已经为OpenGL调用做好了准备,而不需要任何额外的努力。

sf::Window window(sf::VideoMode(800, 600), "OpenGL");

// it works out of the box
glEnable(GL_TEXTURE_2D);
...

如果您认为它太自动化,sf::Window的构造函数有一个额外的参数,它允许您更改底层OpenGL上下文的设置。这个参数是sf::ContextSettings结构的一个实例,它提供了以下设置的访问:

  • depthBits是用于深度缓冲区的每像素位数(0为禁用)
  • stencilBits是用于模板缓冲区的每像素位数(0为禁用)
  • antialiasingLevel是多重采样级别
  • majorVersion和minorVersion包括所请求的OpenGL版本
sf::ContextSettings settings;
settings.depthBits = 24;
settings.stencilBits = 8;
settings.antialiasingLevel = 4;
settings.majorVersion = 3;
settings.minorVersion = 0;

sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, settings);

如果任何这些设置不受显卡支持,SFML会尝试找到最接近的有效匹配。例如,如果4倍抗锯齿过高,它会尝试2倍,然后回退到0。

无论如何,您可以使用getSettings函数检查SFML实际使用的设置:

sf::ContextSettings settings = window.getSettings();

std::cout << "depth bits:" << settings.depthBits << std::endl;
std::cout << "stencil bits:" << settings.stencilBits << std::endl;
std::cout << "antialiasing level:" << settings.antialiasingLevel << std::endl;
std::cout << "version:" << settings.majorVersion << "." << settings.minorVersion << std::endl;

SFML支持OpenGL版本3.0以上(只要您的显卡驱动程序可以处理它们)。在SFML 2.3中添加了选择3.2+上下文的配置文件以及设置上下文调试标志的支持。不支持向前兼容性标志。默认情况下,SFML使用兼容性配置文件创建3.2+ 上下文,因为图形模块使用遗留的OpenGL功能。如果您打算使用图形模块,请务必创建不带核心配置文件设置的上下文,否则图形模块将无法正常工作。在OS X上,SFML仅支持使用核心配置文件创建OpenGL 3.2+上下文。如果您想在OS X上使用图形模块,则仅限于使用遗留上下文,这意味着OpenGL版本为2.1。

典型的OpenGL-with-SFML程序

下面是一个完整的使用SFML编写的OpenGL程序示例:

#include <SFML/Window.hpp>
#include <SFML/OpenGL.hpp>

int main()
{
    // create the window
    sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, sf::ContextSettings(32));
    window.setVerticalSyncEnabled(true);

    // activate the window
    window.setActive(true);

    // load resources, initialize the OpenGL states, ...

    // run the main loop
    bool running = true;
    while (running)
    {
        // handle events
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                // end the program
                running = false;
            }
            else if (event.type == sf::Event::Resized)
            {
                // adjust the viewport when the window is resized
                glViewport(0, 0, event.size.width, event.size.height);
            }
        }

        // clear the buffers
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // draw...

        // end the current frame (internally swaps the front and back buffers)
        window.display();
    }

    // release resources...

    return 0;
}

在这里,我们不使用window.isOpen()作为主循环的条件,因为我们需要窗口在程序结束之前保持打开状态,以便在循环的最后一次迭代和清理代码时仍具有有效的OpenGL上下文。

如果您有其他问题,请不要犹豫,可以在SFML SDK中查看“OpenGL”和“Window”示例,它们更加完整,并且很可能包含您问题的解决方案。

管理OpenGL上下文

SFML自动为每个创建的窗口提供OpenGL上下文。调用任何OpenGL函数时,它们会在当前活动的上下文上运行。因此,在调用OpenGL函数时,需要激活上下文。如果在调用OpenGL函数时未激活上下文,则函数调用不会产生所需的效果,因为没有状态可以对其产生影响。

为了激活窗口的上下文,使用window.setActive(),该函数与window.setActive(true)相同。在另一个上下文处于活动状态时激活上下文,将导致当前活动的上下文被隐式停用,然后再激活新的上下文。为了显式停用窗口的上下文,请使用window.setActive(false)。如果要在另一个线程上激活上下文,则必须这样做。然而,在每次完成一批OpenGL操作后,建议简单地停用上下文。遵循这个建议,每个操作批次将在激活和停用调用之间可见地包装。可以编写一个RAII帮助程序类来实现此目的。

// activate the window's context
window.setActive(true);

// set up OpenGL states
// clear framebuffers
// draw to the window

// deactivate the window's context
window.setActive(false);

在调试SFML中的OpenGL问题时,第一步始终是确保在调用OpenGL函数时有一个活动上下文。不要假定SFML会隐式激活上下文,或者SFML在调用库时会保留当前活动的上下文。唯一提供的保证是当前线程上激活的上下文在调用window.setActive(true)和window.setActive(false)之间不会更改,只要在两个调用之间没有进行其他调用。在所有其他情况下,必须假定当前上下文可能已更改,因此需要显式重新激活先前的活动上下文,以确保之前的活动上下文再次处于活动状态。还要确保在调用OpenGL函数时激活正确的上下文。活动上下文不仅为OpenGL操作提供执行环境,还指定了任何绘图命令的目标帧缓冲区。在活动上下文没有可见帧缓冲区的情况下调用OpenGL绘图函数将导致这些绘图命令不会产生任何可见输出。将OpenGL操作分为多个上下文还会将状态更改分散在上下文中。如果任何后续绘图操作假定已设置某些状态,则在这种情况下将不会产生正确的结果。

编写OpenGL代码时的一个极其推荐的做法是,在每个OpenGL函数调用后始终检查是否产生了任何OpenGL错误。这可以通过glGetError()函数来完成。在每个函数调用后检查错误将有助于缩小可能出现错误的位置,并显著提高调试效率。

根据可用的上下文版本和功能,必须小心地调用实际在当前上下文中有效的函数。否则,通常会生成GL_INVALID_OPERATION或GL_INVALID_ENUM错误。要查询使用窗口或分别创建的上下文的实际版本和功能,请分别使用window.getSettings()或context.getSettings()。请注意,如果OpenGL实现无法满足所有要求,则这些设置可能与创建上下文时传递的设置不同。建议始终检查创建的上下文实际上是否提供OpenGL代码执行所需的功能。当在更高级别的上下文中加载OpenGL扩展并尝试在更低级别的上下文中使用它们时,这可能会令人困惑,反之亦然。

管理多个OpenGL窗口

管理多个OpenGL窗口并不比管理一个更复杂,只需要记住一些事情就可以。

OpenGL调用是在活动上下文(即活动窗口)上进行的。因此,如果您想在同一程序中绘制到两个不同的窗口,您必须在绘制某些内容之前选择哪个窗口是活动的。这可以使用setActive函数来完成:

// activate the first window
window1.setActive(true);

// draw to the first window...

// activate the second window
window2.setActive(true);

// draw to the second window...

在线程中只能有一个上下文(窗口)处于活动状态,因此您无需在激活另一个窗口之前取消激活窗口,它会自动取消激活。这就是OpenGL的工作原理。

另一个需要知道的是,SFML创建的所有OpenGL上下文共享它们的资源。这意味着您可以在任何活动的上下文中创建纹理或顶点缓冲器,并在任何其他上下文中使用它。这还意味着您无需在重新创建窗口时重新加载所有OpenGL资源。只有可共享的OpenGL资源才可以在上下文之间共享。一个不可共享资源的例子是顶点数组对象。

没有窗口的OpenGL

有时可能需要在没有活动窗口(因此没有OpenGL上下文)的情况下调用OpenGL函数。例如,当您从单独的线程中加载纹理或在创建第一个窗口之前。SFML允许您使用sf::Context类创建无窗口上下文。您只需实例化它即可获得有效的上下文。

int main()
{
    sf::Context context;

    // load OpenGL resources...

    sf::Window window(sf::VideoMode(800, 600), "OpenGL");

    ...

    return 0;
}

多线程渲染

典型的多线程程序配置是在一个线程中处理窗口和其事件(主线程),在另一个线程中进行渲染。如果你这样做,有一个重要的规则需要记住:如果一个上下文(窗口)在另一个线程中已经被激活,那么你不能再次激活它。这意味着在启动渲染线程之前,你必须先取消激活窗口。

void renderingThread(sf::Window* window)
{
    // activate the window's context
    window->setActive(true);

    // the rendering loop
    while (window->isOpen())
    {
        // draw...

        // end the current frame -- this is a rendering function (it requires the context to be active)
        window->display();
    }
}

int main()
{
    // create the window (remember: it's safer to create it in the main thread due to OS limitations)
    sf::Window window(sf::VideoMode(800, 600), "OpenGL");

    // deactivate its OpenGL context
    window.setActive(false);

    // launch the rendering thread
    sf::Thread thread(&renderingThread, &window);
    thread.launch();

    // the event/logic/whatever loop
    while (window.isOpen())
    {
        ...
    }

    return 0;
}

使用OpenGL配合图形模块

本教程涉及将OpenGL与sfml-window模块混合使用,这相对比较容易,因为这个模块的唯一目的就是与OpenGL混合使用。与graphics模块混合使用则稍微复杂一些:sfml-graphics模块也使用了OpenGL,因此必须特别小心,以避免SFML和用户状态之间发生冲突。

如果你还不了解graphics模块,你只需要知道sf::Window类被替换为sf::RenderWindow,它继承了所有sf::Window函数,并添加了用于绘制SFML特定实体的功能。

避免SFML和自己的OpenGL状态之间发生冲突的唯一方法是在从OpenGL切换到SFML时保存/恢复它们。

- draw with OpenGL

- save OpenGL states

- draw with SFML

- restore OpenGL states

- draw with OpenGL

...

最简单的解决方案是让SFML为你完成,使用pushGLStates/popGLStates函数:

glDraw...

window.pushGLStates();

window.draw(...);

window.popGLStates();

glDraw...

由于SFML对你的OpenGL代码没有任何了解,因此它无法优化这些步骤,结果是它保存/恢复了所有可用的OpenGL状态和矩阵。这对于小型项目来说可能是可以接受的,但对于需要最大性能的大型程序来说可能过于缓慢。在这种情况下,你可以自己处理保存和恢复OpenGL状态,使用glPushAttrib/glPopAttrib、glPushMatrix/glPopMatrix等函数。
如果你这样做,仍然需要在绘制之前恢复SFML的状态。这可以通过resetGLStates函数来实现。

glDraw...

glPush...
window.resetGLStates();

window.draw(...);

glPop...

glDraw...

自己保存和恢复OpenGL状态,可以管理你真正需要的状态,从而减少不必要的驱动程序调用。这样可以提高程序的性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面我会给你讲解如何使用SFML来实现n-puzzle游戏的可视化。首先,我们需要了解一下SFML是什么。 SFML是一个跨平台的图形库,它提供了简单易用的API,可以帮助我们快速创建2D游戏和图形应用程序。它支持多种编程语言,包括C++、Python、Java等。 接下来,我们可以开始实现n-puzzle游戏的可视化了。首先,我们需要创建一个窗口来显示游戏界面。以下是创建窗口的代码: ```cpp #include <SFML/Graphics.hpp> int main() { sf::RenderWindow window(sf::VideoMode(800, 600), "N-Puzzle Game"); while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } window.clear(sf::Color::White); // 绘制游戏界面 window.display(); } return 0; } ``` 这段代码创建了一个800x600大小的窗口,并且在每一帧都会清空窗口并绘制游戏界面。接下来,我们需要实现游戏界面的绘制。 n-puzzle游戏的界面由一个N*N的网格组成,每个格子都会有一个数字。我们可以使用SFML的矩形来表示每个格子,并且使用文本来显示数字。以下是绘制游戏界面的代码: ```cpp const int N = 3; const int tileSize = 100; sf::RectangleShape tile(sf::Vector2f(tileSize, tileSize)); tile.setFillColor(sf::Color::White); tile.setOutlineColor(sf::Color::Black); tile.setOutlineThickness(2); sf::Font font; font.loadFromFile("arial.ttf"); sf::Text text("", font, tileSize / 2); text.setFillColor(sf::Color::Black); text.setStyle(sf::Text::Bold); text.setOrigin(text.getGlobalBounds().width / 2, text.getGlobalBounds().height / 2); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { int number = i * N + j + 1; if (number == N * N) continue; tile.setPosition(j * tileSize, i * tileSize); text.setString(std::to_string(number)); text.setPosition(j * tileSize + tileSize / 2, i * tileSize + tileSize / 2); window.draw(tile); window.draw(text); } } ``` 这段代码首先定义了每个格子的大小为100x100,并且使用白色填充和黑色边框。然后,它加载了一个字体文件,并且创建了一个文本对象来显示数字。最后,它使用两个嵌套循环来绘制整个游戏界面。 这样,我们就完成了n-puzzle游戏的可视化。完整代码如下: ```cpp #include <SFML/Graphics.hpp> const int N = 3; const int tileSize = 100; int main() { sf::RenderWindow window(sf::VideoMode(N * tileSize, N * tileSize), "N-Puzzle Game"); sf::RectangleShape tile(sf::Vector2f(tileSize, tileSize)); tile.setFillColor(sf::Color::White); tile.setOutlineColor(sf::Color::Black); tile.setOutlineThickness(2); sf::Font font; font.loadFromFile("arial.ttf"); sf::Text text("", font, tileSize / 2); text.setFillColor(sf::Color::Black); text.setStyle(sf::Text::Bold); text.setOrigin(text.getGlobalBounds().width / 2, text.getGlobalBounds().height / 2); while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } window.clear(sf::Color::White); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { int number = i * N + j + 1; if (number == N * N) continue; tile.setPosition(j * tileSize, i * tileSize); text.setString(std::to_string(number)); text.setPosition(j * tileSize + tileSize / 2, i * tileSize + tileSize / 2); window.draw(tile); window.draw(text); } } window.display(); } return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值