【PlayCanvas】自作のポストエフェクト作成用のスクリプト

### PlayCanvasでカスタムポストエフェクトを作成する方法
PlayCanvasでプロジェクトを作成した際にポストエフェクトを作成したい場合があります、その時に使える便利なスクリプトとなります。Engine単体での利用については[Examples](https://playcanvas.github.io/#/graphics/post-effects)に用意されています。また、BloomやFXAAのポストエフェクトについては、PlayCanvasのStoreからインポートして利用することができます。

こちらのストア、GitHubに公開されているポストエフェクトをまとめたプロジェクトはこちらに作成したものがあります。
https://playcanvas.com/project/907474

この記事では、GLSLを記述し、自作のポストエフェクトを作成する際の手順を紹介致します。

### 1. 準備 - カメラコンポーネントの準備
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257048/b3b16ec9-1bee-d131-fe45-1409c3282e00.png)

ポストエフェクトを利用するには、PlayCanvasのカメラコンポーネントを利用する必要があります。一週間前にアップデートされたPlayCanvas [Editor v1.23.3](https://github.com/playcanvas/editor/releases)から`Color Grabpass`, `Depth Grabpass`のプロパティ追加されました。こちらにチェックを入れることでPostEffectのシェーダー側でUniform値として`uniform sampler2D uSceneDepthMap`と`uniform sampler2D uSceneColorMap`が利用できるようになります。


### 2. スクリプトの追加

ポストエフェクトを作成するには、この３つのファイルを利用します。
- スクリプトファイル(PlayCanvas Editorから作成)
- 
 バーテックスシェーダ(posteffect.vert)
 (2023年 5月 `pc.PostEffect.quadVertexShader` で代用可能になりました。)
- フラグメントシェーダ(posteffect.frag)


#### JavaScriptファイル
```javascript
class PostEffectRenderer extends pc.PostEffect {
    constructor(props) {
        const { graphicsDevice, fshader } = props;

        super(graphicsDevice);

        this.customShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'CustomPostEffect', {
            aPosition: pc.SEMANTIC_POSITION
        });

    }
    render(inputTarget, outputTarget, rect) {
        this.device.scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer);
        pc.drawFullscreenQuad(
            this.device,
            outputTarget,
            this.vertexBuffer,
            this.customShader,
            rect
        );
    }
}

class CustomPostEffect extends pc.ScriptType {
    initialize() {
        const fshader = this.frag.resource;

        this.time = 0;
        this.effect = new PostEffectRenderer({
            graphicsDevice: this.app.graphicsDevice,
            vshader: pc.PostEffect.quadVertexShader,
            fshader
        });
        const queue = this.entity.camera.postEffects;
        queue.addEffect(this.effect);

        this.app.mouse.on(pc.EVENT_MOUSEMOVE, (e) => {
            this.app.graphicsDevice.scope.resolve("iMouse").setValue([e.x, e.y]);
        });


        this.on("state", function (enabled) {
            if (enabled) {
                queue.addEffect(this.effect);
            } else {
                queue.removeEffect(this.effect);
            }
        });
        this.on("destroy", function () {
            queue.removeEffect(this.effect);
        });
    }
    update(dt) {
        this.time += dt;
        this.app.graphicsDevice.scope.resolve("iTime").setValue(this.time);
        this.app.graphicsDevice.scope.resolve("iTimeDelta").setValue(dt);
    }
}
pc.registerScript(CustomPostEffect)
CustomPostEffect.attributes.add("frag", { type: "asset" });
```

#### 
バーテックスシェーダー (2023年 5月 `pc.PostEffect.quadVertexShader` で代用可能になりました。)


詳細はこちら
https://developer.playcanvas.com/en/api/pc.PostEffect.html#quadVertexShader


```posteffect.vert
#version 300 es
precision highp float;

in vec2 aVertexPosition;
out vec2 vUv0;

void main(void){ 
    gl_Position = vec4(aVertexPosition, 0.0, 1.0);
    vUv0 = (aVertexPosition.xy + 1.0) * 0.5;
}
```

#### フラグメントシェーダ
基本的にはこちらを記述していきます。
（今回は小さいコードとなっております）

```posteffect.frag
precision highp float;
uniform vec4 uScreenSize;
uniform vec2 iMouse;

uniform float iTime;
uniform sampler2D uSceneColorMap; // Grab Texture without cubemap;
uniform sampler2D uSceneDepthMap; // Depth Map
uniform sampler2D uColorBuffer; // Grab Texture

varying vec2 vUv0;

void main(){
  float t = sin(iTime);
  vec4 color = texture2D(uSceneColorMap, vUv0);
  gl_FragColor = vec4(color * t);
}
```

### PlayCanvas Editorからカメラのスクリプト属性に 
バーテックスシェーダ
 とフラグメントシェーダーを設定

![スクリーンショット 2022-11-28 14.06.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257048/9f2192e7-fbef-7b26-beaf-d598b83b2159.png)
`frag`および
 `vert` 
 に対してカスタムシェーダーを追加することで利用することができます。

GitHubのExamplesでは、フラグメントシェーダとバーテックスシェーダについては文字列としてそのまま定義されている場合がありますが今回はresourcesとして利用します。


### ※補足（分かること）

[Engineのソースコード](https://github.com/playcanvas/engine/blob/6c4935fb1d23590690363b751e8e7aa3f0af604d/src/framework/graphics/scene-grab.js)を見ると`uDepthMap`、`texture_grabPass`も追加されるようです。（こちらはduprecatedですが、まだ利用はできます）

Uniformの値についてはLaunchなどでPlayCanvas実行中にコンソールでこちらを見ると色々な値確認ができます。
詳細は[こちらの記事](https://zenn.dev/link/comments/c34d3882680daa)を御覧ください
```javascript
// this.app.graphicsDevice.scope.variables
pc.app.graphicsDevice.scope.variables // Uniform値を取得
```


### WebGL2で実行される場合には `varying`の記述は`in`など、WebGL2の記述に置き換わります。

### Spector.jsのデバッガーでの確認

[Spector.js](https://chrome.google.com/webstore/detail/spectorjs/denbgaamihkadbghdceggmchnflmhpmk
)を利用して、PlayCanvasが利用するWebGLのコードを確認するとこのようなコードとなります。

### バーテックスシェーダー
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257048/b600a37d-06b2-fe25-5abd-2dcfeebf2838.png)

### フラグメントシェーダー
 ![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257048/0c4a4a12-9aa6-e98c-2d2a-f2916b483164.png)