Akashic Engine のシェーダを使ってワンランク上の演出を導入した

# 概要
[Akashic Engine Advent Calendar 2019](https://qiita.com/advent-calendar/2019/akashic-engine) 22日目の記事です。
**[Akashic Engine](https://akashic-games.github.io/) のシェーダ機能を作って、2D ゲームに今風でワンランク上の演出を導入**しました。

- 記事の中のゲームは_**[なにかしらツク〜ル](http://nanikasiratkool.web.fc2.com/)**_さんのマップチップ・背景素材を使用させていただいております。いつもありがとうございます。

# はじめに
みなさん、ゲームを作られていますか。作られていますよね。
私は昨年に意気揚々と作り始めたゲーム制作の進捗が、

```
Date:   Fri Jan 4 17:08:36 2019 +0900
    feat(shader): add snow shader
```
で止まっていることからお察しです。

コミットメッセージはどうやら「雪のシェーダを追加した」と思わしきものになっています。そう、私は **[Akashic Engine](https://akashic-games.github.io/) のシェーダ機能を使いたい**のです。

以下は今年最後に作っていたシーンの一部です。どうやらゲームのオープニングとして降雪演出を調整していたようです。
<img width=480 alt="shader-just-snow" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64028/9e138344-cd19-f0d4-5923-7ea77850ded2.png" />

  - _**Shader: [Just snow](https://www.shadertoy.com/view/ldsGDn)**_
      - _Created by baldand_
      - _Copyright (c) 2013 Andrew Baldwin (twitter: baldand, www: http://thndl.com), License = Attribution-NonCommercial-ShareAlik_

シェーダは、**スプライトアニメーションのように画像を用意する手間が省け、演算内容の細かな調整が行え、何より既成のシェーダ資産をそのまま活用できる**という素晴らしいメリットがあります。ハードウェアアクセラレーションが有効な環境では CPU にも優しく、リッチな演出が期待できます。これは使わざるを得ません。

本文書では、Akashic Engine のサポートするシェーダ機能を確認しつつ、実際に自作ゲームへシェーダを導入するまでを本文書で紹介します。皆さんのゲーム制作の一助となれば幸いです。

# シェーダ機能
Akashic Engine がサポートするシェーダ機能に関しては[公式サイトの文書](https://akashic-games.github.io/guide/shader.html)に記述があります。WebGL 1.0 を使用可能な環境が必要です。

2019 年 12 月現在、[WebGL 1.0 は主要な環境でサポート](https://caniuse.com/#feat=webgl)されています。**ポータビリティを是とする Akashic Engine においてカジュアルに使っても許される**大変ありがたい機能となっております。胸を張って使っていきましょう。

# シェーダを使うには
ここでは [Shadertoy](https://www.shadertoy.com/) で公開されている、いわゆる DEMO をゲームに導入してみます。
せっかくなので上記で紹介した **[Just snow](https://www.shadertoy.com/view/ldsGDn)** を使用させていただきましょう。

## シェーダアセットのロードとシーンへの反映
Akashic Engine はシェーダアセットというものがないため、DEMO のフラグメントシェーダをテキストファイルのアセットとして取り込みます。

```sh
$ akashic scan asset text --text-asset-extension=txt
```
成功すると game.json に登録され、コード上から識別子でアクセスできるようになります。（以下は `Snow`）

```json
"Snow": {
    "type": "text",
    "path": "text/glsl/Snow.txt"
}
```

以下はフラグメントシェーダのテキストを読み込み `g.ShaderProgram` を作る実装です:

```typescript
// シーンとシェーダアセットID、シェーダに紐づく uniforms の取得方法を与えて `g.ShaderProgram` を返す:
export function makeShaderProgram(
  scene: g.Scene,
  assetId: string,
  uniformsGetter?: () => { [key: string]: g.ShaderUniform }
) {
  const fragmentShaderText = (scene.assets[assetId] as g.TextAsset).data;
  return new g.ShaderProgram({
    fragmentShader: fragmentShaderText,
    uniforms: uniformsGetter ? uniformsGetter() : undefined
  });
}
```
これを**シーンの中で使ってみましょう**。ひとまず背景となるスプライトを以下のように表示させてみました。
<img width=480 alt="shader-just-snow" src="https://shunsuke-tanaka.github.io/demo/2019-12-22-qiita/images/bg_1.png" />

_「急に何？」「どういうゲーム？」_ と疑問に思われたかもしれませんが気にしないでください。今回の目的はシェーダの演出です。

このスプライトの上にシェーダを重ねることにします:

```typescript
export class DemoScene extends g.Scene {
  static ASSET_IDS = [
    Shader.SNOW,
    Image.BG_DEMO
  ];

  // シーンに追加するための背景オブジェクトを作成する
  makeBackground(): g.E {
    const snow = makeShaderProgram(this, Shader.SNOW, () => ({ // 後述
    }));
    // 背景のスプライト:
    const background = new g.Sprite({
      scene: this,
      src: this.assets[Image.BG_DEMO],
      x: -160,
      y: -32,
      width: this.game.width,
      height: this.game.height
    });
    // スプライトの上に重ねるシェーダ領域：
    const shader = new g.Pane({
      scene: this,
      width: this.game.width,
      height: this.game.height,
      shaderProgram: snow,
      opacity: 0.5
    });
    background.append(shader);
    return background;
  }
  // 以下略
}
```

## フラグメントシェーダを Akashic Engine 用に編集する

先の実装ではシェーダに与える uniform 変数が与えられていません。シェーダのパラメータとして `g.ShaderUniform` を定義し、実装します。
[Shadertoy の Input Uniforms](https://www.shadertoy.com/howto#q1) は、そのままでは Akashic Engine に対応していません。元のシェーダに含まれる定義を編集しましょう。改変にあたってはシェーダのライセンスにご注意ください。

```glsl
// "Just snow"
// Simple (but not cheap) snow made from multiple parallax layers with randomly positioned
// flakes and directions. Also includes a DoF effect. Pan around with mouse.
precision mediump float;
uniform vec3 iResolution;
uniform float iTime;
// (*1) uniform float iTimeDelta;
// (*1) uniform float iFrame;
// (*1) uniform float iChannelTime[4];
uniform vec4 iMouse;
// (*1) uniform vec4 iDate;
// (*1) uniform float iSampleRate;
// (*1) uniform vec3 iChannelResolution[4];
// (*1) uniform samplerXX iChanneli;

// (*2) Akashic Engine に必要なパラメータ:
varying vec2 vTexCoord;
uniform sampler2D uSampler;
uniform float uAlpha;
```

- (*1) 不要のため消します。利用している場合は参照箇所を定数に置き換えてしまいましょう。
- (*2) Akashic Engine に必要なパラメータを追加します。

そして、フラグメントシェーダの実装に、以下の Akashic Engine から読みだすためのエントリポイントを追加します。基本は Shadertoy のエントリである `mainImage()` に描画元オブジェクトのテクスチャを与えています。

```glsl
void main(void) {
  vec4 color = texture2D(uSampler, vTexCoord);
  mainImage(color, gl_FragCoord.xy);
  color.a *= uAlpha;
  gl_FragColor = color;
}
```

## シェーダにパラメータを設定する
先ほどの `makeShaderProgram(）` の紹介で省略していた、シェーダに紐づく uniforms を与えるように設定します。ゲームの画面に合わせて解像度などを指定していきます。

```typescript
  // シーンに追加するための背景オブジェクトを作成する
  makeBackground(): g.E {
    const snow = makeShaderProgram(this, Shader.SNOW, () => ({
      iResolution: {
        type: "vec3",
        value: new Float32Array([width, height, 1.0])
      },
      iTime: {
        type: "float",
        value: 0.0
      },
      iMouse: {
        type: "vec4",
        value: new Float32Array([width / 2, height / 2, 0.0, 0.0])
      }
    }));
    // 省略
```

さらに、これをゲームのフレームが進行するごとに変化させていきましょう。ここでは背景の更新時にシェーダの `iTime` 変数を更新するようにしています。

```typescript
    background.update.add(() => {
      const timeScaleFactor = Math.sin(this.frame * 1e-3) + 1;
      snow.uniforms.iTime.value = (this.frame + timeScaleFactor) * 1e-3;
      background.modified();
    });
```

## 描画

これで準備が完了しました。シーンを描画してみましょう。
<img width=640 alt="snow-demo" src="https://shunsuke-tanaka.github.io/demo/2019-12-22-qiita/images/demo_1.gif" />

スプライトの上に DEMO が表示されました。やりましたね！

# おわりに
**既成のシェーダを使い 2D ゲームにワンランク上の演出を導入すること**に成功しました。今回は静的なシーンでの導入になりましたが、次回はより動きのあるシーンでの活用とチューニングを行っていきたいものですね。皆様も是非お試しください。

ところで、シェーダを使う目的は果たしたのですが、それを使ったオレゲー制作の進捗に関しては今年一年間を通して**ゼロ**でした。

私はもう疲れました。また来年お会いしましょう。良いお年を。

# 関連リンク
- [Akashic Engine でライティング](https://qiita.com/blackspotbear/items/5379f4ad8ae15dd6f337)