コピペで慣れる生WebGL 04_フレームバッファー
前回、03_ビデオと3Dについて紹介しました。行列を使い3Dをレンダリングをする手法を紹介しました。今回はフレームバッファーを使ったサンプルを紹介します。
フレームバッファ
フレームバッファーを利用してオフスクリーンレンダリングの処理を行います。オフスクリーンレンダリングとは画面へ直接書き込まない描画処理のことを指し、メモリに描画結果を書き込んでいきます。オフスクリーンでレンダリングしたものをテクスチャとして利用することができ、かなりのパワーを発揮します。
以下のサイトでオフスクリーンレンダリングやフレームバッファについて詳しく説明しています。
フレームバッファを使ったオフスクリーンレンダリングを行うことでブルームエフェクトやブラーエフェクトなど様々なエフェクトを与えることができ、表現の幅は広がると思います。
今回紹介するサンプルです。
マウスの動きに応じてシーンを変える簡単なサンプルです。2つのシーンを毎フレームフレームバッファーにレンダリングし、マウスの位置に応じてシーンを遷移させています。
生WebGLバージョン+コード
three.jsバージョン+コード
立方体・カメラの作成と前回のサンプルと同じものを使用しています。
シェーダーに関しては生WebGLバージョンとthree.jsバージョンは比較しやすくなればと思い同じものを利用しています。シェーダに関しては、以前も掲載しましたが以下のサイトがとても詳しく説明しております。
『フレームバッファー』を中心に説明して行きます。
フレームバッファの初期化
生WebGLでフレームバッファー作成します。
// フレームバッファ作成
function createFramebuffer() {
// フレームバッファにバインドするテクスチャ作成
let targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
windowWid,
windowHig,
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_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// フレームバッファを作成
let fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// フレームバッファへのテクスチャの紐付け
let attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, 0);
// デプスバッファを作成
const depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
//レンダラーバッファーをデブスバッファーとして設定
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, windowWid, windowHig);
// レンダラーバッファをフレームバッファに紐付ける
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return { framebuffer: fb, texture: targetTexture, depthBuffer: depthBuffer };
}
three.jsだと、WebGLRenderTargetを使うだけで完了します。
new THREE.WebGLRenderTarget(windowWid, windowHig)
フレームバッファを使用し、オフスクリーンレンダリングする
先程作成したフレームバッファーを使用し、テクスチャにオフスクリーンレンダリングします。生WebGLだと以下のようになり、
for (let ii = 0; ii < 2; ii++) {
// ターゲットのフレームバッファー設定する
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffers[ii].framebuffer);
gl.viewport(0, 0, windowWid, windowHig);
gl.clearColor(0, 0, 0, 1);
gl.clearDepth(1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 背景の描画
drawBg(scenes[ii]);
gl.clear(gl.DEPTH_BUFFER_BIT);
// 立方体の描画
drawBox(scenes[ii], ii);
}
前回のサンプルとほとんど同じですが、1点の大きな違いはdraw関数を実行する前に
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffers[ii].framebuffer);
とターゲットとなるフレームバッファー設定することです。
three.jsだと以下のようになります。
for (let ii = 0; ii < scenes.length; ii++) {
renderer.clearTarget(renderTargetArr[ii], true, true, true);
bgMat.uniforms.uColor0.value.set(
sceneColors[ii][0][0],
sceneColors[ii][0][1],
sceneColors[ii][0][2]
);
bgMat.uniforms.uColor1.value.set(
sceneColors[ii][1][0],
sceneColors[ii][1][1],
sceneColors[ii][1][2]
);
renderer.render(bgScene, perspectiveCamera, renderTargetArr[ii]);
renderer.clearTarget(renderTargetArr[ii], false, true, false);
boxMat.uniforms.uColor0.value.set(
sceneColors[ii][0][0],
sceneColors[ii][0][1],
sceneColors[ii][0][2]
);
boxMat.uniforms.uColor1.value.set(
sceneColors[ii][1][0],
sceneColors[ii][1][1],
sceneColors[ii][1][2]
);
renderer.render(scenes[ii], perspectiveCamera, renderTargetArr[ii]);
}
three.jsでは
renderer.render(scenes[ii], perspectiveCamera, renderTargetArr[ii]);
のようにrenderTargetを引数として設定しておきます。
オフスクリーンレンダリングでレンダリングしたテクスチャーを利用し、スクリンーにレンダリングする
function drawOutput() {
gl.useProgram(outputProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
// フレームテクスチャ
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, frameBuffers[0].texture);
gl.uniform1i(outputUniforms.uMainTexture, 0);
//
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, frameBuffers[1].texture);
gl.uniform1i(outputUniforms.uMain2Texture, 1);
//
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, perlinTexture);
gl.uniform1i(outputUniforms.uTransTexture, 2);
gl.uniform1f(outputUniforms.uTrans, obj.trans);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
シェーダーは以下のようになっています。
precision mediump float;
varying vec2 vUv;
uniform sampler2D uMainTexture;
uniform sampler2D uMain2Texture;
uniform sampler2D uTransTexture;
uniform float uTrans;
void main(){
vec4 color1 = texture2D(uMainTexture, vUv);
vec4 color2 = texture2D(uMain2Texture, vUv);
float trans = texture2D(uTransTexture, vUv).r;
float rate = clamp( uTrans * 1.5 - trans * 0.5, 0.0, 1.0);
gl_FragColor = mix(color1, color2, rate);
// gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
2つのシーンをそれぞれuMainTextureとuMain2Textureとして、uTransTextureとuTransで遷移させています。
three.jsの場合、最終的な描画用にmainMeshを作成し、mainMeshをレンダリングするためにmainSceneを用意します。あらかじめmainMeshのマテリアルにのuniformsの値にuMainTexture、uMain2Textureをそれぞれ設定しておきます。
let geo = new THREE.PlaneGeometry(2, 2);
let mat = new THREE.RawShaderMaterial({
uniforms: {
uMainTexture: { value: renderTargetArr[0].texture },
uMain2Texture: { value: renderTargetArr[1].texture },
uTransTexture: { value: null },
uTrans: { value: 0 }
},
vertexShader: renderVertexShaderSrc,
fragmentShader: renderFragmentShaderSrc
});
mainMesh = new THREE.Mesh(geo, mat);
mainScene.add(mainMesh);
毎フレーム以下のように実行します。
mainMesh.material.uniforms.uTrans.value = obj.trans;
renderer.render(mainScene, perspectiveCamera);
やはりthree.js。とてもシンプルですね。
今回はフレームバッファーを紹介しました。
用途が非常に広くて、使用する機会が非常に多いところです。ネット上で色々なテクニックを紹介していたりするサイトや本などあり、参考になるのではないかと思います。
CodePenでも多くのサンプルがあるので、いくつか紹介しておきます。
これは生WebGL書かれています。
こちらはthree.jsで書かれています。
- 00 はじめに
- 01 ディストーションエフェクト
- 02 パーティクル
- 03 ビデオと3D