GLSL(WebGL)でズンドコキヨシ

#GLSLのみでズンドコキヨシ

まず、「ズンドコキヨシ」とは何かについては[こちら](http://qiita.com/shunsugai@github/items/971a15461de29563bf90)を参照してください。

WebGLのフラグメントシェーダーの記述言語であるGLSLのみでズンドコキヨシです。GPUのみでズンドコキヨシします。

例によって実働ページから。WebGLサポートのブラウザでみてください。

http://wakufactory.jp/wgl/zundoko2.html

実行例
![zundoko_s.png](https://qiita-image-store.s3.amazonaws.com/0/74838/f6d2d668-3fd7-f646-8434-f6ee28c372c9.png)

GPUを駆使してるのにこの地味な画面！しかも結構GPUパワー食ってますw

##解説

通常のグラフィック描画では、座標を指定して図形や文字を描く、というAPIになっていますが、フラグメントシェーダーでは、「指定された座標の点の色を決める」という関数で記述する必要があります。

GLSLはC言語に似ていますが、いろいろ制約があり、普通に書けると思えることが書けなかったりします。ビット演算が無いとか、ループは固定回数しか回せないとか・・・そのため普通こんな書き方しないよというコードになっているところがあります。

フラグメントシェーダーのソースは次のようになっています。

```lang:GLSL
varying vec2 p ; // axis -1.0 <= x, y <= 1.0 
uniform sampler2D texture; //2D sample texture
uniform int count ; //loop count
uniform int time ; //random seed

const int WD = 10 ;
const int HD = 20 ;

int srand ;

int setCh(ivec2 p,int s) {
  int ch=0  ;
  int zc = 0 ;
  int kf = 0 ;
  int pofs = p.x + p.y*WD ;

  for(int i=0; i<WD*HD; i++) {
    ch = 6 ;
    if(i>=s) ch=6 ; //blank
    else if(kf==0) {
      srand =  srand*1103515245+12345 ;
      int r = srand/16384 ;
      if(r<0) r = - r ;

      ch = (r - r/2*2)*2 ; // 0=zun 2=doko
      if(ch==0) zc++ ;
      else {
        if(zc>=4) kf = i ;
        zc = 0 ;
      }
    } else {
      if(i==kf+1) ch = 4 ; //　koyo
      if(i==kf+2) ch = 5 ; //  shi!
    }
    if(i==pofs) break ; //指定座標まできたら抜ける
  }
  return ch ;
}

void main() {
  srand = time ;
　//座標を正規化し、表示するキャラクタを求める
  vec2 np = vec2((p.x+1.0)/2.,1.0-(p.y+1.0)/2.) ;
  float px = np.x * float(WD) ;
  float py = np.y * float(HD) ;
  vec2 op = vec2( fract(px),fract(py)) ;
  ivec2 pa = ivec2(floor(px),floor(py) ) ;
  int ch = setCh(pa,count/10) ;

  //表示キャラクタからテクスチャのオフセットを算出
  float ox = mod(float(ch),2.) * 0.5 ;
  float oy = float(ch/2) * 0.25 ;
  vec2 tp = vec2(op.x/2.+ox,op.y/4.+oy) ;
  
　//表示色を決める
  vec4 col ;
  if(ch/2==0 ) col = vec4(1.0,0,1.0,1.0) ;
  else if(ch/2==1 ) col = vec4(0,1.0,0,1.0) ;
  else col = vec4(1.0,0,0,1.0) ;
  
　//ピクセルの色を決定
  gl_FragColor = texture2D(texture,tp) * col ;
}
```

頭の4行が、外部から与えられている変数の定義です。
vec2 p が計算対象の座標の2次元ベクトルで、-1から1に正規化されています。この値が描画領域のピクセルごとに変わって呼ばれるわけです。sampler2D texture はテクスチャで、ここでは文字をテクスチャとして用意しています。
countは、アニメーション用のカウンタで、1/30秒ごとにカウントアップして再描画されるようになっています。timeは乱数のseed用に、実行開始時のutimeが入ってくるようになっています。

setChが、指定文字座標にある文字を計算する関数です。ここで乱数を発生させ、ズンドコの文字列を決める処理をしています。シェーダーでは、各ピクセルの計算が独立して行われるため、ピクセルごとに毎度計算し直す必要があるのです。

また、ズンドコを1つずつ表示させるために、パラメータsによって、s回ズンかドコを出力した時点で打ち切るようになっています。ここにメインから渡されたcountを分周したものを与えることによって、ズンドコを一定リズムでアニメーション出力しています。

文字自体は、テクスチャの画像からビットマップを拾い出す形で描画します。テクスチャとしてこんな画像を用意しています。

![zundoko.png](https://qiita-image-store.s3.amazonaws.com/0/74838/2b829cb1-3803-8f40-4a64-64aa0cc4e7e1.png)

表示させたい文字の座標から、テクスチャのビットマップをtexture2Dで取得し、最後に色を掛け算して、ピクセル値として gl_FragColor に代入しておしまいです。

この関数が、フレームごとに、すべてのピクセルで同時並行的にGPUによって実行されるというわけです。

無理やりフラグメントシェーダーだけで実装しているので、ピクセルごとにズンドコ配列を計算し直すなど無駄なことをしています。

まっとうなやり方なら、ズンドコの計算はjavascript側で行い、それをuniform変数を通じてシェーダに読み込ませ、シェーダが描画のみを担当する、というのが筋でしょう。

#追記3/23

GLSLの条件分岐は非常に遅い、ましてやループの中でやるなんて言語道断、ということらしいので、ifを3項演算子に書き直してみました。

```lang:GLSL
int setCh(ivec2 p,int s) {
  int ch=0  ;
  int zc = 0 ;
  int kf = 0 ;
  int pofs = p.x + p.y*WD ;

  for(int i=0; i<WD*HD; i++) {
    srand =  srand*1103515245+12345 ;
    int r = srand/16384 ;
    r = (r<0)?-r:r ;
    r = (r - r/2*2)*2 ;   //0:zun 2:doko

    //6:blank, 4:kiyo 5:shi! 
    ch = (i>=s)?6: ((kf==0)?r: ((i==kf+1)?4:((i==kf+2)?5:6))) ;

    kf = (kf!=0)?kf:((zc>=4 && r==2)?i:0) ;
    zc = (r==0)?zc+1:0 ;

    if(i==pofs) break ;　//指定座標まできたら抜ける
  }
  return ch ;
}
```
可読性は下がりますが、軽くなった・・・気がします！