いきなりですが、ここに追記です
結論からですが、webGLで4Kは無茶でした。crinkler.exeは、文字も圧縮してくれるようです。
せっかく書いたのだから削除せずに置いておきます。4kと言わず8kとかなら、何とかなるような気がします。
4K introをwebGLでやってみます
4k introをC言語を書いてcrinkler.exeで実行ファイルを作るのが、余りにもニッチ過ぎるので、webGLで4Kをやる方法を模索してみた。今のところwebGLの方が0.2kばかり不利だけど、このくらいなら問題無いと思う。たぶん?!htmlもミニ化する方法もまだあると思うし、何しろ、お気楽と言う事で合格でしょ。
とりあえずミニ化してないHTML
いきなりミニ化したwebGL載せても引かれるので、解りやすいとこから、とは言っても難解部分もあります。わかりやすくするのも面倒なところもあるので端折ってます。基本は、music shaderと一枚ポリゴンのgraphic shaderで作ってます。shaderはshadertoyでnewすれば出てくるテンプレートを、ちょっと直して使ってます。
<html>
<head>
<style>
*{
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script id="ms" type="x-shader/x-vertex">#version 300 es
uniform float sampleRate;
out vec2 gain;
void main(){
float time=float(gl_VertexID)/sampleRate;
gain=vec2(sin(6.2831*440.*time)*exp(-3.*time));
}
</script>
<script id="gs" type="x-shader/x-fragment">#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec2 resolution;
uniform float time;
void main() {
vec2 uv=(gl_FragCoord.xy*2.-resolution)/resolution.y;
vec3 col=.5+.5*cos(time+uv.xyx+vec3(0,2,4));
fragColor=vec4(col,1);
}
</script>
<script>
c.width = window.innerWidth;
c.height = window.innerHeight;
GL = c.getContext("webgl2") || c.getContext("experimental-webgl2");
var duration = 120;
var audio = new AudioContext();
var sampleRate = audio.sampleRate;
var bufferSize = audio.sampleRate * duration;
var audioBuffer = audio.createBuffer(2, bufferSize, audio.sampleRate);
var node = audio.createBufferSource();
node.buffer = audioBuffer;
node.loop = false;
node.connect(audio.destination);
var aryBuffer = new ArrayBuffer(bufferSize*4*2);
var dataView = new DataView(aryBuffer);
with(GL) {
p = createProgram ();
for (t = 0; s = createShader(VERTEX_SHADER - t); compileShader(s), attachShader(p, s)) {
shaderSource(s,t++ ? "#version 300 es\nvoid main(){}":ms.text);
}
transformFeedbackVaryings(p, ['gain'], SEPARATE_ATTRIBS);
linkProgram(p);
useProgram(p);
uniform1f(getUniformLocation(p, "sampleRate"), sampleRate);
VBOs = [createBuffer(),createBuffer()];
for (i = 0; i < 2; i++) {
bindBuffer(ARRAY_BUFFER, VBOs[i]);
bufferData(ARRAY_BUFFER, new Float32Array(bufferSize*2), STATIC_DRAW);
}
bindBufferBase(TRANSFORM_FEEDBACK_BUFFER, 0, VBOs[0]);
enable(RASTERIZER_DISCARD);
beginTransformFeedback(POINTS);
drawArrays(POINTS, 0, bufferSize);
endTransformFeedback();
disable(RASTERIZER_DISCARD);
bindBufferBase(TRANSFORM_FEEDBACK_BUFFER, 0, null);
bindBuffer(ARRAY_BUFFER, VBOs[0]);
getBufferSubData(ARRAY_BUFFER, 0, dataView);
p = createProgram();
for (t = 0; s = createShader(VERTEX_SHADER - t); compileShader(s), attachShader(p, s)) {
shaderSource(s,t++ ? gs.text:"#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");
}
linkProgram(p);
useProgram(p);
uniform2f(getUniformLocation(p, "resolution"), c.width, c.height);
l=function () {
uniform1f(getUniformLocation(p, "time"),Math.min(audio.currentTime,duration));
drawArrays(TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(l);
};
}
var buf = new Float32Array(aryBuffer);
var data0 = audioBuffer.getChannelData(0);
var data1 = audioBuffer.getChannelData(1);
for(var i = 0; i < bufferSize; i++) {
data0[i] = buf[i*2];
data1[i] = buf[i*2+1];
}
node.start(0);
l();
</script>
</body>
</html>
ワンポイントjavaScriptスキル
<script id="ms" type="x-shader/x-vertex">...</script>
これの中にあるshaderのソースの文字列は
ms.text
で取り出せます。idが単純に変数になります。
<canvas id="c"></canvas>
と書いておけばjavaScript側で、あっさりと
GL = c.getContext("webgl2") || c.getContext("experimental-webgl2");
って処理できます。
JavaScriptをミニ化していきます
本来ならば改行を消して1行が王道ですが可読性の為、改行は入れてあります。
<html>
<style>
*{
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<body>
<canvas id="c"/>
<script>
c.width=window.innerWidth;
c.height=window.innerHeight;
G=c.getContext("webgl2")||c.getContext("experimental-webgl2");
for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];
D=120;
A=new AudioContext();
R=A.sampleRate;
S=R*D;
B=A.createBuffer(2,S,R);
N=A.createBufferSource();
N.buffer=B;
N.connect(A.destination);
a=new ArrayBuffer(S*8);
with(G){
o=function(a,b){for(t=0;s=rhr(ESR-t);oSr(s),thr(p,s))hoe(s,t++?a:b)};
p=rrm();
o("#version 300 es\nvoid main(){}",
`#version 300 es\n
uniform float sampleRate;
out vec2 gain;
void main(){
float time=float(gl_VertexID)/sampleRate;
gain=vec2(sin(6.2831*440.*time)*exp(-3.*time));
}
`);
rrs(p,["gain"],EES);
igm(p);
srm(p);
n1f(eon(p,"sampleRate"),R);
V=[rur(),rur()];
for(i=0;i<2;)ifr(RUR,V[i++]),uaa(RUR, new Float32Array(S*2),TDW);
bindBufferBase(RRR,0,V[0]);
enable(AZD);
eak(POINTS);
ras(POINTS,0,S);
nsk();
disable(AZD);
bindBufferBase(RRR,0,null);
ifr(RUR,V[0]);
eea(RUR,0,new DataView(a));
p=rrm();
o(`#version 300 es\n
precision highp float;
out vec4 fragColor;
uniform vec2 resolution;
uniform float time;
void main() {
vec2 uv=(gl_FragCoord.xy*2.-resolution)/resolution.y;
vec3 col=.5+.5*cos(time+uv.xyx+vec3(0,2,4));
fragColor=vec4(col,1);
}
`,"#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");
igm(p);
srm(p);
n2f(eon(p,"resolution"),c.width,c.height);
l=function(){
n1f(eon(p,"time"),Math.min(A.currentTime,D));
ras(REP,0,4);
requestAnimationFrame(l)
};
}
b = new Float32Array(a);
for(i=S;i--;)for(j=2;j--;)B.getChannelData(j)[i]= b[i*2+j];
N.start(0);
l();
</script>
</body>
</html>
ここでのwebGLのミニ化については
http://jsdo.it/cx20/5ivN
ここに詳しく書かれてます。ポイントは
for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];
です。よく考えられてます。
javaScript限らずにカウントダウンしていく、たまに見かけるミニ化スキル
for(j=2;j--;)
も使ってます。これは、console.log()とかで追っかけみれば挙動が解ります。
for(j=2;--j;)
とすると挙動が変わります。最後を1にしたい時に使えます。
これの改行抜きは
<html><style>*{margin:0;padding:0;overflow:hidden;}</style><body><canvas id="c"/><script>c.width=window.innerWidth;c.height=window.innerHeight;G=c.getContext("webgl2")||c.getContext("experimental-webgl2");for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];D=120;A=new AudioContext();R=A.sampleRate;S=R*D;B=A.createBuffer(2,S,R);N=A.createBufferSource();N.buffer=B;N.connect(A.destination);a=new ArrayBuffer(S*8);with(G){o=function(a,b){for(t=0;s=rhr(ESR-t);oSr(s),thr(p,s))hoe(s,t++?a:b)};p=rrm();o("#version 300 es\nvoid main(){}","#version 300 es\nuniform float sampleRate; out vec2 gain; void main(){ float time=float(gl_VertexID)/sampleRate; gain=vec2(sin(6.2831*440.*time)*exp(-3.*time));}");rrs(p,["gain"],EES);igm(p);srm(p);n1f(eon(p,"sampleRate"),R);V=[rur(),rur()];for(i=0;i<2;)ifr(RUR,V[i++]),uaa(RUR, new Float32Array(S*2),TDW);bindBufferBase(RRR,0,V[0]);enable(AZD);eak(POINTS);ras(POINTS,0,S);nsk();disable(AZD);bindBufferBase(RRR,0,null);ifr(RUR,V[0]);eea(RUR,0,new DataView(a));p=rrm();o("#version 300 es\nprecision highp float;out vec4 fragColor;uniform vec2 resolution;uniform float time;void main() { vec2 uv=(gl_FragCoord.xy*2.-resolution)/resolution.y; vec3 col=.5+.5*cos(time+uv.xyx+vec3(0,2,4)); fragColor=vec4(col,1);}","#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");igm(p);srm(p);n2f(eon(p,"resolution"),c.width,c.height);l=function(){n1f(eon(p,"time"),Math.min(A.currentTime,D));ras(REP,0,4);requestAnimationFrame(l)};}b=new Float32Array(a);for(i=S;i--;)for(j=2;j--;)B.getChannelData(j)[i]=b[i*2+j];N.start(0);l();</script></body></html>
これのファイルサイズは1.57 KB (1,608 バイト)
ちょっと new Audio("data:Audio/WAV;base64...が失敗した件
ここ http://www.p01.org/JS1K_2016_lrnz_snglrt/ にAudio()を使ったミニ化のスキルが載っていた。しかし、試したところ、firefoxでは走ったが、chromeでは動かなかった。残念。今後の動向を見守りたいです。TDFのデフォルトがchromeであるからミニ化の魅力はあるけど見送りですかね。
crinkler.exeで作った実行ファイルと比べてみる
以前書いた記事
お試し 4K intro https://qiita.com/gaziya5/items/667a23b3bfe453a90cc1
で使ったshaderでwebGLを書くと
<html><style>*{margin:0;padding:0;overflow:hidden;}</style><body><canvas id="c"/><script>c.width=window.innerWidth;c.height=window.innerHeight;G=c.getContext("webgl2")||c.getContext("experimental-webgl2");for(i in G)G[i[1]+i[7]+i[i.length-1]]=G[i];D=120;A=new AudioContext();R=A.sampleRate;S=R*D;B=A.createBuffer(2,S,R);N=A.createBufferSource();N.buffer=B;N.connect(A.destination);a=new ArrayBuffer(S*8);with(G){o=function(a,b){for(t=0;s=rhr(ESR-t);oSr(s),thr(p,s))hoe(s,t++?a:b)};p=rrm();o("#version 300 es\nvoid main(){}","#version 300 es\nout vec2 gain;uniform float sampleRate;void main(){float time = float(gl_VertexID) / sampleRate;gain = vec2(sin(6.2831*440.0*fract(time)));}");rrs(p,["gain"],EES);igm(p);srm(p);n1f(eon(p,"sampleRate"),R);V=[rur(),rur()];for(i=0;i<2;)ifr(RUR,V[i++]),uaa(RUR, new Float32Array(S*2),TDW);bindBufferBase(RRR,0,V[0]);enable(AZD);eak(POINTS);ras(POINTS,0,S);nsk();disable(AZD);bindBufferBase(RRR,0,null);ifr(RUR,V[0]);eea(RUR,0,new DataView(a));p=rrm();o("#version 300 es\nprecision highp float;out vec4 fragColor;uniform vec2 resolution;uniform float time;void main(){vec2 p = (2.0 * gl_FragCoord.xy - resolution.xy) / resolution.y;p += vec2(cos(time), sin(time)) * 0.5;float g = exp(-1.5 * dot(p,p));fragColor = vec4(g, g, g, 1.0);}","#version 300 es\nvoid main(){gl_Position=vec4(ivec2(gl_VertexID%2,gl_VertexID/2)*2-1,0,1);}");igm(p);srm(p);n2f(eon(p,"resolution"),c.width,c.height);l=function(){n1f(eon(p,"time"),Math.min(A.currentTime,D));ras(REP,0,4);requestAnimationFrame(l)};}b=new Float32Array(a);for(i=S;i--;)for(j=2;j--;)B.getChannelData(j)[i]=b[i*2+j];N.start(0);l();</script></body></html>
これのファイルサイズは、1.60 KB (1,639 バイト)
記事の中に、この実行ファイルのサイズは、1.43 KBと書いてあります。実際crinkler.exeを使うのが面倒なので記事の内容を鵜呑みです。その差は0.17Kです。
なんとなくjavaScriptでもいいかな?って感じです。
ちなみにshader部分を除いたファイルサイズは1192byte。4kのファイルサイズは4096byte。なのでmusic shaderとgraphic shaderを合わせて2904chars使える。shadertoyのエディターだとcharsのカウントが解るので目安になります。
実際のdemosceneの細かいルールは把握してない。
個人的にdemosceneに面白くて首を突っ込んでるけど、実際の細かいルールは把握していない。もしかして、これはJS4kというジャンルになるかもしれない。でもTDFは、これを問題なく受け入れてくれるし、4Kを超えてもエントリー可能です。
javaScriptで、4k introならエントリーも増えるような気がする。やりましょう。8k introもokだよ。結構、縛りは緩いTDFだよね。これを参考にしてもらえれば幸いです。
追記
このソースをfirefoxで試したところ、音がでませんでした。TDFのデフォルトがchromeのままなら使えます。ミニ化のせいなのか、ちょっと不明。TDFの時のブラウザのヴァージョンとかチェックしないと怖い気はする。
htmlで<body></body>を消してもchromeでは動いてました。まあ、やり過ぎもキケンなので、探り探りやってほうが良いのかな?