绘制坐标系

1,007 阅读1分钟

源码:github.com/buglas/shad…

前言

坐标系可以分成坐标轴和坐标栅格两部分来绘制。

我们可以基于之前说过的投影坐标系绘制绘图。

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);
}

效果如下:

04

在上面的代码中,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);
}

image-20220822164356090

这个问题可以通过偏导数来解决。

2.用偏导数绘制x轴

image-20220822164850002

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);
}

效果如下:

image-20220822170123756

我们还可以让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);
}

效果如下:

image-20220822170623325

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);
}

效果如下:

image-20220822171118591

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-栅格

有些时候,我们需要对投影坐标系进行缩放,以此来观察尺寸更大的物体。

这时候,栅格可以让我们更好的知道坐标系的缩放程度。

05

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;
}

效果如下:

05

在上图中,我们要注意一个现象。

坐标轴要比网格线粗一倍,这是因为坐标轴是对轴线两侧的正负片元进行了着色;而网格线只对栅格线的右侧或上方的片元进行了着色。

因此,栅格线是往右上方偏移了半个线宽的,我们可以在grid 方法里,这样计算fraction,使栅格线居中:

vec2 fraction = 1.0 - 2. * abs(fract(uv) - 0.5);

不过,因为我们不是在做精密仪器,栅格只是辅助观察的,所以我就不做调整了,大家知道这个原理即可。

接下来,我们使用这个坐标系绘制一些基础图形。

参考链接:space.bilibili.com/10707223/ch…