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

2023-10-22 19:19:48 浏览数 (1)

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

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

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

wasm 初探,写个 Hello World

红色三角形

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

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

代码语言:javascript复制
#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:绘制三角形

执行下面命令进行编译

代码语言:javascript复制
emcc red_triangle.cpp -std=c  11 -s WASM=1 -s USE_SDL=2 -O3 -o index.html

效果

更新三角形顶点位置

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

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

代码语言:javascript复制
#include <emscripten.h>

然后声明要暴露的方法:

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

完整 C 代码:

代码语言:javascript复制
#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: %fn", n1, n2);
  vertices[0] = n1;
  vertices[1] = n2;
  render();
}

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

代码语言:javascript复制
emcc update_triangle.cpp -std=c  11 -s WASM=1 -s USE_SDL=2 -O3 -o index.js

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

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

代码语言:javascript复制
<!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 更新画布。

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

0 人点赞