この記事の概要
前回の記事ではマウスの位置により見た目を変化させました。
今回は、スクロールにあわせて変化させてみます。
HTML, CSS の調整
これまでの記事では、幅と高さ 100% の canvas を 1 枚用意していただけでした。
スクロールするために適当に文章を増やします。
  <body>
    <canvas id="webgl-canvas"></canvas>
+   <div class="container">
+     <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non aliquam, facere vel beatae, ipsum, tenetur illum molestias inventore ut earum eum. Totam maiores id itaque officia saepe quia debitis fugiat!</p>
+     <!-- 以下、適当に文章を増やす -->
    </div>
    <script type="module" src="/src/main.ts"></script>
  </body>
canvas 要素を背景に固定し、先ほど追加した文章のレイアウトに少しだけ手を加えます。
この記事とは直接関係ありませんが、最近 CSS でネスト記法が使えるようになったので使ってみています。
  #webgl-canvas {
    display: block;
    height: 100dvh;
    width: 100dvw;
+   position: fixed;
+   top: 0;
+   z-index: -1;
  }
+ .container {
+   width: 640px;
+   margin: auto;
+
+   p {
+     margin-top: 2rem;
+     color: #fff;
+   }
+ }
前回の記事でも使ったグラデーションのシェーダーを適用して、
  #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);
  }
現状はこのようになりました。
スクロール量を取得しシェーダーへ送る
次にスクロール量を取得して、シェーダーから操作できるようにします。
まずは TypeScript の変更です。
  // canvas要素の取得とWebGL2コンテキストの取得
  const canvas = document.getElementById("webgl-canvas") as HTMLCanvasElement;
  const gl = canvas.getContext("webgl2") as WebGL2RenderingContext;
  // 中略
  // シェーダーに渡すデータ
+ const scrollPositionUniformLocation = gl.getUniformLocation(shaderProgram, "uScrollPosition");
  // 描画の実行
  function drawScene(width: number, height: number, time: number) {
    const currentTime = time * 0.001;
    gl.uniform1f(timeUniformLocation, currentTime);
    gl.uniform2f(resolutionUniformLocation, width, height);
    gl.uniform2f(mousePositionUniformLocation, mousePosition.x, mousePosition.y);
+   gl.uniform1f(scrollPositionUniformLocation, window.scrollY);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame((currentTime) => drawScene(width, height, currentTime));
  }
次にシェーダーの変更です。
  #version 300 es
  precision mediump float;
  uniform vec2 uResolution;
  uniform vec2 uMousePosition;
+ uniform float uScrollPosition;
  out vec4 outColor;
  void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
-   outColor = vec4(uv.x, 0.0, 0.0, 1.0);
+   outColor = vec4(uv.x, sin(uScrollPosition * 0.001), 0.0, 1.0);
  }
単にスクロール量を RGB 成分に入れてしまうと [0, 1] の範囲では済まないので、sin に渡しています。
マウス操作との合わせ技
前回の記事で使えるようにした uMousePosition と組み合わせれば、なかなかインタラクティブな画面の変化を起こせます。
  #version 300 es
  precision mediump float;
  uniform vec2 uResolution;
  uniform vec2 uMousePosition;
  uniform float uScrollPosition;
  out vec4 outColor;
  void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
-   outColor = vec4(uv.x, sin(uScrollPosition * 0.001), 0.0, 1.0);
+   outColor = vec4(uv.x, sin(uScrollPosition * 0.001), uMousePosition.y, 1.0);
  }
最後に
ユニフォーム変数として渡せば後はいかようにも操作できそうな気配を感じられてきました。
そろそろ完全に理解したと言えるかもしれません。


