将你的 C++ OpenGL 代码带到 Web





5.00/5 (3投票s)
如何将你的 C++ OpenGL 代码带到 Web 上
目录
- 引言
- 渲染函数
- 使用 SDL2 设置 OpenGL
- 使用 Emscripten 设置 OpenGL
- OpenGL Shader 精度
- 内联你的 Shader 代码
- 加载资源
- 《将你的...》系列的其他文章
- 历史
引言
在阅读本文之前,如果您还没有设置 Emscripten,您需要阅读这篇文章:将你的 C++ 代码带到 Web 上。请注意:这 *不是* 一个关于 OpenGL 的教程!可能需要阅读多达 100 页的 OpenGL 教材才能显示一个三角形。在这篇短文中涵盖 OpenGL 的基础知识是有点勉强。它只涵盖了修改你的 OpenGL ES 2.0 应用程序以在 Web 上运行所需的更改。 OpenGL ES 2.0 是 OpenGL 2.0 的一个子集,对应于 WebGL 1.0。 OpenGL ES 2.0 中的每个函数都可以轻松映射到 WebGL 的等效函数。这使得移植到 Emscripten 变得轻而易举。
渲染函数
在每个 OpenGL 应用程序中,都有一个 render
或 draw
函数,该函数在主循环中重复调用。在 Emscripten 中,我们必须设置 render
函数,使其被 JavaScript 的 requestAnimationFrame()
调用,方法是将 render
函数传递给 emscripten_set_main_loop
,其第二个参数表示 fps
,设置为 0
。第三个参数是 simulate_infinite_loop
,将其设置为零值会导致它进入 emscripten_set_main_loop
。
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()
中初始化顶点和着色器。
//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 画布名称以及宽度和高度。 majorVersion
和 minorVersion
应该分别为 1 和 0,因为我们的目标是 WebGL 1.0。接下来,我们创建 WebGL 上下文并使其成为当前的上下文。与上面的 SDL 2 代码一样,我们初始化 GLEW 和 OpenGL 对象,如顶点和着色器。我们使用 __EMSCRIPTEN__
宏使此代码处于活动状态,以便在 Emscripten 构建期间可以看到该代码。
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 Shader 精度
在 OpenGL ES 2.0 中,我们必须在着色器代码开始之前指定浮点精度。 highp
、mediump
和 lowp
是可用的选项。 mediump
是精度和性能之间的一个很好的折衷。对我来说,lowp
的分辨率太低,无法正确显示图像。仅当为 Emscripten 编译时,才将以下代码作为顶点和片段着色器中的第一行插入。记住在为桌面编译时删除该行。
"precision mediump float; \n"
内联你的 Shader 代码
我建议保持着色器代码内联而不是存储在文件中,以便在 Emscripten 中,你不需要下载着色器来加载它们。有两种方法可以内联代码:连续字符串文字或 C++11 原始字符串文字。前者要求你在每行末尾插入一个换行符以提高可读性。所有连续字符串文字将连接到同一个字符串文字中。下面的顶点和片段着色器正在使用连续字符串文字。
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 更改为您的 localhost 和本地端口,并将资源复制到 Web 服务器。
#ifdef __EMSCRIPTEN__
emscripten_async_wget("https://: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 类型。
-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 Store
- 将你的 C++ OpenGL 代码带到 Web
历史
- 2019 年 8 月 24 日:初始版本