上篇讲了个如何使用gpu.js这个库来进行简单的gpu计算 虽然简单易用 但是本身的局限也很多 目前这个库也不是非常完善 有待改进 那咱就从原理开始 来自己搞一个吧 当然 并不是指实现一个这个的通用的库 而是使用相关原理 完成一个利用GPU计算的demo 当然还是矩阵的乘法
前端使用GPU的能力是通过webgl实现的 更加广泛的理解的可以认为是通过canvas来说实现的 canvas估计对大多数前端来说并不陌生 canvas有许多个像素组成 每个像素的颜色可以有RGBA
四个维度表示 每个维度范围为0-255 既8位 把RGBA表示成数值的话 那每个像素可以存32位 这就是前端使用gpu计算最为核心的一点 每个像素可以存储一个32位的值, 刚刚好就是一个int
或者uint
0.基本WebGL绘制
首先从最简单的绘制一个图像开始 webgl绘图的流程 最简单的就这样
其中两个vertex shader
和fragment shader
为两个GLSL
代码片段 分别处理坐标数据和颜色数据 vertex shader
和fragment shader
的执行是以像素为单位
canvas开始绘制的时候 vertex shader
中得到 每个需要绘制的像素的坐标 视需要可以对坐标进行各种转换 最终得到一个最终位置 这个过程中可以将数据作为输出传入fragment shader
参与下一步的计算
fragment shader
接受各种输入 最终输出一个RGBA
颜色数据作为该像素点的颜色值
当所有像素都绘制完成之后 画布绘制完成
0.0 js中的流程就比较简单了
- 创建
webgl program
- 初始化两个
shader
- 传入各个顶点坐标
- 开始绘制
因为咱们主要是计算 所以对坐标相关的数据可以不用太多关注 咱们直接画一个铺满画布的矩形就可以了
// 加载资源
async function loadRes(file) {
const resp = await fetch(file);
return resp.text();
}
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl2");
const program = gl.createProgram();
// 载入shader
function initShader(code, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, code);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
throw new Error("compile: " + gl.getShaderInfoLog(shader));
gl.attachShader(program, shader);
}
// 获取attribute参数的地址
function getAttribLoc(name) {
const loc = gl.getAttribLocation(program, name);
if (loc == -1) throw `getAttribLoc ${name} error`;
return loc;
}
async function startDraw(vertexShader, fragmentShader) {
// 加载shader的代码
const vshaderCode = await loadRes(vertexShader);
const fshaderCode = await loadRes(fragmentShader);
// 载入shader
initShader(vshaderCode, gl.VERTEX_SHADER);
initShader(fshaderCode, gl.FRAGMENT_SHADER);
gl.linkProgram(program);
gl.useProgram(program);
// 传入坐标信息 具体含义后有说明
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
const vecPosXArr = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
gl.bufferData(gl.ARRAY_BUFFER, vecPosXArr, gl.STATIC_DRAW);
// 将顶点信息绑定到vertex shader中的变量 以两个数值作为一组数据
// 所以上述8个数值实际标明了4个顶点坐标
// g_pos为vertex中的自定义的变量名
const posAtrLoc = getAttribLoc("g_pos");
gl.enableVertexAttribArray(posAtrLoc);
gl.vertexAttribPointer(posAtrLoc, 2, gl.FLOAT, false, 0, 0);
// 清理画布
gl.clearColor(.0, .0, .0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 使用4个坐标连续绘制两个三角形
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
需要注意的一点vertex shader
中得到的坐标是以canvas中心为(0,0)
水平向右为x轴正方向 垂直向上为y轴正方向 两轴的取值范围为[-1, 1]
所以上面js代码中传入的顶点坐标范围为[-1, 1]
的浮点数
另外OpenGL中绘制面都是以三角形为单位的 webgl中也不例外 提供了一个绘制连续三角形的方式 一个矩形是两个三角形 所以传入四个顶点就可以了 当然也可以传入六个顶点 分别绘制两个三角形
顶点的传入实际上是传入一个数组 然后vertexAttribPointer()
方法指定各个顶点如何使用这个坐标数组 可以认为是8个一维坐标 也可以认为是2个二维坐标 或者是2个四维坐标 所以上述的例子实际是传入了4个2维坐标
接下来就是两个shader中的流程 目前大部分浏览器已经支持WebGL 2.0标准 对应OpenGL ES 3.0
所以shader中的语法需要遵循相关语法
具体的版本可以使用gl.getParameter(gl.SHADING_LANGUAGE_VERSION)
获取
0.1 首先vertex shader
:
#version 300 es
precision highp float;
precision highp int;
in vec4 g_pos;
out vec2 v_pos;
void main() {
float curX = (g_pos.x + 1.) / 2.;
float curY = (g_pos.y + 1.) / 2.;
v_pos = vec2(curX, curY);
gl_Position = g_pos;
}
具体的语法啊变量类型啊什么的可以看官