THREE.jsでオリジナルのポストプロセスをやってみる
前回オフスクリーンレンダリングについて書きました。
今回は、オフスクリーンレンダリングを利用して、ポストプロセスをやってみたいと思います。
ポストプロセスを簡単に言うと、作成済みのシーンに対して、あとから何か処理を行うことです。
今回は以下のような2つのシーンを掛け合せてみようと思います。
ベースとなるシーンを描く
まずはベースとなるシーンをオフスクリーンレンダリングして、その情報をテクスチャとしてシェーダーに情報を送ります。 この時点では何も表示されません。
JS
const baseVert = require('./../shader/base.vert'); const baseFrag = require('./../shader/base.frag'); var theta = 0; // ベースの描画処理(renderTarget への描画用) baseScene = new THREE.Scene(); //ベースの描画処理用カメラ baseCamera = new THREE.PerspectiveCamera(50, windowWidth / windowHeight, 0.1, 1000); baseCamera.position.z = 20; //ベース用のマテリアルとジオメトリ var baseGeometry = new THREE.BoxGeometry(5, 5, 5); var baseMaterial = new THREE.ShaderMaterial({ vertexShader: baseVert, fragmentShader: baseFrag }); var baseMesh = new THREE.Mesh(baseGeometry, baseMaterial); baseScene.add(baseMesh); //オフスクリーンレンダリング用 renderTarget = new THREE.WebGLRenderTarget(windowWidth, windowHeight, { magFilter: THREE.NearestFilter, minFilter: THREE.NearestFilter, wrapS: THREE.ClampToEdgeWrapping, wrapT: THREE.ClampToEdgeWrapping }); render(); function render() { theta += 0.01; baseMesh.rotation.set(theta,theta,0); //オフスクリーンレンダリング renderer.setClearColor(new THREE.Color(0xffffff), 1.0); renderer.render(baseScene, baseCamera, renderTarget); requestAnimationFrame(render); }
頂点シェーダー
varying vec3 vNormal; void main() { vNormal = normalMatrix * normal; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }
フラグメントシェーダー
precision mediump float; varying vec3 vNormal; void main(){ gl_FragColor = vec4(vec3(vNormal),1.0) ; }
ポストプロセス用のシーンを描く
ポストプロセスは通常画面全体に対して適応します。 その為、画面全体を覆うポリゴンを作成します。
const postVert = require('./../shader/post.vert'); const postFrag = require('./../shader/post.frag'); var clock = new THREE.Clock(); var time = 0.0; //ポストプロセス用 postScene = new THREE.Scene(); postCamera = new THREE.PerspectiveCamera(60, windowWidth / windowHeight, 0.1, 1000); postCamera.position.z = 20; //ポストプロセス用ジオメトリ、マテリアル var postGeometry = new THREE.Geometry(); postGeometry.vertices = [ new THREE.Vector3(-1.0 * aspect, 1.0, 0.0), new THREE.Vector3(1.0 * aspect, 1.0, 0.0), new THREE.Vector3(-1.0 * aspect, -1.0, 0.0), new THREE.Vector3(1.0 * aspect, -1.0, 0.0) ]; postGeometry.faces = [ new THREE.Face3(0, 2, 1), new THREE.Face3(1, 2, 3) ]; var postUniforms = { 'uTex': { type: 't', //ベース用シーンをテクスチャとして渡す value: renderTarget }, 'uTime': { type: 'f', value: time } }; var postMaterial = new THREE.ShaderMaterial({ uniforms: postUniforms, vertexShader: postVert, fragmentShader: postFrag }); var postMesh = new THREE.Mesh(postGeometry, postMaterial); postScene.add(postMesh); render(); function render() { //画面にレンダリング renderer.setClearColor(new THREE.Color(0x000000), 1.0); renderer.render(postScene, postCamera); requestAnimationFrame(render); }
頂点シェーダー
varying vec2 vUv; void main() { gl_Position = vec4( position, 1.0 ); //uv座標はpositionをそのまま使う。中央の座標の(0.0,0.0)にする為 +1.0) * 0.5する vUv = (position.xy + 1.0) * 0.5; }
フラグメントシェーダー
precision mediump float; varying vec2 vUv; uniform float uTime; //ベースシーンのレンダリング結果 uniform sampler2D uTex; float rnd(vec2 n){ return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); } void main(){ float n = rnd(gl_FragCoord.st + mod(uTime, 10.0)) * 0.5 + 0.7; gl_FragColor = texture2D(uTex, vUv) * vec4(vec3(n),1.0); }
ベース用シーンとポストプロセス用シーンを組み合わせる
両方合わせると以下のようになります。
const baseVert = require('./../shader/base.vert'); const baseFrag = require('./../shader/base.frag'); const postVert = require('./../shader/post.vert'); const postFrag = require('./../shader/post.frag'); window.onload = function () { var renderer; var postCamera, postScene; var baseCamera, baseScene; var renderTarget; var theta = 0; var clock = new THREE.Clock(); var time = 0.0; var windowWidth = window.innerWidth; var windowHeight = window.innerHeight; var aspect = windowWidth / windowHeight; // rendererの作成 var renderer = new THREE.WebGLRenderer(); renderer.setClearColor('#CCC'); // canvasをbodyに追加 document.body.appendChild(renderer.domElement); // canvasをリサイズ renderer.setSize(windowWidth, windowHeight); // ベースの描画処理(renderTarget への描画用) baseScene = new THREE.Scene(); //ベースの描画処理用カメラ baseCamera = new THREE.PerspectiveCamera(50, windowWidth / windowHeight, 0.1, 1000); baseCamera.position.z = 20; var baseUniforms = { 'uResolution': { type: 'v2', value: new THREE.Vector2(windowWidth, windowHeight) } }; //ベース用のマテリアルとジオメトリ var baseGeometry = new THREE.BoxGeometry(5, 5, 5); var baseMaterial = new THREE.ShaderMaterial({ uniforms: baseUniforms, vertexShader: baseVert, fragmentShader: baseFrag }); var baseMesh = new THREE.Mesh(baseGeometry, baseMaterial); baseScene.add(baseMesh); //オフスクリーンレンダリング用 renderTarget = new THREE.WebGLRenderTarget(windowWidth, windowHeight, { magFilter: THREE.NearestFilter, minFilter: THREE.NearestFilter, wrapS: THREE.ClampToEdgeWrapping, wrapT: THREE.ClampToEdgeWrapping }); //ポストプロセス用 postScene = new THREE.Scene(); postCamera = new THREE.PerspectiveCamera(60, windowWidth / windowHeight, 0.1, 1000); postCamera.position.z = 20; //ポストプロセス用ジオメトリ、マテリアル var postGeometry = new THREE.Geometry(); postGeometry.vertices = [ new THREE.Vector3(-1.0 * aspect, 1.0, 0.0), new THREE.Vector3(1.0 * aspect, 1.0, 0.0), new THREE.Vector3(-1.0 * aspect, -1.0, 0.0), new THREE.Vector3(1.0 * aspect, -1.0, 0.0) ]; postGeometry.faces = [ new THREE.Face3(0, 2, 1), new THREE.Face3(1, 2, 3) ]; var postUniforms = { 'uTex': { type: 't', value: renderTarget }, 'uTime': { type: 'f', value: time } }; var postMaterial = new THREE.ShaderMaterial({ uniforms: postUniforms, vertexShader: postVert, fragmentShader: postFrag }); var postMesh = new THREE.Mesh(postGeometry, postMaterial); postScene.add(postMesh); render(); function render() { theta += 0.01; baseMesh.rotation.set(theta, theta, 0); time = clock.getElapsedTime(); postMaterial.uniforms.uTime.value = time; //オフスクリーンレンダリング renderer.setClearColor(new THREE.Color(0xffffff), 1.0); renderer.render(baseScene, baseCamera, renderTarget); //画面にレンダリング renderer.setClearColor(new THREE.Color(0x000000), 1.0); renderer.render(postScene, postCamera); requestAnimationFrame(render); } };
表示結果
See the Pen wpKZEd by nogson (@satofaction) on CodePen.