WebGL で色々な表現に挑戦する - canvas 自体のサイズや中身の描画を画面サイズにあわせる

## この記事の概要

前回の記事では 50 行くらいのコードを書いて、ようやく 1 枚の単色板ポリを生成しました。

https://qiita.com/xrxoxcxox/private/e4ea15a00c48a0c05d89

しかし現状では描画がボケています。
具体的なグラフィックを作る前に、画面サイズにあわせた描画ができるようにします。

## 現状の確認

前回作った赤い四角形を拡大してみます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/5f6009b4-6cb9-e800-1ef2-a0bca24c4698.png)

ボケボケです。
今は単色なのでフチがボケているだけに見えますが、この状態でグラデーションやノイズを実装するとすべてがボケたグラフィックに仕上がります。

しかし、`canvas` 要素自体のサイズはちゃんと画面いっぱいに広がっています。
スクリーンショットの数値でいうと `1104 x 852` のサイズです。
[最初の記事](https://qiita.com/xrxoxcxox/private/5ba20dfa989d252c043f#canvas-%E4%BB%A5%E5%A4%96%E3%81%AE%E6%BA%96%E5%82%99:~:text=%23webgl%2Dcanvas%20%7B%0A%20%20display%3A%20block%3B%0A%20%20height%3A%20100dvh%3B%0A%20%20width%3A%20100dvw%3B%0A%7D)で、`canvas` に対して `{height: 100dvh; width: 100dvw;}` を指定していたので当然と言えば当然です。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/539f614f-1672-77b9-4099-b3c3878e6712.png)

ひとまず、`canvas` 自体のサイズには問題がないのに中身がボケている、ということをご認識ください。

## 解決策

1. もともと `// 描画の実行` とコメントをつけていた処理を関数にする → `drawScene()`
1. 画面のサイズとcanvasのサイズ（描画バッファサイズ）を一致させる関数を作る → `resizeCanvasToDisplaySize()`
1. `resize` と `DOMContentLoaded` にあわせて動かす

```diff_typescript
  // 描画の実行
- gl.clearColor(0.0, 0.0, 0.0, 1.0);
- gl.clear(gl.COLOR_BUFFER_BIT);
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+ function drawScene() {
+   gl.clearColor(0.0, 0.0, 0.0, 1.0);
+   gl.clear(gl.COLOR_BUFFER_BIT);
+   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+ }
+ 
+ // ウィンドウサイズに合わせてcanvasのサイズを変更する
+ function resizeCanvasToDisplaySize() {
+   canvas.width = window.innerWidth;
+   canvas.height = window.innerHeight;
+   gl.viewport(0, 0, canvas.width, canvas.height);
+   drawScene();
+ }
+ 
+ // ウィンドウリサイズイベントに対するリスナーを設定
+ window.addEventListener("resize", resizeCanvasToDisplaySize);
+ 
+ // 初期表示時にもリサイズを実行
+ document.addEventListener("DOMContentLoaded", resizeCanvasToDisplaySize);
```

これにより、角がちゃんと綺麗になっています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/768d907b-5306-f1c9-b0bc-7105cf847e41.png)

ポイントは `resizeCanvasToDisplaySize()` の部分です。
`canvas` 要素はサイズの概念に `表示サイズ` と `描画バッファサイズ` があります。

`表示サイズ` は通常 CSS で指定するようなサイズのことです。
先ほどのスクリーンショットで言えば `1104 x 852` と表示されていたサイズのことです。

`描画バッファサイズ` は `canvas` 要素の中で実際に描画をするピクセル数です。
[1 つ目の記事](https://qiita.com/xrxoxcxox/items/5ba20dfa989d252c043f)で WebGL コンテキストを取得した際、次のようになっていました。

```
WebGL2RenderingContext {
  canvas: canvas#webgl-canvas,
  drawingBufferWidth: 300,
  drawingBufferHeight: 150,
  drawingBufferColorSpace: 'srgb',
  unpackColorSpace: 'srgb'
}
```

`drawingBufferWidth: 300, drawingBufferHeight: 150` とありました。
つまり `300 x 150` の要素を `1104 x 852` に引き延ばして表示している状態でした。

小さな画像を無理やり引き伸ばしてボケてしまった経験はあると思います。
それと同じことが起きていました。

`1104 x 852` の要素を `1104 x 852` のサイズで表示するために、以下の処理を追加していた、というわけです。

```typescript
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
```

## 余談

ちなみに、常にウィンドウサイズと canvas のサイズが一緒になるように変えたため、CSS も少し簡略化できます。

```diff_css
  #webgl-canvas {
    display: block;
-   height: 100dvh;
-   width: 100dvw;
  }
```

この程度のコードならあってもなくてもそこまで変わりませんが、CSS と JavaScript の両方からサイズを指定する必要もないので消しました。

:::note warn
もっと言うなら `getElementById` するために指定した `id` をセレクターとしてスタイルを当てること自体イマイチではありますが、一旦このまま進めます。
:::

## 最後に

ここまでのコードをすべて合わせると以下のようになります。

```typescript
// canvas要素の取得とWebGL2コンテキストの取得
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);

// 描画の実行
function drawScene() {
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

// ウィンドウサイズに合わせてcanvasのサイズを変更する
function resizeCanvasToDisplaySize() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  gl.viewport(0, 0, canvas.width, canvas.height);
  drawScene();
}

// ウィンドウリサイズイベントに対するリスナーを設定
window.addEventListener("resize", resizeCanvasToDisplaySize);

// 初期表示時にもリサイズを実行
document.addEventListener("DOMContentLoaded", resizeCanvasToDisplaySize);
```

まだ、見た目は「真っ黒い背景に赤い板が浮かんでいるだけ」です。
あと 1, 2 記事したら見た目をいじるステップに進めるはず……。

次の記事はこちらです。

https://qiita.com/xrxoxcxox/items/3c63b165b84a4929b199