WebGLとCanvas2Dを併用できないかな〜とぼんやり考えてたら、以下のAPIを見つけました。
なんと第1引数のimageにimg要素、canvas要素、video要素が指定できるとっ!
これで描画して更新すれば、canvas2DにWebGL描画できそうなのでやってみました。
WebGL描画をCanvas2Dに描画
ソースはこんな感じです。
ここには書いてないですが、別途WebGLのcanvasをgetElementByIdし、変数canvasを渡しておきdrawImageの第1引数に指定します
// WebGLのcanvasとは別に、2D用のcanvasタグをHTML側に作って参照する
var can_2d = document.getElementById("2dcanvas");
var ctx_2d = can_2d.getContext("2d");
// WebGLの描画処理後に呼び出す
function draw_2DCanvas(){
// canvasクリア
ctx_2d.clearRect(0,0, can_2d.width, can_2d.height);
// webglのcanvasを指定
ctx_2d.drawImage(canvas, 0, 0);
// 繰り返し
requestAnimationFrame(draw_2DCanvas);
}
これをLive2D WebGL SDKに適用してみました。
左がWebGL、右がCanvas2D。ちゃんと毎フレーム描画されています
あとはWebGLのcanvasをCSSでdisplay:noneして消してやればOK!
canvas2Dでパーティクル表示
せっかくなので、WebGL描画の上にCanvas2Dでパーティクル加えてみました。
つまり、この方法を使えばCanvas2Dのスキルを活かしつつ、WebGLも活用できますね!
Live2D SDKのSimpleプロジェクトを元にカスタムしました。
HTML側はcanvas2D用のタグを1行追加しています。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Live2D Simple</title>
</meta>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=4.0">
</meta>
</head>
<body onload="Simple(); draw_2DCanvas();">
<canvas id="glcanvas" style="border:dashed 1px #CCC;display:none;"></canvas>
<canvas id="2dcanvas" 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>
</body>
</html>
js側は、258行目辺りからのcanvas2D描画を追加しました。
パーティクルは以下のサイトを参考にさせていただきました。
→ HTML5+Canvasでパーティクルっぽいのを作ってみる
var CANVAS_SIZE = 300;
var canvas;
// JavaScriptで発生したエラーを取得
window.onerror = function(msg, url, line, col, error) {
var errmsg = "file:" + url + "<br>line:" + line + " " + msg;
Simple.myerror(errmsg);
}
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オブジェクトを取得
canvas = document.getElementById("glcanvas");
canvas.width = canvas.height = CANVAS_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
};
var gl = Simple.getWebGLContext(canvas, para);
if (!gl) {
Simple.myerror("Failed to create WebGL context.");
return;
}
// 描画エリアを白でクリア
gl.clearColor( 0.0 , 0.0 , 0.0 , 0.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 );
}
//------------ 描画ループ ------------
(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コンテキスト*/)
{
// Canvasをクリアする
gl.clear(gl.COLOR_BUFFER_BIT);
// 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.0,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));
// Live2Dモデルを更新して描画
live2DModel.update(); // 現在のパラメータに合わせて頂点等を計算
live2DModel.draw(); // 描画
};
/*
* 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>");
};
/*
* Canvas2D描画
* http://www.webopixel.net/javascript/1003.html
*/
var can_2d = document.getElementById("2dcanvas");
can_2d.width = can_2d.height = CANVAS_SIZE;
var ctx_2d = can_2d.getContext("2d");
var Particle = function(scale, color, speed) {
this.scale = scale; //大きさ
this.color = color; //色
this.speed = speed; //速度
this.position = { // 位置
x: 100,
y: 100
};
};
Particle.prototype.draw = function() {
ctx_2d.beginPath();
ctx_2d.arc(this.position.x, this.position.y, this.scale, 0, 2*Math.PI, false);
ctx_2d.fillStyle = this.color;
ctx_2d.fill();
};
var density = 40; //パーティクルの密度
var particles = []; //パーティクルをまとめる配列
var colors = ['#D0A000', '#6DD0A5', '#E084C5'];
for (var i=0; i<density; i++) {
var color = colors[~~(Math.random()*3)];
var scale = ~~(Math.random()*(8-3))+3;
particles[i] = new Particle(scale, color, scale/2);
particles[i].position.x = Math.random()*can_2d.width;
particles[i].position.y = Math.random()*can_2d.height;
particles[i].draw();
}
function draw_2DCanvas(){
// canvasクリア
ctx_2d.clearRect(0,0, can_2d.width, can_2d.height);
// webglのcanvasを指定
ctx_2d.drawImage(canvas, 0, 0);
// パーティクルを描画する
draw_Particle();
// 繰り返し
requestAnimationFrame(draw_2DCanvas);
}
function draw_Particle() {
for (var i=0; i<density; i++) {
particles[i].position.x += particles[i].speed;
particles[i].draw();
if (particles[i].position.x > can_2d.width) particles[i].position.x = -30;
}
}
CSSで描画順を制御する
ちなみに上記やり方でなくても、CanvasをCSSで重ねてやる方法もあります。
WebGLのcanvas3枚重ねた例ですが、z-indexとabsoluteを使っていけます。
以前作ったものですが、こちらが参考になるかと思います。
→ WebGLのcanvas3枚重ねたサンプル
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Test</title>
<style>
.waku{
position: relative;
margin:0 auto;
width:500px;
height:auto;
}
#frontcanvas{
position:absolute;
z-index: 50;
left:0px;
top:0px;
}
#glcanvas{
position:absolute;
z-index: 1;
left:0px;
top:0px;
}
#Live2Dcanvas{
position:absolute;
z-index: 10;
left:0px;
top:0px;
}
</style>
</head>
<body>
<div class="waku">
<canvas id="frontcanvas"></canvas>
<canvas id="Live2Dcanvas"></canvas>
<canvas id="glcanvas"></canvas>
</div>
</body>
</html>
WebGLのjsライブラリなどを組み合わせて描画するのは手間なので、これで簡単に組み合わせられます!