-->

Unity UI集中線シェーダー

Unity UI集中線シェーダー

[Unity URP] UI集中線シェーダー。(Radial Line Shader)
漫画のような集中線の作り方。

関連ページ 参考URL
  ※コードが古くなっていたのでURP用にコードを改修しました。(2026/04/30)  


サークル画面遷移シェーダー の経験を経て、本丸の集中線シェーダーの作成に取り掛かりました。
前回学習した通り、集中線はサークル状のグラデーションとノイズを組み合わせると比較的簡単に作れます。

当初の予定では、Unity Shader Graphの Simple Noise を丸パクリして実装するつもりでした。
しかしSimple Noiseを解析するうちに、今回はここまで複雑なモノを使う必要がないと思い、結果的に自前でコードを用意することになりました。

またこのシェーダーの知識を応用した FTLドライブシェーダー も作ったのでそちらもどうぞ。

集中線シェーダーの概要

画面端から中心に向けて、いくつかの線がランダムに伸びるシェーダー。漫画でよく見る表現です。
プロパティの設定次第で、この線はUnity再生中に自動的にアニメーションします。
下がそのサンプル動画です。


基本的には、UI Canvasの一番下に画面全体を覆うようにImageを用意し、そこにマテリアルをアタッチして使います。
集中線シェーダーのHierarchy

集中線シェーダー、コード全文

以下コード全文。uGUI Imageへの反映方法は こちらの記事 参照。
Shader "UI/RadialLine"
{
    Properties
    {
        [HideInInspector] _MainTex("-",2D)="white"{}
        [KeywordEnum(Rough, Sharp)] _Line("Line", Int) = 0
        _Color("Color", Color) = (0,0,0,1)

        [Space(10)]
        _NoiseScale("NoiseScale", Range(100, 1000)) = 500
        _PatternSeed("PatternSeed", Range(10, 100)) = 1  
        
        [Space(10)]
        _Edge1("Edge1", Range(0, 1)) = 0.5
        _Edge2("Edge2", Range(0, 1)) = 1

        [Space(10)]
        [Toggle] _IsAutoAnim("IsAutoAnim", float) = 0
        _AutoAnimSpeed("AutoAnimSpeed", Range(1, 20)) = 10
    }

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

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

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

                struct Varyings
                {
                    float4 positionCS : POSITION;                    
                    half2 uv : TEXCOORD0;
                };

                sampler2D _MainTex;

                
//SRP Batcherを適用、動的に変更される数値はこの中に定義

                CBUFFER_START(UnityPerMaterial)                
                    half4 _Color;

                    half _NoiseScale;
                    half _PatternSeed;

                    half _Edge1;
                    half _Edge2;

                    half _IsAutoAnim;
                    half _AutoAnimSpeed;
                CBUFFER_END                   

                
//radianの最大値、degreeで言うと360度のこと

                static const float PI2 = 3.14159 * 2;

                Varyings vert(Attributes input)
                {
                    Varyings output;
                    output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                    output.uv = input.uv;
                    return output;
                }
            
                
//あらゆる範囲の2次元変数を0~1の範囲の1次元変数に変換する

                half random(half2 uv)
                {
                    return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453);
                }

                
//引数に渡された対象座標の角度を0~1の範囲に圧縮して返す。

                
//反時計周りに徐々に値が大きくなる。

                half2 getUvAngle(half2 uv)
                {
                    
//uv座標を、画面中央を原点として(0,0)>(1,1)から(-1,-1)>(1,1)の範囲に修正する

                    half2 fixUv = uv * 2 - 1;
                    
//修正したuv座標の角度をradian値で取得

                    half angle = atan2(fixUv.y, fixUv.x);
                    
//0~PI*2の値を0~1の範囲に圧縮

                    return angle / PI2;
                }

                
//半時計周りにグラデーションする0~1の値を、バラバラの、かつある程度はまとまった集中線に変換する

                half getRadialLine(half angle)
                {
                    
//_NoiseScaleを大きくするほど、集中線が細かくなる。

                    
//これは_NoiseScaleで値を大きくすると、floorで切り捨てられる要素が少なくなり

                    
//angleの細かい値まで漏れなく後のrandom関数に反映されるため。

                    
//random関数は、どんな巨大な値も0~1の範囲に収めるため、与える値の大きさは問題ではなく

                    
//どこからどこまでのangle値を同値として扱うかが重要になる

                    half2 lineSeed = floor(half2(angle, angle) * _NoiseScale);
                    half lineVal = random(lineSeed); ;

                    
//sin関数はあらゆる値を規則的な-1~1の範囲の波形に変換する。

                    
//ここでは集中線の出現パターンを変える効果に使っている。

                    
//_IsAutoAnimがtrueの時は、Unity再生時に自動でアニメーションを実行し、

                    
//_IsAutoAnimがfalseの時は、_PatternSeedを操作することで集中線の見た目が変化する

                    lineVal = (_IsAutoAnim * sin(lineVal * _Time.w * _AutoAnimSpeed)) + 
                                ((1 - _IsAutoAnim) * sin(lineVal * _PatternSeed));
                    return lineVal;
                }

                
//引数のuv座標を参照し、中心から外側に向けて0から1に遷移していく値を返す

                half getCenterCircle(half2 uv)
                {
                    
//uv座標を、画面中央を原点として(0,0)>(1,1)から(-1,-1)>(1,1)の範囲に修正する

                    half2 fixUv = uv * 2 - 1;

                    #ifdef _LINE_ROUGH
                        
//ランダム関数を使ってちょっと線をザラザラにする

                        half2 i = random(uv) * 0.5 + 0.7;
                        return length(fixUv) * i;
                    #else
                        return length(fixUv);
                    #endif
                }

                half4 frag (Varyings input) : SV_Target
                {
                    half4 col = tex2D(_MainTex, input.uv);

                    
//以下の処理は全てalpha値を算出するための処理

                    half angle = getUvAngle(input.uv);
                    half lineVal = getRadialLine(angle);
                    half circle = getCenterCircle(input.uv);
                    
//中心まで届く集中線と、中心に近いほど0に近づく円を掛け算して、

                    
//中心付近は空になる集中線を作る

                    half resultLine = saturate(lineVal * circle);
                    
//smoothstepで濃い所はより濃く、薄い所はより薄くする

                    half smoothAlpha = smoothstep(_Edge1, _Edge2, resultLine);

                    col = _Color;
                    col.a = smoothAlpha;
                    return col;
                }
            ENDHLSL
        }
    }
}

プロパティの解説

今回用意してあるプロパティは8つで、結構多いです。
集中線シェーダーのプロパティ
 Line 
: Variant 。RoughとSharpが用意してあり、Roughはザラザラな線、Sharpはツルツルな線を表現する。
 Color 
: 集中線の色。
 NoiseScale 
: 集中線の細かさ。大きいほど細かくなる。
 PatternSeed 
: 集中線の出現パターン。マニュアルでアニメーションさせる時に使う。
 Edge1 
: 集中線をどこまで中央に伸ばすかの値。小さいほど中央まで伸びる。
 Edge2 
: 集中線の濃さ。Edge1の値に近づけるほど線がくっきり色濃く表示される。
 IsAutoAnim 
: 自動アニメーションのトグル。Onにすると、Unity再生中に自動アニメーションを実行する。
 AutoAnimSpeed 
: 自動アニメーションのスピード。大きいほど切り替わりが早くなる。

下は
 Rough 
 Sharp 
の比較画像。作った時はテンション上がったけどこうしてみると微妙な差かもしれないです。
LineをRoughにした時の集中線
LineをSharpにした時の集中線
下は
 Color 
 NoiseScale 
 Edge1/Edge2 
を弄った際のサンプル動画。



コードを少し解説

今回のシェーダー処理の肝は、サークル状グラデーションをランダムノイズを使って集中線にしている部分になります。
例えば下のような、反時計周りにalpha値が0~1に遷移する画像があったとします。
集中線の説明1

このalpha値を、ランダムノイズ関数に渡します。
ここで言うランダムノイズ関数の処理は、この世のあらゆる数値を0~1の範囲に収めてくれる処理で、同じ入力に対しては必ず同じ出力を返します。
つまりある
入力に対して毎回ランダムな出力をするのではなく、入力に対しランダムに分布する値を返す
ということです。
言い換えると、入力と出力の間に一見すると関連性が見られない値を返します。
            
//あらゆる範囲の2次元変数を0~1の範囲の1次元変数に変換する

            fixed random(fixed2 uv)
            {
                return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453);
            }

ランダムノイズ関数にalpha値を渡す際には、floorを使って小数点以下を切り捨て、ある程度値を大雑把にしておきます。
この処理をしないとただのモザイクになって集中線になりません。
以前は集中線を作るにはグラデーションと シンプルノイズ が必要だと思ってましたが、今回必要なのはシンプルノイズ中のこのfloor処理だけでした。
                fixed2 lineSeed = floor(fixed2(angle, angle) * _NoiseScale);
                fixed lineVal = random(lineSeed); ;
結果は次の通りで、グラデーションだったalphaの値が集中線に変化しています。
集中線の説明2

0
0