three.js 03-06 之 RectAreaLight 光源

时间:2022-01-26 04:55:30

    上篇我们介绍了 three.js 中比较特殊的 HemisphereLight 半球光光源。本篇我们将介绍另一种比较特殊的光源 RectAreaLight 平面光光源。这种光源可以定义一个发光的矩形,我们姑且先把它理解成一面会反光的镜面。在旧版本中,这种光源并不在标准 three.js 库中,而是在它的扩展库中,而且名称也不同叫 AreaLight。但是新版已经包含在标准库中了。所以对于旧版的用法在此不作介绍,感兴趣的读者可以自己研究,这里我们以新版(本人试验的版本基于 r88) API 来作介绍。照例,我们还是先上一个完整的示例,代码如下所示:

<!DOCTYPE html>
<html>
<head>
    <title>示例 03.06 - 平面光光源</title>

	<script src="../build/js/libs/dat.gui.min.js"></script>
	<script src="../build/js/libs/stats.min.js"></script>
	<script src="../build/three.js"></script>
	<script src="../build/js/Detector.js"></script>
	<script src="../build/js/controls/OrbitControls.js"></script>
	
	<script src="../jquery/jquery-3.2.1.min.js"></script>
    <style>
        body {
            /* 设置 margin 为 0,并且 overflow 为 hidden,来完成页面样式 */
            margin: 0;
            overflow: hidden;
        }
		/* 统计对象的样式 */
		#Stats-output {
			position: absolute;
			left: 0px;
			top: 0px;
		}
    </style>
</head>
<body>

<!-- 用于 WebGL 输出的 Div -->
<div id="WebGL-output"></div>
<!-- 用于统计 FPS 输出的 Div -->
<div id="Stats-output"></div>

<!-- 运行 Three.js 示例的 Javascript 代码 -->
<script type="text/javascript">

	var scene;
	var camera;
	var render;
	var controls;
	var stats;
	var guiControls;
	
	var plane;
	var cube;
	var sphere;
	
	var areaLights = [];

    $(function() {
		if (!Detector.webgl)
			Detector.addGetWebGLMessage();
		
		stats = initStats();
		
		scene = new THREE.Scene();
		camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2147483647); // 2147483647
		camera.position.set(0, 90, 90);
		// antialias 抗锯齿
		render = new THREE.WebGLRenderer({
			antialias: true
		});
		render.setClearColor(0x000000, 1.0);
		render.setSize(window.innerWidth, window.innerHeight);
		render.setPixelRatio(window.devicePixelRatio);
		render.shadowMap.enabled = true; // 允许阴影投射
		render.shadowMap.type = THREE.PCFSoftShadowMap;
		render.gammaInput = true;
		render.gammaOutput = true;
		
		$('#WebGL-output')[0].appendChild(render.domElement);
		window.addEventListener('resize', onWindowResize, false);
		var target = new THREE.Vector3(0, 0, 0);
		controls = new THREE.OrbitControls(camera, render.domElement);
		controls.target = target;
		camera.lookAt(target);
		
		// 加入一个平面(带草的纹理)
		var planeGeometry = new THREE.PlaneGeometry(500, 500, 1, 1);
		var planeMaterial =  new THREE.MeshStandardMaterial( {color: 0x808080, roughness: 0.045, metalness: 0.1} );
		plane = new THREE.Mesh(planeGeometry, planeMaterial);
		plane.rotation.x = -0.5 * Math.PI; // 沿着 X轴旋转-90°
		plane.receiveShadow = true; // 非线框几何平面接收阴影
		scene.add(plane);
		
		// 加入一个立方体
		var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
		var cubeMaterial = new THREE.MeshStandardMaterial( {color: 0xA00000, roughness: 0.1, metalness: 0.1} );
		cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
		cube.position.x = -10;
		cube.position.y = 3;
		cube.position.z = 0;
		cube.castShadow = true; // 立方体投射阴影
		scene.add(cube);
		
		// 加入一个球体
		var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
		var sphereMaterial = new THREE.MeshStandardMaterial( {color: 0xA00000, roughness: 0.1, metalness: 0.1} );
		sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
		sphere.position.x = 10;
		sphere.position.y = 4;
		sphere.position.z = 0;
		sphere.castShadow = true; // 球体投射阴影
		scene.add(sphere);
		
		// 加入一个环境光源
		var ambientLight = new THREE.AmbientLight(0xffffff, 0.0);
		scene.add(ambientLight);
		
		// 加入一组平面光光源
		areaLights.push(new THREE.RectAreaLight(0xff0000, 500, 20, 25));
		areaLights.push(new THREE.RectAreaLight(0x00ff00, 500, 20, 25));
		areaLights.push(new THREE.RectAreaLight(0x0000ff, 500, 20, 25));
		areaLights.push(new THREE.RectAreaLight(0xffffff, 5000, 50, 50));
		
		areaLights[0].position.set(-25, 10, -35);
		areaLights[1].position.set(0, 10, -35);
		areaLights[2].position.set(25, 10, -35);
		areaLights[3].position.set(0, 10, 0);
		
		areaLights[0].rotation.set(Math.PI / 6, 0, 0);
		areaLights[1].rotation.set(Math.PI / 6, 0, 0);
		areaLights[2].rotation.set(Math.PI / 6, 0, 0);
		areaLights[3].rotation.set(-Math.PI / 6, Math.PI, 0);
		
		scene.add(areaLights[0]);
		scene.add(areaLights[1]);
		scene.add(areaLights[2]);
		scene.add(areaLights[3]);
		
		// 加入一组表示平面光光源的线框对象
		for (var i = 0; i < 4; i++) {
			var lightHelper = new THREE.RectAreaLightHelper(areaLights[i]);
			scene.add(lightHelper);
		}
		
		/** 用来保存那些需要修改的变量 */
		guiControls = new function() {
			this.rotationSpeed = 0.02;
			this.bouncingSpeed = 0.04;
			this.intensity = 0.1;
			
			function addLightParam(color, intensity) {
				return new function() {
					this.color = color;
					this.intensity = intensity;
				}
			};
			this.lightParams = [addLightParam('#ff0000', 500), 
								addLightParam('#00ff00', 500), 
								addLightParam('#0000ff', 500)];
		}
		/** 定义 dat.GUI 对象,并绑定 guiControls 的几个属性 */
		var gui = new dat.GUI();
		
		var folder = gui.addFolder('AmbientLight');
		folder.add(guiControls, 'intensity', 0.0, 3.0).onChange(function(e){
			ambientLight.intensity = e;
		});
		
		for (var j = 0; j < 3; j++) {
			folder = gui.addFolder('AreaLight [' + j + ']');
			folder.addColor(guiControls.lightParams[j], 'color').onChange( function(e) {
				updateLight();
			});
			folder.add(guiControls.lightParams[j], 'intensity', 100, 5000).onChange( function(e) {
				updateLight();
			});
		}
		
		folder = gui.addFolder('CubeMaterial');
		folder.add(cubeMaterial, 'roughness', 0.0, 1.0);
		folder.add(cubeMaterial, 'metalness', 0.0, 1.0);
		
		folder = gui.addFolder('SphereMaterial');
		folder.add(sphereMaterial, 'roughness', 0.0, 1.0);
		folder.add(sphereMaterial, 'metalness', 0.0, 1.0);
		
		renderScene();
    });
	
	/** 渲染场景 */
	function renderScene() {
		rotateCube(); // 旋转立方体
		bounceSphere(); // 弹跳球体
		rotateRectAreaLight(); // 旋转白色平面光源
		
		stats.update();
		requestAnimationFrame(renderScene);
		render.render(scene, camera);
	}
	
	/** 初始化 stats 统计对象 */
	function initStats() {
		stats = new Stats();
		stats.setMode(0); // 0 为监测 FPS;1 为监测渲染时间
		$('#Stats-output').append(stats.domElement);
		return stats;
	}
	
	/** 当浏览器窗口大小变化时触发 */
	function onWindowResize() {
		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		render.setSize(window.innerWidth, window.innerHeight);
	}
	
	/** 转动立方体 */
	function rotateCube() {
		cube.rotation.x += guiControls.rotationSpeed;
		cube.rotation.y += guiControls.rotationSpeed;
		cube.rotation.z += guiControls.rotationSpeed;
	}
	
	/** 弹跳球体 */
	var step = 0;
	function bounceSphere() {
		step += guiControls.bouncingSpeed;
		sphere.position.x = 10 + (10 * Math.cos(step));
		sphere.position.y = 2 + (10 * Math.abs(Math.sin(step)));
	}
	
	/** 转动白色平面光源:围绕中心点移动光源 */
	function rotateRectAreaLight() {
		var t = ( Date.now() / 1000 );
		var r = 35.0;

		var lx = r * Math.cos( t );
		var lz = r * Math.sin( t );
		var ly = areaLights[3].position.y;

		areaLights[3].position.set( lx, ly, lz );
		areaLights[3].lookAt( plane.position );
	}
	
	function updateLight() {
		for (var i = 0; i < 3; i++) {
			areaLights[i].color = new THREE.Color(guiControls.lightParams[i].color);
			areaLights[i].intensity = guiControls.lightParams[i].intensity;
		}
	}

</script>
</body>
</html>
    我们先来看看平面光光源的构造函数:

RectAreaLight( color, intensity, width, height );

color - (可选) 十六进制数字表示的光照颜色。默认值是 0xffffff (白色);
intensity - (可选) 光的 锐度/强度(strength/intensity) 的数值。默认值是 1;
width - (可选) 光的宽度。默认值是 10;
height - (可选) 光的高度。默认值是 10;
    在这个示例中,我们在场景中放置了 plane(地面)、cube(立方体)、sphere(球体)等三个物体。需要提醒的是,他们的材质必须采用 THREE.MeshStandardMaterial 对象类型,因为只有这种材质才能对平面光光源做出反应。另外,我们定义了一个数组 var areaLights = []; 用来存放四种不同颜色的平面光对象。其中 红色、绿色、蓝色 三种颜色的平面光光源我们沿着 x 轴正方向倾斜了 Math.PI/6 角度。而白色的平面光光源我们则沿着相反的方向倾斜了 -Math.PI/6 角度。并通过 rotateRectAreaLight() 方法让它围绕中心点不停地旋转,以便观察不同角度下对场景中其他物体表面所产生的光影效果。另外,读者也可以通过右上角的下拉菜单对前三种平面光的颜色和强度进行调整来观察各种光影效果。

    未完待续···