一个完整的3D环境包含以下元素:


1.场景(Scene):是物体、光源等元素的容器,
2.相机(Camera):控制视角的位置、范围以及视觉焦点的位置,一个3D环境中只能存在一个相机
3.物体对象(Mesh):包括二维物体(点、线、面)、三维物体、粒子
4.光源(Light):包括全局光、平行光、点光源
5.渲染器(Renderer):指定渲染方式,如webGL\canvas2D\Css2D\Css3D等。
6.控制器(Control): 相机控件,可通过键盘、鼠标控制相机的移动


场景(Scene)

物体、光源、控制器的添加必须使用secen.add(object)添加到场景中才能渲染出来。

一个3D项目中可同时存在多个scene,通过切换render的scene来切换显示场景


var scene = new THREE.Scene(); var mesh=scene.getObjectByName("sky");//获取场景中name=sky的物体;


相机(Camera)

基本概念

相机根据投影方式分两种:正交投影相机和透视投影相机


正交投影相机:THREE.OrthographicCamera(left, right, top, bottom, near, far),大小不因远近而变化
透视投影相机:THREE.PerspectiveCamera(fov, aspect, near, far),遵循近大远小的空间规则


一般情况下,我们使用的是透视投影相机,其参数为:


fov: 垂直方向夹角
aspect:可视区域长宽比 width/height
near:渲染区域离摄像机最近的距离
far:渲染区域离摄像机最远的距离,3仅在距离摄像机near和far间的区域会被渲染到 canvas中


相机有两个重要的参数:


camera.position:控制相机在整个3D环境中的位置(取值为3维坐标对象-THREE.Vector3(x,y,z))
camera.lookAt:控制相机的焦点位置,决定相机的朝向(取值为3维坐标对象-THREE.Vector3(x,y,z))


以下代码创建一个垂直夹角为45度、渲染区域为距离镜头1px到1000000px的透视投影相机,设置其位置为x:500px,y:0,z:500px,镜头朝向空间坐标轴原点x:0,y:0,z:0


var camera = new THREE.PerspectiveCamera( 45, 1920 / 1000, 1, 1000000 ); camera.position.set(500, 0, 500); camera.lookAt(new THREE.Vector3(0,0,0));


主要应用


  • 1.设置相机焦点位置为原点坐标或某物体的位置坐标:camera.lookAt(new THREE.Vector3(0,0,0)),循环改变camera.position的位置,可以实现围绕物体旋转 360度观看物体的动画。
  • 2.同时循环设置camera.lookAt和camera.position,可以实现以第一人称视角在空间自由移动的动画

物体(Mesh)

一个完整的物体对象mesh包括形状Geometry和材质Material



  • Mesh:三维物体,包括Geometry、Material,设置其name属性可以通过scene.getObjectByName(name)获取该物体对象;
  • Geometry:包括平面Plane、圆形Circle、立方体Cube、球体Sphere、圆柱Cylinder、多面体Polyhedron等,拥有以下属性:
  • Material:包括基础材质MeshBasicMaterial,深度材质MeshDepthMaterial、法向材质MeshNormalMaterial、面材质MeshFacelMaterial、朗柏材质MeshLambertMaterial、phone材质MeshPhongMaterial、着色器材质ShaderMaterial


以下代码以创建一个立方体为例


//画一个立方体             var cubeGeometry=new THREE.BoxGeometry(4,4,4);             var cubeMaterial=new THREE.MeshLambertMaterial({color:0xff0000});             cube=new THREE.Mesh(cubeGeometry,cubeMaterial);             cube.position.set(-4,3,0);             //投射阴影             cube.castShadow=true;             scene.add(cube);



记住以下几点:


  • 1.物体mesh的大小是由构成三维物体形状的顶点坐标​​mesh.geometry.vertices​​​和缩放级别​​mesh.scale​​​(vector3)决定的,初始化创建物体时由传入的参数确定了形状顶点坐标​​mesh.geometry.vertices​​​,后面需要修改物体大小使用​​mesh.scale​​进行缩放
  • 2.物体的透明度是材质的透明度属性​​mesh.material.opacity​​​决定的,若需要设置透明度,需将材质的是否支持半透明属性​​mesh.material.transparent​​设置为true
  • 3.在页面运行期间需要改变物体的材质或属性需要同步设置​​mesh.material.needsUpdate=true​
  • 4.若物体使用了THREE.MultiMaterial材质(如上面的例子如果多张图作为贴图材质),则会将所有材质放到​​mesh.meterials​​​数组中,修改材质需要对每一个材质​​mesh.materials.material​​进行单独修改
  • 5.修改​​mesh.rotation​​​、​​mesh.position​​​、​​mesh.visible​​​可设置物体旋转角度、位置、可见性。​​注意将物体的visible从false改成true,可能会造成画面卡顿​

光源(Light)


全局光:THREE.AmbientLight,影响整个scene的光源,一般是为了弱化阴影或调整整体色调,可设置光照颜色,以颜色的明度确定光源亮度
平行光:THREE.DirectionalLight,模拟类似太阳的光源,所有被照射的区域亮度是一致的,可设置光照颜色、光照方向(通过向量确定方向),以颜色的明度确定光源亮度
点光源:THREE.PointLight:单点发光,照射所有方向,可设置光照强度,光照半径和光颜色


以下分别在 scene中添加了全局光,1个平行光及5个点光源,项目中平行光作为主要光源、点光源主要用于暗部补光。


var ambient = new THREE.AmbientLight( 0xcccccc ); scene.add( ambient ); var directionalLight = new THREE.DirectionalLight( 0xcccccc ); directionalLight.position.set( -2, 9, 1).normalize();//设置平行光方向 scene.add( directionalLight ); var pointlight = new THREE.PointLight(0xffffff, 1, 3000); pointlight.position.set(0, 0, 2000);//设置点光源位置 scene.add( pointlight ); var pointlight2 = new THREE.PointLight(0xffffff, 1, 2000); pointlight2.position.set(-1000, 1000, 1000); scene.add( pointlight2 ); var pointlight_back = new THREE.PointLight(0xffffff, 1, 2000); pointlight_back.position.set(0, 0, -2000); scene.add( pointlight_back ); var pointlight_left = new THREE.PointLight(0xffffff, 1, 2000); pointlight_left.position.set( -2000, 0,0); scene.add( pointlight_left ); var pointlight_right = new THREE.PointLight(0xffffff, 1, 2000); pointlight_right.position.set(0,  -2000, 0); scene.add( pointlight_right );


渲染器(Renderer)

一般情况下我们使用的是WebGL渲染器,


var renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio );//设置canvas的像素比为当前设备的屏幕像素比,避免高分屏下模糊 renderer.setSize( _container.offsetWidth, _container.offsetHeight );//设置渲染器大小,即canvas画布的大小 container.appendChild( renderer.domElement );//在页面中添加canvas


控制器(Controls)


FlyControls:飞行控制,用键盘和鼠标控制相机的移动和转动
OrbitControls::轨道控制器,模拟轨道中的卫星,绕某个对象旋转平移,用键盘和鼠标控制相机位置
PointerLockControls:指针锁定,鼠标离开画布依然能被捕捉到鼠标交互,主要用于游戏
TrackballControls:轨迹球控制器,通过键盘和鼠标控制前后左右平移和缩放场景
TransformControls:变换物体控制器,可以通过鼠标对物体的进行拖放等操作


项目中使用的是轨道控制器OrbitControls,并限制了上下旋转的角度范围和滚轮控制相机离中心点的最大距离和最小距离


//轨道控制器             var controls = new THREE.OrbitControls(camera,document.getElementById("space"));             controls.maxPolarAngle=1.5;//上下两极的可视区域的最大角度             controls.minPolarAngle=1;//上下两极的可视区域最小角度             controls.enableDamping=true;//允许远近拉伸             controls.enableKeys=false;//禁止键盘控制             controls.enablePan=false;//禁止平移             controls.dampingFactor = 1;//鼠标滚动一个单位时拉伸幅度             controls.rotateSpeed=1;//旋转速度             controls.minDistance=0;//离中心物体的最近距离             controls.maxDistance=1000;//离中心物体的最远距离



下面用以上基础知识写一个threejs的场景例子

Threejs之基础场景脚本_点光源

左上角有fps显示,右上角有gui控制,可改变参数和点击按钮添加mesh,源码如下:


<!DOCTYPE html> <html>  <head>     <meta charset="UTF-8">     <title></title>     <script src="three.min.js"></script>     <script src="OrbitControls.js"></script>     <script src="stats.js"></script>     <script src="dat.gui.js"></script>     <style>         body {             margin: 0;             overflow: hidden;         }     </style> </head>  <body>     <div id="space"></div>     <div id="stats"></div>       <script>         var render,scene,camera,stats,planeGeometry,plane;         var cube,sphere;         var step=0;                  function init(){             //场景             scene=new THREE.Scene();             scene.background =new THREE.Color(0xC2E65A);             //相机             camera=new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);             //相机方位和朝向             camera.position.set(-30,40,30);                 //渲染器             render=new THREE.WebGLRenderer();             render.alpha=true;             render.setSize(window.innerWidth,window.innerHeight);             render.shadowMapEnabled=true;             //坐标轴  红线是X轴,绿线是Y轴,蓝线是Z轴             // var axes=new THREE.AxisHelper(20);             // scene.add(axes);              //画一个平板             planeGeometry=new THREE.PlaneGeometry(60,40,1,1);             var planeMaterial=new THREE.MeshLambertMaterial({color:0x364801});             plane=new THREE.Mesh(planeGeometry,planeMaterial);             //绕x轴旋转90度             plane.rotation.x=-0.5*Math.PI;             plane.position.set(0,0,0);             //接受阴影             plane.receiveShadow=true;             scene.add(plane);              //画一个立方体             var cubeGeometry=new THREE.BoxGeometry(4,4,4);             var cubeMaterial=new THREE.MeshLambertMaterial({color:0xff0000});             cube=new THREE.Mesh(cubeGeometry,cubeMaterial);             cube.position.set(-4,3,0);             //投射阴影             cube.castShadow=true;             scene.add(cube);               //画一个球体             var sphereGeometry=new THREE.SphereGeometry(4,20,20);             var sphereMaterial=new THREE.MeshLambertMaterial({color:0x00eeff});             sphere=new THREE.Mesh(sphereGeometry,sphereMaterial);             sphere.position.set(20,4,2);             sphere.castShadow=true;             scene.add(sphere);              //添加光源             var spotLight=new THREE.SpotLight(0xffffff);             spotLight.position.set(-40,60,-10);             spotLight.castShadow=true;             scene.add(spotLight);              //添加雾化效果             //scene.fog=new THREE.FogExp2(0xffffff,0,01);             //全场景统一材质             //scene.overrideMaterial=new THREE.MeshLambertMaterial({color:0xffffff});                          //轨道控制器             var controls = new THREE.OrbitControls(camera,document.getElementById("space"));             controls.maxPolarAngle=1.5;//上下两极的可视区域的最大角度             controls.minPolarAngle=1;//上下两极的可视区域最小角度             controls.enableDamping=true;//允许远近拉伸             controls.enableKeys=false;//禁止键盘控制             controls.enablePan=false;//禁止平移             controls.dampingFactor = 1;//鼠标滚动一个单位时拉伸幅度             controls.rotateSpeed=1;//旋转速度             controls.minDistance=0;//离中心物体的最近距离             controls.maxDistance=1000;//离中心物体的最远距离              //显示fps             stats=initStats();              document.getElementById("space").appendChild(render.domElement);             renderScene();              //参数外控             var gui=new dat.GUI();             gui.add(UISettings,'rotationSpeed',0,0.5);             gui.add(UISettings,'bouncingSpeed',0,0.5);             gui.add(UISettings,'AddMesh');             gui.add(UISettings,'RemoveMesh');             gui.add(UISettings,'Clear');             gui.add(UISettings,'NumberOfObject').listen();              //事件             window.addEventListener( 'resize', onResize, false );         }         window.onload=init;          //参数控制         var UISettings=new function(){             this.rotationSpeed=0.02;             this.bouncingSpeed=0.03;             this.NumberOfObject=0;              this.AddMesh=function(){                 var cubeGeometry;                 var r=Math.ceil((Math.random()*10));                 var cubeSize=Math.ceil((Math.random()*5));                   //随机几何体                 switch(r)                 {                     case 1:                         cubeGeometry=new THREE.SphereGeometry(cubeSize);                         break;                     case 2:                         cubeGeometry=new THREE.CylinderGeometry(cubeSize,cubeSize,cubeSize);                         break;                     case 3:                         cubeGeometry=new THREE.IcosahedronGeometry(cubeSize);                         break;                     case 4:                         cubeGeometry=new THREE.OctahedronGeometry(cubeSize);                         break;                     case 5:                         cubeGeometry=new THREE.TetrahedronGeometry(cubeSize);                         break;                     case 6:                         cubeGeometry=new THREE.TorusGeometry(cubeSize,1,cubeSize,cubeSize);                         break;                     default:                         cubeGeometry=new THREE.BoxGeometry(cubeSize,cubeSize,cubeSize);                 }                 var cubeMaterial=new THREE.MeshLambertMaterial({color:Math.random()*0xffffff});                 var cube1=new THREE.Mesh(cubeGeometry,cubeMaterial);                 cube1.castShadow=true;                 cube1.name="cube-"+scene.children.length;                 cube1.position.x = -30 + Math.round((Math.random() * planeGeometry.parameters.width));                 cube1.position.y = Math.round((Math.random() * 5));                 cube1.position.z = -20 + Math.round((Math.random() * planeGeometry.parameters.height));                  scene.add(cube1);                 this.NumberOfObject=scene.children.length-2;             };              this.RemoveMesh=function(){                 var allchildren=scene.children;                 var lastobject=allchildren[allchildren.length-1];                 if(lastobject instanceof THREE.Mesh)                 {                     scene.remove(lastobject);                     this.NumberOfObject=scene.children.length-2;                 }             };              this.Clear=function(){                 for(var i=scene.children.length-1;i>=0;i--)                 {                     var lastobject=scene.children[i];                     if(lastobject instanceof THREE.Mesh&& lastobject !=plane)                     {                         scene.remove(lastobject);                     }                 }                 this.NumberOfObject=scene.children.length-2;                              };         }          //动画         function renderScene(){             stats.update();             scene.traverse(function(obj){                 if(obj instanceof THREE.Mesh && obj !=plane)                 {                     //自旋转                     obj.rotation.x+=UISettings.rotationSpeed;                     obj.rotation.y+=UISettings.rotationSpeed;                     obj.rotation.z+=UISettings.rotationSpeed;                 }             });              //弹跳             step+=UISettings.bouncingSpeed;             sphere.position.x=20+(10*(Math.cos(step)));             sphere.position.y=2+(10*Math.abs(Math.sin(step)));              requestAnimationFrame(renderScene);             camera.lookAt(scene.position);             render.render(scene,camera);         }          //设置fps         function initStats()         {             var stats=new Stats();             stats.setMode(0);             stats.domElement.style.position='absolute';             stats.domElement.style.left='0px';             stats.domElement.style.top='0px';             stats.domElement.style.width='90px';             document.getElementById("stats").appendChild(stats.domElement);             return stats;         }          //窗口自适应         function onResize(){             camera.aspect=window.innerWidth/window.innerHeight;             camera.updateProjectionMatrix();             render.setSize(window.innerWidth,window.innerHeight);                     }      </script> </body>  </html>