在阅读本文之前,如果还没有设置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);
这是标准的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;
}
}
}
}
上述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 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中,请确保设置这些选项以使用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商店