看到很多 WebGL 文章都是讲 three.js
, 个人觉得抛开第三方库使用 WebGL 还是蛮好玩的. 前端可能没有时间去学 OpenGL ES 这些东西, 所以刚上手 WebGL 会觉得有点奇怪. 因为除了 JavaScript 还要会 GLSL 语法. WebGL 也是有多个版本的, 有 1.0 版本跟 2.0 版本, 1.0 版本对应的是 OpenGL ES 2.0, 对应的 GLSL 语法基本上没区别.
项目用的是 TypeScript, 不想自己配 ts 项目的直接克隆下来使用吧. git 地址
点
HTML 直接放个 div
作为 canvas
的容器.
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= htmlWebpackPlugin.options.title %>title>
<link rel="icon" href="/favicon.ico" type="image/x-icon"/>
head>
<body>
<div class="canvas-area">
div>
body>
html>
复制代码
直接在脚本里生成 canvas
加载到 HTML 去.
const canvas = document.createElement('canvas');
canvas.className = 'canvas';
canvas.id = "webgl-canvas";
canvas.width = innerWidth;
canvas.height = innerHeight;
$('.canvas-area').appendChild(canvas);
复制代码
canvas
是 WebGL 活动的场所, 所以需要把这个东西加载上 HTML 上, 然后就可以开始活动了. 一般使用 canvas
都是用 canvas.getContext('2d')
获取上下文对象来进行一系列操作, WebGL 也逃不出 canvas 的基础套路.
let gl = canvas.getContext('webgl');
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
...
复制代码
WebGL 除了用上上下文的一些函数, 还有个很重要的东西, 那就是 shader
, 这玩意是 GPU 执行的, 稍微了解过一点 OpenGL, 应该知道 OpenGL 的渲染流程有很多环节都是 shader
处理, 写 WebGL 应用其实我们基本上只要关注 vertex shader 跟 fragment shader. 一个是描述图形在 canvas 的位置信息, 一个是描述图形的片段信息. 在一条流水线上, 某一处是 vertex shader, 它处理结束后把东西交给 fragment shader 处理. 一个 WebGL 应用, 要包含这两个 shader, 所以我们要写一点东西加载处理一下 shader.
function shader(gl, type: GLenum, source: string): void | WebGLShader {
const s: WebGLShader = gl.createShader(type);
gl.shaderSource(s, source);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.log('Failed to compile shaders', gl.getShaderInfoLog(s));
gl.deleteShader(s);
return null;
}
return s;
}
function link(gl, vs: string, fs: string): WebGLProgram | void {
const vertex = shader(gl, gl.VERTEX_SHADER, vs);
const fragment = shader(gl, gl.FRAGMENT_SHADER, fs);
if (!!vertex && !!fragment) {
const program: WebGLProgram = gl.createProgram();
gl.attachShader(program, vertex);
gl.attachShader(program, fragment);
gl.linkProgram(program);
return program;
}
return null;
}
function program(gl, vs: string, fs: string): void {
const program = link(vs, fs);
if (!!program) {
gl.useProgram(program);
}
}
复制代码
有这些东西, 再写点 GLSL 语句就好了.
const vertex_shader = `
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
const fragment_shader = `
void main() {
gl_FragColor = vec4(0.3, 0.7, 0.6, 1.0);
}
`;
复制代码
然后调用之前的函数, 加载 shader 加载完了把东西画出来
program(gl, vertex_shader, fragment_shader);
gl.drawArrays(gl.POINTS, 0, 1);
复制代码
刷新页面, 应该能看到中心一个有颜色的点, 但是很不明显, 可以在 vertex_shader
main 函数内加一句 gl_PointSize = 10.0;
现在就能看到放大十倍的点了. drawArrays
函数可以用来将 webgl 上下文绑定的数据画成图, 只要给定要画的类型, 譬如这里给的是 gl.POINTS
类型.
上面说了我们主要关注 vertex_shader
fragment_shader
这两个东西. vertex_shader
文件写好的代码最后是在 GPU 上执行的, 一些矩阵向量运算, 其实全部的运算交给 CPU 处理也是可以的, 但是 GPU 蛮擅长处理这些运算, 专业的事交给专业的人, 现在这边没做什么运算, 只是把图形的位置给写上去了, 加上后面放大 point 尺寸的代码, gl_Position
是 WebGL 内置的一个四分量的向量类型变量, 所以我们要给它赋值就写一个 vec4
类型的值, 每个分量依次代表的是 x
, y
, z
, w
. 我们用 WebGL 来渲染 3D 空间内的图形的, 可以想到用一个三分量的向量来描述图形在 3D 空间的位置, 为什么这个内置的 gl_Position
是一个四分量的向量, 因为我们还要考虑矩阵运算, 所以引入了一个齐次坐标, 齐次坐标的概念就是一个 n 维向量用 n + 1 维向量来表示, vec4(x, y, z, w)
就跟 vec3(x / w, y / w, z / w)
相等. w 分量必须是要大于 0 的, w 越接近 0 就表示图形离我们越远, 所以 w 的值要比 0 大我们才看得到图形. 这里 gl_Position
除了 w
分量其他都是 0.0
, 因为 WebGL 当前我们认为使用的是笛卡尔坐标系, x
, y
, z
, 都是 0.0
的话, 画出来的图就在 canvas
正中间, 其实就是坐标原点. 其实还有很多其他坐标概念, 但是目前就处理一些 2D 图形, 不用考虑那么多.
然后就是 fragment_shader
, 这个 shader
目前是拿来给内置的颜色变量 gl_FragColor
赋值, 这个变量也是个四分量的向量, 也就是 rgba 四个通道的颜色表示.
反正就这样吧, GLSL 学一下, 学点 webgl 上下文函数, 假装自己入门了.