WebGL で色々な表現に挑戦する - スクロールによる変化

## この記事の概要

[前回の記事](https://qiita.com/xrxoxcxox/private/a816dbd371336bf9247f)ではマウスの位置により見た目を変化させました。

今回は、スクロールにあわせて変化させてみます。

## HTML, CSS の調整

これまでの記事では、幅と高さ 100% の canvas を 1 枚用意していただけでした。
スクロールするために適当に文章を増やします。

```diff_html:html
  <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 でネスト記法が使えるようになったので使ってみています。

```diff_css: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;
+   }
+ }
```

前回の記事でも使ったグラデーションのシェーダーを適用して、

```glsl: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);
  }
```

現状はこのようになりました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/0496f20c-5c13-72c0-29ca-79e5273616ec.png)

## スクロール量を取得しシェーダーへ送る

次にスクロール量を取得して、シェーダーから操作できるようにします。

まずは TypeScript の変更です。

```diff_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));
  }
```

次にシェーダーの変更です。

```diff_glsl:plane.frag
  #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` に渡しています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/9824f4c2-1196-afd1-92a3-650336091ab9.gif)

## マウス操作との合わせ技

前回の記事で使えるようにした `uMousePosition` と組み合わせれば、なかなかインタラクティブな画面の変化を起こせます。

```diff_glsl
  #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);
  }
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/e8862309-d430-effb-2cf2-903533486e0f.gif)

## 最後に

ユニフォーム変数として渡せば後はいかようにも操作できそうな気配を感じられてきました。

そろそろ[完全に理解した](https://togetter.com/li/1268851)と言えるかもしれません。