記事の概要
前回の記事では時間経過による変化を実現しました。
今回はマウスの座標にあわせて変化させます。
画面解像度を取得する
1 色のグラデーションを作ったときの記事で以下のようなシェーダーを書いていました。
  #version 300 es
  in vec2 aPosition;
  out vec2 vTextureCoord;
  void main() {
    gl_Position = vec4(aPosition, 0.0, 1.0);
   
    vTextureCoord = aPosition * 0.5 + 0.5;
  }
  #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);
  }
マウスの座標にあわせて背景色を変化させる際など、今のままだと形状を制御しづらいです。
そのため、少し書き方を変えます。
  // シェーダーに渡すデータ
  const positionAttributeLocation = gl.getAttribLocation(
    shaderProgram,
    "aPosition"
  );
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
+ const resolutionUniformLocation = gl.getUniformLocation(shaderProgram, 'uResolution');
  // 描画の実行
- function drawScene() {
+ function drawScene(width: number, height: number) {
+ gl.uniform2f(resolutionUniformLocation, width, height);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+ requestAnimationFrame(() => drawScene(width, height));
  }
  // canvas のリサイズ
  function resizeCanvasToDisplaySize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    gl.viewport(0, 0, canvas.width, canvas.height);
-   drawScene();
+   drawScene(canvas.width, canvas.height);
  }
  #version 300 es
  in vec2 aPosition;
- out vec2 vTextureCoord;
  void main() {
    gl_Position = vec4(aPosition, 0.0, 1.0);
-  
-   vTextureCoord = aPosition * 0.5 + 0.5;
  }
  #version 300 es
  precision mediump float;
- in vec2 vTextureCoord;
+ uniform vec2 uResolution;
  out vec4 outColor;
  void main() {
-   float r = vTextureCoord.x;
+   vec2 uv = gl_FragCoord.xy / uResolution;
-   outColor = vec4(r, 0.0, 0.0, 1.0);
+   outColor = vec4(uv.x, 0.0, 0.0, 1.0);
  }
ややこしくなっただけに見えるかもしれませんが、後々役立ちます。
最終的には座標を正規化するタイミングを、頂点シェーダーからフラグメントシェーダーに移しただけです。
ただ、どちらかと言えば重要なのは uResolution という名前で画面の解像度を取得したことです。
マウスの座標を取得し、シェーダーへ送る
まずは TypeScript の変更です。
  // canvas要素の取得とWebGL2コンテキストの取得
  const canvas = document.getElementById("webgl-canvas") as HTMLCanvasElement;
  const gl = canvas.getContext("webgl2") as WebGL2RenderingContext;
  // 中略
  // シェーダーに渡すデータ
  const positionAttributeLocation = gl.getAttribLocation(
    shaderProgram,
    "aPosition"
  );
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
  const resolutionUniformLocation = gl.getUniformLocation(shaderProgram, 'uResolution');
+ const mousePositionUniformLocation = gl.getUniformLocation(shaderProgram, "uMousePosition");
+
+ let mousePosition = { x: 0, y: 0 };
+
+ function getMousePosition(event: MouseEvent) {
+   mousePosition.x = event.clientX / canvas.width;
+   mousePosition.y = 1.0 - (event.clientY / canvas.height); // Y軸を反転
+ }
+
+ canvas.addEventListener('mousemove', getMousePosition);
  // 描画の実行
  function drawScene(width: number, height: number) {
    gl.uniform2f(resolutionUniformLocation, width, height);
+   gl.uniform2f(mousePositionUniformLocation, mousePosition.x, mousePosition.y);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame(() => drawScene(width, height));
  }
次にユニフォーム変数として受け取り、uv.x を uMousePosition.x に変えてみます。
  #version 300 es
  precision mediump float;
  uniform vec2 uResolution;
+ uniform vec2 uMousePosition;
  out vec4 outColor;
  void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
-   outColor = vec4(uv.x, 0.0, 0.0, 1.0);
+   outColor = vec4(uMousePosition.x, 0.0, 0.0, 1.0);
  }
これで、マウスの位置にあわせて色が変わりました。
| uv.x | uMousePosition.x | 
|---|---|
|  |  | 
グリーン成分などに uMousePosition.y を入れれば、マウスを縦横に動かした際背景色が変わります。
マウスの位置にあわせて色を変える
組み込み関数である distance() と smoothstep() を組み合わせ、マウスから 200px の範囲だけ色がつくようにしました。
  void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
+   // マウス位置との距離を計算
+   float mouseDistnace = distance(uMousePosition, uv);
+   float mouseColorIntensity = 1.0 - smoothstep(0.0, 200.0 / uResolution.x, mouseDistnace);
-   outColor = vec4(uMousePosition.x, 0.0, 0.0, 1.0);
+   outColor = vec4(mouseColorIntensity, 0.0, 0.0, 1.0);
  }
しかし、赤い範囲が楕円形になっています。
これが先ほど書いていた「形状を制御しづらい」話です。
修正し、画面のアスペクト比によらず円形となるように変えます。
  void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
+   // アスペクト比を考慮したUV座標の計算
+   vec2 aspectCorrectedUV = uv;
+   aspectCorrectedUV.x *= uResolution.x / uResolution.y;
+   // マウス位置のアスペクト比を補正
+   vec2 aspectCorrectedMouse = uMousePosition;
+   aspectCorrectedMouse.x *= uResolution.x / uResolution.y;
    // マウス位置との距離を計算
    float mouseDistnace = distance(uMousePosition, uv);
    float mouseColorIntensity = 1.0 - smoothstep(0.0, 200.0 / uResolution.x, mouseDistnace);
    outColor = vec4(mouseColorIntensity, 0.0, 0.0, 1.0);
  }
uResolution.x / uResolution.y をかけることで、1:1 の比率となるようにしました。
最後に
マウスの位置にあわせた変化ができたので、ユーザーの操作にあわせてインタラクティブに画面を変えられそうです。
次回はスクロール関連の記事を書きたいと思います。

