OpenGL ES中的跨平台渲染器

sczxawaw  于 2022-11-04  发布在  其他
关注(0)|答案(3)|浏览(155)

我正在写一个跨平台的渲染器。我想在Windows,Linux,Android,iOS上使用它。
您认为避免绝对抽象并直接在OpenGL ES 2.0中编写它是一个好主意吗?
据我所知,我应该能够在PC上编译它对标准的OpenGL,只有一个小的变化,在代码处理上下文和连接到窗口系统。

m3eecexj

m3eecexj1#

您认为避免绝对抽象并直接在OpenGL ES 2.0中编写它是一个好主意吗?
您在这方面的主要困难是处理ES 2.0规范中与OpenGL 2.1实际上不相同的部分。
例如,您不能通过桌面GLSL1.20编译器来使用ES2.0着色器。这些在GLSL 1.20中是非法构造。
但是你可以用#define来处理它们,但是这需要一些手动干预。你必须在着色器源文件中插入一个#ifdef。有一些着色器编译技巧可以让这变得更容易。
实际上,由于GL ES使用完全不同的扩展集(尽管有些是桌面GL扩展的镜像和子集),因此您可能希望这样做。
每个GLSL着色器(桌面或ES)都需要有一个“preamble”。着色器中的第一个非注解内容需要是#version声明。幸运的是,桌面GL 2.1和GL ES 2.0的版本是相同的:#version 1.20。问题是接下来会发生什么:#extension列表(如果有)。这将启用着色器所需的扩展。
由于GL ES使用与桌面GL不同的扩展名,您需要更改此扩展名列表。由于您需要的GLSL ES扩展名很可能比桌面GL 2.1扩展名更多,因此这些列表将不仅仅是1:1Map,而是完全不同的列表。
我的建议是使用为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中 * 会 * 工作得很好,它们不一定是最优的。在所有移动的系统这样的big-endian机器上可以正常上传的东西需要在little-endian桌面机器上的驱动程序中做一些小的调整。因此,您可能希望有一种方法在GL ES和桌面GL上指定不同的像素传递参数。
此外,ES 2.0和Desktop GL 2.1中有不同的扩展集,您需要利用这些扩展集。虽然它们中的许多扩展集都试图相互镜像(OES_framebuffer_object是EXT_framebuffer_object的子集),但您可能会遇到类似于上述“不完全是子集”的问题。

jljoyd4f

jljoyd4f2#

根据我的经验,满足这类需求的最佳方法是用纯C语言开发引擎,而不需要额外的层。
我是PATRIA 3D引擎的主要开发人员,该引擎基于您刚才提到的可移植性基本原则,我们仅通过在基本标准库上开发工具就实现了这一点。
在不同的平台上编译代码的工作量非常小。
移植整个解决方案所需的实际工作量可以根据您要嵌入引擎的组件进行计算。
例如:
标准品C:
发动机3D
游戏逻辑
游戏AI
物理学
+
窗口界面(GLUT,EGL等)-取决于平台,无论如何可以是GLUT的桌面和EGL的移动的设备。
人机界面-取决于移植,Java适用于Android,OC适用于IOS,无论是哪种版本的桌面
声音管理器-取决于移植
市场服务-取决于移植
通过这种方式,您可以无缝地重复使用95%的工作。
我们的发动机采用了这种解决方案,到目前为止,它确实值得最初的投资。

wribegjk

wribegjk3#

以下是我在各种平台上实现OpenGL ES 2.0支持的经验结果,我的商业Map和路由库在这些平台上运行。
渲染类设计为在单独的线程中运行。它引用了包含Map数据和当前视图信息的对象,并使用互斥锁来避免在绘图时阅读该信息时发生冲突。它在图形内存中维护OpenGL ES矢量数据的缓存。
所有的渲染逻辑都是用C++编写的,并在以下所有平台上使用。

视窗(MFC)

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

Windows(.NET和WPF)

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

Windows(UWP)

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

Windows、Linux和Mac操作系统上的Qt

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

安卓系统

创建一个实现android. opengl. GLSurfaceView. Renderer的渲染器类。为C渲染对象创建一个JNI Package 器。在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已弃用GLKit,并且最终将不再支持GLKit。
创建一个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实现所接受的着色器语言中存在不兼容性。我在C++代码中使用CompileShader函数中的以下条件编译代码克服了这些问题:

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作为着色器代码的前缀。

相关问题