在webgl使用的坐标系如下图所示:
并且只有在标准立方体范围内物体才会显示在屏幕中.
如果只考虑二维的情况, 坐标系则如下图所示:
我们可以用两个三角形覆盖整个画布:
我们要绘制的4x4网格, 如下图所示:
我们将关注点先放在一个小的单元:
小的单元单独显示如下图:
更进一步, 我将像素也画出来, 其中每个小方格为一个像素:
我们的目标是决定每个像素的颜色, 到底是白色还是灰色. 这个工作在webgl中肯定是在片元着色器中完成.
那判断是是白色还是灰色的依据又是什么?
当然是根据像素的位置信息, 已知画布范围, 并且每行有4个网格每列有4个网格, 那么每单个网格单元的大小为0.5 * 0.5. 假设网格线的宽度为单个网格单元大小的1/10. 我对上图进行了坐标标注:
如上图说所示, 在片元着色器中, 如果我们知道当前像素的坐标(x,y), 那么我们就可以判断出, 该像素是白色还是灰色.
直觉上我们应该这么做, 但是这里有两个问题:
- 如何在片元着色器中获取当前像素的坐标(x,y)
- 上图中我只标注了第一个网格单元的坐标信息, 可以想到, 每个网格单元的坐标信息都是不一样的, 这样我们写的片元着色器就无法适用于所有网格单元.
首先, 如何在片元着色器中获取当前像素的坐标?
我们可以通过线性插值的方式, 类似于获取纹理坐标的方式. 具体实现如下:
attributes: {
position: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1],
],
uv: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1],
],
},
// cell
elements: [[0,1,2], [2,0,3]]
顶点着色器:
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
片元着色器:
precision mediump float;
varying vec2 vUv;
void main () {
vec2 st = vUv; // st.x st.y 就可以代表该像素的xy
}
关于问题2, 本质的原因是网格其实是周期性的, 所以我们需要将[-1, 1]这个线性的区间变成一个周期性的变化, 具体方法如下:
- 将原来的st.xy由映射为:
attributes: {
uv: [
[0, 0],
[0, 4],
[4, 4],
[4, 0],
],
}
如下图所示:
- 形成周期性变换, 可以用fract函数, 它的作用是只取小数部分
precision mediump float;
varying vec2 vUv;
void main () {
vec2 st = fract(vUv);
}
vUv的取值为0-4之间, 这样就可以形成一个周期性的变换, 如下图所示:
- 下面看看完整的片元着色器
precision mediump float;
varying vec2 vUv;
void main () {
vec2 st = fract(vUv);
if(st.x > 0.9 || st.y < 0.1) {
gl_FragColor.rgb = vec3(0.8); // 灰色
} else {
gl_FragColor.rgb = vec3(1); // 白色
}
gl_FragColor.a = 1.0;
}
我们还可以这么来写, 和上面的效果是等价的:
void main () {
vec2 st = fract(vUv);
float d1 = step(st.x, 0.9);
float d2 = step(0.1, st.y);
gl_FragColor.rgb = mix(vec3(0.8), vec3(1.0), d1 * d2);
gl_FragColor.a = 1.0;
}
step的使用: 当 step(a, b) 中的 b < a 时,返回 0;当 b >= a 时,返回 1.
mix的使用: mix(a, b, c) 表示根据 c 是 0 或 1,返回 a 或者 b.
最后, 如果需要绘制16x16, 32x32的网格, 我们需要将uv改一下, 比如:
attributes: {
uv: [
[0, 0],
[0, 16],
[16, 16],
[16, 0],
],
}
这样还是有点麻烦, 我们稍微调整一下: uv的范围我们设置成, 然后在片元着色器中将vUv乘以16, 效果其实是一样的. 其实做的事情都是将区间映射成了区间.
uniforms: {
rows: 16,
},
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
}
uniform float rows;
void main () {
vec2 st = fract(vUv * rows);
...
}