抗锯齿

1,208 阅读2分钟

源码:github.com/buglas/shad…

1-抗锯齿的概念

1-1-锯齿出现的原因

仔细去看,我们之前所绘制的线条上面有许多锯齿:

image-20220826105347926

这是因为canvas画布是点阵图,着色时,又是非黑即白,所以导致其边缘有凌厉的锯齿(aliasing)。

以三角形为例,更直观的给大家演示一下锯齿出现的原因。

下面这个三角形要画在4*4的canvas 画布上。

image-20220826112037883

然而,因为像素已经是着色的最小单位,按照我们之前非黑即白的方式来绘制,它可能会是这样的:

image-20220826112541880

1-2-消除锯齿的思路

消除锯齿的思路有2种:

  • 提高图像分辨率,让图像边缘的锯齿小到肉眼难以察觉,如下图

image-20220826113204279

  • 羽化图像边缘,让图像边缘与周围的环境有一个柔和的过度,如下图:

image-20220826113723695

在实际工作中,我们所说的抗锯齿,就是羽化图像边缘。

2-抗锯齿的方法

抗锯齿的方法可以分成两类:在绘图之前抗锯齿和在绘图之后抗锯齿。

2-1-在绘图之前抗锯齿

在绘图之前抗锯齿的方法有很多,我简单说两种常见的:

  • 距离场抗锯齿,基于着色点到图形的距离做过度,这种方法有一些局限性,实际用得并不多。

image-20220826141111082

  • 多重采样抗锯齿,将像素点细分后取均值,这种方法是当前用得最多的。

image-20220826125259765

2-2-在绘图之后抗锯齿

在绘图之后抗锯齿会对渲染图像进行后处理,比如查找图像边缘,然后对其进行重绘。

这种抗锯齿的方法属于计算机图像处理,我在此不做讲解。

接下来,我们重点来说在绘图之前的抗锯齿方法。

3-距离场抗锯齿

先以直线为例说一下其实现原理。

3-1-直线的抗锯齿

我们可以基于着色点到直线的距离,为其添加一个过渡色,如下图所示:

image-20220826223502148

代码如下:

1.基于当先片元到直线的距离和线宽,羽化直线的边缘。

vec4 Line(in vec2 C, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 向量AB的单位向量
  vec2 ABn = normalize(B - A);
  // 向量AC
  vec2 AC = C - A;
  // 点C到直线AB的距离 = 向量AC与单位向量ABn的叉乘
  float distance = abs(AC.x * ABn.y - AC.y * ABn.x);
  // 偏移导数
  float dx = dFdx(C.x);
  // 线宽
  float width = dx * lineWidth;
  // 收缩线宽
  float shrinkWidth = max(width - dx * 2., dx);
  // 用于抗锯齿的透明度
  float a = 1. - smoothstep(shrinkWidth, width, distance);
  // 直线
  return distance < width ? vec4(vec3(lineColor), a * lineColor.a) : vec4(0);
}

smoothstep(edge0,edge1,x) 是抗锯齿的重要方法:

  • 当x小于edge0时,返回0
  • 当x大于edge1时,返回1
  • 当x在edge0和edge1之间时,返回x在其中的位置比,即(x-edge0)/(edge1-edge0)

在上面的代码中,我让宽度线往里收了2个像素,并且使其不小于一个像素:

float shrinkWidth = max(width - dx * 2., dx);

smoothstep(width - dx * 2., width, distance)的取值范围是[0,1],我想让其与线条的透明度正相关,所以我在前面又用1减去了此值。

线条的透明度会受其自身透明度和边界透明度的影响:

// 用于抗锯齿的透明度
float a = 1. - smoothstep(shrinkWidth, width, distance);
// 直线
return distance < width ? vec4(vec3(lineColor), a * lineColor.a) : vec4(0);

2.绘制直线。

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, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));
  // 直线
  vec4 line = Line(coord, vec2(-1, -1), vec2(1, 1), 5., vec4(1));
  // 最终的颜色
  fragColor = mix(backgroundColor + projectionHelper, line, line.a);
}

重点看最后一行。

mix(x,y,a) :基于a 值对x和y进行融合:

  • 当a=0时,返回x
  • 当a=1时,返回y
  • 当a在(0,1) 之间时,对x和y进行融合,融合算法为:
x*(1-a)+y*a

最终的颜色便是基于直线的透明度,将直线和栅格背景进行了融合。

fragColor = mix(backgroundColor + projectionHelper, line, line.a);

最终效果如下:

image-20220825081227487

整体代码如下:

// 投影坐标系
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;
}

// 直线
vec4 Line(in vec2 C, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 向量AB的单位向量
  vec2 ABn = normalize(B - A);
  // 向量AC
  vec2 AC = C - A;
  // 点C到直线AB的距离 = 向量AC与单位向量ABn的叉乘
  float distance = abs(AC.x * ABn.y - AC.y * ABn.x);
  // 基于偏移导数的线宽
  float width = dFdx(C.x) * lineWidth;
  // 直线
  return distance < width ? lineColor : vec4(0);
}

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, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));
  // 直线
  vec4 line = Line(coord, vec2(-1, -1), vec2(1, 1), 1., vec4(1));
  // 最终的颜色
  fragColor = backgroundColor + projectionHelper + line;
}

以此原理,我们还可以对其它的线条进行抗锯齿。

3-2-线段的抗锯齿

// 投影坐标系
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;
}

//  线段
vec4 Segment(in vec2 C, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 向量AB
  vec2 AB = B - A;
  // 向量AB的单位向量
  vec2 ABn = normalize(AB);
  // 向量AC
  vec2 AC = C - A;
  // 点C到直线AB的距离 = 向量AC与单位向量ABn的叉乘
  float distance = abs(AC.x * ABn.y - AC.y * ABn.x);
  // 偏移导数
  float dx = dFdx(C.x);
  // 基于偏移导数的线宽
  float width = dx * lineWidth;
  // 收缩线宽
  float shrinkWidth = max(width - dx * 2., dx);
  // 用于抗锯齿的透明度
  float a = 1. - smoothstep(shrinkWidth, width, distance);
  // 将有向距离AC和有向距离AB的比值收束在[0,1] 之间
  float ratio = clamp(dot(AC, AB) / dot(AB, AB), 0., 1.);
  // 线段
  return length(ratio * AB - AC) < width ? vec4(vec3(lineColor), a * lineColor.a) : vec4(0);
}

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, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));
  // 线段
  vec4 segment = Segment(coord, vec2(-1, -1), vec2(1, 2), 5., vec4(1));
  // 最终的颜色
  fragColor = mix(backgroundColor + projectionHelper, segment, segment.a);
}

效果如下:

image-20220825083729043

3-3-正弦型曲线的抗锯齿

// 投影坐标系
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;
}

//  线段
vec4 Segment(in vec2 C, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 向量AB
  vec2 AB = B - A;
  // 向量AB的单位向量
  vec2 ABn = normalize(AB);
  // 向量AC
  vec2 AC = C - A;
  // 点C到直线AB的距离 = 向量AC与单位向量ABn的叉乘
  float distance = abs(AC.x * ABn.y - AC.y * ABn.x);
  // 偏移导数
  float dx = dFdx(C.x);
  // 基于偏移导数的线宽
  float width = dx * lineWidth;
  // 收缩线宽
  float shrinkWidth = max(width - dx * 2., dx);
  // 用于抗锯齿的透明度
  float a = 1. - smoothstep(shrinkWidth, width, distance);
  // 将有向距离AC和有向距离AB的比值收束在[0,1] 之间
  float ratio = clamp(dot(AC, AB) / dot(AB, AB), 0., 1.);
  // 线段
  return length(ratio * AB - AC) < width ? vec4(vec3(lineColor), a * lineColor.a) : vec4(0);
}

// 正弦函数(自变量x,振幅a,频率omega,偏移alpha)
float SinFn(float x, float a, float omega, float alpha) {
  return a * sin(omega * x + alpha);
}

// 正弦路径(当前点coord,起点star,结束点end,段数segs,线宽lineWidth,颜色sinColor,振幅a,频率omega,偏移alpha)
vec4 SinPath(vec2 coord, float start, float end, int segs, float lineWidth, vec4 sinColor, float a, float omega, float alpha) {
  vec4 color = vec4(0);
  float step = (end - start) / float(segs);
  for(int n = 0; n < segs; n++) {
    float x = start + float(n) * step;
    float nextX = x + step;
    vec2 A = vec2(x, SinFn(x, a, omega, alpha));
    vec2 B = vec2(nextX, SinFn(nextX, a, omega, alpha));
    vec4 segment = Segment(coord, A, B, lineWidth, sinColor);
    if(segment.a != 0.) {
      color = segment;
    }
  }
  return color;
}

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, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));
  //正弦曲线
  vec4 path = SinPath(coord, -2., 2., 24, 2., vec4(1), 1., 1., 0.);
  // 最终的颜色
  fragColor = mix(backgroundColor + projectionHelper, path, path.a);
}

效果如下:

image-20220825090025865

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

// 圆形
vec4 Circle(vec2 C, vec2 O, float r, vec4 circleColor) {
  // 当前点到圆心的距离
  float distance = length(C - O);
  // 偏移导数
  float dx = dFdx(C.x);
  // 收缩半径
  float shrinkR = max(r - dx * 2., dx);
  // 用于抗锯齿的透明度
  float a = 1. - smoothstep(shrinkR, r, distance);
  // 圆
  return distance < r ? vec4(vec3(circleColor), a * circleColor.a) : vec4(0);
}

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, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));
  //正弦曲线
  vec4 path = Circle(coord, vec2(0), 1., vec4(1));
  // 最终的颜色
  fragColor = mix(backgroundColor + projectionHelper, path, path.a);
}

效果如下:

image-20220825102138640

4-多重采样抗锯齿

多重采样时,对于采样点的定位方式有很多,我在这就说两种比较简单的。

4-1-九点采样抗锯齿

image-20220826231656192

比如蓝点是一个采样位置,我们可以基于此点位向上下左右偏移0.5个像素,然后取蓝点和周围8个红点所对应的颜色的平均值,将其作为当前片元的颜色。

以直线为例,其代码如下:

// 坐标系缩放
#define ProjectionScale 3.

// 投影坐标系
vec2 ProjectionCoord(in vec2 coord) {
  return ProjectionScale * 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;
}

// 直线
vec4 Line(in vec2 C, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 向量AB的单位向量
  vec2 ABn = normalize(B - A);
  // 向量AC
  vec2 AC = C - A;
  // 点C到直线AB的距离 = 向量AC与单位向量ABn的叉乘
  float distance = abs(AC.x * ABn.y - AC.y * ABn.x);
  // 基于偏移导数的线宽
  float width = dFdx(C.x) * lineWidth;
  // 直线
  return distance < width ? lineColor : vec4(0);
}

// 采样点
vec2[9] Samples9() {
  return vec2[9](vec2(0), vec2(-0.5, 0.5), vec2(0, 0.5), vec2(0.5, 0.5), vec2(0.5, 0), vec2(0.5, -0.5), vec2(0, -0.5), vec2(-0.5, -0.5), vec2(-0.5, 0));
}

// 抗锯齿图形
vec4 LineAA9(vec2 coord, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  vec2[9] samples = Samples9();
  vec4 line = vec4(0);
  for(int i = 0; i < 9; i++) {
    vec2 pos = samples[i] * ProjectionScale / iResolution.xy;
    line += Line(coord + pos, A, B, lineWidth, lineColor) / 9.;
  }
  return line;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  // float scale = 3.;
  // 投影坐标
  vec2 coord = ProjectionCoord(fragCoord);
  // 背景色
  vec4 backgroundColor = vec4(0, 0, 0, 1);
  // 投影坐标系辅助对象
  vec4 projectionHelper = ProjectionHelper(coord, 2., vec4(0, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));

  // 直线
  vec4 line = LineAA9(coord, vec2(-.1, -1), vec2(.1, 1), 3., vec4(1));

  // 最终的颜色
  fragColor = mix(backgroundColor + projectionHelper, line, line.a);
}

效果如下:

image-20220827112218615

之前有锯齿的样子是这样的:

image-20220827112327666

由此可见,锯齿已经模糊了很多。

我们重点看一下这部分代码:

// 采样点
vec2[9] Samples9() {
  return vec2[9](vec2(0), vec2(-0.5, 0.5), vec2(0, 0.5), vec2(0.5, 0.5), vec2(0.5, 0), vec2(0.5, -0.5), vec2(0, -0.5), vec2(-0.5, -0.5), vec2(-0.5, 0));
}

// 抗锯齿图形
vec4 LineAA9(vec2 coord, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  vec2[9] samples = Samples9();
  vec4 line = vec4(0);
  for(int i = 0; i < 9; i++) {
    vec2 pos = samples[i] * ProjectionScale / iResolution.xy;
    line += Line(coord + pos, A, B, lineWidth, lineColor) / 9.;
  }
  return line;
}

Samples9()方法返回的就是包括当前点在内的9个采样点。

  • vec2(0) 对应的就是当前点。
  • 0.5 对应的就是半个像素的尺寸。

LineAA9()方法便是对9个采样点所对应的颜色进行了加权平均。

若我们觉得当前抗锯齿的力度还不够,可以增加一下采样数量。

4-2-十七点采样抗锯齿

如下图所示,我们可以在当前采样点的周围再加上8个黄色的采样点。

image-20220827113852534

代码如下:

// 坐标系缩放
#define ProjectionScale 3.

// 投影坐标系
vec2 ProjectionCoord(in vec2 coord) {
  return ProjectionScale * 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;
}

// 直线
vec4 Line(in vec2 C, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 向量AB的单位向量
  vec2 ABn = normalize(B - A);
  // 向量AC
  vec2 AC = C - A;
  // 点C到直线AB的距离 = 向量AC与单位向量ABn的叉乘
  float distance = abs(AC.x * ABn.y - AC.y * ABn.x);
  // 基于偏移导数的线宽
  float width = dFdx(C.x) * lineWidth;
  // 直线
  return distance < width ? lineColor : vec4(0);
}

// 采样点
vec2[17] Samples17() {
  float d = 0.5;
  vec2 p9[9] = vec2[9](vec2(0), vec2(-d, d), vec2(0, d), vec2(d, d), vec2(d, 0), vec2(d, -d), vec2(0, -d), vec2(-d, -d), vec2(-d, 0));
  return vec2[17](p9[0], p9[1], p9[2], p9[3], p9[4], p9[5], p9[6], p9[7], p9[8], p9[1] * 2., p9[2] * 2., p9[3] * 2., p9[4] * 2., p9[5] * 2., p9[6] * 2., p9[7] * 2., p9[8] * 2.);
}

// 抗锯齿图形
vec4 LineAA17(vec2 coord, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  vec2[17] samples = Samples17();
  vec4 line = vec4(0);
  for(int i = 0; i < 17; i++) {
    vec2 pos = samples[i] * ProjectionScale / iResolution.xy;
    line += Line(coord + pos, A, B, lineWidth, lineColor) / 17.;
  }
  return line;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  // float scale = 3.;
  // 投影坐标
  vec2 coord = ProjectionCoord(fragCoord);
  // 背景色
  vec4 backgroundColor = vec4(0, 0, 0, 1);
  // 投影坐标系辅助对象
  vec4 projectionHelper = ProjectionHelper(coord, 2., vec4(0, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));

  // 直线
  vec4 line = LineAA17(coord, vec2(-1, -.1), vec2(1, .1), 3., vec4(1));

  // 最终的颜色
  fragColor = mix(backgroundColor + projectionHelper, line, line.a);
}

效果如下:

image-20220827114132899

对比一下之前的9点采样,可以发现17点采样的效果更加平滑了:

image-20220827112218615

4-3-相邻点抗锯齿

抗锯齿的思路和具体写法是有很多种的,这个需要根据我们的项目需求来确定。

比如,我们取一个像素和其周围的八个像素的平均值也可以实现抗锯齿。

image-20220903085332878

代码如下:

// 坐标系缩放
#define ProjectionScale 3.
// 抗锯齿的行数和列数
#define AA 3

// 投影坐标系
vec2 ProjectionCoord(in vec2 coord) {
  return ProjectionScale * 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;
}

// 直线
vec4 Line(in vec2 C, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 向量AB的单位向量
  vec2 ABn = normalize(B - A);
  // 向量AC
  vec2 AC = C - A;
  // 点C到直线AB的距离 = 向量AC与单位向量ABn的叉乘
  float distance = abs(AC.x * ABn.y - AC.y * ABn.x);
  // 基于偏移导数的线宽
  float width = dFdx(C.x) * lineWidth;
  // 直线
  return distance < width ? lineColor : vec4(0);
}

// 抗锯齿图形 Anti-Aliasing
vec4 LineAA(vec2 fragCoord, in vec2 A, in vec2 B, in float lineWidth, in vec4 lineColor) {
  // 初始颜色
  vec4 color = vec4(0);
  // 行列的一半
  float aa2 = float(AA / 2);
  // 逐行列变了
  for(int y = 0; y < AA; y++) {
    for(int x = 0; x < AA; x++) {
      // 基于像素的偏移距离
      vec2 offset = vec2(float(x), float(y)) / float(AA) - aa2;
      // 投影坐标位
      vec2 coord = ProjectionCoord(fragCoord + offset);
      // 累加周围片元的颜色
      color += Line(coord, A, B, lineWidth, lineColor);
    }
  }
  // 返回周围颜色的均值
  return color / float(AA * AA);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  // 投影坐标
  vec2 coord = ProjectionCoord(fragCoord);
  // 背景色
  vec4 backgroundColor = vec4(0, 0, 0, 1);
  // 投影坐标系辅助对象
  vec4 projectionHelper = ProjectionHelper(coord, 2., vec4(0, .4, 0, 1), vec4(.4, 0, 0, 1), 2., vec4(vec3(.3), 1));

  // 直线
  vec4 line = LineAA(fragCoord, vec2(-1, -.1), vec2(1, .1), 3., vec4(1));

  // 最终的颜色
  fragColor = mix(backgroundColor + projectionHelper, line, line.a);
}

效果如下:

image-20220903092553808

其效果不如之前的19点抗锯齿明显,但其采样点少,渲染更快一些。

扩展

我当前所说的抗锯齿,更多的还是给大家做一个科普,让大家知道锯齿产生的原因,以及抗锯齿的思路。

现在比较主流的多重采样抗锯齿技术有MSAA,TAA;后处理抗锯齿的技术有FXAA,SMAA。

若大家想进一步研究抗锯齿,可以去研究一下上面所说的4种技术。

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

\