-->

【Unity Shader】UIカーテンウェーブシェーダー

【Unity Shader】UIカーテンウェーブシェーダー
  ※コードが古くなっていたのでURP用にコードを改修しました。(2026/08/25)  

以前に業務でカーテンの歪みをパッと実装したものの、クオリティに満足いってなかったので更に研究をしていました。
結局求める内容には届きませんでしたが、供養としてここで公開します。

カーテンウェーブシェーダー概要

下がカーテンシェーダーのサンプル動画です。
窓が途中で光ってる件に関しては、これは演出上 発光シェーダーを入れてみただけで今回のカーテンシェーダーは関係ないです。

カーテンウェーブシェーダーのコード全文

以下コード全文です。uGUI Imageへの反映方法は こちらの記事参照してください。
Copy Code


Shader "UI/CurtainWaveShader"
{
    Properties
    {
        [HideInInspector]_MainTex("-",2D)="white"{}
        _PressPow ("PressPow", Range(-1, 1)) = 0 //カーテンを横に押す力
        _CurvePow ("CurvePow", Range(0, 10)) = 2//カーテンのカーブの強さ
        _ReverseCurveRatio ("ReverseCurveRatio", Range(0, 10)) = 2 //カーブの外カーブと内カーブの比率に影響
    }

    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "Queue" = "Transparent"
        }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off
        ZWrite Off

        Pass
        {
            HLSLPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

                struct Attributes
                {
                    half2 uv : TEXCOORD0;
                    float4 positionOS : POSITION;
                    half4 color : COLOR;
                };

                struct Varyings
                {
                    half2 uv : TEXCOORD0;
                    float4 positionCS : SV_POSITION;
                    half4 color : COLOR;
                };

                //uGUI Image > Source Imageにアタッチされた画像を参照する
                TEXTURE2D(_MainTex);
                SAMPLER(sampler_MainTex);

                //SRP Batcherを適用
                CBUFFER_START(UnityPerMaterial)
                    //プロパティに対応する変数の宣言
                    float _PressPow;
                    float _CurvePow;
                    float _ReverseCurveRatio;
                CBUFFER_END

                //渡されたy軸座標が、全体として縦軸の何%の割合の場所に存在するか
                half partRange(Varyings i)
                {
                    return 1 - i.uv.y;
                }

                //外カーブ率を百パーセントとした場合の、x軸のピクセルの移動値を返す
                half outCurveShift(Varyings i)
                {
                    half y = partRange(i);
                    half slidePos = y * y * pow(y, _CurvePow) * _PressPow;
                    return slidePos;
                }

                //内カーブ率を百パーセントとした場合の、x軸のピクセルの移動値を返す
                half inCurveShift(Varyings i)
                {
                    half y = partRange(i);
                    half slidePos = y * (y - 2)  * pow((y - 2) * (y - 2), _CurvePow) * _PressPow;
                    return -slidePos;
                }

                //内カーブの比率
                half inRatio(Varyings i)
                {
                    half y = partRange(i);
                    half inRatio = y * pow(y, _ReverseCurveRatio);
                    return inRatio;
                }

                //外カーブのピクセル移動値を比率を適用して取得
                half outCurveShiftWithRatio(Varyings i)
                {
                    return outCurveShift(i) * (1 - inRatio(i));
                }

                //内カーブのピクセル移動値を比率を適用して取得
                half inCurveShiftWithRatio(Varyings i)
                {
                    return inCurveShift(i) * inRatio(i);
                }

                //頂点シェーダー
                Varyings vert (Attributes input)
                {
                    Varyings output;
                    output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                    output.uv = input.uv;
                    output.color = input.color;
                    return output;
                }

                //ピクセルシェーダー
                half4 frag (Varyings input) : SV_Target
                {
                    //外カーブと内カーブのピクセル移動値を合成
                    float curveX = input.uv.x + outCurveShiftWithRatio(input) + inCurveShiftWithRatio(input);
                    half2 fixUv = half2(curveX , input.uv.y);

                    //_PressPowに応じてカーテンのy軸のスケールを少し小さくする
                    half scaler1 = (1 + abs(_PressPow / 1));
                    //y軸のスケールを小さくしたのに応じて、uv座標を少し上にあげる
                    half adjust1 = (1 - scaler1) * 0.9;
                    fixUv = half2(fixUv.x, fixUv.y * scaler1 + adjust1);

                    half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, fixUv);
                    col *= input.color;
                    return col;
                }
            ENDHLSL
        }
    }
}

プロパティを解説

インスペクターでプロパティの値を操作し、効果を試すことが出来ます。プロパティは3つしかないです。
 PressPow 
: カーテンを横に押す力
 CurvePow 
: カーテンのカーブの強さ
 ReverseCurveRatio 
: カーブの外カーブと内カーブの比率に影響

コードを少し解説

基本的にやってることは、
 Varyings i 
に渡された各ピクセルのy軸座標の大きさに応じて、x軸の座標を横にずらすという処理をしています。

uv座標のy軸は、下から上に向けて0から1に進むので、
 partRange 
関数内で1から引かせています。
こうすることで、カーテンが下にいくほどx軸のズレが大きくなっています。
Copy Code

                //渡されたy軸座標が、全体として縦軸の何%の割合の場所に存在するか
                half partRange(Varyings i)
                {
                    return 1 - i.uv.y;
                }

y軸座標からx軸の移動値を算出する際に、対象のy軸座標に極端なEaseをかけて値を歪め、x軸のピクセル移動のカーブ処理につなげています。
カーテンの上半分を占める外カーブは、OutQuadの式の一部であるt * tを参考にしました。
ただし、t(時間)の値をyに置き換えています。
Copy Code

                //外カーブ率を百パーセントとした場合の、x軸のピクセルの移動値を返す
                half outCurveShift(Varyings i)
                {
                    half y = partRange(i);
                    half slidePos = y * y * pow(y, _CurvePow) * _PressPow;
                    return slidePos;
                }
powのべき乗によって、
 _CurvePow 
の値に応じて更に強いEaseをかけています。

カーテンの下半分を占める内カーブは、InQuadの式の一部であるt * (t - 2)を参考にしました。こちらもtをyに置き換えています。
Copy Code

                //内カーブ率を百パーセントとした場合の、x軸のピクセルの移動値を返す
                half inCurveShift(Varyings i)
                {
                    half y = partRange(i);
                    half slidePos = y * (y - 2)  * pow((y - 2) * (y - 2), _CurvePow) * _PressPow;
                    return -slidePos;
                }
0
0