大家好,我是前端西瓜哥。
我在尝试用 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
头文件。
#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 更新画布。
我是前端西瓜哥,关注我,学习更多前端图形知识。