LoginSignup
3
2

WebGL で色々な表現に挑戦する - マウスの座標による変化

Last updated at Posted at 2023-12-21

記事の概要

前回の記事では時間経過による変化を実現しました。

今回はマウスの座標にあわせて変化させます。

画面解像度を取得する

1 色のグラデーションを作ったときの記事で以下のようなシェーダーを書いていました。

plane.vert
  #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;
  }
plane.frag
  #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);
  }

マウスの座標にあわせて背景色を変化させる際など、今のままだと形状を制御しづらいです。
そのため、少し書き方を変えます。

main.ts
  // シェーダーに渡すデータ
  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);
  }
plane.vert
  #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;
  }
plane.frag
  #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 の変更です。

main.ts
  // 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.xuMousePosition.x に変えてみます。

plane.frag
  #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
movie.gif

グリーン成分などに uMousePosition.y を入れれば、マウスを縦横に動かした際背景色が変わります。

マウスの位置にあわせて色を変える

組み込み関数である distance()smoothstep() を組み合わせ、マウスから 200px の範囲だけ色がつくようにしました。

plane.frag
  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);
  }

しかし、赤い範囲が楕円形になっています。
これが先ほど書いていた「形状を制御しづらい」話です。

修正し、画面のアスペクト比によらず円形となるように変えます。

plane.frag
  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 の比率となるようにしました。

最後に

マウスの位置にあわせた変化ができたので、ユーザーの操作にあわせてインタラクティブに画面を変えられそうです。

次回はスクロール関連の記事を書きたいと思います。

3
2
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
3
2