GLSLをHLSLに書き換える
GLSLをCg/HLSLに書き換えてUnityで動作させる。
言語名について
GLSL: クロノスグループのシェーダー言語。OpenGLで採用
Cg: NVIDIAのシェーダー言語。ディスコン
HLSL: マイクロソフトのシェーダー言語。DirectXで採用
おまけ
IRIS: シリコングラフィックスのシェーダー言語。シェーダーのさきがけ
ShadertoyやGLSL Sandboxは下記のやり方でコードを移植できる。
Unityで表示する場合は、板ポリのマテリアルに割り当てたUnlitシェーダーにコードを記載する。
ワンポイント
シェーダーでの定数や関数は、「上の方の行で定義されていれば参照できる」。
関数の上下を入れ替えるとコンパイルエラーになるので気を付けよう。
定数宣言
すごく雑に説明すると、定数はGLSLではconstを、Unityではstaticを使います。
code:const.GLSL
const
code:const.HLSL
static
円周率
GLSLでは定数でPIを定義しているコードがよくみられる。UnityではUnityCG.cgincのUNITY_PIを利用することができる。
code:pi.GLSL
const float PI = 3.1415926525; // 定数を定義
Kanetaaaaaaaaaaaa神が使うacos(-1.)がいいんじゃないすかね。
code:acospi.GLSL
const float PI = acos(-1.0);
code:pi.HLSL
sin(UNITY_PI / 2) // 組み込み定数を利用
他にもUNITY_TWO_PI(2π)とかもあります。
定数のバリエーションはいろいろあるので、UnityのインストールディレクトリからUnityCG.cgincを探して中身を覗いてみると良いです。
SRP(URP,HDRP)ではPIが使えるようになりました。
型
vec3みたいなのはfloat3に、mat4みたいなのはfloat4x4みたいに書き換えればOK。
code:var.HLSL
vec2
mat2
code:var.HLSL
float2
float2x2
時間
時間を扱うシェーダー処理系の組み込み変数を変更する。1秒で値が1.0になるものは_Time.yに置き換える。
code:time.GLSL
time
iGlobalTime
// など。変数名は環境によって異なる。
code:time.HLSL
Unityのシェーダーで時間を扱うには、他にも_Time.xとか_Time.zなどもある。
コンストラクタの違い
変数の定義をしている箇所がシェーダーエラーになった場合、該当の行を見てもなかなか気づきづらかったりする。
あと、Swizzleの書き方をおさえておくと書き換えがスムーズになる。以下のよう(.x)に書きます。
code:constructor.GLSL
vec3(0.0)
code:constructor.HLSL
(0).xxx
(float3)0
float3(0.0, 0.0, 0.0)
GLSLでfloatは0.0とか0.、あるいは.0と明示的に書かないとコンパイルが通りませんが、HLSLだと暗黙キャストしてくれるので0とか書いてしまってOK。
code:コラムSwizzleとは.txt
スウィズルは「配列の何番目かの要素にアクセスする」書き方。.xは添え字0の要素、.yは添え字1の要素と同じポインターになる。
スウィズルに2つ以上の添え字を指定できる。そのためarray.xyz、array.xxxと書くことができ、これらはそれぞれfloat3(array.x, array.y, arra.z)、float3(array.x, array.x, arra.x)と同じものになる。
スウィズルはxyzwで添え字0から3までをあらわせるが、これ以外にもrgba、pqstといったスウィズルもある。
動作は同じ。color.rrrと書けば、Rチャンネルの値を3つ返すことになる。
種類の違うものを混ぜてarray.xgswのように書くと、GLSLでもHLSLでもコンパイルエラーになる。
行列の掛け算
行列とベクトルの掛け算を書き換える。*はmul()で書き換える。
code:matmul.GLSL
mat3 * vec3
code:matmul.HLSL
mul(float3x3, float3)
GLSLで描かれたものを見かけたことはないですが、これをUnityに持ってきた場合、サイズの同じ行列どうしの掛け算はコンパイルエラーにならないので、問題になるかもしれません。
mat3 * mat3
Cg/HLSLでfloat3x3 * float3x3と書き直すと、これは行列の掛け算ではなく、「同じm-n要素どうしの掛け算」になります。
mulで書き換える必要がありますが、長いシェーダーだと探し当てるのが大変かもしれません。
組み込み関数の変更
mix → lerp 線形補完。
atan → atan2 アークタンジェント。つまり、原点から一直線上にある任意の座標に対して同じ値が返ってくる。
fract → frac 小数点以下の数字を取り出す。
mod → fmod 剰余。Cg/HLSLにはmodがないのでfmodで置き換えることができる。ただし、fmodはmodと丸めの実装が違うので、負数の場合に動作が変わる。 ※正しく動く場合もある
fmodを使うのではなく、mod関数を追記すれば、GLSLと見た目をあわせることができる。
modで表示した例
https://gyazo.com/485e4f33c2a544d0d652e7e148045744
fmodに置き換えたら見た目が変わってしまったMenger Sponge/门格海绵の例
https://gyazo.com/b7aba54e389c898148958db42fdd6278
Unityシェーダーで使うための各種modは一式コピペして利用すると良いでしょう。
ググればkeijiro神のGitHubリポジトリがヒットするので、それを使うと楽ちん。
でもまあ、割ってfloorとって掛け直したものを引いて……と考えれば、その場でコードを書くことも可能でしょう。
code:mod.HLSL
// GLSLのmodと同じ計算を行う関数を追加すると解決する
// 次元数の違うものをすべて機械的にコピペすると楽
// float2~float4のものは呼び出しでひっかかるものが出てくるので、コンストラクタの違いにも注意。mod(3.0)みたいなやつ。
float mod(float x, float y)
{
return x - y * floor(x / y);
}
float2 mod(float2 x, float2 y)
{
return x - y * floor(x / y);
}
float3 mod(float3 x, float3 y)
{
return x - y * floor(x / y);
}
float4 mod(float4 x, float4 y)
{
return x - y * floor(x / y);
}
https://gyazo.com/144105a11124a733a9bb37b18593127f
中央がx座標0で、右側が正、左側が負の描画例。
https://gyazo.com/df91a1793b947aaa195b267d58c301cc
https://gyazo.com/5c0ce63adb07376b05c52c33f91b65ce
上はmodのグラフ。下はfmodと同等(x % a)のグラフ。負数では<0になっている。
(逆に、modとfmodを切り替えるとルックがガラっと変わるので、面白い絵が作れたりしないですかね。(オレオレメモ))
↓やってみた。
https://gyazo.com/57dd8f040f7b577ab45b89aa57fa500a
https://gyazo.com/73dfa87e4394024cf12dab2ab7d83ec5
https://gyazo.com/583c04bea0f09dfc466ccd2bb7d3ab35
code:notice
GLSLのmod
This is computed as x - y * floor(x/y).
HLSLのfmod
The floating-point remainder is calculated such that x = i * y + f, where i is an integer, f has the same sign as x, and the absolute value of f is less than the absolute value of y.
saturate GLSLにはsaturateは存在しないので今回はオフトピだけど、逆にCg/HLSLから持っていく時はclamp(p, .0, .1)に直すことになる。
座標の初期加工
code:coord.HLSL
float2 uv = ((i.uv * _ScreenParams.xy) - 0.5 * _ScreenParams.xy) / _ScreenParams.y;
だいたいの場合は2倍して1を引くか、0.5を引くパターンでOK。
以下、今までに見かけたいくつかのパターンのショウケース。
vec2 p = gl_FragCoord.xy / resolution; → float2 p = i.uv;
vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y); → float2 p = 2.0 * i.uv - 1.0;
vec2 p = (gl_FragCoord.xy - resolution / 2.0) / resolution.y; → float2 p = i.uv - 0.5;
でもまあ、正しくはresolution.xyを _ScreenParams.xyで丁寧に書き換えるのが良いでしょう。
フラグメントシェーダーの返りパラメータの変更
void main( out vec4 fragColor, in vec2 fragCoord ) → fixed4 frag(v2f i)、outパラメータはreturn fixed4にする(下記)
fragColor = vec4( col, 1.0 ); → return fixed4(col.xxx, 1);
ループの変数名変更
フラグメントシェーダーのinパラメータはv2f iなので、ループ変数でiが使われていると変数名が重複する。
for(int i=0; i<16; i++)
↓変数名変更
for(int j=0; j<16; j++)
ループカウンタをiのまま扱っても問題ない場合もありますが(エラーにはならない)、構造体v2fの情報が失われてintで置き換わります。なので、このループ以降でi.uvのような参照をしているとエラーになります。
その他の座標の置き換え
ここまでで紹介した変更方法をすべて施した上で、それでもなお見た目が大きくズレるとしたら、フラグメントシェーダーの先頭でuvを設定した以外にも座標を扱っている可能性があります。
レイ、カメラ、描画するオブジェクトの座標で、値がどうなっているのかをたどることになります。ここは根性で探し出すことになります。念入りにコードをたどりましょう。
気付きづらいバグ
GLSLから書き換える話に限らないけれど、下記のコードはエラー表示されることなく動作する。
何が起きているか、おわかりいただけただろうか……。
code:bugs.HLSL
float map(float p) {
return length(p) - 1;
}
frag(...) {
float3 p = float3(i.uv, 0);
float d = map(p);
...
}
map関数の宣言で引数float3の「3」を打ちもらしてしまうと、割と探しづらいことになります。エラーにならないので結構厄介。
1次元向けのfloat mod(float x, float y)だけが存在して、2次元、3次元のmodを定義していない場合などは特に見つけづらくなります。
and more
ShadertoyやGLSL Sandboxには独自の組み込み変数(マウス座標やテクスチャ指定など)があるので、それは別途書き換えが必要。
ShadertoyのテクスチャをUnityに持ってきた場合は、Tex2Dで利用すれば良い。
↑これはやり方を追記したい
noise関数はGLSLにもCg/HLSLにもないので、そのまま関数をコピーすると動くようにできる。
パーリンノイズnoiseはMicrosoft DirectXのHLSLには存在するが、UnityのCGPROGRAM内では利用できない。
逆にHLSLからGLSLへ書き換える場合は、以下のリンクが役に立つかもしれない。
GLSLではintからfloatへの暗黙のキャストができない
float a = 0.; // 「.」がないとGLSLではエラーとなる
資料