Live2DxWebGLのポストエフェクトで遊んでみる

前に[UnityのImageEffectで遊んでみる](http://qiita.com/naotaro0123/items/5b7e993d7eb7d86a1f63)を書きましたが、最近WebGLでのやり方も理解できました。
ポストエフェクトとは、画像加工ソフトでいうフィルタのようなものです（アンチエイリアスとかぼかしとか）
3DCGの世界だと、ポストエフェクトの事をオフスクリーンとかフレームバッファとかいう事もあるみたいです。
今回はLive2DのWebGL SDKと組み合わせてみました。
オフスクリーンに描く事で[Live2D Unityのフェードイン・アウト](http://qiita.com/naotaro0123/items/9da45690d36347c73503)もWebGLでも可能と思います。

# 基本的なオフスクリーンの仕方
まずはLive2D WebGL SDKのSampleプロジェクトを元に修正していきます。
修正箇所としては、主に以下３点です。
１）オフスクリーン処理を追加
２）オフスクリーン描画するPlaneオブジェクトを追加
３）オフスクリーン用のGLSLコード追加

# ソースコード
HTML部分には、GLSLコードと行列演算ライブラリ追加しました。
行列演算ライブラリは[wgld.orgのminMatrix.js](http://wgld.org/d/webgl/w010.html)をお借りしました

```html:simple.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Live2D Simple</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=4.0">
    </meta>
</head>
<!-- オフスクリーンの頂点シェーダー -->
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
attribute vec2 textureCoord;
uniform   mat4 mvpMatrix;
varying   vec4 vColor;
varying   vec2 vTextureCoord;

void main(void){
    vColor        = color;
    // シェーダーで上下反転させる
    vTextureCoord = vec2(textureCoord.x, 1.0 - textureCoord.y);
    gl_Position   = mvpMatrix * vec4(position, 1.0);
}
</script>
<!-- オフスクリーンのフラグメントシェーダー -->
<script id="fs" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D texture;
varying vec4      vColor;
varying vec2      vTextureCoord;

void main(void){
    vec4 smpColor = texture2D(texture, vTextureCoord);
    gl_FragColor  = vColor * smpColor;
}
</script>
<body>
    <canvas id="glcanvas" style="border:dashed 1px #CCC"></canvas>
    <div id="myconsole" style="color:#BBB">---- Log ----</div>
    <!-- Live2D Library -->
    <script src="lib/live2d.min.js"></script>
    <!-- User's Script -->
    <script src="src/Simple.js"></script>
    <script src="src/minMatrix.js"></script>
</body>
</html>
```
jsの方にはオフスクリーン処理の追加しました

```js:Simple.js
// gl、 プログラムオブジェクト、テクスチャ、オフスクリーン用のテクスチャ
var gl, program, ftexture;
// キャンバスサイズ
var CAN_SIZE = 512;
// カウンタの宣言
var count = 0;

// JavaScriptで発生したエラーを取得
window.onerror = function(msg, url, line, col, error) {
    var errmsg = "file:" + url + "<br>line:" + line + " " + msg;
    Simple.myerror(errmsg);
}

window.onload = function(){
    Simple();
}

var Simple = function() {
    // Live2Dモデルのインスタンス
    this.live2DModel = null;
    // アニメーションを停止するためのID
    this.requestID = null;
    // モデルのロードが完了したら true
    this.loadLive2DCompleted = false;
    // モデルの初期化が完了したら true
    this.initLive2DCompleted = false;
    // WebGL Image型オブジェクトの配列
    this.loadedImages = [];
    // Live2D モデル設定。
    this.modelDef = {
        "type":"Live2D Model Setting",
        "name":"haru",
        "model":"assets/haru/haru.moc",
        "textures":[
            "assets/haru/haru.1024/texture_00.png",
            "assets/haru/haru.1024/texture_01.png",
            "assets/haru/haru.1024/texture_02.png"
        ]
    };

    // Live2Dの初期化
    Live2D.init();
    // canvasオブジェクトを取得
    var canvas = document.getElementById("glcanvas");
    canvas.width = canvas.height = CAN_SIZE;

    // コンテキストを失ったとき
    canvas.addEventListener("webglcontextlost", function(e) {
        Simple.myerror("context lost");
        loadLive2DCompleted = false;
        initLive2DCompleted = false;

        var cancelAnimationFrame =
            window.cancelAnimationFrame ||
            window.mozCancelAnimationFrame;
        cancelAnimationFrame(requestID); //アニメーションを停止

        e.preventDefault();
    }, false);

    // コンテキストが復元されたとき
    canvas.addEventListener("webglcontextrestored" , function(e){
        Simple.myerror("webglcontext restored");
        Simple.initLoop(canvas);
    }, false);

    // Init and start Loop
    Simple.initLoop(canvas);
};

/*
* WebGLコンテキストを取得・初期化。
* Live2Dの初期化、描画ループを開始。
*/
Simple.initLoop = function(canvas/*HTML5 canvasオブジェクト*/)
{
    //------------ WebGLの初期化 ------------

    // WebGLのコンテキストを取得する
    var para = {
        premultipliedAlpha : true,
//        alpha : false
    };
    gl = Simple.getWebGLContext(canvas, para);
    if (!gl) {
        Simple.myerror("Failed to create WebGL context.");
        return;
    }
    // 描画エリアを白でクリア
    gl.clearColor( 1.0 , 1.0 , 1.0 , 1.0 );

    //------------ Live2Dの初期化 ------------

    // mocファイルからLive2Dモデルのインスタンスを生成
    Simple.loadBytes(modelDef.model, function(buf){
        live2DModel = Live2DModelWebGL.loadModel(buf);
    });

    // テクスチャの読み込み
    var loadCount = 0;
    for(var i = 0; i < modelDef.textures.length; i++){
        (function ( tno ){// 即時関数で i の値を tno に固定する（onerror用)
            loadedImages[tno] = new Image();
            loadedImages[tno].src = modelDef.textures[tno];
            loadedImages[tno].onload = function(){
                if((++loadCount) == modelDef.textures.length) {
                    loadLive2DCompleted = true;//全て読み終わった
                }
            }
            loadedImages[tno].onerror = function() {
                Simple.myerror("Failed to load image : " + modelDef.textures[tno]);
            }
        })( i );
    }

    // フレームバッファ用の初期化処理
    Simple.Init_framebuffer();

    //------------ 描画ループ ------------

    (function tick() {
        Simple.draw(gl); // 1回分描画

        var requestAnimationFrame =
            window.requestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.msRequestAnimationFrame;
        requestID = requestAnimationFrame( tick , canvas );// 一定時間後に自身を呼び出す
    })();
};

Simple.draw = function(gl/*WebGLコンテキスト*/)
{
    // Live2D初期化
    if( ! live2DModel || ! loadLive2DCompleted )
        return; //ロードが完了していないので何もしないで返る

    // ロード完了後に初回のみ初期化する
    if( ! initLive2DCompleted ){
        initLive2DCompleted = true;

        // 画像からWebGLテクスチャを生成し、モデルに登録
        for( var i = 0; i < loadedImages.length; i++ ){
            //Image型オブジェクトからテクスチャを生成
            var texName = Simple.createTexture(gl, loadedImages[i]);

            live2DModel.setTexture(i, texName); //モデルにテクスチャをセット
        }

        // テクスチャの元画像の参照をクリア
        loadedImages = null;

        // OpenGLのコンテキストをセット
        live2DModel.setGL(gl);

        // 表示位置を指定するための行列を定義する
        var s = 2.0 / live2DModel.getCanvasWidth(); //canvasの横幅を-1..1区間に収める
        var matrix4x4 = [ 
            s, 0, 0, 0,
            0,-s, 0, 0,
            0, 0, 1, 0,
           -1, 1, 0, 1 
        ];
        live2DModel.setMatrix(matrix4x4);
    }

    // キャラクターのパラメータを適当に更新
    var t = UtSystem.getTimeMSec() * 0.001 * 2 * Math.PI; //1秒ごとに2π(1周期)増える
    var cycle = 3.0; //パラメータが一周する時間(秒)
    // PARAM_ANGLE_Xのパラメータが[cycle]秒ごとに-30から30まで変化する
    live2DModel.setParamFloat("PARAM_ANGLE_X", 30 * Math.sin(t/cycle));

    // フレームバッファをバインドする
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbuffer.framebuffer);
    // canvasを初期化
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Live2Dモデルを更新して描画
    live2DModel.update(); // 現在のパラメータに合わせて頂点等を計算
    live2DModel.draw();   // 描画

    // フレームバッファのバインドを解除
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    // フレームバッファのテクスチャをバインド
    gl.bindTexture(gl.TEXTURE_2D, ftexture);
    // シェーダー切り替え
    gl.useProgram(this.off_prg);
    // canvasを初期化
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
    Simple.Init_vbo_ibo();
    // カウンタを元にラジアンを算出
    count++;
    var rad = (count % 360) * Math.PI / 180;
    // uniform変数にテクスチャを登録
    gl.uniform1i(this.uniLocation[1], 0);
    // モデル座標変換行列の生成
    this.m.identity(this.mMatrix);
    // 回転させる
//    this.m.rotate(this.mMatrix, rad, [0.3, 1.0, 0.2], this.mMatrix);
    // 行列の掛け合わせ
    this.m.multiply(this.tmpMatrix, this.mMatrix, this.mvpMatrix);
    gl.uniformMatrix4fv(this.uniLocation[0], false, this.mvpMatrix);
    // uniform変数の登録と描画
    gl.drawElements(gl.TRIANGLES, this.index.length, gl.UNSIGNED_SHORT, 0);
};

/*
* WebGLのコンテキストを取得する
*/
Simple.getWebGLContext = function(canvas/*HTML5 canvasオブジェクト*/)
{
    var NAMES = [ "webgl" , "experimental-webgl" , "webkit-3d" , "moz-webgl"];

    var param = {
        alpha : true,
        premultipliedAlpha : true
    };

    for( var i = 0; i < NAMES.length; i++ ){
        try{
            var ctx = canvas.getContext( NAMES[i], param );
            if( ctx ) return ctx;
        }
        catch(e){}
    }
    return null;
};


/*
* Image型オブジェクトからテクスチャを生成
*/
Simple.createTexture = function(gl/*WebGLコンテキスト*/, image/*WebGL Image*/)
{
    var texture = gl.createTexture(); //テクスチャオブジェクトを作成する
    if ( !texture ){
        mylog("Failed to generate gl texture name.");
        return -1;
    }

    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);    //imageを上下反転
    gl.activeTexture( gl.TEXTURE0 );
    gl.bindTexture( gl.TEXTURE_2D , texture );
    gl.texImage2D( gl.TEXTURE_2D , 0 , gl.RGBA , gl.RGBA , gl.UNSIGNED_BYTE , image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);
    gl.bindTexture( gl.TEXTURE_2D , null );

    return texture;
};

/*
* ファイルをバイト配列としてロードする
*/
Simple.loadBytes = function(path , callback)
{
    var request = new XMLHttpRequest();
    request.open("GET", path , true);
    request.responseType = "arraybuffer";
    request.onload = function(){
        switch( request.status ){
        case 200:
            callback( request.response );
            break;
        default:
            Simple.myerror( "Failed to load (" + request.status + ") : " + path );
            break;
        }
    }
    request.send(null);
};

/*
* 画面ログを出力
*/
Simple.mylog = function(msg/*string*/)
{
    var myconsole = document.getElementById("myconsole");
    myconsole.innerHTML = myconsole.innerHTML + "<br>" + msg;
    console.log(msg);
};

/*
* 画面エラーを出力
*/
Simple.myerror = function(msg/*string*/)
{
    console.error(msg);
    Simple.mylog( "<span style='color:red'>" + msg + "</span>");
};

/*
* フレームバッファの初期化処理
*/
Simple.Init_framebuffer = function()
{
    // 頂点シェーダとフラグメントシェーダの生成
    var off_v_shader = Simple.create_shader('vs');
    var off_f_shader = Simple.create_shader('fs');
    // プログラムオブジェクトの生成とリンク
    this.off_prg = Simple.create_program(off_v_shader, off_f_shader);
    // 深度テストを有効にする
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);
    gl.clearDepth(1.0);
    // フレームバッファを生成
    this.fbuffer = Simple.create_framebuffer(CAN_SIZE, CAN_SIZE);
};

/*
* フレームバッファの初期化処理
*/
Simple.Init_vbo_ibo = function()
{
    // attributeLocationを配列に取得
    var attLocation = new Array();
    attLocation[0] = gl.getAttribLocation(this.off_prg, 'position');
    attLocation[1] = gl.getAttribLocation(this.off_prg, 'color');
    attLocation[2] = gl.getAttribLocation(this.off_prg, 'textureCoord');
    // attributeの要素数を配列に格納
    this.attStride = new Array();
    this.attStride[0] = 3;
    this.attStride[1] = 4;
    this.attStride[2] = 2;
    // 頂点の位置
    this.position = [
        -1.0,  1.0,  0.0,
         1.0,  1.0,  0.0,
        -1.0, -1.0,  0.0,
         1.0, -1.0,  0.0
    ];
    // 頂点色
    this.color = [
        1.0, 1.0, 1.0, 1.0,
        1.0, 1.0, 1.0, 1.0,
        1.0, 1.0, 1.0, 1.0,
        1.0, 1.0, 1.0, 1.0
    ];
    // テクスチャ座標
    this.textureCoord = [
        0.0, 0.0,
        1.0, 0.0,
        0.0, 1.0,
        1.0, 1.0
    ];
    // 頂点インデックス
    this.index = [
        0, 1, 2,
        3, 2, 1
    ];
    // VBOとIBOの生成
    var vPosition     = Simple.create_vbo(this.position);
    var vColor        = Simple.create_vbo(this.color);
    var vTextureCoord = Simple.create_vbo(this.textureCoord);
    var VBOList       = [vPosition, vColor, vTextureCoord];
    var iIndex        = Simple.create_ibo(this.index);
    // VBOとIBOの登録
    Simple.set_attribute(VBOList, attLocation, this.attStride);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iIndex);
    // uniformLocationを配列に取得
    this.uniLocation = new Array();
    this.uniLocation[0]  = gl.getUniformLocation(this.off_prg, 'mvpMatrix');
    this.uniLocation[1]  = gl.getUniformLocation(this.off_prg, 'texture');

    // 各種行列の生成と初期化
    this.m = new matIV();
    this.mMatrix   = this.m.identity(this.m.create());
    this.vMatrix   = this.m.identity(this.m.create());
    this.pMatrix   = this.m.identity(this.m.create());
    this.tmpMatrix = this.m.identity(this.m.create());
    this.mvpMatrix = this.m.identity(this.m.create());
    // ビュー×プロジェクション座標変換行列
    this.m.lookAt([0.0, 0.0, 2.4], [0, 0, 0], [0, 1, 0], this.vMatrix);
    this.m.perspective(45, CAN_SIZE / CAN_SIZE, 0.1, 100, this.pMatrix);
    this.m.multiply(this.pMatrix, this.vMatrix, this.tmpMatrix);
};

/*
* シェーダーコンパイル
*/
Simple.create_shader = function(id)
{
    // シェーダを格納する変数
    var shader;
    // HTMLからscriptタグへの参照を取得
    var scriptElement = document.getElementById(id);
    // scriptタグが存在しない場合は抜ける
    if(!scriptElement){return;}
    // scriptタグのtype属性をチェック
    switch(scriptElement.type){
        // 頂点シェーダの場合
        case 'x-shader/x-vertex':
            shader = gl.createShader(gl.VERTEX_SHADER);
            break;
        // フラグメントシェーダの場合
        case 'x-shader/x-fragment':
            shader = gl.createShader(gl.FRAGMENT_SHADER);
            break;
        default :
            return;
    }
    // 生成されたシェーダにソースを割り当てる
    gl.shaderSource(shader, scriptElement.text);
    // シェーダをコンパイルする
    gl.compileShader(shader);
    // シェーダが正しくコンパイルされたかチェック
    if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
        // 成功していたらシェーダを返して終了
        return shader;
    }else{
        // 失敗していたらエラーログをアラートする
        alert(gl.getShaderInfoLog(shader));
    }
};

/*
 * プログラムオブジェクトを生成しシェーダをリンクする関数
 */
Simple.create_program = function(vs, fs){
    // プログラムオブジェクトの生成
    program = gl.createProgram();
    // プログラムオブジェクトにシェーダを割り当てる
    gl.attachShader(program, vs);
    gl.attachShader(program, fs);
    // シェーダをリンク
    gl.linkProgram(program);
    // シェーダのリンクが正しく行なわれたかチェック
    if(gl.getProgramParameter(program, gl.LINK_STATUS)){
        // 成功していたらプログラムオブジェクトを有効にする
        gl.useProgram(program);
        // プログラムオブジェクトを返して終了
        return program;
    }else{
        // 失敗していたらエラーログをアラートする
        alert(gl.getProgramInfoLog(program));
    }
};

/*
 * VBOを生成する関数
 */
Simple.create_vbo = function(data){
    // バッファオブジェクトの生成
    var vbo = gl.createBuffer();
    // バッファをバインドする
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    // バッファにデータをセット
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
    // バッファのバインドを無効化
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    // 生成した VBO を返して終了
    return vbo;
};

/*
 * VBOをバインドし登録する関数
 */
Simple.set_attribute = function(vbo, attL, attS){
    // 引数として受け取った配列を処理する
    for(var i in vbo){
        // バッファをバインドする
        gl.bindBuffer(gl.ARRAY_BUFFER, vbo[i]);
        // attributeLocationを有効にする
        gl.enableVertexAttribArray(attL[i]);
        // attributeLocationを通知し登録する
        gl.vertexAttribPointer(attL[i], attS[i], gl.FLOAT, false, 0, 0);
    }
};

/*
 * IBOを生成する関数
 */
Simple.create_ibo = function(data){
    // バッファオブジェクトの生成
    var ibo = gl.createBuffer();
    // バッファをバインドする
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
    // バッファにデータをセット
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW);
    // バッファのバインドを無効化
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    // 生成したIBOを返して終了
    return ibo;
};

/*
 * フレームバッファを生成する
 */
Simple.create_framebuffer = function(width, height){
    // フレームバッファオブジェクトの生成
    var framebuffer = gl.createFramebuffer();
    // フレームバッファをバインドする
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
    // レンダーバッファオブジェクトの生成
    var depthrenderbuffer = gl.createRenderbuffer();
    // レンダーバッファをバインドする
    gl.bindRenderbuffer(gl.RENDERBUFFER, depthrenderbuffer);
    // レンダーバッファのフォーマット設定
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
    // フレームバッファへの深度バッファの関連付ける
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthrenderbuffer);

    // テクスチャオブジェクトの生成
    var frametexture = gl.createTexture();
    // テクスチャをバインドする
    gl.bindTexture(gl.TEXTURE_2D, frametexture);
    // テクスチャへイメージを適用
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    // テクスチャパラメーター
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
    // フレームバッファにテクスチャを関連付ける
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, frametexture, 0);
    // テクスチャのバインドを無効化
    gl.bindTexture(gl.TEXTURE_2D, null);
    // レンダーバッファのバインドを無効化
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    // フレームバッファのバインドを無効化
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    // 生成したテクスチャをグローバル変数に代入
    ftexture = frametexture;
    // 返り値
    return {framebuffer: framebuffer, depthrenderbuffer: depthrenderbuffer, texture:ftexture};
};
```

# 少し解説
Webページを表示すると「ん？何が変わったの？」と思うかもですが、ちゃんと裏側ではオフスクリーンができてます。
WebGL Inspectorで確認するとオフスクリーン用の描画が確認できます。
![001_inspecter.png](https://qiita-image-store.s3.amazonaws.com/0/15028/4b9e4ce0-1f14-9998-99b0-78b1b0e7ca7f.png)
オフスクリーンができたので、これでポストエフェクトの下準備ができました。
とりあえず今回はGLSLコード部分の修正だけで色々エフェクトを試してみました。

# ネガポジ反転シェーダーかけてみる

```glsl:simple.html
<!-- オフスクリーンの頂点シェーダー -->
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
attribute vec2 textureCoord;
varying   vec4 vColor;
varying   vec2 vTextureCoord;

void main(void){
    vColor        = color;
    // シェーダーで上下反転させる
//        vTextureCoord = vec2(textureCoord.x, 1.0 - textureCoord.y);
    vTextureCoord = textureCoord;
    gl_Position   = vec4(position, 1.0);
}
</script>
<!-- オフスクリーンのフラグメントシェーダー -->
<script id="fs" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D texture;
varying vec4      vColor;
varying vec2      vTextureCoord;

void main(void){
    vec4 texColor = texture2D(texture, vec2(vTextureCoord.x, 1.0 - vTextureCoord.t));
    // 色の反転
    vec3 negaColor = 1.0 - texColor.rgb;
    gl_FragColor = vec4(negaColor, texColor.a);
}
</script>
```
![002_negapoji.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/751d2b27-1a9e-082a-c135-cfdd528a03f9.gif)

# ブラウン管シェーダーかけてみる

```glsl:simple.html
<!-- オフスクリーンの頂点シェーダー -->
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
attribute vec2 textureCoord;
varying   vec4 vColor;
varying   vec2 vTextureCoord;

void main(void){
    vColor        = color;
    // シェーダーで上下反転させる
//        vTextureCoord = vec2(textureCoord.x, 1.0 - textureCoord.y);
    vTextureCoord = textureCoord;
    gl_Position   = vec4(position, 1.0);
}
</script>
<!-- オフスクリーンのフラグメントシェーダー -->
<script id="fs" type="x-shader/x-fragment">
// ブラウン管シェーダー
precision mediump float;

uniform sampler2D texture;
varying vec4      vColor;
varying vec2      vTextureCoord;

void main(void){
    vec2 centerOrigin = vTextureCoord * 2.0 - 1.0;
    float cornerShade = 1.0 - pow(length(centerOrigin * 0.75), 10.0);
    // sinを使ってノイズを表現
    float noiseLine = (3.0 + sin(vTextureCoord.y * 200.0)) / 4.0;
    vec4 texColor = texture2D(texture, vec2(vTextureCoord.x, 1.0 - vTextureCoord.y));
    gl_FragColor  = vec4(texColor.rgb * noiseLine, texColor.a);
}
```
![003_buraunkan.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/ce99f1f7-e81d-2d76-8a3c-731d868e2b4c.gif)

# モノクロシェーダーかけてみる

```glsl:simple.html
<!-- オフスクリーンの頂点シェーダー -->
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
attribute vec2 textureCoord;
varying   vec4 vColor;
varying   vec2 vTextureCoord;


void main(void){
    vColor        = color;
    // シェーダーで上下反転させる
//        vTextureCoord = vec2(textureCoord.x, 1.0 - textureCoord.y);
    vTextureCoord = textureCoord;
    gl_Position   = vec4(position, 1.0);
}
</script>
<!-- オフスクリーンのフラグメントシェーダー -->
<script id="fs" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D texture;
varying vec4      vColor;
varying vec2      vTextureCoord;

const float redScale   = 0.298912;
const float greenScale = 0.586611;
const float blueScale  = 0.114478;

void main(void){
    vec4 texColor = texture2D(texture, vec2(vTextureCoord.x, 1.0 - vTextureCoord.t));
    // 内積を求める
    float  mono = dot(texColor.rgb, vec3(redScale, greenScale, blueScale));
    gl_FragColor = vec4(vec3(mono), texColor.a);
}
</script>
```
![004_monotoon.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/3e6497ed-4cf4-209d-6509-8d287515398d.gif)

# セピアシェーダーかけてみる

```glsl:simple.html
<!-- オフスクリーンの頂点シェーダー -->
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
attribute vec2 textureCoord;
varying   vec4 vColor;
varying   vec2 vTextureCoord;


void main(void){
    vColor        = color;
    // シェーダーで上下反転させる
    vTextureCoord = vec2(textureCoord.x, 1.0 - textureCoord.y);
//    vTextureCoord = textureCoord;
    gl_Position   = vec4(position, 1.0);
}
</script>
<!-- オフスクリーンのフラグメントシェーダー -->
<script id="fs" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D texture;
varying vec4      vColor;

const float redScale   = 0.298912;
const float greenScale = 0.586611;
const float blueScale  = 0.114478;
const vec3  monochromeScale = vec3(redScale, greenScale, blueScale);

const float sRedScale   = 1.07;
const float sGreenScale = 0.74;
const float sBlueScale  = 0.43;
const vec3  sepiaScale = vec3(sRedScale, sGreenScale, sBlueScale);

varying vec2 vTextureCoord;

void main(){
    // はじを暗くして真ん中を明るくする
    vec2 v = vTextureCoord * 2.0 - 1.0;
    float l = 1.25 - length(v);
    
    vec3 smpColor = texture2D(texture, vTextureCoord).rgb;
    float gray = dot(monochromeScale, smpColor);
    vec3 sepiaColor = vec3(gray) * sepiaScale;
    gl_FragColor = vec4(sepiaColor * l, 1.0);
}
</script>
```
![005_sepia.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/9652d5b7-75b2-d4e5-228f-8787caf3d604.gif)

# モザイクシェーダーかけてみる

```glsl:simple.html
<!-- オフスクリーンの頂点シェーダー -->
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
attribute vec2 textureCoord;
varying   vec4 vColor;
varying   vec2 vTextureCoord;

void main(void){
    vColor        = color;
    // シェーダーで上下反転させる
    vTextureCoord = vec2(textureCoord.x, 1.0 - textureCoord.y);
//    vTextureCoord = textureCoord;
    gl_Position   = vec4(position, 1.0);
}
</script>
<!-- オフスクリーンのフラグメントシェーダー -->
<script id="fs" type="x-shader/x-fragment">
// モザイクシェーダー
precision mediump float;

uniform sampler2D texture;
varying vec4      vColor;
varying vec2      vTextureCoord;

void main(void){
    vec2 uv = vTextureCoord;
    uv = floor(uv * 40.0) / 40.0;
    vec4 color = texture2D(texture, uv);
    gl_FragColor = color;
}
</script>
```
![006_mozaiku.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/6ad9f566-ed7f-ed65-fdaa-818705c3a597.gif)

# テクスチャカラーの変更してみる
ポストエフェクトではないですが、Live2Dモデルのテクスチャカラーも以下のコードで変更できます。
背景画像やエフェクトに合わせてテクスチャカラーを変更するといい感じになるかと思います。
setBaseColorはモデル描画の際、全体に掛け合わされる色設定ができます。

```js:Simple.js
    // テクスチャカラー(alpha, red, green, blue)
    live2DModel.drawParamWebGL.setBaseColor(1.0, 1.0, 0.0, 0.0);    // 赤
//    live2DModel.drawParamWebGL.setBaseColor(1.0, 0.0, 0.0, 0.0);    // 黒
//    live2DModel.drawParamWebGL.setBaseColor(1.0, 0.0, 1.0, 0.0);    // 緑
//    live2DModel.drawParamWebGL.setBaseColor(1.0, 0.0, 0.0, 1.0);    // 青
//    live2DModel.drawParamWebGL.setBaseColor(1.0, 1.0, 1.0, 1.0);    // 通常
```
![008_texture.png](https://qiita-image-store.s3.amazonaws.com/0/15028/cff9b45e-ddd4-3dc2-089a-da53c3384dd1.png)

他にも色々なシェーダーがあるので、色々試してみると面白いかと思います！

2015/12/01追記
cocos2d-xのSimpleプロジェクトの場合は、SampleLive2DSprite.cppで以下のように追記すれば、テクスチャカラー変更できます
>live2DModel->getDrawParam()->setBaseColor(1.0, 1.0, 0.0, 0.0);