Chapter 30

ブレンドモード

miku
miku
2021.11.21に更新

ブレンドモード

ピクセルに色を指定した場合、すでにそのピクセルに塗られている色は上書きされるが、ブレンドモードという機能を利用すると、上書きではなく特定の計算式で色を混ぜたものがピクセルにセットされる。

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

  fill(255, 0, 0);
  circle(width / 4 - 25, height / 2, 100);

  fill(0, 255, 0);
  circle(width / 4 + 25, height / 2, 100);

  blendMode(ADD);

  fill(255, 0, 0);
  circle((width / 4) * 3 - 25, height / 2, 100);

  fill(0, 255, 0);
  circle((width / 4) * 3 + 25, height / 2, 100);
}

左側の円の重なりは通常の重なりで、右側はブレンドモードの ADD という機能を適用した場合の重なりになる。blendMode(機能名) でブレンドモードを適用して、引数には ADD のような機能の名前を書く。

ブレンドモードの計算はピクセルのRGBごとに行う。たとえば ADD モードの状態で B がすでに塗られている色、AB の上に塗ろうとしている色だと (A.r + B.r, A.g + B.g, A.b + B.b) がピクセルの色として適用される。上記例だと赤 (255, 0, 0) の上に緑 (0, 255, 0) を重ねると黄色 (255, 255, 0) になる。

RGBごとに計算が行われる際、結果は 0~255 の範囲に必ず収まる。p5.jsで利用できる代表的なブレンドモードと計算式は下記の通り。AB の色の範囲は 0~1 として扱う。

名前 計算式 or 効果
ADD A + B
MULTIPLY AB
SCREEN 1 - (1 - A)(1 - B)
OVERLAY A < 0.5 ? 2ab : 1 - 2(1 - A)(1 - B)
DARKEST min(A, B)
LIGHTEST max(A, B)
DIFFERENCE abs(A - B)
EXCLUSION A + B - 2AB
REPLACE CanvasをクリアしてAだけを描く
REMOVE Aの形状で切り抜く(透明にする)

ADD

作例のコードを見る
function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  const c = "#0555ab";

  drawA(0, 0, width / 2, height);
  drawA(width / 2, 0, width / 2, height / 2);
  drawB(width / 2, height / 2, width / 2, height / 2, c);

  blendMode(ADD);
  drawB(0, 0, width / 2, height, c);
}

function drawA(x, y, w, h) {
  translate(x, y);
  const n = 5;
  const tw = w / n;
  for (let i = 0; i < n; i++) {
    fill(map(i, 0, n, 0, 255));
    rect(tw * i, y, tw, h);
  }
  resetMatrix();
}

function drawB(x, y, w, h, c) {
  translate(x, y);
  fill(c);
  rect(0, 0, w, h);
  resetMatrix();
}

計算式: A + B

ADD の計算は足し算で、結果が明るくなるので、光の表現や後の章で扱うパーティクルで活躍する。黒のピクセルはもとのピクセルに影響を与えないので、実質透過扱いにできる。逆光のように明るくなりすぎることがあるので、代わりに SCREEN を利用するといいかもしれない。

MULTIPLY

作例のコードを見る
function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  const c = "#0555ab";

  drawA(0, 0, width / 2, height);
  drawA(width / 2, 0, width / 2, height / 2);
  drawB(width / 2, height / 2, width / 2, height / 2, c);

  blendMode(MULTIPLY);
  drawB(0, 0, width / 2, height, c);
}

function drawA(x, y, w, h) {
  translate(x, y);
  const n = 5;
  const tw = w / n;
  for (let i = 0; i < n; i++) {
    fill(map(i, 0, n, 0, 255));
    rect(tw * i, y, tw, h);
  }
  resetMatrix();
}

function drawB(x, y, w, h, c) {
  translate(x, y);
  fill(c);
  rect(0, 0, w, h);
  resetMatrix();
}

計算式: AB

MULTIPLY の計算は掛け算なので、片方が完全な白でない限り、重ねると必ず暗くなる。もとの2つの色より明るくなることはない。

SCREEN

作例のコードを見る
let colors;

function setup() {
  createCanvas(windowWidth, windowHeight);
  angleMode(DEGREES);
  blendMode(SCREEN);
  strokeWeight(40);
  stroke(240);
  noFill();

  colors = [
    { angle: 0, anglev: 1, color: "#ffff00" },
    { angle: 0, anglev: -1, color: "#00ffff" },
    { angle: 180, anglev: 1.5, color: "#ff00ff" },
  ];
}

function draw() {
  clear();
  translate(width / 2, height / 2);

  colors.forEach((c) => {
    stroke(c.color);
    arc(0, 0, 200, 200, c.angle, c.angle + 120);
    c.angle += c.anglev;
  });
}

計算式: 1 - (1 - A)(1 - B)

SCREEN の計算は MULTIPLY とは逆で、乗算で明るくしていく形式になる。もとの2つの色より暗くなることはない。

OVERLAY

作例のコードを見る
function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  const c = "#0555ab";

  drawA(0, 0, width / 2, height);
  drawA(width / 2, 0, width / 2, height / 2);
  drawB(width / 2, height / 2, width / 2, height / 2, c);

  blendMode(OVERLAY);
  drawB(0, 0, width / 2, height, c);
}

function drawA(x, y, w, h) {
  translate(x, y);
  const n = 5;
  const tw = w / n;
  for (let i = 0; i < n; i++) {
    fill(map(i, 0, n, 0, 255));
    rect(tw * i, y, tw, h);
  }
  resetMatrix();
}

function drawB(x, y, w, h, c) {
  translate(x, y);
  fill(c);
  rect(0, 0, w, h);
  resetMatrix();
}

計算式: A < 0.5 ? 2ab : 1 - 2(1 - A)(1 - B)

塗ろうとしている色が真ん中の明るさより明るいか暗いかで処理の内容が変わる。明るいとスクリーンほどではないやや明るい形に、暗いと乗算ほどではないやや暗い形になる。

DARKEST

作例のコードを見る
function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  const c = "#0555ab";

  drawA(0, 0, width / 2, height);
  drawA(width / 2, 0, width / 2, height / 2);
  drawB(width / 2, height / 2, width / 2, height / 2, c);

  blendMode(DARKEST);
  drawB(0, 0, width / 2, height, c);
}

function drawA(x, y, w, h) {
  translate(x, y);
  const n = 5;
  const tw = w / n;
  for (let i = 0; i < n; i++) {
    fill(map(i, 0, n, 0, 255));
    rect(tw * i, y, tw, h);
  }
  resetMatrix();
}

function drawB(x, y, w, h, c) {
  translate(x, y);
  fill(c);
  rect(0, 0, w, h);
  resetMatrix();
}

計算式: min(A, B)

AB 暗い方の色を採用する。先程も説明したが、これはRGB単位の計算になるので、RGBごとに比較して暗い方を取り出したものを一つの色してくっつけたもの、つまり (min(A.r, B.r), min(A.g, B.g), min(A.b, B.b)) がピクセルにセットされる。

LIGHTEST

作例のコードを見る
function setup() {
  createCanvas(windowWidth, windowHeight);
  noStroke();

  const c = "#0555ab";

  drawA(0, 0, width / 2, height);
  drawA(width / 2, 0, width / 2, height / 2);
  drawB(width / 2, height / 2, width / 2, height / 2, c);

  blendMode(LIGHTEST);
  drawB(0, 0, width / 2, height, c);
}

function drawA(x, y, w, h) {
  translate(x, y);
  const n = 5;
  const tw = w / n;
  for (let i = 0; i < n; i++) {
    fill(map(i, 0, n, 0, 255));
    rect(tw * i, y, tw, h);
  }
  resetMatrix();
}

function drawB(x, y, w, h, c) {
  translate(x, y);
  fill(c);
  rect(0, 0, w, h);
  resetMatrix();
}

計算式: max(A, B)

AB 明るい方の色を採用する。