#Unityでブラー画像動的生成
という、ちょっと釣りっぽい題名にしましたが。
元を正すと今やってるお仕事で、すでにある画像に対してブラーがかかった画像を動的に生成しなきゃいけない処理があって。
とりあえず、シェーダーで書いてみたはいいものの、コードがほぼほぼピクセルシェーダーで近傍ピクセルを見るとか負荷大丈夫? って思ったのと、
これって途中でアニメーションするわけでもなし、初回でブラー画像を生成しちゃえばあとはそれを普通にテクスチャとして使うだけなんじゃ?でもそれってUnityでできるの? という素朴な疑問からクリスマスイブイブなのにボチボチと調査開始。
#結論から言うと
すごく簡単にできた。(ただし、メモリ使用量や処理速度を考慮しなければ)
ソースコード的には http://d.hatena.ne.jp/colorcle/20090628/1246208145 のガウシアンフィルをほぼ丸パクリ。
必要なのがそのもののブラー画像ではなく単色だったので、アルファ値だけを取得するようにすこし修正。
さらに、何気にキャッシュしてすでにブラー画像作成済みの場合はキャッシュしたテクスチャを返すようにしてみたり。
Releaseメソッド用意してみたけど、これでちゃんと開放されるかどうかは不明(Dictionaryをclearしてるだけ)
using System.Linq;
using UnityEngine;
using System.Collections.Generic;
public static class BuildTexture
{
private static Dictionary<Texture2D, Texture2D> _textureCache = new Dictionary<Texture2D, Texture2D>();
public static Texture2D CreateBlurTexture(Texture2D tex, float sig,bool isCache = true)
{
if (isCache && _textureCache.ContainsKey(tex)) return _textureCache[tex];
int W = tex.width;
int H = tex.height;
int Wm = (int)(Mathf.Ceil(3.0f * sig) * 2 + 1);
int Rm = (Wm - 1) / 2;
//フィルタ
float[] msk = new float[Wm];
sig = 2 * sig * sig;
float div = Mathf.Sqrt(sig * Mathf.PI);
//フィルタの作成
for (int x = 0; x < Wm; x++) {
int p = (x - Rm) * (x - Rm);
msk[x] = Mathf.Exp(-p / sig) / div;
}
var src = tex.GetPixels(0).Select(x => x.a).ToArray();
var tmp = new float[src.Length];
var dst = new Color[src.Length];
//垂直方向
for (int x = 0; x < W; x++) {
for (int y = 0; y < H; y++) {
float sum = 0;
for (int i = 0; i < Wm; i++) {
int p = y + i - Rm;
if (p < 0 || p >= H) continue;
sum += msk[i] * src[x + p * W];
}
tmp[x + y * W] = sum;
}
}
//水平方向
for (int x = 0; x < W; x++) {
for (int y = 0; y < H; y++) {
float sum = 0;
for (int i = 0; i < Wm; i++) {
int p = x + i - Rm;
if (p < 0 || p >= W) continue;
sum += msk[i] * tmp[p + y * W];
}
dst[x + y * W] = new Color(1, 1, 1, sum);
}
}
var createTexture = new Texture2D(W, H);
createTexture.SetPixels(dst);
createTexture.Apply();
if (isCache)
{
_textureCache.Add(tex, createTexture);
}
return createTexture;
}
public static void Release()
{
_textureCache.Clear();
}
}
#事前準備として
・元のテクスチャのImportSettingのRead/Write Enable が有効であること
があります。 じゃないと、テクスチャのピクセル情報が読めないので。
#既知の問題点
Unity2DのSpriteにも適用したかったのだけれど、Spriteの元画像のTextureをブラー化することができても、Spriteにそれを適用する手段がわからない。
しょうがないので、Sprite.Createってのを使ってSpriteそのものを生成することに。
var spr = GetComponent<SpriteRenderer>();
var tex = BuildTexture.CreateBlurTexture(spr.sprite.texture, 2.0f);
spr.sprite = Sprite.Create(tex, spr.sprite.rect,new Vector2(0.5f,0.5f),16.0f);
とりあえず、これで今使ってるSpriteをブラー画像に差し替えれました。
けど、Sprite.Create に渡すPixelsPerUnitの値が何処から取得すればいいのか不明。
合わせないとサイズがずれてしまうのでどうしたものか。今回は16とわかっていたので16を設定しましたが・・・。
こんな感じの事ができるようになります(そのまま後ろに置いたり、色変えてみたり、OrderInLayerであえて上に重ねてみたり)
テクスチャ自体は別物なのでDrawCall削減とかにはまったくなりませんが、毎フレームGPUでシェーダー処理するより有効な場合もあるのでは・・・?
・・・無いか・・・。
このコンテンツは、『ユニティちゃんライセンス』で提供されています