2021年05月04日 - WebVR・Three.js

Three.jsでオフスクリーンレンダリング

Three.jsでオフスクリーンレンダリング

オフスクリーンレンダリングが使えるようになると、表現の幅が広がります。そこで「シェーダで画像エフェクト」に続き、Three.jsでオフスクリーンレンダリングを試しました。※Three.jsはr128を使用しています。

Three.jsでオフスクリーンレンダリング

オフスクリーンレンダリングは、スクリーンではなくメモリ上にレンダリングした結果を、そのままテクスチャとして使用する手法です。

● トーラスを生成

まずは、TorusGeometryを使用してトーラスを生成します。マテリアルはライティングを必要としないMeshNormalMaterialを使用します。

トーラスを生成
const geometry = new THREE.TorusGeometry(2.5,1,32,100);
const material = new THREE.MeshNormalMaterial();
const torus = new THREE.Mesh(geometry,material);
scene.add(torus);

● オフスクリーンレンダリング

トーラスを生成して、オフスクリーンレンダリング用のシーンに追加します。次にスクリーン用の平面にレンダーターゲットオブジェクトのテクスチャを設定して、レンダリングします。

//オフスクリーンレンダリング用のシーン
const sceneRTT = new THREE.Scene();

//オフスクリーンレンダリング用のカメラ
const cameraRTT = new THREE.PerspectiveCamera(50,1,1,50);
cameraRTT.position.set(0,0,11);

//レンダーターゲットオブジェクト
const renderTarget = new THREE.WebGLRenderTarget(1024,1024);

//トーラス
const geometry = new THREE.TorusGeometry(2.5,1,32,100);
const material = new THREE.MeshNormalMaterial();
const torus = new THREE.Mesh(geometry,material);
sceneRTT.add(torus);

//スクリーン用の平面
const planeGeometry = new THREE.PlaneGeometry(4,4,2,2);
const planeMaterial = new THREE.MeshPhongMaterial({
	color:0xFFFFFF,

	//テクスチャを設定
	map:renderTarget.texture,
});
const plane = new THREE.Mesh(planeGeometry,planeMaterial);
scene.add(plane);

function rendering(){
	requestAnimationFrame(rendering);

	//オフスクリーンレンダリング
	renderer.setClearColor(0x115558,1.0);
	renderer.setRenderTarget(renderTarget);
	renderer.render(sceneRTT,cameraRTT);
	renderer.setRenderTarget(null);

	//レンダリング
	renderer.setClearColor(0x000000,1.0);
	renderer.render(scene,camera);
}

● script.js

必要なライブラリを読み込みます。

<script src="js/preloadjs.min.js"></script>
<script src="js/TweenMax.min.js"></script>
<script src="js/script.js" type="module"></script>

完成したscript.jsになります。

//===============================================================
// Import Library
//===============================================================
import * as THREE from './lib/three_jsm/three.module.js';
import { OrbitControls } from './lib/three_jsm/OrbitControls.js';
import { scene, camera, container, renderer } from './lib/basescene.js';

//===============================================================
// Init
//===============================================================
window.addEventListener('load',function(){
   init();
});

let orbitControls;
let torus;
let sceneRTT,cameraRTT,renderTarget;
let time = 0;

function init(){
	setLoading();
}

function setLoading(){
	TweenMax.to('.loader',0.1,{opacity:1});
	TweenMax.to('#loader_wrapper',1,{
        opacity:0,
        delay:1,
        onComplete: function(){
            document.getElementById('loader_wrapper').style.display = 'none';
            TweenMax.to('.loader',0,{opacity:0});
        }
    });
	threeWorld();
	setLight();
	setControll();
	rendering();
}

//===============================================================
// Create World
//===============================================================
function threeWorld(){
	renderer.outputEncoding = THREE.sRGBEncoding;

	sceneRTT = new THREE.Scene();
	cameraRTT = new THREE.PerspectiveCamera(50,1,1,50);
	cameraRTT.position.set(0,0,11);
	renderTarget = new THREE.WebGLRenderTarget(1024,1024);

	const geometry = new THREE.TorusGeometry(2.5,1,32,100);
	const material = new THREE.MeshNormalMaterial();
	torus = new THREE.Mesh(geometry,material);
	sceneRTT.add(torus);

	const planeGeometry = new THREE.PlaneGeometry(4,4,2,2);
	const planeMaterial = new THREE.MeshPhongMaterial({
		color:0xFFFFFF,
		shininess:70,
		reflectivity:1,
		map:renderTarget.texture,
		side:THREE.DoubleSide
	});
	const plane = new THREE.Mesh(planeGeometry,planeMaterial);
	scene.add(plane);
}

function setLight(){
	const ambientlight = new THREE.AmbientLight(0x333333,0.1);
	scene.add(ambientlight);

	const pointLight = new THREE.PointLight(0XFFFFFF,4,3.7,1);
	pointLight.position.set(0,0,3);
	scene.add(pointLight);

	const pointLightHelper = new THREE.PointLightHelper(pointLight,0.3);
	scene.add(pointLightHelper);
}

function setControll(){
	document.addEventListener('touchmove',function(e){e.preventDefault();},{passive:false});
	orbitControls = new OrbitControls(camera,renderer.domElement);
	orbitControls.enableDamping = true;
	orbitControls.dampingFactor = 0.5;
}

function rendering(){
	requestAnimationFrame(rendering);

	if(orbitControls){
		orbitControls.update();
	}

	time++;
	torus.rotation.x = time * 0.25 * Math.PI / 180;
	torus.rotation.y = time * 0.5 * Math.PI / 180;

	renderer.setClearColor(0x115558,1.0);
	renderer.setRenderTarget(renderTarget);
	renderer.render(sceneRTT,cameraRTT);
	renderer.setRenderTarget(null);

	renderer.setClearColor(0x000000,1.0);
	renderer.render(scene,camera);
}

● basescene.js

sceneやcameraなど基本的な設定を管理するbasescene.jsです。

//===============================================================
// Import Library
//===============================================================
import * as THREE from './three_jsm/three.module.js';

//===============================================================
// Base scene
//===============================================================
let scene,camera,container,renderer;

init();

function init(){
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,1,100);
	camera.position.set(0,0,10);
	scene.add(camera);

	renderer = new THREE.WebGLRenderer({antialias:true});
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setSize(window.innerWidth,window.innerHeight);
	renderer.setClearColor(new THREE.Color(0x000000));

	container = document.querySelector('#canvas_vr');
	container.appendChild(renderer.domElement);

	window.addEventListener('resize',function(){
		camera.aspect = window.innerWidth/window.innerHeight;
		camera.updateProjectionMatrix();
		renderer.setSize(window.innerWidth,window.innerHeight);
	},false);
}

export { scene, camera, container, renderer }

完成したデモになります。Three.jsでオフスクリーンレンダリングを試しました。