将OpenGL ES 2.0应用程序移植到Web平台

在阅读本文之前,如果还没有设置Emscripten,请务必先阅读文章《Bring Your C++ Code to the Web》。请注意,本文并不是一个关于OpenGL的教程!要展示一个三角形,可能需要阅读多达100页的OpenGL教科书。本文只是覆盖了将OpenGL ES 2.0应用程序修改为在Web上运行所需的变更。OpenGL ES 2.0是OpenGL 2.0的一个子集,与WebGL1.0相对应。OpenGL ES 2.0中的每个函数都可以很容易地映射到WebGL的等效函数。这使得移植到Emscripten变得非常简单。

渲染函数

在每个OpenGL应用程序中,都有一个在主循环中反复调用的渲染或绘制函数。在Emscripten中,需要设置渲染函数,使其通过JavaScript的requestAnimationFrame()被调用,通过将渲染函数传递给emscripten_set_main_loop,其中第二个参数指的是帧率(fps),设置为0。第三个参数是simulate_infinite_loop,将其设置为零值会导致进入emscripten_set_main_loop。

C++ emscripten_set_main_loop(render, 0, 0);

使用SDL2设置OpenGL

这是标准的SDL 2代码,用于设置窗口和OpenGL 2.0。如果窗口系统不是SDL 2,可以忽略这一节。可以自由使用任何想要的OpenGL窗口系统。接下来,设置垂直同步(VSync)。接下来是GLEW。对于那些不熟悉GLEW的人,GLEW代表OpenGL Extension Wrangler Library,是一个跨平台的C/C++库,有助于加载OpenGL函数。在最后的设置步骤中,在initGL()中初始化顶点和着色器。

C++ //The window we'll be rendering to SDL_Window* gWindow = NULL; //OpenGL context SDL_GLContext gContext; //Initialize SDL if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("SDL could not initialize! SDL Error: %s\n", SDL_GetError()); success = false; } else { //Use OpenGL 2.1 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); //Create window gWindow = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); if (gWindow == NULL) { printf("Window could not be created! SDL Error: %s\n", SDL_GetError()); success = false; } else { //Create context gContext = SDL_GL_CreateContext(gWindow); if (gContext == NULL) { printf("OpenGL context could not be created! SDL Error: %s\n", SDL_GetError()); success = false; } else { //Use Vsync if (SDL_GL_SetSwapInterval(1) < 0) { printf("Warning: Unable to set VSync! SDL Error: %s\n", SDL_GetError()); } GLenum err = glewInit(); if (GLEW_OK != err) { printf("GLEW init failed: %s!\n", glewGetErrorString(err)); success = false; } //Initialize OpenGL if (!initGL(userData)) { printf("Unable to initialize OpenGL!\n"); success = false; } } } }

使用Emscripten设置OpenGL

上述SDL 2设置代码以前可以不修改地用于Emscripten。不知道哪个提交实际上破坏了Emscripten上的SDL2实现。现在必须使用下面的代码。在emscripten_set_canvas_element_size中,指定HTML5 canvas的名称和宽度和高度。majorVersion和minorVersion应该是1和0,因为目标是WebGL1.0。接下来,创建WebGL上下文并使其成为当前上下文。像上面的SDL 2代码一样,初始化GLEW和OpenGL对象,如顶点和着色器。使用__EMSCRIPTEN__宏使这段代码在Emscripten构建期间可见。

C++ emscripten_set_canvas_element_size("#canvas", SCREEN_WIDTH, SCREEN_HEIGHT); EmscriptenWebGLContextAttributes attr; emscripten_webgl_init_context_attributes(&attr); attr.alpha = attr.depth = attr.stencil = attr.antialias = attr.preserveDrawingBuffer = attr.failIfMajorPerformanceCaveat = 0; attr.enableExtensionsByDefault = 1; attr.premultipliedAlpha = 0; attr.majorVersion = 1; attr.minorVersion = 0; EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr); emscripten_webgl_make_context_current(ctx); GLenum err = glewInit(); if (GLEW_OK != err) { printf("GLEW init failed: %s!\n", glewGetErrorString(err)); success = false; } //Initialize OpenGL if (!initGL(userData)) { printf("Unable to initialize OpenGL!\n"); success = false; }

OpenGL着色器精度

在OpenGL ES 2.0中,必须在着色器代码开始之前指定浮点精度。highp、mediump和lowp是可用的选项。mediump是在精度和性能之间的一个很好的折中。对来说,lowp的分辨率太低,无法正确显示图像。只有在为Emscripten编译时,才将下面的代码作为顶点和片段着色器的第一行插入。记住在为桌面编译时删除这行代码。

C++ "precision mediump float; \n"

内联着色器代码

建议将着色器代码内联而不是存储在文件中,这样在Emscripten中,就不需要下载着色器来加载它们。有两种方法可以内联代码:连续的字符串字面量或C++11原始字符串字面量。前者要求在每行的末尾插入一个换行符以提高可读性。所有的连续字符串字面量将连接成同一个字符串字面量。下面的顶点和片段着色器使用连续的字符串字面量。

C++ const char vShaderStr [] = "precision mediump float; \n" "uniform mat4 WorldViewProjection;\n" "attribute vec3 a_position; \n" "attribute vec2 a_texCoord; \n" "varying vec2 v_texCoord; \n" "void main() \n" "{ \n" "gl_Position = WorldViewProjection * vec4(a_position, 1.0); \n" "v_texCoord = a_texCoord; \n" "} \n" ; const char fShaderStr [] = "precision mediump float; \n" "varying vec2 v_texCoord; \n" "uniform sampler2D s_texture; \n" "void main() \n" "{ \n" "gl_FragColor = texture2D( s_texture, v_texCoord );\n" "} \n" ;

加载资源

有两种方法可以加载资源,如3D模型和图像纹理。一种是预先加载文件夹中的文件,并在Makefile中指定这个位置。另一种方法是异步下载。如果资源在每次运行应用程序时都不会改变,那么预加载是很好的选择。比如游戏资源。正在做一个根据用户上传的照片变化的幻灯片。所以将使用异步下载。使用emscripten_async_wget,第一个参数是下载URL,第二个参数是目标文件名,第三和第四个参数是成功和失败下载事件的加载和错误回调。对于Emscripten,请记得在构建之前将下面的URL更改为本地主机和本地端口,并将资源复制到Web服务器。

C++ #ifdef __EMSCRIPTEN__ emscripten_async_wget("http://localhost:16564/yes.png", IMG_FILE, load_texture, load_error); #endif void load_texture(const char* file) { gUserData.textureId = init_texture(file); ++gUserData.images_loaded; } void load_error(const char* file) { printf("File download failed: %s", file); }

Makefile设置

在Makefile中,请确保设置这些选项以使用OpenGL ES 2.0、asm.js、无内存初始化文件、SDL 2窗口和SDL 2 Image。可以指定-s WASM=1用于Webassembly,但请确保Web服务器可以提供wasm文件。如果不可以,请查阅Web服务器文档,了解如何为wasm添加MIME类型。

C++ -s FULL_ES2=1 -s WASM=0 --memory-init-file=0 -s USE_SDL=2 -s USE_SDL_IMAGE=2

运行示例代码

当运行附带的源代码时,应该看到这张图片向前和向后移动。示例代码托管在GitHub上。

将C++代码带到Web上

C++图形带到Web上

将动画带到H264/HEVC视频

将现有应用程序带到Microsoft商店

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485