4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

webGLで4K Introに挑んでも無茶じゃないと思う

Last updated at Posted at 2018-07-22

いきなりですが、ここに追記です

結論からですが、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では動いてました。まあ、やり過ぎもキケンなので、探り探りやってほうが良いのかな?

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?