SPARKCREATIVE Tech Blog

https://www.spark-creative.jp/

GLSLSandBoxのコードをUnityに移植する ~解説と整理編~

こんにちは、予定日より投稿が大幅に遅れてしまったことを猛省しているクライエントエンジニアの中島龍清(ナカシマリュウセイ)です。

前回は移植作業がメインだったので、今回はGLSLSandboxからUnityに移植したシェーダーについてのそれぞれの処理内容の解説と整理をおこなっていきます。
前回の内容はこちらから確認できますので、読んでくださると幸いです。

はじめに

まずは、前回移植したシェーダーの全体を改めて確認します。
全体の内容は以下になります。

// https://glslsandbox.com/e#75651.0

Shader "Unlit/GLSL2HLSL"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
	_Mouse ("MousePos", Vector) = (0,0,0,0)
	_Resolution ("Resolution", Vector) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            #define PI 3.14159265359

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float2 _Mouse;
            float2 _Resolution;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

	    float2x2 rotate2d(float angle) 
	    {
		return float2x2(cos(angle), sin(angle) , -sin(angle), cos(angle));
	    }

            fixed4 frag (v2f i) : SV_Target
            {
		float2 p = (i.uv * 2.0 - _Resolution.xy) / min(_Resolution.x, _Resolution.y);
		p = mul(rotate2d(_Time.y / 2.0 * PI), p);
		float t = 0.02 / abs(abs(sin(_Time.y)) - length(p));
		return fixed4(float3(t,t,t) * float3(p.x,p.y,1.0), 1.0); 
            }
            ENDCG
        }
    }
}

シェーダーの解説

frag関数の処理内容を上から順番に確認していきます。

①float2 p = (i.uv * 2.0 - _Resolution.xy) / min(_Resolution.x, _Resolution.y);
_Resolutionは解像度を指定するパラメーターです。
こちらの処理はUVを0~nから-n~nの範囲に変換する処理となっております。
Unityシェーダーではuvの値は0~1の範囲であるため_Resolutionの値も(1,1)固定となります。
_Resolutionを1に変換すると以下のようになります。

float2 p = (i.uv * 2.0ffloat2(1.0f,1.0f)) / min(1.0f,1.0f);

min(1,1)は必ず1が返ってくるため省略することができます。
省略すると以下のようになります。

float2 p = (i.uv * 2.0ffloat2(1.0f,1.0f));

②p = mul(rotate2d(_Time.y / 2.0 * PI), p);
rotate2dは回転行列を生成する関数で、引数で回転する角度(ラジアン)を決定します。
今回は引数に経過時間(秒)を返す_Time.yを使用しているので毎秒角度が更新されます。
分かりやすいように引数として渡している角度をangleという変数を新たに作成してそこに格納します。
記述内容は以下のようになります。

float angle = _Time.y / 2.0 * PI;
p = mul(rotate2d(angle), p);

以下の画像は確認用にangleをプロパティとして操作可能にした際のものです。

確認用としてangleを手動で変更している

③float t = 0.02 / abs(abs(sin(_Time.y)) - length(p));
0.02は線の太さで、abs(sin(_Time.y))は半径になります。
分かりやすいように線の太さはwidth、半径はradiusという変数を新たに作成してそこに格納します。
記述内容としては以下のようになります。

float width = 0.02f;
float radius = abs(sin(_Time.y));

こちらも先ほどの回転処理のように経過時間(秒)を返す_Time.yを使用しているので毎秒半径が更新されます。
また、sinの絶対値を利用しているので半径は0~1の範囲になります。
新しく用意した変数を元の処理に当てはめると以下のようになります。

float t = width / abs(radius – length(p));

以下の画像は確認用にwidthとradiusをプロパティとして操作可能にした際のものです。

確認用としてwidthとradiusを手動で変更している

④return fixed4(float3(t,t,t) * float3(p.x,p.y,1.0), 1.0);
float3(t,t,t) * float3(p.x,p.y,1.0)の計算結果が基本色となります。
座標が格納されているpを乗算しているため座標によって色が決定します。
また、pは回転の計算が行われているため座標による色も回転します。
分かりやすいようにcolorという変数を新たに作成してrgbのそれぞれに分けて格納します。
変更後の記述内容としては以下のようになります。

float3 color ;
color.r = t * p.x;
color.g = t * p.y;
color.b = t;

新しく用意した変数を当てはめると戻り値は以下のようになります。

return fixed4(color,1.0);

解説と整理は以上となります。

整理後の全体のコードを載せると以下のようになります。

// https://glslsandbox.com/e#75651.0

Shader "Unlit/GLSL2HLSL"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
	_Mouse ("MousePos", Vector) = (0,0,0,0)
	_Resolution ("Resolution", Vector) = (1,1,1,1)

	//_Angle("Angle", Range(0,1)) = 0		//確認用
	//_Width("Width", Range(0,1)) = 0.02		//確認用
	//_Radius("Radius", Range(0,1)) = 0.5		//確認用
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            #define PI 3.14159265359

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float2 _Mouse;
            float2 _Resolution;

	    //float _Angle;		//確認用
	    //float _Radius;	        //確認用
	    //float _Width;		//確認用

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

	    float2x2 rotate2d(float angle) 
	    {
		return float2x2(cos(angle), sin(angle) , -sin(angle), cos(angle));
	    }

            fixed4 frag (v2f i) : SV_Target
            {
		// UVを0 ~ 1 から -1 ~ 1に変換
		float2 p = (i.uv * 2.0f - float2(1.0f,1.0f));
		// 回転角度(ラジアン)
		float angle = _Time.y / 2.0f * PI;
		// 回転行列を生成する関数に回転角度を渡す
		p = mul(rotate2d(angle), p);
		// 太さ
		float width = 0.02f;
		// 半径 時間によって0~1に変化
		float radius = abs(sin(_Time.y));

		//angle = _Angle * 2.0f * PI;	//確認用
		//width = _Width;		//確認用
		//radius = _Radius;		//確認用

		// 時間で円の大きさを変化 length(p)は中心からの距離
		float t = width / abs(radius - length(p));
		// 色の決定
		float3 color;
		color.r = t * p.x;
		color.g = t * p.y;
		color.b = t;
		return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
}

「確認用」とコメントされている箇所をコメントアウト解除するとプロパティとしてそれぞれの値を操作可能になります。

まとめ

今回おこなったのはコードの整理であるため前回と描画結果が変わることはありませんが、
整理したことでどの処理がどの部分に影響するのか分かりやすくなったと思います。
次回は整理した内容を応用したシェーダーを作成していけたら良いなと思っております。