WebGL で色々な表現に挑戦する - 1 色のグラデーション表現

## この記事の概要

[前回の記事](https://qiita.com/xrxoxcxox/items/3c63b165b84a4929b199)までで、コードの準備がおおよそ終わりました。いよいよタイトルにあるように色々な表現に挑戦してこうと思います。

今回の記事では 1 色のグラデーションを考えます。

## 下準備

これまでは、canvas 内で色が塗られている場所を分かりやすくするために、画面の半分のサイズの領域を描画していました。
今後は表示の見やすさも含めて全画面に変えます。

```diff_typescript
  const positions = new Float32Array([
-   -0.5, -0.5,
-    0.5, -0.5,
-   -0.5,  0.5,
-    0.5,  0.5,
+   -1.0, -1.0,
+    1.0, -1.0,
+   -1.0,  1.0,
+    1.0,  1.0,
  ]);
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
```

また、xy 座標を正規化しておきます。
現状は x, y ともに　[-1, 1] の範囲で表されているので、これを [0, 1] に変換します。

```diff_glsl:plane.vert
  #version 300 es

  in vec2 aPosition;
+ out vec2 vTextureCoord;

  void main() {
    gl_Position = vec4(aPosition, 0.0, 1.0);
   
+   // 座標を[-1, 1]から[0, 1]に正規化する
+   vTextureCoord = aPosition * 0.5 + 0.5;
  }
```

`out vec2 vTextureCoord` は、正規化した座標をフラグメントシェーダーで使うための宣言です。
JavaScript 的な書き方をするなら `export const vTextureCoord` みたいな雰囲気と思ってください。

## 水平方向のグラデーション

まずは水平方向のグラデーションです。

```diff_glsl
  #version 300 es

  precision mediump float;

+ in vec2 vTextureCoord;
  out vec4 outColor;

  void main() {
+   float r = vTextureCoord.x;
+   outColor = vec4(r, 0.0, 0.0, 1.0);
-   outColor = vec4(1.0, 0.0, 0.0, 1.0); // 赤色
  }
```

`in vec2 vTextureCoord` は、先ほど頂点シェーダーで定義した `vTextureCoord` を受け取っています。
雰囲気としては `import { vTextureCoord } from ./plane.vert` みたいな感じです。

現状の見た目は以下のようになっています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/9ba6b09c-bf2d-7888-2e65-24cb418ee92b.png)

`float r = vTextureCoord.x; outColor = vec4(r, 0.0, 0.0, 1.0);` の部分を日本語っぽく表現するのであれば `x 座標の値 0 から 1 までを r の値として当てはめる`といった具合です。

## 垂直方向のグラデーション

先ほどのコードから、次のように `x` と `y` を入れ替えてみます。

```diff_glsl
  #version 300 es

  precision mediump float;

  in vec2 vTextureCoord;
  out vec4 outColor;

  void main() {
-   float r = vTextureCoord.x;
+   float r = vTextureCoord.y;
    outColor = vec4(r, 0.0, 0.0, 1.0);
  }
```

すると、このようにグラデーションの方向が変わります。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/26cd7032-4439-7612-0f3d-3d198cf3cc94.png)

## 斜め方向のグラデーション

x と y の両方の値をとり、平均します。

```diff_glsl
  #version 300 es

  precision mediump float;

  in vec2 vTextureCoord;
  out vec4 outColor;

  void main() {
+   float r = (vTextureCoord.x + vTextureCoord.y) / 2.0;
-   float r = vTextureCoord.y;
    outColor = vec4(r, 0.0, 0.0, 1.0);
  }
```

これで斜め方向のグラデーションになりました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/a275316c-3780-4514-b297-27948bc1e6a7.png)

## 複雑なグラデーション

ここまでの単なるグラデーションなら CSS で書いた方が早いです。
しかし、WebGL の場合はより複雑な計算もできます。
例えば以下のように書き換えるとしましょう。

```diff_glsl
  void main(void) {
+  float pi = acos(-1.0);
+   float r = abs(sin((vTextureCoord.x + vTextureCoord.y) * pi * 3.0));
-   float r = (vTextureCoord.x + vTextureCoord.y) / 2.0;
    float g = vTextureCoord.y;
    outColor = vec4(r, 0.0, 0.0, 1.0);
  }
```

斜めのグラデーションにしても、若干複雑なものができました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/44572a17-cd91-62a3-d714-6376e5a222f6.png)

更に式に手を入れてみます。

```diff_glsl
  void main() {
    float pi = acos(-1.0);
+   float r = sin(vTextureCoord.x * pi * 10.0) * cos(vTextureCoord.y * pi * 5.0) + 0.5;
-   float r = abs(sin((vTextureCoord.x + vTextureCoord.y) * pi * 3.0));
    outColor = vec4(r, 0.0, 0.0, 1.0);
  }
```

千鳥格子のようなグラデーションになりました。
このあたりまで来れば CSS だけではできないような気がします。[^1]

[^1]: 1 色のグラデーションのうち、CSS でストレートな書き方が浮かばないものを考えてみた状態です。複雑な書き方でなら実現できる、とかだったらすみません。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/02184742-1596-0147-09fd-cb9d122cf582.png)

このように、座標を自由に扱いつつ、三角関数なども使えることによって、複雑なものも表現できるのです。
このグラフィック自体に何か意味があるわけではありませんが、デモンストレーション的な内容でした。

## 最後に

ここまでの記事は「CSS で書いた方が早くない？」というものばかりでしたが、ここにきてようやく表現力の高いものを出せた気がしています。

次の記事では 2 色以上のグラデーションを扱います。

https://qiita.com/xrxoxcxox/items/1dbc2d7c9add19cb131f