【问题标题】:Cross-platform renderer in OpenGL ESOpenGL ES 中的跨平台渲染器
【发布时间】:2012-01-19 09:52:45
【问题描述】:

我正在编写一个跨平台的渲染器。我想在 Windows、Linux、Android、iOS 上使用它。

您认为避免绝对抽象并直接在 OpenGL ES 2.0 中编写它是一个好主意吗?

据我所知,我应该能够在 PC 上针对标准 OpenGL 编译它,只需对处理上下文和与窗口系统的连接的代码进行少量更改。

【问题讨论】:

  • 你听说过 Kivy 吗?它是一种适用于 Linux、Windows、MacOSX、Android 和 IOS 的开源跨平台编程语言,可在 OpenGL (kivy.org) 中呈现所有视图。该语言还包括它自己的小部件工具包。我只是想我会把它扔在那里,因为如果解决方案已经存在并且您可能还没有发现它,您可能不想重新发明轮子。
  • 如果您使用的是 Android 或 iOS,请尝试在 Play Store 或 App Store 中搜索“Kivy”以查看其使用示例。

标签: opengl opengl-es cross-platform renderer


【解决方案1】:

您认为避免绝对抽象并直接在 OpenGL ES 2.0 中编写它是一个好主意吗?

您的主要困难在于处理 ES 2.0 规范中实际上与 OpenGL 2.1 不同的部分。

例如,您不能通过桌面 GLSL 1.20 编译器推送 ES 2.0 着色器。在 ES 2.0 中,您使用诸如指定精度之类的东西;这些是 GLSL 1.20 中的非法构造。

可以但是#define 在他们周围,但这需要一些手动干预。您必须将#ifdef 插入到着色器源文件中。您可以使用一些着色器编译技巧来简化此操作。

确实,因为 GL ES 使用一组完全不同的扩展(尽管有些是桌面 GL 扩展的镜像和子集),您可能想要这样做。

每个 GLSL 着色器(桌面或 ES)都需要有一个“序言”。着色器中的第一个非注释内容必须是 #version 声明。幸运的是,桌面 GL 2.1 和 GL ES 2.0 的版本相同:#version 1.20。问题是接下来会发生什么:#extension 列表(如果有的话)。这会启用着色器所需的扩展。

由于 GL ES 使用与桌面 GL 不同的扩展程序,您需要更改此扩展程序列表。而且由于可能性很大,您将需要比桌面 GL 2.1 扩展更多的 GLSL ES 扩展,这些列表将不仅仅是 1:1 映射,而是完全不同的列表。

我的建议是利用为 GLSL 着色器提供多个字符串的能力。也就是说,您的实际着色器文件没有有任何序言内容。他们只有有实际的定义和功能。着色器的主体。

在 GL ES 上运行时,您有一个全局序言,您将把它附加到着色器的开头。您将在桌面 GL 中获得不同的全局序言。代码如下所示:

GLuint shader = glCreateShader(/*shader type*/);
const char *shaderList[2];
shaderList[0] = GetGlobalPreambleString(); //Gets preamble for the right platform
shaderList[1] = LoadShaderFile(); //Get the actual shader file
glShaderSource(shader, 2, shaderList, NULL);

序言可以包括特定于平台的#define。当然是用户定义的。这样,您就可以#ifdef 为不同平台编写代码。

两者之间还有其他区别。例如,虽然有效的 ES 2.0 纹理上传函数调用在桌面 GL 2.1 中正常工作,但它们不一定是最佳的。在像所有移动系统这样的大端机器上可以正常上传的东西将需要在小端桌面机器中的驱动程序中进行一些操作。因此,您可能希望有一种方法可以在 GL ES 和桌面 GL 上指定不同的像素传输参数。

此外,ES 2.0 和桌面 GL 2.1 中有不同的扩展集供您使用。虽然它们中的许多试图相互镜像(OES_framebuffer_object 是 EXT_framebuffer_object 的一个子集),但您可能会遇到类似的“不完全是一个子集”问题,就像上面提到的那样。

【讨论】:

  • 感谢您详尽的回答。所以你认为创建某种 OpenGL 渲染器抽象会更好吗?例如,我可以有一个由 Texture2D 类表示的纹理。这个类将包含两种规范共有的东西,但有些东西的实现会有所不同。
  • @hubrobin:不需要那么抽象。您只需要在特定位置使用一些特定于平台的代码。现在,如果您的目标是 GL 3.3 而不是 2.1,那么您将需要更多的抽象。
  • 我不想在 PC 上支持更多功能。所以你基本上是说,它是可行的?
  • 兴趣点:现代 ARM 设备是小端的,例如 x86。
【解决方案2】:

根据我的谦虚经验,满足此类要求的最佳方法是以纯 C 风格开发引擎,而无需在其上添加任何层。

我是PATRIA 3D引擎的主要开发者,它基于你刚才提到的可移植性的基本原则,我们通过在基本标准库上开发工具来实现这一点。

在不同平台上编译代码的工作量非常小。

可以根据您要嵌入引擎的组件来计算移植整个解决方案的实际工作量。

例如:


标准 C:

引擎 3D

游戏逻辑

游戏人工智能

物理


+


窗口界面(GLUT、EGL 等)- 取决于平台,无论如何可能是用于桌面的 GLUT 和用于移动设备的 EGL。

人机界面 - 取决于移植,Android 的 Java,IOS 的 OC,任何版本的桌面

声音管理器 - 取决于移植

市场服务 - 取决于移植


通过这种方式,您可以无缝地重复使用 95% 的工作。

我们已经为我们的引擎采用了这个解决方案,到目前为止,它确实值得最初的投资。

【讨论】:

    【解决方案3】:

    这是我为运行我的商业映射和路由库的各种平台实现 OpenGL ES 2.0 支持的经验结果。

    渲染类被设计为在单独的线程中运行。它具有对包含地图数据和当前视图信息的对象的引用,并使用互斥锁来避免在绘图时读取该信息时发生冲突。它在图形内存中维护 OpenGL ES 矢量数据的缓存。

    所有的渲染逻辑都是用C++编写的,在以下所有平台上都可以使用。

    Windows (MFC)

    使用 ANGLE 库:链接到 libEGL.lib 和 libGLESv2.lib 并确保可执行文件可以访问 DLL libEGL.dll 和 libGLESv2.dll。 C++ 代码创建了一个线程,该线程以适当的速率(例如,每秒 25 次)重绘图形。

    Windows(.NET 和 WPF)

    使用 C++/CLI 包装器创建 EGL 上下文并调用直接在 MFC 实现中使用的 C++ 呈现代码。 C++ 代码创建了一个线程,该线程以适当的速率(例如,每秒 25 次)重绘图形。

    Windows (UWP)

    在 UWP 应用代码中创建 EGL 上下文,并通过 C++/CXX 包装器调用 C++ 呈现代码。您将需要使用 SwapChainPanel 并创建在不同线程中运行的自己的渲染循环。示例代码参见GLUWP 项目。

    Windows、Linux 和 Mac OS 上的 Qt

    使用 QOpenGLWidget 作为您的窗口。使用 Qt OpenGL ES 包装器创建 EGL 上下文,然后在您的 paintGL() 函数中调用 C++ 渲染代码。

    安卓

    创建一个实现 android.opengl.GLSurfaceView.Renderer 的渲染器类。为 C++ 呈现对象创建 JNI 包装器。在 onSurfaceCreated() 函数中创建 C++ 渲染对象。在 onDrawFrame() 函数中调用 C++ 渲染对象的绘图函数。您需要为您的渲染器类导入以下库:

    import javax.microedition.khronos.egl.EGLConfig;
    import javax.microedition.khronos.opengles.GL10;
    import android.opengl.GLSurfaceView.Renderer;
    

    创建一个从 GLSurfaceView 派生的视图类。在您的视图类的构造函数中,首先设置您的 EGL 配置:

    setEGLContextClientVersion(2); // use OpenGL ES 2.0
    setEGLConfigChooser(8,8,8,8,24,0);
    

    然后创建一个渲染器类的实例并调用 setRenderer 来安装它。

    iOS

    使用 METALAngle 库,而不是 GLKit,Apple 已弃用它,最终将不再支持。

    创建一个 Objective C++ 渲染器类来调用您的 C++ OpenGL ES 绘图逻辑。

    创建一个从 MGLKView 派生的视图类。在视图类的 drawRect() 函数中,创建一个渲染器对象(如果它尚不存在),然后调用它的绘图函数。也就是说,你的 drawRect 函数应该是这样的:

    -(void)drawRect:(CGRect)rect
        {
        if (m_renderer == nil && m_my_other_data != nil)
            m_renderer = [[MyRenderer alloc] init:m_my_other_data];
        if (m_renderer)
            [m_renderer draw];
        }
    

    在您的应用中,您需要一个视图控制器类来创建 OpenGL 上下文并对其进行设置,使用如下代码:

    MGLContext* opengl_context = [[MGLContext alloc] initWithAPI:kMGLRenderingAPIOpenGLES2];
    m_view = [[MyView alloc] initWithFrame:aBounds context:opengl_context];
    m_view.drawableDepthFormat = MGLDrawableDepthFormat24;
    self.view = m_view;
    self.preferredFramesPerSecond = 30;
    

    Linux

    在 Linux 上使用 Qt 是最简单的(见上文),但也可以使用 GLFW 框架。在您的应用程序类的构造函数中,调用 glfwCreateWindow 创建一个窗口并将其存储为数据成员。调用 glfwMakeContextCurrent 使 EGL 上下文当前,然后创建一个数据成员来保存渲染器类的实例;像这样:

    m_window = glfwCreateWindow(1024,1024,"My Window Title",nullptr,nullptr);
    glfwMakeContextCurrent(m_window);
    m_renderer = std::make_unique<CMyRenderer>();
    

    在你的应用类中添加一个 Draw 函数:

    bool MapWindow::Draw()
        {
        if (glfwWindowShouldClose(m_window))
            return false;
        m_renderer->Draw();
        /* Swap front and back buffers */
        glfwSwapBuffers(m_window);
        return true;
        }
    

    你的 main() 函数将是:

    int main(void)
        {
        /* Initialize the library */
        if (!glfwInit())
            return -1;
    
        // Create the app.
        MyApp app;
    
        /* Draw continuously until the user closes the window */
        while (app.Draw())
            {
    
            /* Poll for and process events */
            glfwPollEvents();
            }
    
        glfwTerminate();
        return 0;
        }
    

    着色器不兼容

    各种 OpenGL ES 2.0 实现所接受的着色器语言存在不兼容性。我在我的 CompileShader 函数中使用以下条件编译代码在 C++ 代码中克服了这些问题:

    const char* preamble = "";
    
    #if defined(_POSIX_VERSION) && !defined(ANDROID) && !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
    // for Ubuntu using Qt or GLFW
    preamble = "#version 100\n";
    #elif defined(USING_QT) && defined(__APPLE__)
    // On the Mac #version doesn't work so the precision qualifiers are suppressed.
    preamble = "#define lowp\n#define mediump\n#define highp\n";
    #endif
    

    preamble 然后作为着色器代码的前缀。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多