WebGL で色々な表現に挑戦する - 単色の板ポリを配置する

## この記事の概要

前回の記事では、まずは `canvas` を用意して単色で塗りつぶしました。

https://qiita.com/xrxoxcxox/items/5ba20dfa989d252c043f

このままでは `div` 要素を配置して `background-color` を指定するのとなんら変わりません。
今回は、`canvas` 内に単色の板ポリを配置することまでを実施します。

## 全体像

ステップバイステップだと、途中まではエラーしか出ません。
そのため、いきなり完成したコードの全体像を載せます。

```typescript
// 前回のコード
const canvas = document.getElementById("webgl-canvas") as HTMLCanvasElement;
const gl = canvas.getContext("webgl2") as WebGL2RenderingContext;

// シェーダープログラムの作成
const vertexShaderSource = `#version 300 es
in vec2 aPosition;
void main() {
  gl_Position = vec4(aPosition, 0.0, 1.0);
}`;

const fragmentShaderSource = `#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
  outColor = vec4(1.0, 0.0, 0.0, 1.0); // 赤色
}`;

const vertexShader = gl.createShader(gl.VERTEX_SHADER) as WebGLShader;
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) as WebGLShader;
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);

const shaderProgram = gl.createProgram() as WebGLProgram;
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);

// バッファの作成と設定
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

// canvasの幅と高さの半分の大きさの矩形の頂点データ
const positions = new Float32Array([
  -0.5, -0.5,
   0.5, -0.5,
  -0.5,  0.5,
   0.5,  0.5,
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "aPosition");
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

// 前回のコード
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

// 新たに追加した描画のためのコード
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
```

見た目はこのようになっています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/dea6931f-0a7c-1aae-c3da-b1239ca1402d.png)

## シェーダープログラムの作成

シェーダーというものを使って、どんな頂点をもつ要素に、どんな色を塗るかを指定します。
今回は記事タイトルにもあるように板ポリ（厚みを持たないただの四角い板）を赤一色で塗ります。

### シェーダーの定義

シェーダーには `vertex shader` と `fragment shader` の 2 種類があります。
`vertex shader` に頂点を、`fragment shader` に色を指定します。

```typescript
const vertexShaderSource = `#version 300 es
in vec2 aPosition;
void main() {
  gl_Position = vec4(aPosition, 0.0, 1.0);
}`;

const fragmentShaderSource = `#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
  outColor = vec4(1.0, 0.0, 0.0, 1.0); // 赤色
}`;
```

`vertexShaderSource` には `in vec2 position` や `gl_Position = vec4(position, 0.0, 1.0)` という記述があります。
これは後で JavaScript から頂点データを受け取り、平面的にマッピングするためのコードです。

`gl_Position` の 3 つ目の引数の `0.0` は奥行き方向を表しています。
いくつかの座標の配列を XY 平面にプロットする、と捉えていてください。

`fragmentShaderSource` の `outColor` には `r, g, b, a` を指定しています。
Web デザインではよく HEX 値を使いますが、ここではそれぞれの値を 0 から 1 にマッピングすることに注意してください。

`gl_Position` が組み込みの変数であることや、`void main()` という関数が必須なことなど、初見だと難しく感じることも多いかもしれません。
まずはとりあえず真似をして、書式そのものに慣れるのも重要に思います。

### シェーダーオブジェクトの作成とコンパイル

`vertex shader` と `fragment shader` のそれぞれのコードは。WebGLのシェーダーオブジェクトに関連付けられコンパイルされます。

```typescript
const vertexShader = gl.createShader(gl.VERTEX_SHADER) as WebGLShader;
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) as WebGLShader;
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
```

### シェーダープログラムの作成とリンク

最終的に、シェーダーは 1 つのシェーダープログラムに結合されます。

```typescript
const shaderProgram = gl.createProgram() as WebGLProgram;
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
```

## バッファの作成と設定

バッファとは、頂点の位置や色といったデータを格納するためのメモリ領域です。
後で頂点データをシェーダーに渡すために使用します。

### バッファの作成

新しいバッファオブジェクトを作成します。

```typescript
const positionBuffer = gl.createBuffer();
```

### バッファのバインド

先ほど作成した `positionBuffer` をアクティブなバッファとしてバインドします。

```typescript
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
```

`gl.ARRAY_BUFFER` は、バッファが頂点のデータを含むことを示しています。

### 頂点データの設定

四角形の頂点データを `Float32Array` の配列に格納します。
このデータを `gl.bufferData` を使って、バッファに設定します。

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

画面の横方向が -1 から 1、縦方向も -1 から 1 にマッピングされているので、この指定だと画面の半分の位置

配列の順番が大切で、例えば以下のように指定してしまうと……。

```typescript
const positions = new Float32Array([
  -0.5, -0.5,
   0.5, -0.5,
   0.5,  0.5, // 4 行目と入れ替えた
  -0.5,  0.5, // 3 行目と入れ替えた
]);
```

結果がこうなってしまいます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/aa63abf9-9053-f749-21d4-6af72010ba08.png)

:::note
厳密に言えばこの後記載する `TRIANGLE_STRIP` の設定とも組み合わさってこの見た目になっているのですが、とにかく「頂点の順番は適当ではいけない」と認識しておけば大丈夫だと思います。
:::

### 属性ポインタの設定

`vertex shader` で定義した `position` 属性にデータを結びつけています。

```typescript
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "aPosition");
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
```

## 描画の実行

前回の記事のコードに 1 行追加しています。

```typescript
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
```

「三角形を組み合わせて、4 つの頂点を使って、四角形を描画する」といった内容です。

## 最後に

随分と長いコードを書きましたが、まだ見た目はこれです（記事の前半に載せた画像の再掲）。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/dea6931f-0a7c-1aae-c3da-b1239ca1402d.png)

しかも手元で動かしてもらうと分かると思いますが、赤い四角形のフチがボケボケ（？）です。
次はそのあたりを修正していきます。

リッチな表現を実装するまで、まだ先は長い……。

次の記事はこちらです。

https://qiita.com/xrxoxcxox/items/8b226078e7605276fcb7