WebGL 入门实例

看到很多 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 上下文函数, 假装自己入门了.

你可能感兴趣的:(WebGL 入门实例)