SPARKCREATIVE Tech Blog

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

【Unity】GLSLSandboxのコードをUnityに移植する

こんにちは、クライエントエンジニアの中島龍清(ナカシマリュウセイ)です。
今回でブログ投稿2回目となりますが未だに探りながら作成しているので
前回同様に拙い箇所が多々あることを予めご了承ください。

なぜGLSLSandboxのコードをUnityに移植しようと考えたのかというと
自作でなにかシェーダーを作成しようと思った際に、
最初から作るのはハードルが高いと感じたので
参考となるシェーダーを探すにあたりGLSLSandboxを利用しようと思ったからです。

はじめに

UnityのシェーダーはおもにHLSLがベース(正確にはCg/HLSL)のため
GLSLSandboxのコードをそのままコピペで移すことはできません。
GLSLからHLSLに変換する作業が必要となります。

GLSLSandboxについて

GLSLSandboxとはGLSLのフラグメントシェーダーをリアルタイムでコーディング及びプレビューできるWebサイトです。
作成したシェーダーを投稿することも可能で、多くの人のシェーダーが共有されています。
glslsandbox.com

GLSLとHLSL

GLSLとHLSLでどう違うのか
まずはそれぞれがどのようなものなのか確認してみましょう。

GLSL

GLSLとはOpenGL Shading Languageの略であり
主にOpenGLで使用することを想定としたシェーディング言語です。
OpenGL及びGLSLの詳細については以下の公式Wikiをご参照ください。
OpenGL公式Wiki(英語):
www.khronos.org

HLSL

HLSLとはHigh Level Shading Languageの略であり
主にDirectXで使用することを想定とした高レベルのシェーディング言語です。
HLSLの詳細については以下のMicroSoft公式ドキュメントのページをご参照ください。
MicroSoft公式ドキュメント:
docs.microsoft.com

GLSLとHLSLについて結論

調べれば調べるほど細かな違いが明らかになりますが、
移植を行うにあたってはどちらもC言語がベースで基本的な文法は同じなため、
独自の関数と定義済みパラメーター、パラメーターの型に気を付ければ概ね大丈夫です。

実装

実装準備

GLSLSandboxから参考にする作品を選定します。
今回はこちらの作品を参考にさせていただきます。

Unity側の準備としてUnlitシェーダーを作成し、フォグなどの不要な処理を削除して以下のような状態にします。

Shader "Unlit/GLSL2HLSL"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}
GLSLコードの分析

GLSLのコードを分析していきます。

precision mediump float;
uniform float time;
uniform vec2  mouse;
uniform vec2  resolution;

#define PI 3.14159265359

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

void main(void){
   	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
	p = rotate2d(time / 2.0 * PI) * p;
	float t = 0.02 / abs(abs(sin(time)) - length(p));
	gl_FragColor = vec4(vec3(t) * vec3(p.x,p.y,1.0), 1.0);
}

まず、それぞれの型の確認を行います。
vec2vec3vec4はそれぞれ浮動小数点のベクトル型であることを示しています。
HLSLではfloat2float3float4が同じ意味なので代用できます。
mat2は2x2の行列型(マトリクス型)であることを示しています。

HLSLでは2x2の行列型の定義はfloat2x2が該当します。

型の確認については以上となります。


次に定義済みパラメーターの確認を行います。
今回のコードで使用されている定義済みパラメーターはgl_FragCoordgl_FragColorになります。

gl_FragCoordはフラグメントの座標を示しています。
先ほど作成したUnlitシェーダーではテクスチャを使用するので、テクスチャ座標を取得しているuvで代用できます。

gl_FragColorはフラグメント色を示しています。
先ほど作成したUnlitシェーダーの場合、frag関数の戻り値がフラグメント色となるのでgl_FragColorに渡される値をfrag関数の戻り値とすれば同様に動作します。

定義済みパラメーターの確認については以上となります。


それ以外の注意点としては

GLSLでは行列とベクトルの乗算を"*(アスタリスク)"でおこなっていますが、HLSLではmul関数を使わないとエラーとなります

vec3(t)のようなベクトル型の初期化はできないためfloat3(t,t,t)と各要素をそれぞれ指定する必要があります。

最初に定義されているmediump 精度修飾子と呼ばれるものです。
GLSLのフラグメントシェーダーでは小数点の精度を事前に定義する必要があるため記述されていますが、
UnityシェーダーのベースであるHLSLではその必要がないのでこちらの1行は不要となります。

先頭にuniformとついたパラメーターはグローバルなパラメーターであることを意味します。
グローバルなパラメーターとして定義されているtime、mouse、resolutionですが、これはそれぞれJavaScriptから時間、マウス座標、スクリーン解像度を取得しています。
Unityでは同じように取得することはできないのでそれぞれ代わりとなるパラメーターを用意する必要があります。
今回は以下のもので代用いたします。
time(時間) → Unityシェーダーの定義済みパラメーター”_Time”を利用
mouse(マウス座標) → ベクトル型パラメーター”_Mouse”を新規に定義
resolution(スクリーン解像度) → ベクトル型パラメーター”_Resolution”を新規に定義

今回使用するコードでの注意点は以上となります。

変換してUnityに実装

先述したようにGLSLSandboxの作品はフラグメントシェーダーによるものなので
作成したUnlitシェーダーのfrag関数内に実装します。
分析したことを踏まえて変換して実装すると以下のようになります。

// 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
        }
    }
}

実装結果

描画結果はこのようになります。

f:id:spark-nakashima-ryusei:20211129195428g:plain
変換後描画結果

GLSLSandboxのものと比べると回転が逆になってしまっています。
これはHLSLとGLSLでは上下の定義が逆になっているためそのままだと必然的に回転も逆になります。
なので上下が逆なことを考慮して処理を修正します。

float2x2 rotate2d(float angle) 
{
	return float2x2(cos(angle), sin(angle) , -sin(angle), cos(angle));  //上下反転対応のため-sinとsinを逆にしました
}

修正したことでGLSLSandboxのものと同じ回転になりました。

f:id:spark-nakashima-ryusei:20211129195931g:plain
上下修正後描画結果

以上で実装は完了です。

改めて全体のコードを載せます。

// 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
        }
    }
}

まとめ

GLSLSandboxには今回使用した作品のような参考となる作品がたくさんあります。
GLSLSandbox以外にもシェーダーが共有されているサイトは他にもあるので、
Unityでシェーダーを作成する際はUnityで作成されている作品以外に
GLSL等の他のシェーディング言語で作成されている作品を参考にすることでより知見が広がるかもしれません。