前に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をお借りしました
<!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の方にはオフスクリーン処理の追加しました
// 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で確認するとオフスクリーン用の描画が確認できます。
オフスクリーンができたので、これでポストエフェクトの下準備ができました。
とりあえず今回はGLSLコード部分の修正だけで色々エフェクトを試してみました。
ネガポジ反転シェーダーかけてみる
<!-- オフスクリーンの頂点シェーダー -->
<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>
ブラウン管シェーダーかけてみる
<!-- オフスクリーンの頂点シェーダー -->
<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);
}
モノクロシェーダーかけてみる
<!-- オフスクリーンの頂点シェーダー -->
<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>
セピアシェーダーかけてみる
<!-- オフスクリーンの頂点シェーダー -->
<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>
モザイクシェーダーかけてみる
<!-- オフスクリーンの頂点シェーダー -->
<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>
テクスチャカラーの変更してみる
ポストエフェクトではないですが、Live2Dモデルのテクスチャカラーも以下のコードで変更できます。
背景画像やエフェクトに合わせてテクスチャカラーを変更するといい感じになるかと思います。
setBaseColorはモデル描画の際、全体に掛け合わされる色設定ができます。
// テクスチャカラー(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); // 通常
他にも色々なシェーダーがあるので、色々試してみると面白いかと思います!
2015/12/01追記
cocos2d-xのSimpleプロジェクトの場合は、SampleLive2DSprite.cppで以下のように追記すれば、テクスチャカラー変更できます
live2DModel->getDrawParam()->setBaseColor(1.0, 1.0, 0.0, 0.0);