2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WebGL(GLSL)でカラースケール関数を実装する

2
Posted at

カラースケール概要

データの値に応じて色を変えたい場合、下図のようなカラースケールを用いることがある。温度によって色分けされたサーモグラフィーの画面や、気温に応じて色分けされた地図などを思い浮かべれば良い。
ダウンロード.png

カラースケールは大きく分けて以下の3種類に区分することができる。

    1. Categorical
    • 質的・離散的な色分け。例えば折れ線グラフの1本目、2本目、...を色分けする場合などに用いる。
      category10.png
    1. Diverging
    • ある値を境として2色に分岐する。例えば赤字/黒字の程度などを示す場合に用いる。
    1. Sequential
    • 連続的な色分け。例えば温度の高さを青〜黄〜赤で表現する場合など。単色のグラデーションも含む。
    • スクリーンショット 2019-10-28 20.54.46.png

このうち、Categoricalなカラースケールは単にRGBの配列を用意することで容易に実装できるが、残りのDiverging, SequentialRGBの連続的な補間が必要になり、言語ごとに実装が必要になる。このような連続的カラースケールをWebGL(GLSL)で実装するのが本記事の内容。

d3.jsの場合

JavaScriptでは、d3.jsの一部であるd3-scale-chromaticに全3パターンのカラースケールが実装されている。連続的なカラースケールの場合、0-1の範囲の任意の実数値を受けて色を返してくれるような関数になっている。

これらのソースコード(例えば冒頭の虹色スケールd3.interpolateSpectral()に関するSpectral.js)を読んでみると、いくつか(10程度)のRGBの配列を直書きしたうえで、それらのRGBをSpline補間することで任意の実数値を受けられるようにしていることがわかる。
補間前のRGBの配列はd3.schemeSpectralのような形で取得することができるので、これを借りてきて補間処理を実装すれば、他の言語でもカラースケール関数が利用できることになる。WebGLのGLSLの場合はJavaScriptから値を渡すことができるので、d3から補間前の配列を直接渡せばOKということになる。

GLSLでの実装

以下の手順によってGLSLで連続的なカラースケール関数を利用する

    1. (JavaScriptで)d3-scale-chromaticからカラースケールの元となるRGB配列を取得する
    1. 上記配列は#ff0000のようなhex形式なので、0から1の間のRGBの実数値配列に変換する
    1. 上記RGBの配列をuniformとしてJavaScriptからGLSLに渡す
    1. GLSLでBーspline補間を実装する

補間処理はd3-scale-chromaticのJavaScriptでの実装をGLSLに翻訳すれば良い。

補間処理とJavaScriptから渡されたカラースケールを用いて本記事冒頭のようなカラースケールを表示するフラグメントシェーダのコードは以下の通り。

このShaderを利用して実際に描画を行うJavaScriptコードはこちらを参照

fragmentShader
# define MAX_SCHEME_LENGTH 16

precision highp float;

uniform float u_size;
uniform int u_n;
uniform vec3 u_colorScheme[MAX_SCHEME_LENGTH];


// Use this function because we cannot use dynamic value for array index in GLSL
vec3 getData(float x) {
  for (int j=0; j<MAX_SCHEME_LENGTH; j++) {
    if (j == int(floor(x))) return u_colorScheme[j];
  }
  return vec3(0.0, 0.0, 0.0);
}

// GLSL translation of basis() in d3
// https://github.com/d3/d3-interpolate/blob/master/src/basis.js
vec3 basis(float t1, vec3 v0, vec3 v1, vec3 v2, vec3 v3) {
  float t2 = t1 * t1;
  float t3 = t2 * t1;
  return ((1.0 - 3.0 * t1 + 3.0 * t2 - t3) * v0
    + (4.0 - 6.0 * t2 + 3.0 * t3) * v1
    + (1.0 + 3.0 * t1 + 3.0 * t2 - 3.0 * t3) * v2
    + t3 * v3) / 6.0;
}

// GLSL translation of exported function in the URL below
// https://github.com/d3/d3-interpolate/blob/master/src/basis.js
vec3 getColor(float t) {
  int n = u_n - 1;

  float i  = t <= 0.0 ? 0.0 : (t >= 1.0 ? float(n) - 1.0 : floor(t * float(n)));
  float t2 = t <= 0.0 ? 0.0 : (t >= 1.0 ? 1.0 : t);
  vec3 v1 = getData(i);
  vec3 v2 = getData(i+1.0);
  vec3 v0 = i > 0.0 ? getData(i-1.0) : 2.0 * v1 - v2;
  vec3 v3 = i < float(n) - 1.0 ? getData(i+2.0) : 2.0 * v2 - v1;

  return basis((t2 - i / float(n)) * float(n), v0, v1, v2, v3);
}


void main() {
  // Fill pixels based on x coordinate
  vec2 pos = gl_FragCoord.xy / u_size;
  gl_FragColor = vec4(getColor(pos.x), 1.0);
}

以上

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?