前军教程网

中小站长与DIV+CSS网页布局开发技术人员的首选CSS学习平台

WASM + OpenGL + C++ 入门:绘制三角形

大家好,我是前端西瓜哥。

我在尝试用 C++ 写一段 OpenGL 代码,用 Emscripten 编译成 WASM,运行在浏览器。OpenGL 最后会被 WASM 转换为 WebGL 进行渲染。

先装 Emscripten SDK。安装和入门可以看这篇文章:

《wasm 初探,写个 Hello World》

红色三角形

还是老样子,图形渲染的 helloworld:画一个红色三角形。

创建一个 red_triangle.cpp 文件,输入以下内容。

#include <functional>
#include <SDL.h>
#include <stdio.h>
#define GL_GLEXT_PROTOTYPES 1
#include <SDL_opengles2.h>
const char *vertexShaderSource =
    "attribute vec4 a_position;\n"
    "void main() {\n"
    "    gl_Position = a_position;\n"
    "}\n";
const char *fragmentShaderSource =
    "precision mediump float;\n"
    "void main() {\n"
    "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "}\n";
int main()
{
  printf("前端西瓜哥正在渲染红色三角形~~~\n");
  // 创建一个 400x300 的画布
  SDL_Window *window;
  SDL_CreateWindowAndRenderer(400, 300, 0, &window, nullptr);
  // 针对 OpenGL ES,表示要生成几个 vao,后面顶点属性绑定 vbo 时会保存到这里
  GLuint vao;
  glGenVertexArraysOES(1, &vao);
  glBindVertexArrayOES(vao);
  // 1. 顶点着色器
  GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
  glCompileShader(vertexShader);
  // 2. 片元着色器
  GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
  glCompileShader(fragmentShader);
  // 3. 程序对象
  GLuint program = glCreateProgram();
  // 将着色器附加到程序对象上
  glAttachShader(program, vertexShader);
  glAttachShader(program, fragmentShader);
  glLinkProgram(program);
  glUseProgram(program);
  // 顶点数据
  GLfloat vertices[] = {0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f};
  GLuint vbo;
  glGenBuffers(1, &vbo);
  // 绑定缓冲区到上下文
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  // 将 顶点数据 复制到 缓冲区
  glBufferData(GL_ARRAY_BUFFER, 24, vertices, GL_STATIC_DRAW);
  // 获取顶点着色器中的 position 属性
  GLint position = glGetAttribLocation(program, "a_position");
  // 设置读取方式
  glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, 0);
  // 启用顶点属性数组
  glEnableVertexAttribArray(position);
  // 清空屏幕
  glClearColor(0, 1, 1, 0);   // 背景色为 #00FFFF
  glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓存
  glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形
  glDeleteBuffers(1, &vbo);
}

WebGL 代码对照:

《一起学 WebGL:绘制三角形》

执行下面命令进行编译

emcc red_triangle.cpp -std=c++11 -s WASM=1 -s USE_SDL=2 -O3 -o index.html

效果

更新三角形顶点位置

再尝试通过 JavaScript 给 wasm 通信,更新三角形的顶部的顶点信息然后重新渲染。

我们要暴露方法给 JavaScript 调用,对此需引入 emscripten.h 头文件。

#include <emscripten.h>

然后声明要暴露的方法:

// 定义一个 updateColor 方法给 js 用。全局会出现一个 _updateColor 方法。
// EMSCRIPTEN_KEEPALIVE 宏防止方法编译时被优化掉。
extern "C" void EMSCRIPTEN_KEEPALIVE updateColor(float n1, float n2)
{
  printf("n1: %f, n2: %f\n", n1, n2);
  vertices[0] = n1;
  vertices[1] = n2;
  render();
}

完整 C++ 代码:

#include <functional>
#include <SDL.h>
#include <stdio.h>
#define GL_GLEXT_PROTOTYPES 1
#include <SDL_opengles2.h>
// wasm 需要暴露方法给 js,引入这个头文件
#include <emscripten.h>
const char *vertexShaderSource =
    "attribute vec4 a_position;\n"
    "void main() {\n"
    "    gl_Position = a_position;\n"
    "}\n";
const char *fragmentShaderSource =
    "precision mediump float;\n"
    "void main() {\n"
    "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "}\n";
GLfloat vertices[] = {0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f};
void render()
{
  printf("前端西瓜哥正在渲染红色三角形~~~\n");
  // 创建一个 400x300 的画布
  SDL_Window *window;
  SDL_CreateWindowAndRenderer(400, 300, 0, &window, nullptr);
  GLuint vao;
  glGenVertexArraysOES(1, &vao);
  glBindVertexArrayOES(vao);
  GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
  glCompileShader(vertexShader);
  GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
  glCompileShader(fragmentShader);
  GLuint program = glCreateProgram();
  glAttachShader(program, vertexShader);
  glAttachShader(program, fragmentShader);
  glLinkProgram(program);
  glUseProgram(program);
  GLuint vbo;
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, 24, vertices, GL_STATIC_DRAW);
  GLint position = glGetAttribLocation(program, "a_position");
  glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, 0);
  glEnableVertexAttribArray(position);
  glClearColor(0, 1, 1, 0);    
  glClear(GL_COLOR_BUFFER_BIT); 
  glDrawArrays(GL_TRIANGLES, 0, 3);
  glDeleteBuffers(1, &vbo);
}
int main()
{
  render();
}
// 定义一个 updateColor 方法给 js 用。全局会出现一个 _updateColor 方法。
// EMSCRIPTEN_KEEPALIVE 宏防止方法编译时被优化掉
extern "C" void EMSCRIPTEN_KEEPALIVE updateColor(float n1, float n2)
{
  printf("n1: %f, n2: %f\n", n1, n2);
  vertices[0] = n1;
  vertices[1] = n2;
  render();
}

编译。这次不要导出 html 文件了,这个我们自己写。

emcc update_triangle.cpp -std=c++11 -s WASM=1 -s USE_SDL=2 -O3 -o index.js

编译的 wasm 默认会暴露到全局的 Module 对象上。

我们可以 通过这个 Module 预设置一些属性,比较重要的是指定好要渲染的画布元素。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>前端西瓜哥在更新三角形</title>
  </head>
  <body>
    <canvas></canvas>
    <script>
      const canvas = document.querySelector('canvas');
      var Module = {
        // 指定要渲染的画布元素
        canvas: canvas,
      };
    </script>
    <script src="./index.js"></script>
  </body>
</html>

效果

结尾

简单体验了一下用 C++ 写 OpenGL,编译成 WASM 在浏览器上运行,基于 WebGL 渲染出三角形,并用 JavaScript 将数据传给 WASM 更新画布。

我是前端西瓜哥,关注我,学习更多前端图形知识。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言