LoginSignup
3
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-07-30

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

基本的なオフスクリーンの仕方

まずはLive2D WebGL SDKのSampleプロジェクトを元に修正していきます。
修正箇所としては、主に以下3点です。
1)オフスクリーン処理を追加
2)オフスクリーン描画するPlaneオブジェクトを追加
3)オフスクリーン用のGLSLコード追加

ソースコード

HTML部分には、GLSLコードと行列演算ライブラリ追加しました。
行列演算ライブラリはwgld.orgのminMatrix.jsをお借りしました

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の方にはオフスクリーン処理の追加しました

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
オフスクリーンができたので、これでポストエフェクトの下準備ができました。
とりあえず今回は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

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

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

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

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

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

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

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

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

テクスチャカラーの変更してみる

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

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

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

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

live2DModel->getDrawParam()->setBaseColor(1.0, 1.0, 0.0, 0.0);

3
3
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
3
3