つぶやきGLSLで今すぐ使えるシェーダーminifyテクニック11選!

最近、1ツイート(280文字)内にシェーダーをおさめる つぶやきGLSL というものが流行っています。

一見「もうこれ以上詰められないよ~」となってしまっても、よくよく探すと意外なところを削れることがあったります。

この記事では、自分が使っている、つぶやきGLSLで文字を詰めるためのテクニックをチートシートとしてまとめてみました。

つぶやきGLSLの作例

つぶやきGLSLの参考記事

webgl.souhonzan.org

今回使うエディタ

この記事では、twigl.appを前提として解説します。 twigl.app

基本的なテクニック

twigl.appのregulationはclassicではなくgeeker(300 es)を使う

twigl.appには、いくつかのregulationが存在します。

f:id:notargs:20200430205529p:plain

デフォルトで選ばれているのは classic ですが、 classicマゾ向けです。強いこだわりがなければ geeker(300 es)を選びましょう。

geekerが選ばれている作品もよく見ますが、geeker(300 es)にすることでgl_FragColor=vec4(p.xxy,1);o=vec4(p.xxy,1);と省略できるため、300 esのほうがおすすめです。

各レギュレーションの比較

f:id:notargs:20200430205722p:plain f:id:notargs:20200430205748p:plain f:id:notargs:20200430205807p:plain

数値リテラルを短く書く

GLSLでは、0.2f.28.0f8.のように、小数点を挟んだ0とfは省略できます。

また、指数表記を使うことで、1000001e5のように節約して書くこともできます。

ベクトルの定義を短く書く

GLSLではベクトルの初期化時に、

vec4 a=vec4(0,0,0,0);

のように、int型を代入できます。

更に、全て同じ値であれば、

vec4 a=vec4(0);

のように1つの要素を用いて初期化もできます。

geeker(300 es)のサンプルコードをminifyしてみる

geeker(300 es)を選んだときに、最初から入っているサンプルコードを小さくしてみます。

void main(){vec2 p=(gl_FragCoord.xy*2.-r)/min(r.y,r.x)-m;for(int i=0;i<8;++i){p.xy=abs(p)/abs(dot(p,p))-vec2(.9+cos(t*.2)*.4);}o=vec4(p.xxy,1);}

座標を計算する

twigl.appのデフォルトでは次のような座標を計算するスクリプトが入っています。

vec2 p=(gl_FragCoord.xy*2.-r)/min(r.y,r.x)-m;

これもちらほら表現によっては不要なところがあるので、削ってしまいましょう。

まず、マウス位置はいらないので削ってしまいます。

vec2 p=(gl_FragCoord.xy*2.-r)/min(r.y,r.x);

大抵の場合縦幅のほうが横幅より小さいので、常に縦幅で割るように変更します。

vec2 p=(gl_FragCoord.xy*2.-r)/r.y;

これだけで10文字削れました。

また、少しズルいですが、rを決め打ちしてしまうことで、より文字数を減らすこともできます。

vec2 p=gl_FragCoord.xy/1e2-3.;

暗黙的な型変換を活用する

GLSLには、ベクトルとスカラーの足し算を行ったときなど、自動でスカラーをベクトルと見なして演算してくれる機能があります。

例えば、twigl.appのサンプルにあるこの式は、

p.xy=abs(p)/abs(dot(p,p))-vec2(.9+cos(t*.2)*.4);

このように省略して書けます。

p.xy=abs(p)/abs(dot(p,p))-(.9+cos(t*.2)*.4);

邪魔なvec2のconstructがなくなったので、マイナスを展開するとかっこが外せて2文字節約できます。

p.xy=abs(p)/abs(dot(p,p))-.9-cos(t*.2)*.4;

左辺のpも右辺も同じvec2なので、ついでに不要な.xyも消してしまいましょう。

p=abs(p)/abs(dot(p,p))-.9-cos(t*.2)*.4;

同じ命令は出来る限りまとめる

|a/b| = |a|/|b|なので、

abs(p)/abs(dot(p,p))

abs(p/dot(p,p))

のように書けます。

他にも、(ab+ac)=a(b+c)など、数式の変形を用いて順序を変えることでより文字数を減らせる局面があるので、ぜひ探してみましょう。

for文のカッコを省略する

for文のカッコ{}は、forの内部が1つの式であれば省略できます。

for(int i=0;i<8;++i){p=abs(p/dot(p,p))-.9-cos(t*.2)*.4;}

for(int i=0;i<8;++i)p=abs(p/dot(p,p))-.9-cos(t*.2)*.4;

for文の中身に複数の式があるときも、セミコロン;の代わりにカンマ,を使うことで、複数の式を結合して一つの式にできます。

for(int i=0;i<8;++i){p=abs(p/dot(p,p));p-=.9-cos(t*.2)*.4;}

for(int i=0;i<8;++i)p=abs(p/dot(p,p)),p-=.9-cos(t*.2)*.4;

出力のα値は何でも良いことを利用する

twigl.appの現バージョンでは、出力の4つめの値(本来は透明度を示す値)はどんな値でも正しく表示されるようになっています。 適当な値を入れて問題ないため、

o=vec4(p.xxy,1);

o=p.xxyy;

のように省略できます。

出力用の変数oを利用する

geeker(300 es)の出力用変数であるvec4型のoは、main関数内で何度でも再代入できます。 あるものは使ってしまいましょう。

void main(){
    vec2 p=gl_FragCoord.xy/1e2-3.;
    for(int i=0;i<8;++i)p=abs(p/dot(p,p))-.9-cos(t*.2)*.4;
    o=p.xxyy;
}

void main(){
    o=gl_FragCoord.xxyy/1e2-3.;
    for(int i=0;i<8;++i)o=abs(o/dot(o.xz,o.xz))-.9-cos(t*.2)*.4;
}

pの定義を削り、oをp代わりに使うように変更しました。

元のコードと見比べてみる

元のコードと見比べてみます。

void main(){vec2 p=(gl_FragCoord.xy*2.-r)/min(r.y,r.x)-m;for(int i=0;i<8;++i){p.xy=abs(p)/abs(dot(p,p))-vec2(.9+cos(t*.2)*.4);}o=vec4(p.xxy,1);}

void main(){o=gl_FragCoord.xxyy/1e2-3.;for(int i=0;i<8;++i)o=abs(o/dot(o.xz,o.xz))-.9-cos(t*.2)*.4;}

144chars→100charsと、なかなか短くできたのではないでしょうか。

その他応用的なテクニック

ここからは、その他の応用的なテクニックについて解説します。

回転行列を短く定義する

glslでは、回転を表現するために、

v = mat2(cos(a), -sin(a), sin(a), cos(a)) * v;

のような回転行列がよく使われます。

これをより短く定義できないか考えてみましょう。

cos(θ) = sin(θ + π/2) なので、sin/cosを統一して次のように書くことができます。

#define PI 3.1415926535
v = mat2(cos(a+vec4(0,-PI/2.,PI/2,0))) * v;

更に、PIの定義が長すぎるため、ここをより短く定義できないかを考えます。 cosは2πで一周するため、cos(-π/2)cos(π/2)に近い値となる、整数の数値を探してみます。

sin(π/2) = 1
sin(8) = 0.98935824662
sin(-π/2) = -1
sin(5) = -0.95892427466

どうやら8と5が近そうです。

v = mat2(cos(a+vec4(0,8,5,0))) * v;

かなり短くなりました!

変数の代入を式として使う

a = vec3(1);
a += a;
a *= a;

のような代入は、実はvec3を返す式として扱うことができます。 そのまま変数に代入できるため、

a*=a+=(a=vec3(1));

のようにminifyできます。

まとめ

つぶやきGLSLはお手軽に始めることができ、なおかつコードゴルフとアートが密接に結びついた、とても楽しい娯楽だと思います。 ぜひGWをtwigl.appで過ごしましょう!