更新于 

逐片元操作

几何形状的装配和光栅化

ColoredTriangle

修改MultiAttributeColor里drawArrays函数的第一个参数为 TRIANGLES

1
gl.drawArrays(gl.TRIANGLES, 0, n);
ColoredTriangle运行结果
ColoredTriangle运行结果
图元装配与光栅化过程
考虑几个小问题
Fragment_Shader如何进行逐片元操作的?

答: 传入顶点坐标、图形装配、光栅化、执行片元着色器。

谁来确定哪些像素需要被着色?

答: gl.drawArray()函数的第一个参数就决定几何图形的类型,即图元装配过程的最终结果决定需要被着色的像素位置。

谁来负责调用片元着色器?

答: 一旦光栅化过程结束后,程序就开始逐片元调用片源着色器。

片元着色器是怎样处理每个片元的?

答: 片元着色器计算出该片元的颜色,并写入颜色缓冲区。

逐片元操作步骤
  1. 获取顶点坐标
  2. 图元装配:将离散顶点坐标装配成几何图形。
  3. 光栅化:将装配好的几何图形转化为片元。
  4. 执行片元着色器
图元(primitives)

几何图形装配(geometric shape assembly),
又称图元装配过程(primitive assembly process),
图元(primitives): 指被装配出的基本图形(点线面)

光栅化(rasterization)

显示在屏幕上的图形是由 像素 组成的,
因此需要将图形转化为 片元,这个过程即称为 光栅化
片元数目 就是图形最终在屏幕上所覆盖的像素数。

逐片元操作步骤分解

以绘制单色三角形为例

step1
执行顶点着色器,将缓冲区对象第一个顶点坐标传递给gl_Position,使其存储进入图形装配区域。
step2
同样方法传入并存储第二个顶点坐标。
step3
同样方法传入并存储第三个顶点坐标。
step4
使用传入坐标,根据gl.drawArrays()第一个参数,开始装配图形。
step5
将图形转化为片元,即光栅化。
step6-15
逐片元调用片元着色器,计算每个片元的颜色,写入颜色缓冲区。

Colored Triangle

本案例可以证明片元着色器对每个片元的操作确实存在。

gl_FragCoord

片元着色器使用 内置变量 gl_FragCoord 来访问片元的坐标。

注意gl_FragCoord采用的是canvas坐标系统。

ColoredTriangle Code
ColoredTriangle运行结果
ColoredTriangle运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../examples/lib/cuon-matrix.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="./ColoredTriangle.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="300" height="300"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
let VSHADER_SOURCE =
`
attribute vec4 a_Position;
void main(){
gl_Position = a_Position;
}
`;
let FSHADER_SOURCE =
`
precision mediump float;
uniform float u_Width;
uniform float u_Height;
void main(){
gl_FragColor = vec4(
0.0,
gl_FragCoord.x/u_Width,
gl_FragCoord.y/u_Height,
1.0
);
}
`;
function initVertexBuffer(gl) {
let vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);
let n = 3;
let vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
return -1;
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
return n;
}
function main() {
let canvas = document.getElementById('webgl');
let gl = getWebGLContext(canvas);
if (!gl) {
console.log('获取上下文失败');
return;
}
if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
let n = initVertexBuffer(gl);
if (n < 0) {
console.log('创建缓冲区失败');
return;
}
let u_Width = gl.getUniformLocation(gl.program, 'u_Width'),
u_Height = gl.getUniformLocation(gl.program, 'u_Height');
gl.uniform1f(u_Width, canvas.width);
gl.uniform1f(u_Height, canvas.height);

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
}

varying变量的作用和内插过程

内插过程

v_shader中的varying变量在传入f_shader之前经过了内插过程

为什么要将这种在着色器之间传递数值的类型称为varying?

答: 因为片元着色器中声明的同名同类型变量和顶点着色器声明的变量实际上并不是一回事

要绘制一条两端颜色不同的线段,只要向 顶点着色器的varying变量 附上两种颜色,
WebGL就会 自动计算出线段上的所有片元(点)的颜色,
然后赋值给 片元着色器的varying变量

这个过程被称为 内插过程(interpolation process)

光栅化负责将矢量几何图形转变为栅格化的片元(像素)。