ImageEffectとは
Unity でいう ImageEffect とは、一般的に(?)ポストエフェクトと呼ばれる3Dレンダリング後の画面全体にかかる画像処理のことを指します。
Unity の標準の ImageEffect は Assets->Import Package->Effect からインポートできます。
ぼかしや残像等色々な ImageEffect がありますが、今回は勉強がてら1からラスタースクロールを作ってみます。
ラスタースクロールとは、昔のゲームでよくあった画面がうねうね波打つ演出のことです。
ちなみに私の好きなラスタースクロールは、サンダーフォースIIIのGorgonステージの背景です。
ImageEffect用スクリプトの基底クラス
ImageEffect は、カメラからのレンダリング結果を加工するため、Camera コンポーネントがついた GameObject にImageEffect用のスクリプトを追加する必要があります。
Camera がレンダリングした後、Camera がついた GameObject にある全てのコンポーネントの OnRenderImage が呼ばれます。
その中で Graphics.Blit(source, destination, material) を呼んで画像を加工します。
まずは基底クラスを作ってそこから派生させていきます。
using UnityEngine;
[RequireComponent(typeof(Camera))] // カメラのレンダリング結果を使うのでカメラコンポーネント必須
public abstract class ImageEffectBase : MonoBehaviour {
#region Fields
private Material material; // シェーダーでゴニョゴニョするためのMaterial
#endregion
#region Properties
public abstract string ShaderName { get; } // シェーダーの名前
protected Material Material { get { return material; } }
#endregion
#region Messages
protected virtual void Awake()
{
Shader shader = Shader.Find(ShaderName); // シェーダー取得
material = new Material(shader); // シェーダーを割り当てたMaterial作成
}
// Source:カメラからのレンダリング結果 destination:シェーダーでゴニョゴニョした結果
protected virtual void OnRenderImage(RenderTexture source, RenderTexture destination)
{
UpdateMaterial();
Graphics.Blit(source, destination, material); // ゴニョゴニョする
}
#endregion
#region Methods
protected abstract void UpdateMaterial(); // シェーダーに渡すパラメータ等の処理
#endregion
}
ラスタースクロール用スクリプト
前述の ImageEffectBase を継承してラスタースクロールをする ImageEffect スクリプトを作成します。
frequency(周波数), power(ラスタースクロールの強さ), speed(スクロールの速さ)の3つのパラメータが増えました。
これらをラスタースクロール用シェーダーに渡します。
using UnityEngine;
[ExecuteInEditMode]
public class ImageEffectRasterScroll : ImageEffectBase {
#region Fields
[SerializeField]
[Range(0, 100)]
private float frequency; // 周波数
[SerializeField]
[Range(0, 1)]
private float power; // ラスタースクロールの強さ
[SerializeField]
[Range(0, 100)]
private float speed; // スクロールの速さ
// シェーダープロパティID
private int propertyIDFreq;
private int propertyIDPower;
private int propertyIDSpeed;
#endregion
#region Properties
public override string ShaderName
{
get
{
return "Custom/RasterScroll";
}
}
#endregion
#region Methods
protected override void Awake()
{
base.Awake();
// シェーダープロパティID取得
propertyIDFreq = Shader.PropertyToID("_Freq");
propertyIDPower = Shader.PropertyToID("_Power");
propertyIDSpeed = Shader.PropertyToID("_Speed");
}
protected override void UpdateMaterial()
{
// シェーダーにInspectorで設定したパラメータを渡す
Material.SetFloat(propertyIDFreq, frequency);
Material.SetFloat(propertyIDPower, power);
Material.SetFloat(propertyIDSpeed, speed);
}
#endregion
}
ラスタースクロール用シェーダー
ラスタースクロールを実現するには、
UV座標の横方向だけ画面のY座標に合わせて一定周期でずらしていくようにします。
UVのY座標(V?)はそのままです。
そうすることで波打ってるように見えます。
重要な点は2点あり、
一つは vert 関数内の ComputeScreenPos() です。
引数で渡された座標を画面上の座標に変換してくれます。
もう一つは、frag 関数内の uv.x + sin(i.spos.y * _Freq + _Time.y * _Speed) です。
UV座標の横方向に、スクリプトから渡されたパラメータと ComputeScreenPos で取得した画面上のY座標をsin関数でうねうねさせた値を足します。
上記の式をfmod()とabs()で囲っているのは、画面端の方のUV座標がはみ出た時に反対側のUV座標を持ってくる為です。
Shader "Custom/RasterScroll" {
Properties {
_MainTex ("Source", 2D) = "white" {}
_Freq("Frequency", Float) = 0
_Power("Power", Float) = 0
_Speed("Speed", Float) = 0
}
SubShader{
ZTest Always
Cull Off
ZWrite Off
Fog { Mode Off}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma target 3.0
#include "UnityCG.cginc"
struct v2f {
fixed4 pos : SV_POSITION;
fixed2 uv : TEXCOORD0;
fixed2 spos : TEXCOORD1;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
o.spos = ComputeScreenPos(o.pos); // モデルビュープロジェクション変換された座標を画面上に変換する
return o;
}
sampler2D _MainTex;
fixed _Freq;
fixed _Power;
fixed _Speed;
fixed4 frag(v2f i) : SV_TARGET{
fixed2 uv = i.uv;
uv.x = fmod(abs(uv.x + sin(i.spos.y * _Freq + _Time.y * _Speed) * _Power), 1); // uvの横方向だけ画面のY座標に合わせて一定周期でずらしていく
return tex2D(_MainTex, uv);
}
ENDCG
}
}
FallBack Off
}
実行結果
frequency を大きくするとうねうねの縦の間隔が狭くなっていきます。
power を大きくすると左右の揺れ幅が大きくなります。
speed を大きくするとうねうねが流れる速度が速くなります。