[WebGL寄稿] JS1k 作品で見つけた WebGL 超絶圧縮コードを紐解いてみる

cx20 : 2015-01-11 14:20:07

WebGL 総本山寄稿記事第二弾!

今回は、jsdo.it などを中心に、尋常ではない量のサンプルやデモを多数公開されている @cx20 さんから寄稿いただきましたので掲載させていただきます。

今回のテーマは javascript の圧縮。js1k と呼ばれる、1 キロバイト以下の容量でデモを作り完成度を競うイベントから、コード圧縮のテクニックについて抜粋して解説してくださっています。

WebGL の冗長なメソッド名が非常に短くなるというテクニックで、幅広く使える技術というわけではないかもしれませんが、コードを手入力で圧縮する方法としてすごく斬新なアイデアだなと思いました。

以下、寄稿記事です。

JS1k で見つけた超絶コード

JS1k(js1k.com)という大会をご存じでしょうか。

この大会は、名前にあるように、1キロバイトの JavaScript でゲームやデモを競う大会になります。

去年の作品ですが、WebGL API の圧縮テクニックが素晴らしかった為、紹介したいと思います。

オリジナルコード

GL Dragon Flight By Paul Brunt

w=a.width/=3,h=a.height/=3;for(i in g)g[i[1]+i[7]+i[i.length-1]]=g[i];with(g){for(ifr(RUR,r
ur()),uaa(RUR,new Float32Array([-1,-1,-1,9,9,-1]),35044),p=rrm(),t=0;s=rhr(ESR-t);oSr(s),thr
(p,s))hoe(s,t++?"#ifdef GL_ES\nprecision highp float;\n#endif\nuniform float t;float a(vec3 
b,float o){float c=.0,j;for(float j=.8;j<6.;j+=.7){c+=sin(b.x*j*j*2.)*cos(b.z*j*j-o)*.7/pow(
j,2.5);b.xz=mat2(1.,-.4,.4,1.)*b.xz;}return c;}void main(void){float n=.5+sin(t)*.1,l=0.,d=l
;vec3 p=vec3(l,-1.,-t),c=-normalize(gl_FragCoord.xyz/vec3("+w+","+h+",.5)-n);for(float i=0.;
i<70.;i++){n=a(p,0.)-p.y;d+=n;p+=c*n*.4;l++;if(n<.05||d>60.) break;}l=1.-l/60.;n=max(.5-p.y,
0.);gl_FragColor=vec4(mix(((a(p,1.6)+2.)*.1+l)*vec3(mix(.4,.0,min(1.,n*abs(a(p*.5,0.))+.7)))
+min(1.,pow(a(p*2.,0.)*n,2.))*l,vec3(.9,.9,1.)+c.y*1.5,pow(min(d*.02,1.),2.)),1.);}":"attrib
ute vec2 p;void main(){gl_Position=vec4(p,.5,1.);}");igm(p),srm(p),etr(0,2,5126,0,0,0),ney(0
),s=+new Date,(l=function(){ras(RES,0,3),n1f(eon(p,"t"),.001*(+new Date-s)),requestAnimation
Frame(l)})()}

コード整形した結果

w = a.width/=3;
h = a.height/=3;
fs = "#ifdef GL_ES\nprecision highp float;\n#endif\nuniform float t;float a(vec3 b,float o){float c=.0,j;for(float j=.8;j<6.;j+=.7){c+=sin(b.x*j*j*2.)*cos(b.z*j*j-o)*.7/pow(j,2.5);b.xz=mat2(1.,-.4,.4,1.)*b.xz;}return c;}void main(void){float n=.5+sin(t)*.1,l=0.,d=l;vec3 p=vec3(l,-1.,-t),c=-normalize(gl_FragCoord.xyz/vec3("+w+","+h+",.5)-n);for(float i=0.;i<70.;i++){n=a(p,0.)-p.y;d+=n;p+=c*n*.4;l++;if(n<.05||d>60.) break;}l=1.-l/60.;n=max(.5-p.y,0.);gl_FragColor=vec4(mix(((a(p,1.6)+2.)*.1+l)*vec3(mix(.4,.0,min(1.,n*abs(a(p*.5,0.))+.7)))+min(1.,pow(a(p*2.,0.)*n,2.))*l,vec3(.9,.9,1.)+c.y*1.5,pow(min(d*.02,1.),2.)),1.);}";
vs = "attribute vec2 p;void main(){gl_Position=vec4(p,.5,1.);}";
for(i in g) {
    g[i[1] + i[7] + i[i.length - 1]] = g[i];
}

with(g) {
    ifr(RUR, rur());
    uaa(RUR, new Float32Array([-1, -1, -1, 9, 9, -1]), 35044);
    p = rrm();
    for (t = 0; s = rhr(ESR - t); oSr(s), thr(p, s)) {
        hoe(s, t++ ? fs : vs);
    }
    igm(p);
    srm(p);
    etr(0, 2, 5126, 0, 0, 0);
    ney(0);
    s = +new Date, (
        l = function() {
            ras(RES, 0, 3);
            n1f(eon(p, "t"),.001 * (+new Date - s));
            requestAnimationFrame(l)
        }
    )()
}

見慣れない 3文字の関数や定数 が多く出現していることにお気付きでしょうか?

実は、これらは、WebGL API の関数や定数を圧縮したものになります。

// 圧縮前
bindBuffer(ARRAY_BUFFER, createBuffer());

// 圧縮後
ifr(RUR, rur());

圧縮テクニックのポイント

下記が、WebGL API の圧縮処理になります。

for(i in g) {
    g[i[1] + i[7] + i[i.length - 1]] = g[i];
}

gWebGLRenderingContextの参照となっており、各メンバに対して、圧縮処理が行われます。

短縮の例

"bindBuffer" → "ifr"

この処理により、WebGL の関数や定数を3文字で呼び出せるようになります。

なお、2~3文字目に、8桁目と最終桁を使っているのは、ユニークになるような組み合わせを検証した結果ではないかと思います。

三文字化の例

uniform1fn1f … 8桁目+最終桁がユニークになりやすい

圧縮コードを展開した結果

w = a.width /= 3;
h = a.height /= 3;
fs = 
    "#ifdef GL_ES\n" + 
    "precision highp float;\n" + 
    "#endif\n" + 
    "uniform float t;" + 
    "float a(vec3 b,float o) {" +
    "    float c = .0;" +
    "    float j;" + 
    "    for(float j = .8; j < 6.; j += .7) {" + 
    "        c += sin(b.x * j * j * 2.) * cos(b.z * j * j - o) * .7 / pow(j, 2.5);" + 
    "        b.xz = mat2(1., -.4, .4, 1.) * b.xz;" +
    "    }" + 
    "    return c;" + 
    "}" + 
    "void main(void) {" + 
    "    float n = .5 + sin(t) * .1;" +
    "    float l = 0.;" +
    "    float d = l;" + 
    "    vec3 p = vec3(l, -1., -t);" + 
    "    vec3 c = -normalize(gl_FragCoord.xyz / vec3(" + w + "," + h + ", .5) - n);" + 
    "    for(float i = 0.; i < 70.; i++){" + 
    "        n = a(p, 0.) - p.y;" + 
    "        d += n;" +
    "        p += c * n * .4;" +
    "        l++;" + 
    "        if(n < .05 || d > 60.) break;" + 
    "    }" + 
    "    l = 1. - l / 60.;" + 
    "    n = max(.5 - p.y, 0.);" + 
    "    gl_FragColor = vec4(" +
    "        mix(((a(p, 1.6) + 2.) * .1 + l) * vec3(mix(.4, .0, min(1., n * abs(a(p * .5, 0.)) + .7))) + " +
    "        min(1., pow(a(p * 2., 0.) * n, 2.)) * l," +
    "        vec3(.9,.9,1.) + c.y * 1.5, pow(min(d*.02,1.),2.)), 1." +
    "    );" + 
    "}";
vs = 
    "attribute vec2 p;" + 
    "void main() {" + 
    "    gl_Position = vec4(p, .5, 1.);" + 
    "}";

for(i in g) {
    g[i[1] + i[7] + i[i.length - 1]] = g[i];
}

with(g) {
    bindBuffer(ARRAY_BUFFER, createBuffer());
    bufferData(ARRAY_BUFFER, new Float32Array([-1, -1, -1, 9, 9, -1]), STATIC_DRAW);
    p = createProgram();
    for (t = 0; s = createShader(VERTEX_SHADER - t); ) {
        shaderSource (s, t++ ? fs : vs);
        attachShader(p, s);
        compileShader(s);
    }
    linkProgram(p);
    useProgram(p);
    vertexAttribPointer(0, 2, FLOAT, 0, 0, 0);
    enableVertexAttribArray(0);
    s = +new Date, (
        l = function() {
            drawArrays(TRIANGLES, 0, 3);
            uniform1f(getUniformLocation(p, "t"), .001 * (+new Date - s));
            requestAnimationFrame(l)
        }
    )()
}

実行結果

このようなテクニックを組み合わせることで、JS1k の作品が出来上がっていくわけですね。

他の作品にも、さまざまなテクニックが眠っているかと思いますので、興味がありましたら、是非、解析してみてください。

リンク:

JS1k demo details | GL Dragon Flight ※デモの紹介ページ

js1k.com - the JavaScript code golfing competition ※JS1k トップページ

当サイトでは寄稿記事を随時受け付けています。

WebGL に関連するものであれば、ジャンルは問いません。寄稿記事を書きたい、書いてもいいよ! という方はお気軽にご連絡ください。お待ちしています!

share

follow us in feedly

search

search

monthly

sponsor

social