18.1 WebGL 简洁教程

什么是WebGL

虽然WebGL可以用来实现3D对象绘制,但WebGL并非一个3D引擎

WebGL只是关于绘制点、线、三角形的接口

它是在GPU上运行代码的低级API

着色器(Shaders)

使用着色器(shader)来绘制图形

顶点着色器计算顶点位置

片段着色器处理光栅化(像素点颜色)

GL着色器语言

看起来如何?

特定于GPU的语言 (GLSL) 看起来像C语言,以void main()开始 但是内置了用来处理2D/3D渲染的数据类型和函数

顶点着色器代码

attribute vec3 position; void main() { gl_Position = vec4(position, 1.0); }

着色器通过位置(position)属性从缓冲区获取数据

对位置缓冲区中的每个位置运行着色器

顶点位置通过gl_position设置

片段着色器代码

precision highp float;

void main() {
  vec2 p = gl_FragCoord.xy;
  gl_FragColor = vec4(1.0, 0.5, 0, 1.0);
}

对每个片段(像素)运行片段着色器

像素坐标可以从gl_FragCoord读取

输出颜色以gl_FragColor设置

从JS传递数据

attribute: 顶点着色器从缓冲区中提取一个值并将其存储在此处

uniform: 在执行着色器之前,传递在JS中设置的值,这些值对于绘制调用中的所有顶点保持不变

varying: 变量(Varyings)是一种将值从顶点着色器传递到片段着色器的方法。和常量(Uniforms)不同的是,该值可以被顶点着色器修改,然后在片段着色器中读取(只读)。变量的值是一个插值数据,每个像素都不同。

在线示例:绘制一个渐变色三角形

GL着色器语言

数据类型

primitives (bool, int, float) vectors (vec2, vec3, vec4) matrices (mat2, mat3, mat4) texture data (sampler2D)

常用内置函数

三角函数 sin, cos, atan 线性插值 (mix) 矢量运算 Vector arithmetics (+, -, *, /, dot, cross, length) 矩阵运算 Matrix arithmetics (+, -, *)

编写JavaScript

获取WebGL上下文

const gl = canvas.getContext('webgl')

和2d canvas上下文类似

编译着色器代码


const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentCode);
gl.compileShader();

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(fragmentShader, vertexCode);
gl.compileShader();

和c语言类似,我们需要先把高级语言编译为GPU认识的机器语言。

创建程序


const program = gl.createProgram();
program.attachShader(vertexShader);
program.attachShader(fragmentShader);
program.linkProgram();

也和C语言类似,这两个着色器代码被链接进一个WebGLProgram。

你可以用program.validateProgram()来检查程序是否有效

为顶点着色器定义属性(attributes)


const positionLoc = this.gl.getAttribLocation(program, 'position');
this.gl.enableVertexAttribArray(positionLoc);

通过enableVertexAttribArray来启用属性

创建缓存


// provide 2D data for a triangle
const data = [-1, -1,  -1,  1,  1, -1];
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);

创建一个缓存并通过Float32Array提供数据

设置属性(attribute)指针


const recordSize = 2;
const stride = 0; // 0 = advance through the buffer by recordSize * sizeof(data type)
const offset = 0; // the starting point in the buffer
const type = gl.FLOAT; // data type
const normalized = false; // normalize the data (unused for gl.FLOAT)
gl.vertexAttribPointer(positionLoc, recordSize, type, normalized, stride, offset);

把缓存赋给属性

传递常量(uniform)变量


const uTime = gl.getUniformLocation(program, 'time');
gl.uniform1f(loc, tickCount);

可能的类型:floats, ints, bools, vectors, matrices

从JavaScript传递变量给WebGL,比如屏幕分辨率、时间、鼠标位置以及一些控制参数等。

绘制


createBuffers()
setAttributes();

function animLoop(time = 0) {
  setUniforms();
  gl.drawArrays(gl.TRIANGLES);
  requestAnimationFrame(animLoop);
}

animLoop();
Useful GLSL functions
// normalize coords and set (0, 0) to center
vec2 coords() {
  float vmin = min(width, height);
  return vec2((gl_FragCoord.x - width * .5) / vmin,
              (gl_FragCoord.y - height * .5) / vmin);
}

//rotate
vec2 rotate(vec2 p, float a) {
  return vec2(p.x * cos(a) - p.y * sin(a),
              p.x * sin(a) + p.y * cos(a));
}

//repeat
vec2 repeat(in vec2 p, in vec2 c) {
  return mod(p, c) - 0.5 * c;
}

参考链接