Three.js场景的基本组件
1. 场景Scene
THREE.Scene被称为场景图,可以用来保存所有图形场景的必要信息。每个添加到Scene的对象,包括Scene自身都继承自名为THREE.Object3D对象。Scene不仅仅是一个数组,还包含了场景图树形结构中的所有节点。
THREE.Scene最常用的方法和属性如下:
<!DOCTYPE html> <html> <head> <title>Example 02.01 - Basic Scene</title> <script type="text/javascript" src="./three.js"></script> <script type="text/javascript" src="./stats.js"></script> <script type="text/javascript" src="./dat.gui.js"></script> <style> body { /* 将margin设为0,overflow设为hidden,全屏显示 */ margin: 0; overflow: hidden; } </style> </head> <body> <div id="Stats-output"> </div> <div id="WebGL-output"> </div> <script type="text/javascript"> // 一旦所有东西都被加载,我们运行我们的Three.js东西 function init() { var stats = initStats(); // 创建一个场景,其中包含所有元素,如对象、相机和灯光。 var scene = new THREE.Scene(); // 创建一个摄像头,它定义了我们看的地方。 var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); scene.add(camera); // 创建一个渲染和设置大小 var renderer = new THREE.WebGLRenderer(); renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMapEnabled = true; // 创建接地面 var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1); var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff}); var plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.receiveShadow = true; // 旋转并定位平面 plane.rotation.x = -0.5 * Math.PI; plane.position.x = 0; plane.position.y = 0; plane.position.z = 0; // 将plane对象添加到场景中 scene.add(plane); // 将摄像机定位并指向场景中心 camera.position.x = -30; camera.position.y = 40; camera.position.z = 30; camera.lookAt(scene.position); // 添加微妙的环境照明 var ambientLight = new THREE.AmbientLight(0x0c0c0c); scene.add(ambientLight); // 为阴影添加聚光灯 var spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(-40, 60, -10); spotLight.castShadow = true; scene.add(spotLight); // 将渲染器的输出添加到html元素中 document.getElementById("WebGL-output").appendChild(renderer.domElement); // 调用render函数 var step = 0; var controls = new function () { this.rotationSpeed = 0.02; this.numberOfObjects = scene.children.length; this.removeCube = function () { var allChildren = scene.children; var lastObject = allChildren[allChildren.length - 1]; if (lastObject instanceof THREE.Mesh) { scene.remove(lastObject); this.numberOfObjects = scene.children.length; } }; this.addCube = function () { var cubeSize = Math.ceil((Math.random() * 3)); var cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); var cubeMaterial = new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff}); var cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.castShadow = true; cube.name = "cube-" + scene.children.length; // 在场景中随机放置立方体 cube.position.x = -30 + Math.round((Math.random() * planeGeometry.parameters.width)); cube.position.y = Math.round((Math.random() * 5)); cube.position.z = -20 + Math.round((Math.random() * planeGeometry.parameters.height)); // 将立方体添加到场景中 scene.add(cube); this.numberOfObjects = scene.children.length; }; this.outputObjects = function () { console.log(scene.children); } }; var gui = new dat.GUI(); gui.add(controls, 'rotationSpeed', 0, 0.5); gui.add(controls, 'addCube'); gui.add(controls, 'removeCube'); gui.add(controls, 'outputObjects'); gui.add(controls, 'numberOfObjects').listen(); render(); function render() { stats.update(); // 绕轴旋转立方体 scene.traverse(function (e) { if (e instanceof THREE.Mesh && e != plane) { e.rotation.x += controls.rotationSpeed; e.rotation.y += controls.rotationSpeed; e.rotation.z += controls.rotationSpeed; } }); // 渲染使用requestAnimationFrame requestAnimationFrame(render); renderer.render(scene, camera); } function initStats() { var stats = new Stats(); stats.setMode(0); // 0: fps, 1: ms // 左上的对齐 stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.getElementById("Stats-output").appendChild(stats.domElement); return stats; } } window.onload = init </script> </body> </html>
结果:
解释:
上面addCube方法给每个添加的正方体添加了一个name属性, cube.name = "cube-" + scene.children.length; 将名称设为"cube-"加上scene的物体的数量(scene.children.length),name属性对于调试非常重要们可以通过scene.getObjectByName(name)获取到物体,然后进行其他移动位置等操作。在dat.GUI界面中用 numberOfObjects 实时的检测scene的物体(这种方式也可以实现监测作用)
点击GUI中的removeCube会调用下面方法移除最后一个添加进去的物体:并且代码中检查该对象是不是THREE.Mesh对象,这样做的原因是避免移除摄像机和光源。当我们移除对象之后需要更新场景中的numberOfObjects
this.removeCube = function () { var allChildren = scene.children; var lastObject = allChildren[allChildren.length - 1]; if (lastObject instanceof THREE.Mesh) { scene.remove(lastObject); this.numberOfObjects = scene.children.length; } };
控制界面的最后一个按钮的标签是 outputObjects,这个函数的作用是在console控制台打印所有对象信息,这样做对于代码调试是非常有用的,对于在有name属性的情况下调试代码非常有用。例如已经知道name值为cube-5,也可使用 console.log(scene.getObjectByName("cube-5")); 打印结果如下:
雾化效果
使用fog属性可以为整个场景添加雾化效果。雾化效果就是场景中的物体离得越远就会变得越模糊。
添加雾化非常简单,如下:
var scene = new THREE.Scene(); scene.fog = new THREE.Fog(0xffffff, 0.015, 100);
效果:
2. 相机
Three.js提供了两种相机,正交投影摄像机(THREE.OrthographicCamera)和透视摄像机(PerspectiveCamera),大多数使用的是透视摄像机,更接近于现实世界。
2.1 透视摄像机(PerspectiveCamera)
参数如下:
这些属性组合到一起影响你所看到的景象,如图:
摄像机的fov属性决定了横向视场。基于aspect属性,纵向视场也就相应地确定了。near属性决定了近面距离,far属性决定了远面距离。近面距离和远面距离之间的区域将会被渲染。
2.2 正交投影摄像机(THREE.OrthographicCamera)
正交投影摄像机渲染出的物体大小都是一样,所以不关心使用什么长宽比或者以什么样的视角来观察场景。当使用正交投影摄像机时,你要定义一个被渲染的方块区域。如下属性:
总结起来如下:
2.3 摄像机聚焦在指定点上
在前面介绍了摄像机需要放在场景中的某个位置,以及摄像机能够看到的区域将会被渲染。通常来说,摄像机会指向场景的中心,用坐标表示就是position(0,0,0)。但是改变摄像机的位置非常容易,如下:
camera.lookAt(new THREE.Vector3(x,y,z));
当然你还可以使摄像机追随场景中某个物体。由于THREE.Mesh对象的位置都是THREE.Vector3对象,所以可以使用camera.lookAt(mesh.position)使摄像机指向某个物体。
3. 渲染器(基于摄像机的角度来计算场景对象在浏览器中会渲染成什么样子。)
Three.js提供了许多的渲染器,如下:
CanvasRenderer
DOMRenderer
SVGRenderer
WebGLRenderer
WebGLRenderTarget
WebGLRenderTargetCube
WebGLShaders
使用最多的是WebGLRenderer,new THREE.WebGLRenderer()来新建一个WebGL渲染器。下面介绍其主要的参数:
antialias:
值:true/false
含义:是否开启反锯齿,设置为true开启反锯齿。
precision:
值:highp/mediump/lowp
含义:着色精度选择。
alpha:
值:true/false
含义:是否可以设置背景色透明。
premultipliedAlpha:
值:true/false
含义:?
stencil:
值:true/false
含义:?
preserveDrawingBuffer:
值:true/false
含义:是否保存绘图缓冲,若设为true,则可以提取canvas绘图的缓冲。
maxLights:
值:数值int
含义:最大灯光数,我们的场景中最多能够添加多少个灯光。
制定渲染器的宽高,我们用renderer.setSize(width,height)来设置;
而且一般采用下面直接设置为窗口大小:
renderer.setSize(window.innerWidth, window.innerHeight);
追加生成的canvas元素到容器元素中。canvas对象的获取方法为renderer.domElement;
设置canvas背景色(clearColor)和背景色透明度(clearAlpha),renderer.setClearColor(clearColor,clearAlpha);
renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
4.几何体和网格
在前面已经使用了几何体和网格。比如向场景中添加球体时代码如下:
var sphereGeometry = new THREE.SphereGeometry(4, 20, 20); var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff}); var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
我们使用 THREE.SphereGeometry(4, 20, 20) 定义了物体的形状、使用 THREE.MeshLambertMaterial 定义了物体的外观和材质,并将他们合并成能添加到场景中的网格(THREE.Mesh)。
Three.js提供的几何体如下:
代码如下:
<!DOCTYPE html> <html> <head> <title>Example 02.04 - Geometries</title> <script type="text/javascript" src="../libs/three.js"></script> <script type="text/javascript" src="../libs/ParametricGeometries.js"></script> <script type="text/javascript" src="../libs/ConvexGeometry.js"></script> <script type="text/javascript" src="../libs/stats.js"></script> <script type="text/javascript" src="../libs/dat.gui.js"></script> <style> body { /* set margin to 0 and overflow to hidden, to go fullscreen */ margin: 0; overflow: hidden; } </style> </head> <body> <div id="Stats-output"> </div> <!-- Div which will hold the Output --> <div id="WebGL-output"> </div> <!-- Javascript code that runs our Three.js examples --> <script type="text/javascript"> // once everything is loaded, we run our Three.js stuff. function init() { var stats = initStats(); // create a scene, that will hold all our elements such as objects, cameras and lights. var scene = new THREE.Scene(); // create a camera, which defines where we're looking at. var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // create a render and set the size var renderer = new THREE.WebGLRenderer(); renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMapEnabled = true; // create the ground plane var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1); var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff}); var plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.receiveShadow = true; // rotate and position the plane plane.rotation.x = -0.5 * Math.PI; plane.position.x = 0; plane.position.y = 0; plane.position.z = 0; // add the plane to the scene scene.add(plane); // position and point the camera to the center of the scene camera.position.x = -50; camera.position.y = 30; camera.position.z = 20; camera.lookAt(new THREE.Vector3(-10, 0, 0)); // add subtle ambient lighting var ambientLight = new THREE.AmbientLight(0x090909); scene.add(ambientLight); // add spotlight for the shadows var spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(-40, 40, 50); spotLight.castShadow = true; scene.add(spotLight); // add geometries addGeometries(scene); // add the output of the renderer to the html element document.getElementById("WebGL-output").appendChild(renderer.domElement); // call the render function var step = 0; render(); function addGeometries(scene) { var geoms = []; geoms.push(new THREE.CylinderGeometry(1, 4, 4)); // basic cube geoms.push(new THREE.BoxGeometry(2, 2, 2)); // basic spherer geoms.push(new THREE.SphereGeometry(2)); geoms.push(new THREE.IcosahedronGeometry(4)); // create a convex shape (a shape without dents) // using a couple of points // for instance a cube var points = [ new THREE.Vector3(2, 2, 2), new THREE.Vector3(2, 2, -2), new THREE.Vector3(-2, 2, -2), new THREE.Vector3(-2, 2, 2), new THREE.Vector3(2, -2, 2), new THREE.Vector3(2, -2, -2), new THREE.Vector3(-2, -2, -2), new THREE.Vector3(-2, -2, 2) ]; geoms.push(new THREE.ConvexGeometry(points)); // create a lathgeometry //http://en.wikipedia.org/wiki/Lathe_(graphics) var pts = [];//points array - the path profile points will be stored here var detail = .1;//half-circle detail - how many angle increments will be used to generate points var radius = 3;//radius for half_sphere for (var angle = 0.0; angle < Math.PI; angle += detail)//loop from 0.0 radians to PI (0 - 180 degrees) pts.push(new THREE.Vector3(Math.cos(angle) * radius, 0, Math.sin(angle) * radius));//angle/radius to x,z geoms.push(new THREE.LatheGeometry(pts, 12)); // create a OctahedronGeometry geoms.push(new THREE.OctahedronGeometry(3)); // create a geometry based on a function geoms.push(new THREE.ParametricGeometry(THREE.ParametricGeometries.mobius3d, 20, 10)); // geoms.push(new THREE.TetrahedronGeometry(3)); geoms.push(new THREE.TorusGeometry(3, 1, 10, 10)); geoms.push(new THREE.TorusKnotGeometry(3, 0.5, 50, 20)); var j = 0; for (var i = 0; i < geoms.length; i++) { var cubeMaterial = new THREE.MeshLambertMaterial({wireframe: true, color: Math.random() * 0xffffff}); var materials = [ new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff, shading: THREE.FlatShading}), new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}) ]; var mesh = THREE.SceneUtils.createMultiMaterialObject(geoms[i], materials); mesh.traverse(function (e) { e.castShadow = true }); //var mesh = new THREE.Mesh(geoms[i],materials[i]); //mesh.castShadow=true; mesh.position.x = -24 + ((i % 4) * 12); mesh.position.y = 4; mesh.position.z = -8 + (j * 12); if ((i + 1) % 4 == 0) j++; scene.add(mesh); } } function render() { stats.update(); // render using requestAnimationFrame requestAnimationFrame(render); renderer.render(scene, camera); } function initStats() { var stats = new Stats(); stats.setMode(0); // 0: fps, 1: ms // Align top-left stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.getElementById("Stats-output").appendChild(stats.domElement); return stats; } } window.onload = init </script> </body> </html>
在Three.js中几何体基本上是三维空间中的点集(也被称为定点)和将这些点连接起来的面。以立方体为例:
一个立方体有8个顶点、6个面。在Three.js中每个面都是包含3个顶点的三角形,所以立方体的每个面都是由两个三角形组成的。
比如自定义几何体的方法如下:
var vertices = [ new THREE.Vector3(1, 3, 1), new THREE.Vector3(1, 3, -1), new THREE.Vector3(1, -1, 1), new THREE.Vector3(1, -1, -1), new THREE.Vector3(-1, 3, -1), new THREE.Vector3(-1, 3, 1), new THREE.Vector3(-1, -1, -1), new THREE.Vector3(-1, -1, 1) ]; var faces = [ new THREE.Face3(0, 2, 1), new THREE.Face3(2, 3, 1), new THREE.Face3(4, 6, 5), new THREE.Face3(6, 7, 5), new THREE.Face3(4, 5, 1), new THREE.Face3(5, 0, 1), new THREE.Face3(7, 6, 2), new THREE.Face3(6, 3, 2), new THREE.Face3(5, 7, 0), new THREE.Face3(7, 2, 0), new THREE.Face3(1, 3, 4), new THREE.Face3(3, 6, 4), ]; var geom = new THREE.Geometry(); geom.vertices = vertices; geom.faces = faces; geom.computeFaceNormals();
效果:
vertices数组中保存了几何体的顶点,faces数组保存了点连接起来的三角形面。如 new THREE.Face3(0, 2, 1), 就是使用vertices数组中的点0、2、1创建而成的三角形面。有了顶点和面然后创建 Geometry 对象并将点和面赋给上面对象,最后调用 geom.computeFaceNormals(); 渲染即可。
补充:Three.js可以用多种材质来创建网格
var clonedGeometry = mesh.children[0].geometry.clone(); var materials = [ new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}), new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}) ]; var mesh2 = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);
2. 网格对象(THREE.Mesh)的方法和属性
创建一个网格需要一个几何体,以及一个或多个材质。网格对象提供的属性和方法如下: (有通用的移动位置、缩放等功能)
例子如下:GUI控制其属性(scale>1为放大)
<!DOCTYPE html> <html> <head> <title>Example 02.06 - Mesh Properties</title> <script type="text/javascript" src="../libs/three.js"></script> <script type="text/javascript" src="../libs/stats.js"></script> <script type="text/javascript" src="../libs/dat.gui.js"></script> <style> body { /* set margin to 0 and overflow to hidden, to go fullscreen */ margin: 0; overflow: hidden; } </style> </head> <body> <div id="Stats-output"> </div> <!-- Div which will hold the Output --> <div id="WebGL-output"> </div> <!-- Javascript code that runs our Three.js examples --> <script type="text/javascript"> // once everything is loaded, we run our Three.js stuff. function init() { var stats = initStats(); // create a scene, that will hold all our elements such as objects, cameras and lights. var scene = new THREE.Scene(); // create a camera, which defines where we're looking at. var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // create a render and set the size var renderer = new THREE.WebGLRenderer(); renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMapEnabled = true; // create the ground plane var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1); var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff}); var plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.receiveShadow = true; // rotate and position the plane plane.rotation.x = -0.5 * Math.PI; plane.position.x = 0; plane.position.y = 0; plane.position.z = 0; // add the plane to the scene scene.add(plane); // position and point the camera to the center of the scene camera.position.x = -30; camera.position.y = 40; camera.position.z = 30; camera.lookAt(scene.position); // add subtle ambient lighting var ambientLight = new THREE.AmbientLight(0x0c0c0c); scene.add(ambientLight); // add spotlight for the shadows var spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(-40, 60, 020); spotLight.castShadow = true; scene.add(spotLight); // add the output of the renderer to the html element document.getElementById("WebGL-output").appendChild(renderer.domElement); // call the render function var step = 0; var controls = new function () { this.scaleX = 1; this.scaleY = 1; this.scaleZ = 1; this.positionX = 0; this.positionY = 4; this.positionZ = 0; this.rotationX = 0; this.rotationY = 0; this.rotationZ = 0; this.scale = 1; this.translateX = 0; this.translateY = 0; this.translateZ = 0; this.visible = true; this.translate = function () { cube.translateX(controls.translateX); cube.translateY(controls.translateY); cube.translateZ(controls.translateZ); controls.positionX = cube.position.x; controls.positionY = cube.position.y; controls.positionZ = cube.position.z; } }; var material = new THREE.MeshLambertMaterial({color: 0x44ff44}); var geom = new THREE.BoxGeometry(5, 8, 3); var cube = new THREE.Mesh(geom, material); cube.position.y = 4; cube.castShadow = true; scene.add(cube); var gui = new dat.GUI(); guiScale = gui.addFolder('scale'); guiScale.add(controls, 'scaleX', 0, 5); guiScale.add(controls, 'scaleY', 0, 5); guiScale.add(controls, 'scaleZ', 0, 5); guiPosition = gui.addFolder('position'); var contX = guiPosition.add(controls, 'positionX', -10, 10); var contY = guiPosition.add(controls, 'positionY', -4, 20); var contZ = guiPosition.add(controls, 'positionZ', -10, 10); contX.listen(); contX.onChange(function (value) { cube.position.x = controls.positionX; }); contY.listen(); contY.onChange(function (value) { cube.position.y = controls.positionY; }); contZ.listen(); contZ.onChange(function (value) { cube.position.z = controls.positionZ; }); guiRotation = gui.addFolder('rotation'); guiRotation.add(controls, 'rotationX', -4, 4); guiRotation.add(controls, 'rotationY', -4, 4); guiRotation.add(controls, 'rotationZ', -4, 4); guiTranslate = gui.addFolder('translate'); guiTranslate.add(controls, 'translateX', -10, 10); guiTranslate.add(controls, 'translateY', -10, 10); guiTranslate.add(controls, 'translateZ', -10, 10); guiTranslate.add(controls, 'translate'); gui.add(controls, 'visible'); render(); function render() { stats.update(); // render using requestAnimationFrame cube.visible = controls.visible; cube.rotation.x = controls.rotationX; cube.rotation.y = controls.rotationY; cube.rotation.z = controls.rotationZ; cube.scale.set(controls.scaleX, controls.scaleY, controls.scaleZ); requestAnimationFrame(render); renderer.render(scene, camera); } function initStats() { var stats = new Stats(); stats.setMode(0); // 0: fps, 1: ms // Align top-left stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.getElementById("Stats-output").appendChild(stats.domElement); return stats; } } window.onload = init </script> </body> </html>
5.光源
Three.js有许多种不同种类的光源,每种光源都有特殊的行为和用法。如下:
1.THREE.SpotLight
最常用的光源(特别是想用阴影效果)。可以想象为手电筒或灯笼产生的光。THREE。PointLight与SpotLight非常相似,只是PointLight不能产生阴影(也就是没有castShadow属性)。
该光源具有产生光的方向和角度,如下:
创建聚光灯光源非常简单,如下:
var pointColor = "#ffffff"; var spotLight = new THREE.SpotLight(pointColor); spotLight.position.set(-40, 60, -10); spotLight.castShadow = true; spotLight.shadowCameraNear = 2; spotLight.shadowCameraFar = 200; spotLight.shadowCameraFov = 30; spotLight.target = plane; spotLight.distance = 0; spotLight.angle = 0.4; scene.add(spotLight);
注意: spotLight 的target属性比较重要,此属性可以指向某一个特定的对象。当然可以指向空间中任意一点,方法如下:
var target = new THREE.Object3D(); target.position = new THREE.Vector3(5, 0, 0); spotLight.target = target;
例如:
<!DOCTYPE html> <html> <head> <title>Example 03.03 - Spot Light</title> <script type="text/javascript" src="../libs/three.js"></script> <script type="text/javascript" src="../libs/stats.js"></script> <script type="text/javascript" src="../libs/dat.gui.js"></script> <style> body { /* set margin to 0 and overflow to hidden, to go fullscreen */ margin: 0; overflow: hidden; } </style> </head> <body> <div id="Stats-output"> </div> <!-- Div which will hold the Output --> <div id="WebGL-output"> </div> <!-- Javascript code that runs our Three.js examples --> <script type="text/javascript"> // once everything is loaded, we run our Three.js stuff. function init() { var stopMovingLight = false; var stats = initStats(); // create a scene, that will hold all our elements such as objects, cameras and lights. var scene = new THREE.Scene(); // create a camera, which defines where we're looking at. var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // create a render and set the size var renderer = new THREE.WebGLRenderer(); renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMapEnabled = true; renderer.shadowMapType = THREE.PCFShadowMap; // create the ground plane var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1); var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff}); var plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.receiveShadow = true; // rotate and position the plane plane.rotation.x = -0.5 * Math.PI; plane.position.x = 15; plane.position.y = 0; plane.position.z = 0; // add the plane to the scene scene.add(plane); // create a cube var cubeGeometry = new THREE.BoxGeometry(4, 4, 4); var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff3333}); var cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.castShadow = true; // position the cube cube.position.x = -4; cube.position.y = 3; cube.position.z = 0; // add the cube to the scene scene.add(cube); var sphereGeometry = new THREE.SphereGeometry(4, 20, 20); var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff}); var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); // position the sphere sphere.position.x = 20; sphere.position.y = 0; sphere.position.z = 2; sphere.castShadow = true; // add the sphere to the scene scene.add(sphere); // position and point the camera to the center of the scene camera.position.x = -35; camera.position.y = 30; camera.position.z = 25; camera.lookAt(new THREE.Vector3(10, 0, 0)); // add subtle ambient lighting var ambiColor = "#1c1c1c"; var ambientLight = new THREE.AmbientLight(ambiColor); scene.add(ambientLight); // add spotlight for a bit of light var spotLight0 = new THREE.SpotLight(0xcccccc); spotLight0.position.set(-40, 30, -10); spotLight0.lookAt(plane); scene.add(spotLight0); var target = new THREE.Object3D(); target.position = new THREE.Vector3(5, 0, 0); var pointColor = "#ffffff"; var spotLight = new THREE.SpotLight(pointColor); spotLight.position.set(-40, 60, -10); spotLight.castShadow = true; spotLight.shadowCameraNear = 2; spotLight.shadowCameraFar = 200; spotLight.shadowCameraFov = 30; spotLight.target = plane; spotLight.distance = 0; spotLight.angle = 0.4; scene.add(spotLight); // add a small sphere simulating the pointlight var sphereLight = new THREE.SphereGeometry(0.2); var sphereLightMaterial = new THREE.MeshBasicMaterial({color: 0xac6c25}); var sphereLightMesh = new THREE.Mesh(sphereLight, sphereLightMaterial); sphereLightMesh.castShadow = true; sphereLightMesh.position = new THREE.Vector3(3, 20, 3); scene.add(sphereLightMesh); // add the output of the renderer to the html element document.getElementById("WebGL-output").appendChild(renderer.domElement); // call the render function var step = 0; // used to determine the switch point for the light animation var invert = 1; var phase = 0; var controls = new function () { this.rotationSpeed = 0.03; this.bouncingSpeed = 0.03; this.ambientColor = ambiColor; this.pointColor = pointColor; this.intensity = 1; this.distance = 0; this.exponent = 30; this.angle = 0.1; this.debug = false; this.castShadow = true; this.onlyShadow = false; this.target = "Plane"; this.stopMovingLight = false; }; var gui = new dat.GUI(); gui.addColor(controls, 'ambientColor').onChange(function (e) { ambientLight.color = new THREE.Color(e); }); gui.addColor(controls, 'pointColor').onChange(function (e) { spotLight.color = new THREE.Color(e); }); gui.add(controls, 'angle', 0, Math.PI * 2).onChange(function (e) { spotLight.angle = e; }); gui.add(controls, 'intensity', 0, 5).onChange(function (e) { spotLight.intensity = e; }); gui.add(controls, 'distance', 0, 200).onChange(function (e) { spotLight.distance = e; }); gui.add(controls, 'exponent', 0, 100).onChange(function (e) { spotLight.exponent = e; }); gui.add(controls, 'debug').onChange(function (e) { spotLight.shadowCameraVisible = e; }); gui.add(controls, 'castShadow').onChange(function (e) { spotLight.castShadow = e; }); gui.add(controls, 'onlyShadow').onChange(function (e) { spotLight.onlyShadow = e; }); gui.add(controls, 'target', ['Plane', 'Sphere', 'Cube']).onChange(function (e) { console.log(e); switch (e) { case "Plane": spotLight.target = plane; break; case "Sphere": spotLight.target = sphere; break; case "Cube": spotLight.target = cube; break; } }); gui.add(controls, 'stopMovingLight').onChange(function (e) { stopMovingLight = e; }); render(); function render() { stats.update(); // rotate the cube around its axes cube.rotation.x += controls.rotationSpeed; cube.rotation.y += controls.rotationSpeed; cube.rotation.z += controls.rotationSpeed; // bounce the sphere up and down step += controls.bouncingSpeed; sphere.position.x = 20 + ( 10 * (Math.cos(step))); sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step))); // move the light simulation if (!stopMovingLight) { if (phase > 2 * Math.PI) { invert = invert * -1; phase -= 2 * Math.PI; } else { phase += controls.rotationSpeed; } sphereLightMesh.position.z = +(7 * (Math.sin(phase))); sphereLightMesh.position.x = +(14 * (Math.cos(phase))); sphereLightMesh.position.y = 10; if (invert < 0) { var pivot = 14; sphereLightMesh.position.x = (invert * (sphereLightMesh.position.x - pivot)) + pivot; } spotLight.position.copy(sphereLightMesh.position); } // render using requestAnimationFrame requestAnimationFrame(render); renderer.render(scene, camera); } function initStats() { var stats = new Stats(); stats.setMode(0); // 0: fps, 1: ms // Align top-left stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.getElementById("Stats-output").appendChild(stats.domElement); return stats; } } window.onload = init; </script> </body> </html>
效果: