忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

Three.jsでDirectionalLightの影を描く

DirectionalLightの影を描くのにハマったのでメモしておきます。どうやらDirectionalLightの影は他の光源とちがって、影を描くのに一手間必要なようです。


ソースコード

<canvas id="canvas" width="600" height="600"></canvas>
床の角度:<input id="angleInput" type="range"></input>

<script src="three.min.js"></script>
<script>


function createCamera(){
	var camera = new THREE.PerspectiveCamera(60, canvas.width / canvas.height, 1, 3000);

	camera.position.z = 200;
	camera.position.y = 100;
	camera.up = new THREE.Vector3(0, 1, 0);
	camera.lookAt(new THREE.Vector3());
	return camera;
}

function createSphere(){
	var sphere = new THREE.Mesh(new THREE.SphereGeometry(20, 32, 32), new THREE.MeshLambertMaterial({color: "crimson"}));
	sphere.position.y = 50;
	return sphere;
}

function createPlane(){
	var plane = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), new THREE.MeshLambertMaterial({color: "lawngreen"}));
	plane.rotation.x = Math.PI * 3 / 2;
	return plane;
}

function createLight(){
	var light = new THREE.DirectionalLight(0xffffff, 1);
	light.position.set(-150, 150, 0);
	light.shadow.camera.right = 100;
	light.shadow.camera.left = -100;
	light.shadow.camera.top = -100;
	light.shadow.camera.bottom = 100;
	return light;
}

function initInputs(plane){
	var angleInput = document.getElementById("angleInput");
	angleInput.valueAsNumber = plane.rotation.x - Math.PI * 3 / 2;
	angleInput.min = "" + 0;
	angleInput.max = "" + (Math.PI * 2);
	angleInput.step = "" + (Math.PI / 60);
	angleInput.oninput = () => {
		plane.rotation.x = angleInput.valueAsNumber + Math.PI * 3 / 2;
	};
}
      
function startAnimation(renderer, scene, camera){
	window.requestAnimationFrame(() => startAnimation(renderer, scene, camera));
	renderer.render(scene, camera);
}

function init3DObjects(scene){
	var sphere = createSphere();
	scene.add(sphere);

	var plane = createPlane();
	scene.add(plane);

	scene.add( new THREE.AmbientLight( 0x333333) );

	var light = createLight();
	scene.add(light);

	light.castShadow = true;
	sphere.castShadow = true;
	plane.receiveShadow = true;

	initInputs(plane);
}

function createRenderer(){
	var renderer = new THREE.WebGLRenderer({canvas:canvas, antialias: true});
	renderer.setClearColor("cornflowerblue", 1);
	renderer.shadowMap.enabled = true;
	return renderer;
}

function main(){
	var renderer = createRenderer();
	var scene = new THREE.Scene();
	var camera = createCamera();
	scene.add(camera);

	init3DObjects(scene);

	startAnimation(renderer, scene, camera);
}

main();
</script>

実行結果

このプログラムは床に球体の影を描きます。つまみで床の角度を動かしてみてください。影の形を変えることができます。

私が詰まっていたのは、次の4行を書いていなかったせいです:

	light.shadow.camera.right = 100;
	light.shadow.camera.left = -100;
	light.shadow.camera.top = -100;
	light.shadow.camera.bottom = 100;

この4行がないと、影は描かれません:

詳しいことはわかりませんが、この4行はシャドウマップの範囲を指定しているように見えます。この範囲の内部では影が描かれますが、この範囲の外では描かれないということでしょう。影を描くのにはパフォーマンス上のコストがかかるので、範囲を制限するのは意味のあることですが、まったく指定しないときには自動で範囲指定をやってくれても良かったのにと負け惜しみを言っておきます。

拍手[1回]

PR