Appearance
WebGL 入门
WebGL 容器(坐标系)
WebGL 坐标系的坐标原点在画布中心, 坐标系的 x 轴方向是朝右的, y 轴方向是朝上的
WebGL 使用的是正交右手坐标系,每个方向都有可使用的值区间,超出该矩形区间图像不会绘制
- x 轴最左边为 -1, 最右边为 1
- y 轴最下边为 -1, 最上边为 1
- z 轴朝向你的方向最大值为 1, 远离你的方向最大值为 -1
这些值与 canvas 的尺寸无关,无论 canvas 的长宽比是多少,WebGL 的区间值都是一致的
WebGL 渲染线管
所谓的渲染管线,实际上就是渲染过程流水线,指的不是具体某一样东西,而是一个流程。因为渲染管线的流程中总是将上一步的结果作为下一步的输入,就像水管一样接起来,管线的名字也因此得来
渲染管线的简要流程
顶点着色器(可编程):首先通过顶点着色器,确定我们设置的顶点位置
图元装配:
gl.drawArray
方法会指定图元装配的方式(点、线、三角形),根据我们设定的装配方式将其组装成我们想要的基本图形光栅化:实际上就是一个将上一步装配好的图形用像素来表示的一个过程
片元着色器(可编程):光栅化完成后,每个像素的片元都会执行片元着色器中的程序,得到最后的颜色值
测试与混合:这一阶段主要是 WebGL 内部进行了一些模版测试、深度测试,最后再与上一帧的数据进行混合。
顶点着色器和片元着色器是可编程的功能单元,拥有更大的自主性。光栅器、深度测试等是不可编程的功能单元
WebGL 可绘制的基本图形
在 WebGL 的世界中,只有三种基本图形:点、线、三角形
js
void gl.drawArrays(mode, first, count);
gl.POINTS
: 绘制一系列点gl.LINES
: 绘制一系列单独线段。每两个点作为端点,线段之间不连接gl.LINE_STRIP
: 绘制一个线条。绘制一系列线段,上一点连接下一点gl.LINE_LOOP
: 绘制一个线圈。绘制一系列线段,上一点连接下一点,最后一点与第一点相连gl.TRIANGLES
: 绘制一系列三角形。每三个点作为顶点gl.TRIANGLE_STRIP
: 绘制一个三角带gl.TRIANGLE_FAN
: 绘制一个三角扇
世界矩阵(Model)、视图矩阵(View)和投影矩阵(Projection)
- 世界矩阵就是确定一个统一的世界坐标,用于组织独立的物体形成一个完整的场景
- 视图矩阵就是我们能看到的那部分场景,由虚拟摄像机负责拍摄
- 投影矩阵就是三维物体的平面影射,把三维场景在一个二维的平面上显示
WebGL 画点(point)
ts
// 创建画布
const canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
// 获取webgl上下文对象
const gl = canvas.getContext("webgl2");
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 顶点着色器的源码
const vertexShaderSource = /* glsl */ `
void main() {
// 声明顶点位置
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
// 声明绘制点的大小
gl_PointSize = 10.0;
}
`;
// 片元着色器的源码
const fragmentShaderSource = /* glsl */ `
void main() {
// 设置像素颜色为红色
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
// 将源码分配给顶点和片元着色器代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
// 编译顶点和片元着色器程序
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
// 创建着色器程序对象
const program = gl.createProgram();
// 将顶点和片元着色器挂载着色器程序上
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 链接着色器程序
gl.linkProgram(program);
// 使用创建好的着色器程序
gl.useProgram(program);
// 设置清空画布颜色为黑色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 使用上一步设置的清空画布颜色清空画布
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
WebGL 画三角形(triangle)
ts
// 创建画布
const canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
// 获取webgl上下文对象
const gl = canvas.getContext("webgl2");
// 定义顶点数组
const vertices = new Float32Array([
-0.6, 0.6, 0,
0.6, 0.6, 0,
0.0, -0.6, 0,
]);
// 创建顶点缓冲区
const vertexBuffer = gl.createBuffer();
// 绑定顶点缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 设置顶点数组到顶点缓冲区
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 顶点着色器的源码
const vertexShaderSource = /* glsl */ `
attribute vec3 coordinates;
void main() {
// 声明顶点位置
gl_Position = vec4(coordinates, 1.0);
// 声明绘制点的大小
gl_PointSize = 1.0;
}
`;
// 片元着色器的源码
const fragmentShaderSource = /* glsl */ `
void main() {
// 设置像素颜色为红色
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
// 将源码分配给顶点和片元着色器代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
// 编译顶点和片元着色器代码
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
// 创建程序对象
const program = gl.createProgram();
// 将顶点和片元着色器挂载着色器程序上
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 链接着色器程序
gl.linkProgram(program);
// 使用创建好的着色器程序
gl.useProgram(program);
// 关联着色器程序到缓冲对象
const coord = gl.getAttribLocation(program, "coordinates");
gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coord);
// 设置清空画布颜色为黑色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 使用上一步设置的清空画布颜色清空画布
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
纹理 alpha 混合方式的原理(公式)
假设一种不透明东西的颜色是A,另一种透明的东西的颜色是B,那么透过B去看A,看上去的颜色C就是B和A的混合颜色,可以用这个式子来近似
设B物体的透明度为 alpha (取值为0-1,0为完全透明,1为完全不透明)
R(C) = alpha * R(B) + (1 - alpha) * R(A)
G(C) = alpha * G(B) + (1 - alpha) * G(A)
B(C) = alpha * B(B) + (1 - alpha) * B(A)
R(x)、G(x)、B(x)分别指颜色x的RGB分量。看起来这个东西这么简单,可是用它实现的效果绝对不简单,应用alpha混合技术,可以实现出最眩目的火光、烟雾、阴影、动态光源等等一切你可以想象的出来的半透明效果