Next.jsのTypeScript環境でGLSLの環境構築をする

こんばんは、びわ湖の限界大学生兼フロントエンドエンジニアのお茶です！
自分は趣味でThree.jsでWebGLを触るのが好きなのですが、そろそろ自前でGLSLを書いてカスタムシェーダーを書いてみたい！　となってから環境構築をする時に自分的詰まるポイントを書いていきたいと思います。

普段Three.jsを書いている人からすると当たり前のことだと思いますが初めて環境構築する時の参考になれば幸いです！

# あるある
Next.jsでReactThreeFiber(以下R3F)を導入してカスタムシェーダーを書く時、
以下のように書いている人が何気に以下のように書いている人多いんじゃないでしょうか？
```ts
    const vertex = `
        void main() {
            // vertexシェーダーをいっぱいかく
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `

    const fragment = `
        void main() {
            // fragmentシェーダーいっぱい書く
            gl_FragColor = vec4((1.0,0.0,0.0),1.0).rgba;
        }
    `
```
公式のExampleでもカスタムシェーダーでGLSLの部分はテンプレートリテラルで持たせているものも多いのでこれがセオリーなのかな？？？と自分も初めは思っていました。

ですが、この記述だと
- LinterやFomatterが効かない
- 文字列なのでインデントの調整やtypoが多発
- ミスっててもどこでミスったかまるでわからない

のでできればGLSLのシェーダーコードは別ファイルに切り出して、形式もglslファイルとして扱いたいです。

が。。。

### TypeScript環境だとGLSLファイルを読み込めない
先ほど書いていたテンプレートリテラルで持たせていたシェーダーを.glslの拡張子で切り出してみます。
- vertex.glsl
```glsl
void main() {
            // vertexシェーダーをいっぱいかく
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
```
- fragment.glsl
```glsl
void main() {
            // fragmentシェーダーいっぱい書く
            gl_FragColor = vec4((1.0,0.0,0.0),1.0).rgba;
        }
```
js環境であれば、型の概念がガバガバなので問題ないのですが、これをtsやtsx内でimportしようとすると
```
import vertex from '~/shaders/vertex.glsl'
```
型宣言が無いとブチギレられます。

今回はNext.jsでR3Fの環境構築でGLSLを一緒に扱えるようにしていこうと思います。

# R3Fの導入
Next.js自体は環境構築できているものとして進めていきます。
こちらは簡単です。R3Fを入れるだけです。Three.jsも依存なのでセットでインストールします。
自分はpnpmを使っていますが、yarnでもnpmでも環境に合わせてコマンドを叩いてください。
```
pnpm add three @types/three @react-three/fiber
```

あとは今回シェーダーを適応する板でもコンポーネントに切り出して作って置きます。
(returnの中だけ書いてます)
- Plane.tsx
```tsx
    <mesh>
      <planeGeometry args={[2, 2]} />
      <shaderMaterial
        vertexShader={vertex}
        fragmentShader={fragment}
      />
    </mesh>
```
適当にカメラとライトを配置して板を表示させておきます。
- index.tsx
```tsx
      <Canvas>
          <ambientLight />
          <Plane />
          <OrthographicCamera position={[-0.5, 0.5, 0.5]} near={0.1} far={10} />
        </Canvas>
```

この状態で切り出したGLSLをPlane.tsxでimportするとブチギレられます。
```ts
import vertex from '~/shaders/vertex.glsl'
import fragment from '~/shaders/fragment.glsl'
```
次で直していきましょう。

# GLSLファイルを読めるようにする
### raw-loaderを使う
これまでGLSLをテンプレート文字列で扱っていたように、今回切り出したファイルの中身も文字列として扱いたいです。
なので.glsl拡張子のファイルを文字列として扱うためにwebpackのraw-loaderを使います。
```
pnpm add -D raw-loader
```
でインストールをして、nextConfigからweb-packの設定をしておきます。
- next.config.js
```js
const nextConfig = {
  reactStrictMode: true,
  webpack: (config) => {
    config.module.rules.push({
      test: /\.glsl$/,
      use: 'raw-loader',
    })
    return config
  },
}
```

### 型定義を追加する
ブチギレの大元はGLSLの型定義が存在しないのが原因なので型定義ファイルを追加します。任意の場所に型定義ファイルを作成します。
- glsl.d.ts
```ts
declare module '*.glsl' {
  const value: string
  export default value
}
```
これでGLSLファイルを読めるようになります。

# 結果
![スクリーンショット 2024-03-02 6.39.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982370/f64fb2ac-3449-01b0-a41b-d1f62fdfe97e.png)
シェーダーが読み込まれて赤い板が表示されました。
めでたしめでたし！！

# まとめ
- R3F入れる
- RawLoaderでGLSLファイルを文字列として扱う
- 型定義の追加を忘れずに！

実は文字列としてtsxファイルに読み込めれば拡張子はなんでも良かったりします。
- vertexShaderは.vert
- fragmentShaderは.frag
などで設定すればもっと扱いやすいかも？

# ドキュメント
https://v4.webpack.js.org/loaders/raw-loader/
https://docs.pmnd.rs/react-three-fiber/getting-started/introduction