我正在编写一个跨平台渲染器。我想在 Windows、Linux、Android、iOS 上使用它。
您认为避免绝对抽象并直接在 OpenGL ES 2.0 中编写它是一个好主意吗?
据我所知,我应该能够在 PC 上针对标准 OpenGL 编译它,只需对处理上下文和与窗口系统连接的代码进行少量更改。
我正在编写一个跨平台渲染器。我想在 Windows、Linux、Android、iOS 上使用它。
您认为避免绝对抽象并直接在 OpenGL ES 2.0 中编写它是一个好主意吗?
据我所知,我应该能够在 PC 上针对标准 OpenGL 编译它,只需对处理上下文和与窗口系统连接的代码进行少量更改。
您认为避免绝对抽象并直接在 OpenGL ES 2.0 中编写它是一个好主意吗?
您的主要困难在于处理 ES 2.0 规范中实际上与 OpenGL 2.1 不同的部分。
例如,您不能通过桌面 GLSL 1.20 编译器推送 ES 2.0 着色器。在 ES 2.0 中,您使用诸如指定精度之类的东西;这些是 GLSL 1.20 中的非法构造。
但是,您可以#define
绕过它们,但这需要一些手动干预。您必须将 a#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 的子集),但您可能会遇到类似的“不完全是子集”问题,例如上面提到的那些。
以我的拙见,满足此类需求的最佳方法是以纯 C 风格开发引擎,而无需额外的层。
我是 PATRIA 3D 引擎的主要开发者,它基于您刚才提到的可移植性的基本原则,我们仅通过在基本标准库上开发工具就实现了这一点。
然后在不同平台上编译代码的工作量非常小。
移植整个解决方案的实际工作量可以根据您要嵌入引擎的组件来计算。
例如:
标准 C:
引擎 3D
游戏逻辑
游戏人工智能
物理
+
窗口界面(GLUT、EGL 等) - 取决于平台,无论如何可能是用于桌面的 GLUT 和用于移动设备的 EGL。
人机界面 - 取决于移植,Android 的 Java,IOS 的 OC,任何版本的桌面
声音管理器 - 取决于移植
市场服务 - 取决于移植
通过这种方式,您可以无缝地重复使用 95% 的努力。
我们已经为我们的引擎采用了这个解决方案,到目前为止,它确实值得最初的投资。