前言
坐标系可以分成坐标轴和坐标栅格两部分来绘制。
我们可以基于之前说过的投影坐标系绘制绘图。
1-坐标轴
因为投影坐标系的原点就在画布中间,所以我们可以根据坐标距离原点的大小来绘制坐标轴。
1.绘制y轴
// 投影坐标系
vec2 ProjectionCoord(in vec2 fragCoord) {
return 2. * (fragCoord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = ProjectionCoord(fragCoord);
vec3 rgb = vec3(0);
if(abs(coord.x) < 0.01) {
rgb = vec3(1);
}
fragColor = vec4(rgb, 1);
}
效果如下:
在上面的代码中,abs(uv.x) < 0.01 便是在判断当前片元在投影坐标系内,到原点的x方向距离是否小于0.01。当此距离小于0.01时,就将此片元的颜色设置为白色。
然而在这种操作是有点缺陷的,当0.01这个距离太小时,比如0.0001时,那由于数据误差问题,可能找不到距离小于0.0001的片元,因此也就绘制不出y轴。
如下所示:
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = ProjectionCoord(fragCoord);
vec3 rgb = vec3(0);
if(abs(coord.x) < 0.0001) {
rgb = vec3(1);
}
fragColor = vec4(rgb, 1);
}
这个问题可以通过偏导数来解决。
2.用偏导数绘制x轴
webgl 里常用的偏导数有以下3个:
- dFdx:像素块中右边像素的值减去左边像素的值
- dFdy:像素块中下面像素的值减去上面像素的值
- fWidth= dFdx + dFdy
用法如下:
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = ProjectionCoord(fragCoord);
vec3 rgb = vec3(0);
float dx = dFdx(coord.x) * 2.;
if(abs(coord.x) < dx) {
rgb = vec3(0, 1, 0);
}
fragColor = vec4(rgb, 1);
}
效果如下:
我们还可以让y轴宽一点,然后给他一个绿色。
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = ProjectionCoord(fragCoord);
vec3 rgb = vec3(0);
float dx=dFdx(coord.x)*2.;
if(abs(coord.x) <dx ) {
rgb = vec3(0,1,0);
}
fragColor = vec4(rgb, 1);
}
效果如下:
3.以同样原理绘制一条红色的x轴。
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = ProjectionCoord(fragCoord);
vec3 rgb = vec3(0);
float dx = dFdx(coord.x) * 2.;
float dy = dFdy(coord.y) * 2.;
if(abs(coord.x) < dx) {
rgb = vec3(0, 1, 0);
} else if(abs(coord.y) < dy) {
rgb = vec3(1, 0, 0);
}
fragColor = vec4(rgb, 1);
}
效果如下:
4.将绘制坐标轴的过程封装成一个方法。
// 投影坐标系
vec2 ProjectionCoord(in vec2 coord) {
return 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
// 坐标轴
vec4 AxisHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * axisWidth;
float dy = dFdy(coord.y) * axisWidth;
if(abs(coord.x) < dx) {
color = yAxisColor;
} else if(abs(coord.y) < dy) {
color = xAxisColor;
}
return color;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = ProjectionCoord(fragCoord);
vec4 backgroundColor = vec4(0, 0, 0, 1);
vec4 axisHelper = AxisHelper(coord, 2., vec4(0, 1, 0, 1), vec4(1, 0, 0, 1));
fragColor = backgroundColor + axisHelper;
}
2-栅格
有些时候,我们需要对投影坐标系进行缩放,以此来观察尺寸更大的物体。
这时候,栅格可以让我们更好的知道坐标系的缩放程度。
1.为投影坐标系添加一个缩放参数。
vec2 ProjectionCoord(in vec2 coord, in float scale) {
return scale * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
2.封装一个绘制栅格的方法。
vec4 GridHelper(in vec2 coord, in float gridWidth, in vec4 gridColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * gridWidth;
float dy = dFdy(coord.y) * gridWidth;
vec2 fraction = fract(coord);
if(fraction.x < dx || fraction.y < dy) {
color = gridColor;
}
return color;
}
- fract(uv):取小数点后面的数字
如果这时候投影坐标系x轴向的坐标范围是[-3,3],那经过fract() 方法转换后,就变成了6段[0,1) 的坐标。
3.结合之前的坐标轴,封装投影坐标系辅助对象。
vec4 ProjectionHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor, in float gridWidth, in vec4 gridColor) {
// 坐标轴
vec4 axisHelper = AxisHelper(coord, axisWidth, xAxisColor, yAxisColor);
// 栅格
vec4 gridHelper = GridHelper(coord, gridWidth, gridColor);
// =投影坐标系
return bool(axisHelper.a) ? axisHelper : gridHelper;
}
4.绘制坐标系
// 投影坐标系
vec2 ProjectionCoord(in vec2 coord, in float scale) {
return scale * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
// 坐标轴
vec4 AxisHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * axisWidth;
float dy = dFdy(coord.y) * axisWidth;
if(abs(coord.x) < dx) {
color = yAxisColor;
} else if(abs(coord.y) < dy) {
color = xAxisColor;
}
return color;
}
// 栅格
vec4 GridHelper(in vec2 coord, in float gridWidth, in vec4 gridColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * gridWidth;
float dy = dFdy(coord.y) * gridWidth;
vec2 fraction = fract(coord);
if(fraction.x < dx || fraction.y < dy) {
color = gridColor;
}
return color;
}
// 投影坐标系辅助对象
vec4 ProjectionHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor, in float gridWidth, in vec4 gridColor) {
// 坐标轴
vec4 axisHelper = AxisHelper(coord, axisWidth, xAxisColor, yAxisColor);
// 栅格
vec4 gridHelper = GridHelper(coord, gridWidth, gridColor);
// =投影坐标系
return bool(axisHelper.a) ? axisHelper : gridHelper;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// 投影坐标
vec2 coord = ProjectionCoord(fragCoord, 3.);
// 背景色
vec4 backgroundColor = vec4(0, 0, 0, 1);
// 投影坐标系辅助对象
vec4 projectionHelper = ProjectionHelper(coord, 2., vec4(0, 1, 0, 1), vec4(1, 0, 0, 1), 2., vec4(1));
// 最终的颜色
fragColor = backgroundColor + projectionHelper;
}
效果如下:
在上图中,我们要注意一个现象。
坐标轴要比网格线粗一倍,这是因为坐标轴是对轴线两侧的正负片元进行了着色;而网格线只对栅格线的右侧或上方的片元进行了着色。
因此,栅格线是往右上方偏移了半个线宽的,我们可以在grid 方法里,这样计算fraction,使栅格线居中:
vec2 fraction = 1.0 - 2. * abs(fract(uv) - 0.5);
不过,因为我们不是在做精密仪器,栅格只是辅助观察的,所以我就不做调整了,大家知道这个原理即可。
接下来,我们使用这个坐标系绘制一些基础图形。