大家好,我是前端西瓜哥。之前教大家绘制一个红色的三角形,这次我们来画个有渐变的三角形。
原来的写法,颜色是在片元着色器中写死的,这次我们来像传顶点数据一样,声明一个颜色数据传递过去。
颜色需要在片元着色器中赋值给内部变量 gl_FragColor,但 attribute 动态类型却不能在片元着色器中使用。
这时候就要用到一个新的类型 varying 了。(意思为:“变化的“)
varying 用于从顶点着色器中将变量传递到片元着色器中。
两个缓冲区对象的写法
着色器代码:
const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
这里我们需要在两种着色器中同时声明 varing 变量,后面的类型也必须是相同的 vec4,变量名也要一致,只能说是完全相同了。
顶点着色器中需要通过 v_Color = a_Color; 赋值。然后在片元着色器中,再将同步过来的 v_Color 赋值给 gl_FragColor。
然后是新增的颜色数组的声明,以及对应缓存区的创建。
// prettier-ignore
const colors = new Float32Array([
1, 0, 0, // 红色
0, 1, 0, // 绿色
0, 0, 1, // 蓝色
])
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color);
贴一下完整代码:
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
// 创建顶点渲染器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
// 创建片元渲染器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
// 程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.program = program;
// 顶点数据
// prettier-ignore
const vertices = new Float32Array([
0, 0.5, // 第一个点
-0.5, -0.5, // 第二个点
0.5, -0.5, // 第三个点
]);
// 创建缓存对象
const vertexBuffer = gl.createBuffer();
// 绑定缓存对象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向缓存区写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 获取 a_Position 变量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
// 将缓冲区对象分配给 a_Position 变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 允许访问缓存区
gl.enableVertexAttribArray(a_Position);
// prettier-ignore
const colors = new Float32Array([
1, 0, 0, // 红色
0, 1, 0, // 绿色
0, 0, 1, // 蓝色
])
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color);
// 清空画布,并指定颜色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
demo 地址:
https://codesandbox.io/s/uqbjsu?file=/index.js。
渲染结果:
我们其实只是给三个顶点设置了红、绿、蓝三个颜色,然后 WebGL 会基于它们计算出中间的过内插颜色,将它们填充满三个点围成区域的像素点。
单缓冲区的实现
前面的实现用了两个缓冲区对象分别保存位置信息和颜色信息。
但实际上可以将它们组合在一起,让数据更紧凑放在一个缓冲区里。
浮点数数组为:
// prettier-ignore
const verticesColors = new Float32Array([
0, 0.5, 1, 0, 0, // 点 1 的位置和颜色信息
-0.5, -0.5, 0, 1, 0, // 点 2
0.5, -0.5, 0, 0, 1, // 点 3
]);
然后是和前一种写法有一些不同的地方:
// 每个数组元素的字节数
const SIZE = verticesColors.BYTES_PER_ELEMENT;
// 获取 a_Position 变量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, SIZE * 5, 0);
gl.enableVertexAttribArray(a_Position);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, SIZE * 5, SIZE * 2);
gl.enableVertexAttribArray(a_Color);
主要是 gl.vertexAttribPointer 方法的最后两个参数 stride 和 offset。
我们看下面这个:
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, SIZE * 5, SIZE * 2);
stride 为 SIZE * 5(单位为字节,所以要乘以一个数组元素的字节大小),表示 5 个数组元素为一趟,然后 offset 为 SIZE * 2,表示从第 2 个元素,取 3 个元素作为这一趟的数据内容。
完整代码实现:
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
// 创建顶点渲染器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
// 创建片元渲染器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
// 程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.program = program;
// 顶点数据
// prettier-ignore
const verticesColors = new Float32Array([
0, 0.5, 1, 0, 0, // 点 1 的位置和颜色信息
-0.5, -0.5, 0, 1, 0, // 点 2
0.5, -0.5, 0, 0, 1, // 点 3
]);
// 每个数组元素的字节数
const SIZE = verticesColors.BYTES_PER_ELEMENT;
// 创建缓存对象
const vertexColorBuffer = gl.createBuffer();
// 绑定缓存对象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
// 向缓存区写入数据
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
// 获取 a_Position 变量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, SIZE * 5, 0);
gl.enableVertexAttribArray(a_Position);
const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, SIZE * 5, SIZE * 2);
gl.enableVertexAttribArray(a_Color);
// 清空画布,并指定颜色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
demo 地址:
https://codesandbox.io/s/bvkv20?file=/index.js。
结尾
本节讲了 varying 的能力:将顶点着色器中的变量传递给片元着色器。并演示了使用两个缓冲区对象,位置数据和颜色数据,以及将它们组合成一个缓冲区对象的实现。