Chapter 31

smoothstep

miku
miku
2021.11.13に更新

以前の章で扱った線形補間では、名前の通りまっすぐの補間になるので、別に滑らかに補間する関数を扱いたい。

シグモイド関数

function setup() {
  createCanvas(windowWidth, windowHeight);

  stroke(240);
  noFill();

  const xMin = -7;
  const xMax = 7;
  let px = 0;
  let py = height;

  for (let i = xMin; i <= xMax; i += 0.1) {
    const x = i;
    const y = sigmoid(x);

    const tx = map(x, xMin, xMax, 0, width);
    const ty = map(y, 0, 1, height, 0);

    line(px, py, tx, ty);

    px = tx;
    py = ty;
  }
}

function sigmoid(x) {
  return 1 / (1 + exp(-x));
}
f(x) = \frac{1}{1+e^{-x}}

シグモイド関数はS字型のカーブを描く関数で、しかも点対称になっている。

カーブを描くにはこの関数で事足りるのだが、exp() は重い処理なのでなるべくこれを使わない形で書きたい。ということで exp() を使用しないシグモイドライクな多項式の関数が smoothstep() になる。

smoothstep()

function setup() {
  createCanvas(windowWidth, windowHeight);

  stroke(240);
  noFill();

  const xMin = 0;
  const xMax = 1;
  let px = 0;
  let py = height;

  for (let i = xMin; i <= xMax; i += 0.01) {
    const x = i;
    const y = smoothstep(x);

    const tx = map(x, xMin, xMax, 0, width);
    const ty = map(y, 0, 1, height, 0);

    line(px, py, tx, ty);

    px = tx;
    py = ty;
  }
}

function smoothstep(x) {
  return x * x * (3 - 2 * x);
}

smoothstep() の多項式として代表的なのが 3x^2 - 2x^3 である。S字型なので、最初はゆっくりだが、途中で加速し、最後に減速するような補間になる。

他にも後の章で扱うパーリンノイズの作者が提唱している 6x^5 - 15x^4 + 10x^3 などがある。この式はパーリンノイズの内部でも使用されている。

次に線形補間と smoothstep() を比較する作例をいくつか挙げる。

色の補間

function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  for (let i = 0; i < width; i++) {
    const t = i / width;

    fill(t * 255, 0, 0);
    rect(i, 0, 1, height / 2);

    fill(smoothstep(t) * 255, 0, 0);
    rect(i, height / 2, 1, height / 2);
  }
}

function smoothstep(x) {
  return x * x * (3 - 2 * x);
}

上が線形補間、下が smoothstep() である。

イージング

let ratio;
let a, b;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x1 = getR();
  x2 = width - getR();
  reset();
}

function reset() {
  ratio = -0.2;
}

function smoothstep(x) {
  return x * x * (3 - 2 * x);
}

function draw() {
  frameRate(60);
  clear();

  const nr = max(min(ratio, 1), 0);
  a = lerp(x1, x2, nr);
  b = lerp(x1, x2, smoothstep(nr));

  circle(a, height / 3, getR() * 2);
  circle(b, (height / 3) * 2, getR() * 2);

  ratio += 0.005;
  if (ratio >= 1.2) {
    reset();
  }
}

function getR() {
  return map(width, 0, 750, 0, 25);
}

上が線形補間、下が smoothstep() である。