これは AfterEffects Advent Calendar 2017 2日目の記事です。
ネオンのような発光(グロー)エフェクトを掛ける場合は、ブラー(ガウス)を何枚か重ねて加算合成すると見栄えが良くなる場合があります。今日はこの現象について数値的に見てみたいと思います。
ガウス関数とは?
ガウス関数というのは、 $$ a \exp \{-(x-b)^2/(2c^2)\} $$ の形で表される関数で、特に正規化したものを正規分布と呼びます。
このガウス関数を画像に畳み込みしたものがガウスぼかしと呼ばれ、様々な画像処理ソフトで実装されています。
ブラー(ガウス)で発光
さて、話が変わるのですが、画像をグロー(発光)させたい場合、次のように、ぼかし量の複数のレイヤーを合成すると良い感じになります。(animation png対応のブラウザでご覧下さい)
今日はこの現象についてより深く見ていきたいと思います。
単純な画像で実験
上のような画像を用意して、加算合成ありとなしでどのように画像の輝度値が変化するか見てみましょう。
・・・ちょっとギザギザしていますね。ディザリングを掛けて滑らかにしてみましょう。
こんな感じに。するとどうでしょうか?
このように、単一のブラー(ガウス)では発光の裾が短かったもの(赤色ほか)を、複数のぼかしを重ねることによって、なだらかで広い裾を持った関数(水色)に変えることができていた、ということがわかりました。やっためう! すごいめう!
・・・ところで、この輝度値のグラフを見る限りでは、AEのブラー(ガウス)は、あまりガウス関数には近くないようです。もしかして、他のブラー系エフェクトを使ったほうが良いのでしょうか?
まとめ
発光の裾は広い方が良い。
CSV出力のソースコード
using System.Drawing; using System.Drawing.Imaging; using System.Text; namespace GlowFunctionAnalysis { class Program { static void Main(string[] args) { // 画像の輝度値をcsvにして出力する StringBuilder s = new StringBuilder(); string[] legend = new[] { "ブラーなし", "20px 40%", "50px 30%", "125px 60%", "312.5px 80%", "合成" }; for (int file_id = 0; file_id <= 5; file_id++) { s.Append(legend[file_id] + ","); var x = ReadImage(@"D:\path_to_png\frame_" + file_id.ToString("00000") + ".png"); for (int i = 1920 * 3 / 8; i < 1920 * 5 / 8; i++) // x軸 { int summation_count = 0; double total = 0; for (int j = x.GetLength(1) * 2 / 4; j < x.GetLength(1) * 3 / 4; j++) // y軸 { if (true) { // ディザリングあり(y方向に輝度値の平均を取る) total += x[i, j].R; total += x[i, j].G; total += x[i, j].B; summation_count += 3; } else { // ディザリングなし total += x[i, j].R; summation_count += 1; break; } } s.Append((total / summation_count) + ","); } s.Append("\n"); } System.IO.File.WriteAllText("glow_function_analysis.csv", s.ToString()); } // 画像ファイルから画像を読み込み、Color[,]型配列に格納して返す。 // https://qiita.com/Toshi332/items/2749690489730f32e63d static Color[,] ReadImage(string inputPath) { Color[,] pixelData; int width, height; ImageFormat format; using (Bitmap img = new Bitmap(Image.FromFile(inputPath))) { width = img.Width; height = img.Height; format = img.RawFormat; pixelData = new Color[img.Width, img.Height]; for (int y = 0; y < img.Height; y++) { for (int x = 0; x < img.Width; x++) { pixelData[x, y] = img.GetPixel(x, y); } } } return pixelData; } } }