【Unity】【シェーダ】解像度を落として画像をぼかすポストエフェクトを実装する

解像度を落として画像をぼかすポストエフェクトの実装を紹介します。

はじめに

この記事では解像度を落とすことによるぼかしのポストエフェクトの実装方法を紹介します。
ポストエフェクトの基礎については以下の記事で紹介していますので、必要に応じて参照してください。

light11.hatenadiary.com

考え方とメリット、デメリット

前提として、解像度を落とすと、画像はぼやけたように見えます。

pc-getter.com

解像度を落とした上で、レンダリングは元の大きさで行うことでぼかし効果を掛けるというのが今回の考え方です。

この方法のメリットは、シェーダを書かず、GPUに組み込まれている処理で完結しているため、処理速度が速いことです。
反対にデメリットは、ぼかされた結果がそこまで綺麗ではない点です。

ただモバイルでは特に処理負荷は大切なので、知っておく方がいいと思います。

スクリプトを書く

早速スクリプトを書いてみます。

using UnityEngine;

[ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class Blur : MonoBehaviour {

    [SerializeField]
    private int _intensity = 1;

    private void OnRenderImage(RenderTexture source, RenderTexture dest){
        var renderTexture = RenderTexture.GetTemporary(source.width / _intensity, source.height / _intensity, 0, source.format);
        Graphics.Blit(source, renderTexture);
        Graphics.Blit(renderTexture, dest);
        RenderTexture.ReleaseTemporary(renderTexture);
    }
}

ポストエフェクトの基本的な部分は次の記事を参照してください。 light11.hatenadiary.com

OnRenderImage() の中では、まず RenderTexture.GetTemporary() でRenderTextureを取得しています。
RenderTextureはこのメソッドを使うことにより、Unity側で作成からキャッシュ、破棄を最適化してもらえます。

GetTemporary() の第一、第二引数には縮小したいサイズを与えます。
第三引数はデプスバッファへの書き込みは今回不要なので0、第四引数はsourceのフォーマットを与えておけばOKです。

その下の二行で

  1. sourceテクスチャをサイズの小さいrenderTextureにレンダリング
  2. サイズの小さいrenderTextureをdestテクスチャにレンダリング

しています。

最後に RenderTexture.ReleaseTemporary() で一時テクスチャを解放しています。

このスクリプトをカメラにアタッチすると、

f:id:halya_11:20180205005220p:plain:w300

この画像が

f:id:halya_11:20180205005238p:plain:w300

こうなります(intensity = 3)。

段階的なサンプリング

このままだと、intensityが小さいうちはまだいいのですが、
intensityを大きくすると失われるピクセルが多く、結果も荒くなってしまいます。

f:id:halya_11:20180205005801p:plain:w300
intensity = 10にすると柵のあたりなどかなり粗い

これを防ぐためには、解像度を落とす処理と解像度を上げる処理を段階的に行います。
解像度を下げる際、一度に半分以下にしなければ失われる色はないはずです。

下記がこの点を修正したスクリプトです。

using UnityEngine;

[ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class Blur : MonoBehaviour {

    [SerializeField, Range(0, 30)]
    private int _iteration = 1;

    private RenderTexture[] _renderTextures = new RenderTexture[30];

    private void OnRenderImage(RenderTexture source, RenderTexture dest){

        var width = source.width;
        var height = source.height;
        var currentSource = source;

        var i = 0;
        RenderTexture currentDest = null;
        // 段階的にダウンサンプリング
        for (; i < _iteration; i++) {
            width /= 2;
            height /= 2;
            if (width < 2 || height < 2) {
                break;
            }
            currentDest = _renderTextures[i] = RenderTexture.GetTemporary(width, height, 0, source.format);
            Graphics.Blit(currentSource, currentDest);
            currentSource = currentDest;
        }

        // アップサンプリング
        for (i -= 2; i >= 0; i--) {
            currentDest = _renderTextures[i];
            Graphics.Blit(currentSource, currentDest);
            _renderTextures[i] = null;
            RenderTexture.ReleaseTemporary(currentSource);
            currentSource = currentDest;
        }

        // 最後にdestにBlit
        Graphics.Blit(currentSource, dest);
        RenderTexture.ReleaseTemporary(currentSource);
    }
}

多少綺麗にぼかされるようになりました。

f:id:halya_11:20180207004612p:plain:w300

関連

light11.hatenadiary.com