光芒とかスターと呼ばれるポストエフェクトを実装してみます。
はじめに
この記事では光芒のポストエフェクトの実装方法を紹介します。
ポストエフェクトの基礎については以下の記事で紹介していますので、必要に応じて参照してください。
実装方法
基本的な流れはブルームと似ています。
ブルームのぼかし方を変える(四方向にぼかす)と光芒になります。
ぼかし方については下記のスライドを参考にしました。
http://www.daionet.gr.jp/~masa/archives/GDC2003_DSTEAL.ppt
実装
まずスクリプトは次のようになります。
using UnityEngine; [ExecuteInEditMode] public class Star : MonoBehaviour { [SerializeField] private Material _material; [SerializeField, Range(0.0f, 1.0f)] private float _threshold = 0.5f; [SerializeField, Range(0.5f, 0.95f)] private float _attenuation = 0.9f; [SerializeField, Range(0.0f, 10.0f)] private float _intensity = 1.0f; private void OnRenderImage(RenderTexture source, RenderTexture dest) { if (_material == null){ Graphics.Blit(source, dest); return; } var paramsId = Shader.PropertyToID("_Params"); _material.SetFloat("_Threshold", _threshold); _material.SetFloat("_Attenuation", _attenuation); _material.SetFloat("_Intensity", _intensity); var tempRT1 = RenderTexture.GetTemporary(source.width, source.height); var tempRT2 = RenderTexture.GetTemporary(source.width, source.height); // SourceをDestにコピーしておく Graphics.Blit(source, dest); // 4方向にスターを作るループ for (int i = 0; i < 4; i++) { // まず明度が高い部分を抽出する Graphics.Blit(source, tempRT1, _material, 0); var currentSrc = tempRT1; var currentTarget = tempRT2; var parameters = Vector3.zero; // x, yにUV座標のオフセットを代入する // (-1, -1), (-1, 1), (1, -1), (1, 1) parameters.x = i == 0 || i == 1 ? -1 : 1; parameters.y = i == 0 || i == 2 ? -1 : 1; // 1方向にぼかしを伸ばしていくループ for (int j = 0; j < 4; j++) { // zに描画回数のindexを代入してマテリアルにセット parameters.z = j; _material.SetVector(paramsId, parameters); // 二つのRenderTextureに交互にBlitして効果を足していく Graphics.Blit(currentSrc, currentTarget, _material, 1); var tmp = currentSrc; currentSrc = currentTarget; currentTarget = tmp; } // Destに加算合成する Graphics.Blit(currentSrc, dest, _material, 2); } // RenderTextureを開放する RenderTexture.ReleaseTemporary(tempRT1); RenderTexture.ReleaseTemporary(tempRT2); } }
※最適化されてません
次にシェーダは次のようにします。
Shader "Star" { Properties{ _MainTex ("Texture", 2D) = "white" {} } SubShader { Cull Off ZTest Always ZWrite Off Tags { "RenderType"="Opaque" } // 0: 明度を抽出するパス Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float _Threshold; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { half4 col = tex2D(_MainTex, i.uv); half brightness = max(col.r, max(col.g, col.b)); half contribution = max(0, brightness - _Threshold); contribution /= max(brightness, 0.00001); return col * contribution; } ENDCG } //1: スターを作るパス Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; half2 uvOffset : TEXCOORD1; half pathFactor : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; half4 _MainTex_TexelSize; // x: offsetU, y: offsetY, z: pathIndex int3 _Params; float _Attenuation; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.pathFactor = pow(4, _Params.z); o.uvOffset = half2(_Params.x, _Params.y) * _MainTex_TexelSize.xy * o.pathFactor; return o; } fixed4 frag (v2f i) : SV_Target { half4 col = half4(0, 0, 0, 1); half2 uv = i.uv; for (int j = 0; j < 4; j++) { col.rgb += tex2D(_MainTex, uv) * pow(_Attenuation, j * i.pathFactor); uv += i.uvOffset; } return col; } ENDCG } // 2: 加算合成パス Pass { Blend One One ColorMask RGB CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float _Intensity; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { return tex2D(_MainTex, i.uv) * _Intensity; } ENDCG } } }
「スターを作るパス」としているのが、ぼかしている部分です。
前節のスライドの式と見比べてみてください。
結果
結果はこのようになります。
ポストエフェクト - 光芒 pic.twitter.com/OMLKER9Ghr
— Haruma:K (@harumak_11) 2018年5月20日
縮小バッファで処理する
なかなか処理負荷が高そうなので、
縮小バッファを使った場合の見え方を検証してみます。
レンダーテクスチャのサイズを縦横それぞれ1/2にします。
var tempRT1 = RenderTexture.GetTemporary(source.width / 2, source.height / 2); var tempRT2 = RenderTexture.GetTemporary(source.width / 2, source.height / 2);
レンダリング結果は次のようになりました。
(前節の結果からパラメータは調整してます)
全然いけそうです。 縦横4分の1にしてもいけるかも?