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

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

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

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

《wasm 初探,写个 Hello World》

红色三角形

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

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

c++ 复制代码
#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:绘制三角形》

执行下面命令进行编译

ini 复制代码
emcc red_triangle.cpp -std=c++11 -s WASM=1 -s USE_SDL=2 -O3 -o index.html

效果

更新三角形顶点位置

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

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

c 复制代码
#include <emscripten.h>

然后声明要暴露的方法:

c++ 复制代码
// 定义一个 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++ 代码:

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 文件了,这个我们自己写。

ini 复制代码
emcc update_triangle.cpp -std=c++11 -s WASM=1 -s USE_SDL=2 -O3 -o index.js

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

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

html 复制代码
<!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 更新画布。

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


相关阅读,

wasm 初探,写个 Hello World

图形编辑器开发:是否要像 Figma 一样上 wasm

相关推荐
程序猿乐锅9 小时前
Streamlit 实现 AI 聊天 Agent:角色提示词、流式输出与会话管理
开发语言·前端·笔记·后端·python·学习
niucloud-admin9 小时前
NIUCLOUD PHP SAAS上门家政使用文档:客户报表
前端·人工智能·ai编程
漫步人生走在路上9 小时前
外贸B2B行业GEO优化白皮书:从0到1完整落地流程
前端·人工智能·搜索引擎·chatgpt·twitter
程序猿乐锅10 小时前
面向对象练习:图书借阅系统里的封装、抽象类与多态
开发语言·前端·数据库·后端·python·学习·1024程序员节
老木避暑研究所10 小时前
负氧离子数据的采集、清洗与可视化:一套可复用的环境监测数据分析模板
前端·人工智能·数据分析
计算机魔术师10 小时前
AI 推翻了一个当了30年地基的假设,查的人恰好是立论者
前端
范同学~10 小时前
项目添加水印
java·前端·javascript
鬼手点金10 小时前
缓存作为架构约束
开发语言·前端·人工智能·python·microsoft·缓存·架构
月夏10 小时前
前端第一次接自己的后端:mock 切真接口踩了 2 个坑,组件只改了一行 import
前端·后端·node.js
IT_陈寒10 小时前
React实战技巧总结:如何优雅处理复杂列表的渲染卡顿问题?
前端·人工智能·后端