-->


Shader "UI/Eclipse3Pass"
{
Properties
{
[HideInInspector] _MainTex("-", 2D) = "white"{} //uGUI及びGraphics.Blitが標準で扱うテクスチャ
[HideInInspector] _CustomTex("-", 2D) = "white"{}
[HideInInspector] _Transition ("Transition", Range(0.0, 1.0)) = 0.0
[HideInInspector] _BlurColor("Blur Color", Color) = (1.0, 1.0, 1.0, 1.0)
[HideInInspector] _BlurCount("Blur Count", float) = 5
[HideInInspector] _BlurDistance("Blur Distance", float) = 3
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Transparent"
}
LOD 100
ZWrite Off
HLSLINCLUDE
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
//uGUI Image > Source Imageにアタッチされた画像を参照できるようにする
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
//最終的に画面に出力されるテクスチャを宣言、中身のデータはC#側から渡される
TEXTURE2D(_CustomTex);
SAMPLER(sampler_CustomTex);
//C#から渡された_CustomTexのサイズを参照できるようにする
float4 _CustomTex_TexelSize;
//SRP Batcherを適用、動的に変更される数値はこの中に定義
CBUFFER_START(UnityPerMaterial)
half _Transition;
half4 _BlurColor;
int _BlurCount;
half _BlurDistance;
CBUFFER_END
static const half MaxTransition = 1;
static const half HalfTransition = MaxTransition / 2;
static const half MaxBlurPower = 10;
struct Attributes
{
half2 uv : TEXCOORD0;
float4 positionOS : POSITION;
half4 color : COLOR;
};
struct Varyings
{
half2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
half4 color : COLOR;
};
Varyings vert (Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
output.color = input.color;
return output;
}
//第二引数で指定した方向に画像をぼかす
half4 executeBlur(half2 uv, half2 direction)
{
//_BlurDistanceに関しては_Transitionの値も影響させる
//_Transitionの値が大きいほど、Blurの距離もどんどん伸びる
half clampValue = clamp(_Transition, 0, MaxTransition) / MaxTransition;
half fixBlurDistance = _BlurDistance * clampValue;
//アルファ以外の値は_BlurColorを参照させる
half4 color = half4(_BlurColor.r, _BlurColor.g, _BlurColor.b, 0);
//上下左右、隣接するピクセル色のalphaを合算する
for (int shift = -_BlurCount; shift <= _BlurCount; shift++)
{
half2 offset = direction * shift * fixBlurDistance * _CustomTex_TexelSize;
half4 samplerColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv + offset);
color.a += samplerColor.a;
}
//加算した回数分だけ割って平均alphaを出す
color.a /= (_BlurCount * 2 + 1);
//画像の際部分を強く光らせる(画像の際から離れるほどalphaを急激に下げる)
color.a = color.a * color.a * 2;
return color;
}
ENDHLSL
//Pass0: 一番上のPassはuGUIのCanvasRendererから自動的に呼び出される、処理的には下のPass1,2より後に実行される
Pass
{
//一番最後は背景とブレンドして透過処理を行う
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
//オリジナルの画像を上に被せた上で、全てのピクセル色の輝度を上げる
half4 frag (Varyings input) : SV_Target
{
//CanvasRendererから呼ばれた際は、_MainTexにはPass1, 2実行前の純粋な元の画像が代入されている
//この_MainTexから指定したUV座標のピクセル色を取得
half4 original = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
//Image ComponentのColorを反映
original *= input.color;
//Pass1,2でBlur処理を通した画像から、指定したUV座標のピクセル色を取得
half4 blurred = SAMPLE_TEXTURE2D(_CustomTex, sampler_CustomTex, input.uv);
//originalの方の色相を反転
half reversalR = abs(original.r - _Transition);
half reversalG = abs(original.g - _Transition);
half reversalB = abs(original.b - _Transition);
//最後に到達する色を黒にするための値
half darkness = 1 - _Transition;
//黒変の変化を直線ではなくカーブにするための値を算出
half ease = darkness * darkness * darkness;
//色相の変化にカーブをかける(後半に急激に黒く変色する)
original.r = reversalR * ease;
original.g = reversalG * ease;
original.b = reversalB * ease;
//Blur画像の上に元の画像を重ねる
//例えば指定UV座標のalphaが1なら、完全に元の画像のピクセルを描画、0なら完全なBlur画像を描画
//0.5なら両者の中間のピクセル色が描画される
half4 combine = lerp(blurred, original, original.a);
return combine;
}
ENDHLSL
}
//Pass1: C#から明示的に呼び出す
Pass
{
//背景は考慮せず画像単体で描画処理
Blend One Zero
HLSLPROGRAM
//x軸にBlurをかける
half4 frag (Varyings input) : SV_Target
{
return executeBlur(input.uv, half2(1, 0));
}
ENDHLSL
}
//Pass2: C#から明示的に呼び出す
Pass
{
//背景は考慮せず画像単体で描画処理
Blend One Zero
HLSLPROGRAM
//y軸にBlurをかける
half4 frag (Varyings input) : SV_Target
{
return executeBlur(input.uv, half2(0, 1));
}
ENDHLSL
}
}
}

using System;
using UnityEngine;
using UnityEngine.UI;
[ExecuteAlways]
/// <summary>
/// RenderTextureをシェーダーに流し込んで日食演出を行う
/// </summary>
public class Eclipse3PassController : MonoBehaviour
{
[Serializable]
/// <summary>
/// シェーダーに渡すパラメータはこちらで管理
/// </summary>
private class Parameters
{
[SerializeField, Range(0, 1)] private float _transition = 0;
public float Transition => _transition;
[SerializeField] private Color _blurColor = Color.white;
public Color BlurColor => _blurColor;
[SerializeField, Range(0, 10)] private int _blurCount = 5;
public int BlurCount => _blurCount;
[SerializeField, Range(0, 10)] private float _blurDistance = 3;
public float BlurDistance => _blurDistance;
// 変更があったときだけ更新するためのキャッシュ
private float _lastLuminance;
private Color _lastBlurColor;
private int _lastBlurCount;
private float _lastBlurDistance;
/// <summary>
/// インスペクターのパラメータに変化があった場合trueを返す
/// </summary>
public bool CahngedParameter => !Mathf.Approximately(_transition, _lastLuminance) ||
_blurColor != _lastBlurColor || _blurCount != _lastBlurCount ||
!Mathf.Approximately(_blurDistance, _lastBlurDistance);
/// <summary>
/// キャッシュに最新のパラメーター値を反映させる
/// </summary>
public void UpdateParameter()
{
_lastLuminance = _transition;
_lastBlurColor = _blurColor;
_lastBlurCount = _blurCount;
_lastBlurDistance = _blurDistance;
}
}
[SerializeField] private Image _targetImage;
[SerializeField] private Shader _targetShader;
[SerializeField] private Parameters _parameters;
private Texture2D _sourceTexture;
private Material _createdMaterial;
private RenderTexture _tmpRenderTexture1;
private RenderTexture _tmpRenderTexture2;
/// <summary>
/// 初期化処理の必要性がある場合trueを返す
/// </summary>
private bool NeedsInitialization => _sourceTexture == null || _sourceTexture != _targetImage.sprite.texture ||
_createdMaterial == null || _targetImage.material != _createdMaterial ||
_tmpRenderTexture1 == null || _tmpRenderTexture2 == null;
/// <summary>
/// 初期化処理
/// </summary>
private void Init()
{
Release();
_sourceTexture = _targetImage.sprite.texture;
_createdMaterial = new (_targetShader);
_createdMaterial.hideFlags = HideFlags.DontSave;
_targetImage.material = _createdMaterial;
//処理負荷軽減のため、大元の画像サイズから1/2のサイズでtmpRenderTexture1, 2を作成
//tmpRenderTexture1, 2は両方ともぼかし処理のための画像なので、サイズが小さくても見た目に違和感は出ない
_tmpRenderTexture1 = new RenderTexture(_sourceTexture.width / 2, _sourceTexture.height /2, 0);
_tmpRenderTexture2 = new RenderTexture(_sourceTexture.width / 2, _sourceTexture.height / 2, 0);
//事前にBlitで更新される予定のtmpRenderTexture2を、_CustomTexの名前でシェーダー側と紐づけておく
//後はCanvasRender内の処理で自動的にPass0を実行させて、その_CustomTexを画像に反映させる
_targetImage.material.SetTexture("_CustomTex", _tmpRenderTexture2);
//初期化後は問答無用でUpdateTextureを呼ぶ
UpdateTexture();
}
/// <summary>
/// 最初にUnityが1回だけ呼ぶ処理
/// </summary>
private void Start()
{
if (NeedsInitialization)
{
Init();
}
}
/// <summary>
/// Unityから毎フレーム呼ばれる処理
/// </summary>
private void Update()
{
if (_targetImage == null || _targetImage.sprite == null || _targetShader == null)
{
return;
}
if (NeedsInitialization)
{
Init();
}
else if(_parameters.CahngedParameter)
{
UpdateTexture();
}
}
/// <summary>
/// 対象のシェーダーのPass1, 2を実行させる
/// </summary>
private void UpdateTexture()
{
_parameters.UpdateParameter();
//輝度をセット
_createdMaterial.SetFloat("_Transition", _parameters.Transition);
//Blur設定をセット
_createdMaterial.SetColor("_BlurColor", _parameters.BlurColor);
_createdMaterial.SetInt("_BlurCount", _parameters.BlurCount);
_createdMaterial.SetFloat("_BlurDistance", _parameters.BlurDistance);
//第1引数の画像を元に、Blitでシェーダー内のPass1を実行させ、結果を第2引数に書き込む
//Blitの仕様上、この書き込み処理は、第3引数のMaterial内の_MainTexを通して行われる
Graphics.Blit(_sourceTexture, _tmpRenderTexture1, _createdMaterial, 1);
//Pass1の結果を元にPass2を実行
Graphics.Blit(_tmpRenderTexture1, _tmpRenderTexture2, _createdMaterial, 2);
}
/// <summary>
/// 作成したRenderTextureやMaterialのメモリを解放する
/// </summary>
private void Release()
{
if (_tmpRenderTexture1 != null)
{
_tmpRenderTexture1.Release();
_tmpRenderTexture2.Release();
}
if (_createdMaterial != null)
{
DestroyImmediate(_createdMaterial);
}
}
/// <summary>
/// このGameObjectを非表示にした時の処理
/// </summary>
private void OnDisable()
{
Release();
}
}
