自动摘要: 正交投影摄像机和透视投影摄像机 1.区别 正交投影摄像机:所有物体被渲染出来的尺寸是一样的,没有近大远小之分。因为对象相对于摄像机的距离对渲染的结果是没有影响的。常用于二维游戏中。透视投影摄 ……..
正交投影摄像机和透视投影摄像机
区别
正交投影摄像机:所有物体被渲染出来的尺寸是一样的,没有近大远小之分。因为对象相对于摄像机的距离对渲染的结果是没有影响的。常用于二维游戏中。透视投影摄像机:透视试图,近大远小。更贴近真实世界。
属性
正交投影摄像机Orthographic Camera: 构造函数:
THREE.OrthographicCamera(left, right, top, bottom, near, far)
参数
描述
left(左边界)
可视范围的左平面,渲染部分的左边界。
right(右边界)
可渲染区域的另一边界
top(上边界)
可渲染区域的最上面
bottom(下边界)
可渲染区域的最下面
near(近面距离)
基于摄像机所处的位置,从这一点开始渲染
far(远面距离)
基于摄像机所处的位置,渲染场景到这一点结束
zoom(变焦)
放大和缩小场景(默认值为1,小于1—缩小,大于1—放大,负数场景会上下颠倒)
透视投影摄像机PerspectiveCamera:![](/images/1666056384697-d28eb3a7-2764-4c7b-ad98-6c6a8021a4f7.jpeg)
构造函数:
THREE.PerspectiveCamera(fov,aspect,near,far,zoom)
参数
描述
fov
视场。摄像机能够看到的那部分场景。一般计算机不能完全显示能够看到的景象,所以一般会选择较小的区域。推荐默认值:50
aspect(长宽比)
渲染结果的横向尺寸和纵向尺寸的比值。推荐默认值:window.innerWidth/window.innerHeight
near(近面距离)
定义从距离摄像机多近的距离开始渲染。推荐默认值:0.1
far(远面距离)
定义了摄像机从它所处的位置能够看多远。推荐默认值:100
zoom(变焦)
放大和缩小场景(默认值为1,小于1—缩小,大于1—放大,负数场景会上下颠倒)
实例:
需求:在Three.js中改变摄像机,通过点击按钮SwitchCamera时,透视摄像机变成正交摄像机。过程:a. 构建基本场景(scene,camera,renderer,plane,spotLight,ambientLight)b. 添加立方体(平铺整个平面)c. 添加初始化性能插件、dat.GUI简化实验流程d. 添加用户交互插件e. 窗口触发函数(页面响应式布局)代码:HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <meta name =viewport content ="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,minimal-ui" > <script type ="text/javascript" charset ="UTF-8" src ="libs/three/three.js" > </script > <script type ="text/javascript" charset ="UTF-8" src ="libs/three/controls/TrackballControls.js" > </script > <script type ="text/javascript" charset ="UTF-8" src ="libs/three/geometries/ConvexGeometry.js" > </script > <script type ="text/javascript" charset ="UTF-8" src ="libs/three/geometries/QuickHull.js" > </script > <script type ="text/javascript" charset ="UTF-8" src ="libs/three/geometries/ParametricGeometries.js" > </script > <script type ="text/javascript" src ="libs/util/Stats.js" > </script > <script type ="text/javascript" src ="libs/util/dat.gui.js" > </script > <script type ="text/javascript" src ="libs/three/controls/OrbitControls.js" > </script > <script type ="text/javascript" src ="js/util.js" > </script > <script type ="text/javascript" src ="04-01.js" > </script > <link rel ="stylesheet" href ="css/default.css" > <title > Title</title > <style > html , body { margin : 0 ; height : 100% ; } </style > <script > </script > </head > <body onload ="draw()" > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,0.1 ,1000 ); camera.position .set (120 ,60 ,180 ); camera.lookAt (scene.position ); } var renderer;function initRender ( ){ renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0x000000 )); renderer.setSize (window .innerWidth ,window .innerHeight ); document .body .appendChild (renderer.domElement ); } var plane;var planeGeometry = new THREE .PlaneGeometry (180 ,180 );function initPlane ( ){ var planeMaterial = new THREE .MeshLambertMaterial ({ color : 0xffffff , }); plane = new THREE .Mesh (planeGeometry, planeMaterial); plane.rotation .x = -0.5 *Math .PI ; plane.position .set (0 ,0 ,0 ); scene.add (plane); } var light;function initLight ( ){ scene.add (new THREE .AmbientLight (0x292929 )); light = new THREE .DirectionalLight (0xffffff ,0.7 ); light.position .set (-20 ,40 ,60 ); scene.add (light); } var cube;function initCube ( ){ var cubeGeometry = new THREE .BoxGeometry (4 ,4 ,4 ); for (var j=0 ;j<(planeGeometry.parameters .height /5 );j++){ for (var i=0 ;i<planeGeometry.parameters .width /5 ;i++){ var rnd = Math .random ()*0.75 +0.25 ; var cubeMaterial = new THREE .MeshLambertMaterial (); cubeMaterial.color = new THREE .Color (rnd,0 ,0 ); cube = new THREE .Mesh (cubeGeometry,cubeMaterial); cube.position .set ( -((planeGeometry.parameters .width )/2 )+2 +(i*5 ), 2 , -((planeGeometry.parameters .height )/2 )+2 +(j*5 )); scene.add (cube); } } } var stats;function initStats ( ){ stats = new Stats (); document .body .appendChild (stats.domElement ); } var trackballControls;var controls;function initControls ( ){ controls = new function ( ){ this .perspective = "Perspective" ; this .switchCamera = function ( ){ if (camera instanceof THREE .PerspectiveCamera ){ camera = new THREE .OrthographicCamera (window .innerWidth /-16 , window .innerWidth /16 , window .innerHeight /16 , window .innerHeight /-16 , -200 ,500 ); camera.position .set (120 ,60 ,180 ); camera.lookAt (scene.position ); trackballControls = initTrackballControls (camera,renderer); this .perspective = "Orthographic" ; } else { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth /window .innerHeight ,0.1 ,1000 ); camera.position .set (120 ,60 ,180 ); camera.lookAt (scene.position ); trackballControls = initTrackballControls (camera,renderer); this .perspective = "Perspective" ; } }; }; var gui = new dat.GUI (); gui.add (controls,'switchCamera' ); gui.add (controls,'perspective' ).listen (); gui.domElement .style .position = 'absolute' ; gui.domElement .style .right = '300px' ; gui.domElement .style .top = 0 ; } function render ( ){ renderer.render (scene,camera); } var interaction;function initInteraction ( ) { interaction = new THREE .OrbitControls ( camera, renderer.domElement ); interaction.enableDamping = true ; interaction.enableZoom = true ; interaction.autoRotate = true ; interaction.minDistance = 200 ; interaction.maxDistance = 600 ; interaction.enablePan = true ; } function onWindowResize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } function animate ( ){ render (); stats.update (); requestAnimationFrame (animate); } function draw ( ){ initScene (); initCamera (); initRender (); initPlane (); initLight (); initCube (); initControls (); initInteraction (); initStats (); animate (); window .onresize = onWindowResize; }
总结:正交投影摄像机和透视投影摄像机的切换,首先用instanceof判断目前摄像机是否是透视摄像机,如果是透视投影摄像机,则用camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far) 设置为正交投影摄像机,反之亦然。后续可以继续设置摄像机的位置,对准场景中心。
从外部资源加载几何体
格式
描述
JSON
OBJ或MTL
OBJ是一种简单的三维文件格式,由Wavefront Technologies创建。它是使用最广泛的三维文件格式,用来定义对象的几何体。MTL文件常同OBJ文件一起使用。Three.js还有一个可定制的OBJ导出器,叫做OBJExporter.js,可以用来将Three.js中的模型导出一个OBJ文件。
STL
STL是STLereoLithography(立体成型术)的缩写,广泛用于快速成型。例如三维打印机的模型文件通常是STL文件。Three.js还有一个可定制的STL导出器,叫作OBJExporter.js,可以用来将Three.js中的模型导出到一个OBJ文件。
PLY
这种格式的全程是多边形(polygon)文件格式,通常用来保存三维扫描仪的信息。
以牙齿模型ply格式为例:首先:引入script加载器
1 <script type="text/javascript" src="libs/three/loaders/PLYLoader.js" ></script>
然后实例化加载对象,加载模型
1 2 3 4 5 6 7 8 9 10 11 12 13 var loader = new THREE .PLYLoader ();loader.load ('tooth-models/arch_lower_aligned.ply' ,function (loadedMesh ){ var material = new THREE .MeshBasicMaterial ({ color : 0x7777ff , }); var group = new THREE .Mesh (loadedMesh, material); group.scale .set (0.1 ,0.1 ,0.1 ); setModelPosition (group); console .log (group); scene.add (group); });
案列完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 var scene;function initScene ( ){ scene = new THREE .Scene (); } var camera;function initCamera ( ){ camera = new THREE .PerspectiveCamera (45 ,window .innerWidth /window .innerHeight ,0.1 ,1000 ); camera.position .set (0 ,0 ,0 ); camera.lookAt (new THREE .Vector3 (0 ,0 ,0 )); scene.add (camera); } var renderer;function initRender ( ){ renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0x000000 )); renderer.setSize (window .innerWidth ,window .innerHeight ); document .body .appendChild (renderer.domElement ); } var light;function initLight ( ) { scene.add (new THREE .AmbientLight (0x444444 )); light = new THREE .PointLight (0xffffff ); light.position .set (0 ,0 ,100 ); light.castShadow = true ; scene.add (light); } function setModelPosition (object ) { object.updateMatrixWorld (); const box = new THREE .Box3 ().setFromObject (object); const boxSize = box.getSize (); const center = box.getCenter (new THREE .Vector3 ()); object.position .x += object.position .x - center.x ; object.position .y += object.position .y - center.y ; object.position .z += object.position .z - center.z ; } function initModel ( ){ var helper = new THREE .AxesHelper (); scene.add (helper); var loader = new THREE .PLYLoader (); loader.load ('tooth-models/arch_lower_aligned.ply' ,function (loadedMesh ){ var material = new THREE .MeshBasicMaterial ({ color : 0x7777ff , }); var group = new THREE .Mesh (loadedMesh, material); setModelPosition (group); group.scale .set (0.1 ,0.1 ,0.1 ); console .log (group); scene.add (group); }); } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } var stats;function initStats ( ){ stats =new Stats (); document .body .appendChild (stats.domElement ); } function render ( ){ renderer.render (scene, camera); } function animate ( ){ render (); stats.update (); requestAnimationFrame (animate); } function draw ( ){ initScene (); initCamera (); initLight (); initRender (); initModel (); initStats (); initControls (); animate (); }
注: 牙齿模型位置有问题(已通过手动修改模型位置解决)、牙齿材质问题(学习完材质之后解决 )、移除模型问题(通过移除最后一个添加到场景的对象)
问题:
JSON文件加载问题:文件加载不出
ply文件加载问题:模型的材质无法正确加载。(ply格式中rgb颜色附加到顶点信息中无法正确编写代码);
可以自定义材质,但是无法正确加载原模型的材质颜色
obj格式模型完整加载:mtl文件中保存了material信息,obj文件中保存了geometry信息
光源 💡没有光源,渲染的场景将不可见(除非使用基础材质或线框材质)
不同种类的光源
名字
描述
简单基础光源
THREE.AmbientLight
基础光源。该光源的颜色会叠加到场景现有物体的颜色上
THREE.PointLight
点光源,从空间的一点向所有方向发射光线。点光源能用来创建阴影
THREE.SpotLight
有聚光效果,类似台灯、手电筒。该光源可以投射阴影
THREE.DirectionalLight
无限光,光线可以看作是平行线,就像太阳光。可以用来创建阴影
特殊
THREE.HemisphereLight
特殊光源,可以通过模拟反光面和光线微弱的天空来创建更加自然的室外光线。无阴影
THREE.AreaLight
使用这种光源可以指定散发光线的平面,而不是一个点。无阴影
THREE.lensFlare
这不是一种光源。可以为场景中的光源增加镜头光晕效果
THREE.AmbientLight 作用描述:
颜色会应用到全局
光源没有特别的来源方向
不会产生阴影,会将所有物体渲染为相同的颜色
最好在使用其他光源的同时使用它,目的是弱化阴影,或给场景添加一些额外的颜色
注意事项:
使用这种光源时,用色应该尽量保守。如果指定的颜色过于明亮,会发现画面的颜色过于饱和。
除了颜色之外,还可以设置强度值。
创建THREE.AmbientLight光源
首先添加实例化THREE.AmbientLight对象
var ambientLight = new THREE.AmbientLight(“#606008”);
将对象添加到场景
scene.add(ambientLight);
案例 结果: 代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera = initCamera ();var renderer;function initRender ( ){ renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0xffffff )); renderer.setSize (window .innerWidth , window .innerHeight ); renderer.shadowMap .enabled = true ; document .body .appendChild (renderer.domElement ); } var ambientLight;function initAmbientLight ( ){ ambientLight = new THREE .AmbientLight (0xffffff ); scene.add (ambientLight); } var spotLight;function initSpotLight ( ){ spotLight = new THREE .SpotLight (0xffffff , 1 , 180 , Math .PI / 4 ); spotLight.shadow .mapSize .set (2048 ,2048 ); spotLight.position .set (-30 ,40 ,50 ); spotLight.castShadow = true ; scene.add (spotLight); } var controls;function initControls ( ) { controls = new function ( ){ this .intensity = ambientLight.intensity ; this .ambientColor = ambientLight.color .getStyle (); this .disableSpotlight = false ; } var gui = new dat.GUI (); gui.add (controls, 'intensity' , 0 , 3 ,0.1 ).onChange (function (e ){ ambientLight.color = new THREE .Color (controls.ambientColor ); ambientLight.intensity = controls.intensity ; }); gui.addColor (controls,'ambientColor' ).onChange (function (e ){ spotLight.color = new THREE .Color (controls.ambientColor ); spotLight.intensity = controls.intensity ; }); gui.add (controls,'disableSpotlight' ).onChange (function (e ){ spotLight.visible = !e; }); } var stats;function initStats ( ){ stats = new Stats (); document .body .appendChild (stats.domElement ); } var controls2;function initControls2 ( ) { controls2 = new THREE .OrbitControls (camera, renderer);0 controls2.enableDamping = true ; controls2.enableZoom = true ; controls2.autoRotate = false ; controls2.minDistance = 50 ; controls2.maxDistance = 200 ; controls2.enablePan = true ; } function render ( ){ renderer.render (scene,camera); } function animate ( ){ stats.update ; requestAnimationFrame (animate); render (); } function windowOnchange ( ){ camera.aspect = window .innerWidth /window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth , window .innerHeight ); } function draw ( ){ initScene (); initCamera (); initStats (); initControls2 (); initRender (); initAmbientLight (); initSpotLight (); addHouseAndTree (scene) initControls (); animate (); window .onresize = windowOnchange; }
THREE.SpotLight 作用描述
聚光灯光源,从特定的一点以锥形发射光线
该光源产生的光具有方向和角度
THREE.SpotLight所有属性
属性
描述
angle(角度)
光源发射出的光束的宽度,单位是弧度,默认值为Math.PI/3
castShadow(投影)
如果设置为true,这个光源就会生成阴影
color(颜色)
光源颜色
decay(衰减)
光源强度随着离开光源的距离而衰减的速度。该值为2时更接近现实世界中的效果,默认值为1。只有当WebGLReader的属性physicallyCorrectLights(物理正确光源)被设置为启用时,decay属性才有效
distance(距离)
光源照射的距离。默认值为0,这意味着光线强度不会随着距离的增加而减弱。
intensity(强度)
光源照射的强度。默认值为1
penumbra(半影区)
该属性设置聚光灯的锥形照明区域在其区域边缘附近的平滑衰减速度。取值范围在0到1之间,默认值为0
position(位置)
光源在场景中的位置
power(功率)
当物理正确模式启用时(WebGLReader的属性physicallyCorrectLights被设置为启用时),该属性指定光源的功率,以流明为单位,默认值为4*Math.PI
target(目标)
使用THREE.SpotLight光源时,它的指向很重要。使用target属性,可以将THREE.SpotLight光源指向场景中的特定对象或特定位置。注意,此属性需要一个THREE.Object3D对象(如THREE.Mesh)
visible(是否可见)
光源是否可见。如果属性设置为true,该光源就会打开;如果设置为false,光源就会关闭
当THREE.SpotLight的shadow属性为enable时,可以用以下属性调节阴影特性
属性
描述
shadow.bias(阴影偏移)
用来偏置阴影的位置。当用非常薄的对象时,可以使用它来解决一些奇怪的效果。如果看到一些奇怪的阴影效果,可以将该属性设置为很小的值(比如0.01)通常可以解决问题。默认值为0
shadow.camera.far(投影远点)
到距离光源的哪一个位置可以生成阴影。默认值为5000.
shadow.camera.fov(投影视场)
用于生成阴影的视场大小。默认值为50
shadow.camera.near(投影近点)
从距离光源的哪一个位置开始生成阴影。默认值为50
shadow.mapSize.width和shadow.mapSize.height (阴影映射宽度和阴影映射高度)
决定了有多少像素用来生成阴影。当阴影具有锯齿状边缘或看起来不光滑时,可以增加这个值。在场景渲染之后无法更改。两者的默认值均为512
shadow.radius(半径)
当该值大于1时,阴影的边缘将有平滑效果。该属性在THREE.WebGLRenderer的shadowMap.type属性为THREE.BasicShadowMap时无效。
案列结果代码
THREE.PointLight
点光源是一种单点发光,照射所有方向的光源。
点光源可以像聚光灯一样启用阴影并设置其属性
属性
描述
color(颜色)
光源颜色
distance(距离)
光源照射的距离。默认值为0,这意味着光的强度不会随着距离增加而减少
intensity(强度)
光源照射的强度。默认值为1
position(位置)
光源在场景中的位置
visible(是否可见)
该属性设置为true,光源就会打开,false——关闭
decay(衰减)
光源强度随着离开光源的距离而衰减的速度。该值为2时更接近现实世界中的效果,默认值为1。只有当WebGLReader的属性physicallyCorrectLights(物理正确光源)被设置为启用时,decay属性才有效
power(功率)
当物理正确模式启用时(WebGLReader的属性physicallyCorrectLights被设置为启用时),该属性指定光源的功率,以流明为单位,默认值为4*Math.PI
案例结果代码
THREE.DirectionalLight
平行光,发出的所有光线都是互相平行的
平行光的一个范例就是太阳光
被平行光照亮的物体的整个区域接收到的光强是一样的
案例结果代码
THREE.HemisphereLight
可以创建出更贴近自然的户外光照效果
创建一个半球光光源
1 2 3 var hemisphereLight = new THREE .HemisphereLight (0xffffff ,0x00ff00 ,0.6 );hemisphereLight.position .set (0 ,500 ,0 ); scene.add (hemisphereLight);
属性
属性
描述
groundColor
从地面发出的光线的颜色
color
从天空发出的光线的颜色
intensity
光线照射的强度
案例结果代码
THREE.AreaLight
使用THREE.AreaLight光源,可以定义一个长方形的发光区域
使用THREE.AreaLight光源,需要现在HTML中导入库RectAreaLightUniformsLib.js
添加THREE.AreaLight光源
1 2 3 4 var areaLight = new THREE .AreaLight (0xffffff ,500 ,4 ,10 );areaLight.position .set (0 ,20 ,30 ); scene.add (areaLight);
在创建THREE.AreaLight光源时,创建出一个垂直平面
不能看到光源本身,只能看到光源发射出的光,而且只有当光照射到某个物体上时才能看见。可以在光源位置增加平面对象来模拟光线照射区域。
案例结果代码
镜头光晕(Lens Flare)
对于游戏和三维图像来说,可以更具真实感
THREE.LensFlare对象接受如下参数
flare = new THREE.LensFlare(texture,size,distance,blending,color,opacity);
参数
描述
texture(纹理)
纹理——图片,用来决定光晕的形状
size(尺寸)
可以指定光晕大小,单位是像素。如果设置为-1,将使用纹理本身的大小
distance(距离)
从光源(0)到摄像机(1)的距离。使用这个参数将镜头光晕放置正确的位置
blending(混合)
可以为光晕提供多种材质,混合模式决定了它们如何混合在一起。默认混合方式是THREE.AdditiveBlending
color(颜色)
光晕的颜色
opacity(不透明度)
定义光晕的不透明度,0完全透明,1完全不透明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var textureFlare0 = THREE .ImageUtils .loadTexture ("../../.." );var flareColor = new THREE .Color (0xffffff );var lensFlare = new THREE .LensFlare (textureFlare0,350 ,0.0 ,THREE .AdditiveBlending ,flareColor);lensFlare.position .copy (spotLight.position ); scene.add (lensFlare); var textureFlare3 = THREE .ImageUtils .loadTexture ("../../.." );lensFlare.add (textureFlare3,60 ,0.6 ,THREE .AdditiveBlending );
案例结果代码
材质(texture) 所有材质:
名称
描述
MeshBasicMaterial(网格基础材质)
基础材质,用于给几何体赋予一种简单的颜色,或者显示几何体的线框
MeshDepthMaterial(网格深度材质)
这个材质的使用从摄像机到网格的距离来决定如何给网格上色
MeshNormalMaterial(网格法向材质)
使用法向向量计算物体的表面颜色
MeshLambertMaterial(网格Lambert材质)
这是一种考虑光照影响的材质,用于创建暗淡,不光亮的物体
MeshPhongMaterial(网格Phong材质)
这是一种考虑光照影响的材质,用于创建光亮的物体
MeshStandardMaterial(网格标准材质)
这种标准材质采用“基于物理的渲染(PBR)”算法来绘制物体表面。能计算出表面与光线的正确互动关系,从而使渲染出来的物体更真实。
MeshPhysicalMaterial(网格物理材质)
这是MeshStandardMaterial(网格标准材质)的扩展材质,为光线反射计算模型提供了更多的控制
MeshToonMaterial(网格卡通材质)
这是MeshPhongMaterial(网格Phong材质)的扩展材质,使得物体渲染卡通化
ShadowMaterial(阴影材质)
专门用于接收用于阴影图的特殊材质。在该材质中只有阴影图像,非阴影部分为完全透明的区域
shaderMaterial(着色器材质)
允许使用自定义的着色器程序,直接控制顶点的放置位置和像素的着色方式
LineBasicMaterial(直线基础材质)
用于THREE.line(直线)几何体,用于创建着色的直线
LineDashMaterial(虚线材质)
这种材质和LineBasicMaterial(直线基础材质)一样,但是允许创建一种虚线的效果。
材质的共有属性 基础属性
属性
描述
id(标识符)
用于识别材质,并在创建材质时赋值。从0开始往上增加
uuid(通用唯一识别符)
这是生成的唯一ID,在内部使用
name(名称)
可以通过这个属性赋予材质名称,用于调试的目的
opacity(不透明度)
定义物体的透明度。与transparent属性一起使用。该属性的赋值范围从0到1
transparent(是否透明)
如果设置为true,Three.js会使用指定的不透明度渲染物体,如果设置为false,这个物体就不透明。如果使用alpha(透明度)通道的纹理,这个值应该设置为true。
overdraw(过度描绘)
当使用THREE.CanvasRender时,多边形会被渲染的稍微大一点。当这个渲染器渲染的物体有间隙时,可以将这个属性设置为true。
visible(是否可见)
定义该材质是否可见。如果设置为false,该物体在场景中就不可见
side(侧面)
可以定义哪一面应用材质。默认值是THREE.FontSide(前面)。
needsUpdate(是否更新)
对于材质的某些修改,是否告诉THREE.js材质已经改变,如果设置为true,Three.js会使用新的材质属性更新它的缓存
colorWrite(是否输出颜色)
如果属性值设置为false,则具有该材质的物体不会被真正的绘制到场景中,实际效果是该物体本身是不可见的,但其他物体被它挡住的部分也不可见
flatShading(平面着色)
该属性控制物体表面法线的生成方式,从而影响光线效果。属性值为true时,在两个相邻但不共面的三角形之间,光照会因为生硬过度而产生棱角,为false时则会产生平滑的过度效果。
lights
该属性值为布尔值。控制物体表面是否接受光照。默认值为true
premultipliedAlpha(预计算Alpha混合)
该属性控制半透明表面的混合方式。默认值为false
dithering(抖动)
该属性控制是否启用颜色抖动模式,在一定程度上可以减轻颜色不均的问题。默认值为false
shadowSide(投影面)
控制哪个面会投射阴影。默认值为null。当属性值为null时。投射阴影的面按照如下原则推定:当side为THREE.FrontSide时,side为后面;当side为THREE.BackSide时,side为前面当side为THREE.DoubleSide时,side为前面双侧
vertexColors(顶点颜色)
可以为物体的每一个顶点指定特有的颜色。默认值是THREE.NoColors。如果设置属性值为THREE.VertexColors,则渲染时将使用THREE.Face3 vertexColors数组指定的颜色,为每一个顶点设定颜色。如果该属性值为THREE.FaceColors,则会使用每一个面自己的颜色属性来设定面的颜色。CanvasRenderer不支持该属性,但WebGLRenderer能够支持
fog(雾)
控制材质是否受雾的影响
融合属性
融合属性决定了我们渲染的颜色如何与它们后面的颜色交互
属性
描述
blending(融合)
该属性决定了物体上的材质如何与背景融合。一般的融合模式是THREE.NormalBlending,在这种模式下只显示材质的上层
blendSrc(融合源)
除了标准融合模式之外,还可以通过设置blendSrc、blendDst和blendEquation来创建自定义的融合模式。这个属性定义了物体(源)如何与背景(目标)相融合。默认值为THREE.SrcAlphaFactor,即使用alpha(透明度通道)进行融合。
blendSrcAlpha(融合源透明度)
该属性为blendSrc指定透明度,默认值为null
blendDst(融合目标)
定义了融合时如何使用背景(目标),默认值为THREE.OneMinusSrcAlphaFactor,其含义是目标也使用源的alpha通道进行融合,只是使用的值是1(源的alpha通道值)
blendDstAlpha(融合目标透明度)
该属性为blendDst指定透明度,默认值为null
blendEquation(融合公式)
定义如何使用不blendSrc和blendDst的值。默认值为使它们相加(AddEquation)。
高级属性 THREE.MeshBasicMaterial 简介
一种简单的材质,不考虑场景中光照的影响
使用这种材质网格会被渲染成简单的平面多边形
可以显示几何体的线框
属性
名称
描述
color(颜色)
设置材质的颜色
wireframe(线框)
设置这个属性可以将材质渲染成线框,适用于调试
wireframeLinewidth(线框线宽)
如果打开了wireframe,这个属性定义线框中线的宽度
wireframeLinecap(线框线段端点)
这个属性定义了线框模式下顶点间线段的端点如何显示。可选的值包括butt(平)、round(圆)和square(方)。默认值是round。在实际使用中,这个属性的修改结果很难看出来。WebGLRenderer对象不支持该属性。
wireframeLinejoin(线框线段连接点)
定义了线段连接点如何显示。可选的值有round(圆)、bevel(斜角)、miter(尖角)。默认值为round。WebGLRenderer对象不支持该属性。
案例 结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,0.1 ,1000 ); camera.position .set (-20 ,50 ,40 ); camera.lookAt (new THREE .Vector3 (10 ,0 ,0 )); scene.add (camera); } var webGLRenderer,canvasRenderer,renderer;function initRenderer ( ) { webGLRenderer = new THREE .WebGLRenderer (); webGLRenderer.setClearColor (new THREE .Color (0x000000 )); webGLRenderer.setSize (window .innerWidth ,window .innerHeight ); webGLRenderer.shadowMap .enabled = true ; canvasRenderer = new THREE .CanvasRenderer (); canvasRenderer.setSize (window .innerWidth ,window .innerHeight ); renderer = webGLRenderer; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ){ stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function initLight ( ){ var ambientLight = new THREE .AmbientLight (0x0c0c0c ); scene.add (ambientLight); var spotLight = new THREE .SpotLight ("#ffffff" ); spotLight.position .set (-40 ,60 ,-10 ); spotLight.castShadow = true ; spotLight.shadow .mapSize = new THREE .Vector2 (1024 ,1024 ); spotLight.shadow .camera .far = 130 ; spotLight.shadow .camera .near = 40 ; scene.add (spotLight); } var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;function initModels ( ){ axes = new THREE .AxesHelper (50 ); scene.add (axes); meshMaterial = new THREE .MeshBasicMaterial ({ color :0x7777ff , name : "Basic Material" , flatShading : true , }); var groundGeometry = new THREE .PlaneGeometry (100 ,100 ,4 ,4 ); var groundMaterial = new THREE .MeshBasicMaterial ({ color :0x777777 , }) ground = new THREE .Mesh (groundGeometry,groundMaterial); ground.rotation .x = -Math .PI / 2 ; ground.position .y = -20 ; ground.receiveShadow = true ; scene.add (ground); var cubeGeometry = new THREE .CubeGeometry (15 ,15 ,15 ); cube = new THREE .Mesh (cubeGeometry,meshMaterial); cube.position .set (0 ,3 ,2 ); cube.castShadow = true ; scene.add (cube); var sphereGeometry = new THREE .SphereGeometry (14 ,20 ,20 ); sphere = new THREE .Mesh (sphereGeometry,meshMaterial); sphere.position .set (0 ,3 ,2 ); sphere.castShadow = true ; var planeGeometry = new THREE .PlaneGeometry (14 ,14 ,4 ,4 ); plane = new THREE .Mesh (planeGeometry,meshMaterial); plane.position = sphere.position ; selectedMesh = cube; } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ) { renderer.render (scene,camera); } function animate ( ){ stats.update (); selectedMesh.rotation .y +=0.02 ; render (); requestAnimationFrame (animate); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var MBM ,MaterialGui ,gui;function initMBM ( ){ MBM = cube.material ; MBM = new function ( ){ this .id = cube.id ; this .uuid = cube.uuid ; this .name = "Basic Material" ; this .opacity = meshMaterial.opacity ; this .transparent = meshMaterial.transparent ; this .overdraw = meshMaterial.overdraw ; this .visible = meshMaterial.visible ; this .side = "FrontSide" ; this .colorWrite = meshMaterial.colorWrite ; this .flatShading = meshMaterial.flatShading ; this .premultipliedAlpha = meshMaterial.premultipliedAlpha ; this .dithering = meshMaterial.dithering ; this .shadowSide = "" ; this .vertexColors = "NoColors" ; this .fog = true ; this .color = meshMaterial.color .getStyle (); this .wireframe = meshMaterial.wireframe ; this .wireframeLinewidth = 6.9 ; this .wireframeLinecap = "round" ; this .wireframeLinejoin = "round" ; this .switchRenderer = function ( ) { if (renderer instanceof THREE .WebGLRenderer ) { renderer = canvasRenderer; document .getElementById ("webgl-output" ).innerHTML = '' ; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } else { renderer = webGLRenderer; document .getElementById ("webgl-output" ).innerHTML = '' ; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } } this .selectedMesh = "cube" ; }; gui = new dat.GUI (); MaterialGui = gui.addFolder ("THREE.Material" ); MaterialGui .add (MBM ,'id' ); MaterialGui .add (MBM ,'uuid' ); MaterialGui .add (MBM ,'name' ); MaterialGui .add (MBM ,'opacity' ,0 ,1 ,0.01 ).onChange (function (e ){ meshMaterial.opacity = e; }); MaterialGui .add (MBM ,'transparent' ).onChange (function (e ){ meshMaterial.transparent = e; }); MaterialGui .add (MBM ,'overdraw' ,0 ,1 ,0.01 ).onChange (function (e ){ meshMaterial.overdraw = e; }); MaterialGui .add (MBM ,'visible' ).onChange (function (e ){ meshMaterial.visible = e; }); MaterialGui .add (MBM ,'side' ,{FrontSide :0 ,BackSide :1 ,BothSides :2 }).onChange (function (e ){ meshMaterial.side = parseInt (e); }); MaterialGui .add (MBM ,'colorWrite' ).onChange (function (e ){ meshMaterial.colorWrite = e; }); MaterialGui .add (MBM ,'flatShading' ).onChange (function (e ){ meshMaterial.flatShading = e; meshMaterial.needsUpdate = true ; }); MaterialGui .add (MBM ,'premultipliedAlpha' ).onChange (function (e ){ meshMaterial.premultipliedAlpha = e; }); MaterialGui .add (MBM ,'dithering' ).onChange (function (e ){ meshMaterial.dithering = e; }); MaterialGui .add (MBM ,'shadowSide' ,{FrontSide :0 ,BackSide :1 ,BothSide :2 }).onChange (function (e ){ meshMaterial.shadowSide = parseInt (e); }); MaterialGui .add (MBM ,'vertexColors' , {NoColors : THREE .NoColors , FaceColors : THREE .FaceColors , VertexColors : THREE .VertexColors }).onChange (function (vertexColors ) { meshMaterial.vertexColors = parseInt (vertexColors); }); MaterialGui .add (MBM ,'fog' ); var MeshBasicMaterialGUI = gui.addFolder ("THREE.MeshBasicMaterial" ); MeshBasicMaterialGUI .addColor (MBM ,'color' ).onChange (function (e ) { meshMaterial.color .getStyle (e); }); MeshBasicMaterialGUI .add (MBM ,'wireframe' ).onChange (function (e ) { meshMaterial.wireframe = e; }); MeshBasicMaterialGUI .add (MBM ,'wireframeLinewidth' ,0 ,20 ).onChange (function (e ){ meshMaterial.wireframeLinewidth = e; }); MeshBasicMaterialGUI .add (MBM ,'wireframeLinecap' ,['butt' ,'round' ,'square' ]).onChange (function (e ) { meshMaterial.wireframeLinecap = e; }); MeshBasicMaterialGUI .add (MBM ,'wireframeLinejoin' ,['round' ,'bevel' ,'miter' ]).onChange (function (e ) { meshMaterial.wireframeLinejoin = e; }); gui.add (MBM ,'switchRenderer' ); gui.add (MBM ,'selectedMesh' ,['cube' ,'sphere' ,'plane' ]).onChange (function (e ){ scene.remove (cube); scene.remove (sphere); scene.remove (plane); switch (e){ case 'cube' : scene.add (cube); selectedMesh = cube; break ; case 'sphere' : scene.add (sphere); selectedMesh = sphere; break ; case 'plane' : scene.add (plane); selectedMesh = plane; break ; } }); gui.domElement .style .position = 'absolute' ; gui.domElement .style .right = "500px" ; } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); initLight (); initModels (); initControls (); initMBM (); animate (); window .onresize = windowOnresize; }
💡这个例子里可以设置side属性,通过这个属性可以指定THREE.Geometry对象的哪个面应用材质。可以通过选择plane(平面)网格验证该属性。side属性设置为FrontSide或者BackSide时,平面旋转时会有一半的时间看不见。side属性设置为BothSides时,这个平面始终都能看见(因为几何体两面都有材质)。设置为BothSides时,由于两面都有材质,所以渲染需要做更多的工作,对场景的性能会有影响。
THREE.MeshDepthMaterial 简介
这个材质使用摄像机到网格的距离来决定如何给网格上色
可以将这种材质和其他材质结合,容易创建出逐渐消失的效果。
这种材质只有两个控制线框显示的属性
属性
属性
描述
wireframe
该属性指定是否显示线框
wireframeLineWidth
该属性指定线框的宽度(这个属性支队THREE.CanvasRenderer有效)
案例 描述:用THREE.MeshDepthMaterial材质渲染方块,基于摄像机的距离上色,点击addCube添加方块、removeCube移除方块(从最后一个添加进场景的方块开始移除)、自定义方块旋转速度、控制摄像机的近面距离和远面距离。
结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 var scene;function initScene ( ) { scene = new THREE .Scene (); scene.overrideMaterial = new THREE .MeshDepthMaterial (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,50 ,110 ); camera.position .set (-50 ,40 ,50 ); camera.lookAt (scene.position ); scene.add (camera); } var renderer;function initRenderer ( ) { renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0x000000 )); renderer.setSize (window .innerWidth ,window .innerHeight ); renderer.shadowMap .enabled = true ; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ) { stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var MDM ,gui,rotationSpeed;function initGUI ( ){ MDM = new function ( ){ this .rotationSpeed = 0.02 ; this .cameraNear = camera.near ; this .cameraFar = camera.far ; this .removeCube = function ( ){ var allChildren = scene.children ; var lastChild = allChildren[allChildren.length - 1 ]; if (lastChild instanceof THREE .Mesh ){ scene.remove (lastChild); } } this .addCube = function ( ) { var cubeSize = Math .ceil (3 +(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.position .x = -60 +Math .round ((Math .random ()*100 )); cube.position .y = Math .round ((Math .random ()*10 )); cube.position .z = -100 +Math .round ((Math .random ()*150 )); scene.add (cube); } } gui = new dat.GUI (); gui.add (MDM ,'removeCube' ); gui.add (MDM ,'addCube' ); gui.add (MDM ,'cameraNear' ,0 ,100 ).onChange (function (e ){ camera.near = e; camera.updateProjectionMatrix (); }); gui.add (MDM ,'cameraFar' ,0 ,200 ).onChange (function (e ){ camera.far = e; camera.updateProjectionMatrix (); }); gui.domElement .style .position = "absolute" ; gui.domElement .style .right = "500px" ; var i = 0 ; while (i < 10 ) { MDM .addCube (); i++; } } function initModel ( ) { var helper = new THREE .AxisHelper (10 ); scene.add (helper); var s = 25 ; var cube = new THREE .CubeGeometry (s, s, s); var material = new THREE .MeshDepthMaterial (); for (var i = 0 ; i < 3000 ; i++) { var mesh = new THREE .Mesh (cube, material); mesh.position .x = 800 * ( 2.0 * Math .random () - 1.0 ); mesh.position .y = 800 * ( 2.0 * Math .random () - 1.0 ); mesh.position .z = 800 * ( 2.0 * Math .random () - 1.0 ); mesh.rotation .x = Math .random () * Math .PI ; mesh.rotation .y = Math .random () * Math .PI ; mesh.rotation .z = Math .random () * Math .PI ; mesh.updateMatrix (); scene.add (mesh); } } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ){ renderer.render (scene,camera); } function animate ( ){ render (); stats.update (); scene.traverse (function (e ){ if (e instanceof THREE .Mesh ){ e.rotation .x += 0.02 ; e.rotation .y += 0.02 ; e.rotation .z += 0.02 ; } }); requestAnimationFrame (animate); } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); initGUI (); initControls (); animate (); window .onresize = windowOnresize; }
联合材质 简介
THREE.MeshDepthMaterial材质没有用来设置颜色的属性,一切都是由材质的默认属性决定
THREE.js库可以通过联合材质创建出可以拥有颜色,同时也是THREE.MeshDepthMaterial材质的物体
如何联合材质?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var cubeMaterial = new THREE .MeshDepthMaterial ();var colorMaterial = new THREE .MeshBasicMaterial ({ color :0xffffff , transparent :true , blending :THREE .MultiplyBlending }); var cube = new THREE .SceneUtils .createMultiMaterialObject (cubeGeometry,[colorMaterial,cubeMaterial]);cube.children [1 ].scale .set (0.99 ,0.99 ,0.99 );
这些方块从MeshDepthMaterial对象获得了亮度,从MeshBasicMaterial对象获得了颜色
案例 描述 在上述THREE.MeshDepthMaterial案例中,使用联合材质方法,给方块添加颜色
结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,50 ,110 ); camera.position .set (-50 ,40 ,50 ); camera.lookAt (scene.position ); scene.add (camera); } var renderer;function initRenderer ( ) { renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0x000000 )); renderer.setSize (window .innerWidth ,window .innerHeight ); renderer.shadowMap .enabled = true ; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ) { stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var MDM ,gui,rotationSpeed;function initGUI ( ){ MDM = new function ( ){ this .rotationSpeed = 0.02 ; this .cameraNear = camera.near ; this .cameraFar = camera.far ; this .color = 0x00ff00 ; this .removeCube = function ( ){ var allChildren = scene.children ; var lastChild = allChildren[allChildren.length - 1 ]; if (lastChild instanceof THREE .Group ){ scene.remove (lastChild); } } this .addCube = function ( ) { var cubeSize = Math .ceil (3 +(Math .random ()*3 )); var cubeGeometry = new THREE .BoxGeometry (cubeSize,cubeSize,cubeSize); var cubeMaterial = new THREE .MeshDepthMaterial (); var colorMaterial = new THREE .MeshBasicMaterial ({ color : MDM .color , transparent : true , blending : THREE .MultiplyBlending }); var cube = new THREE .SceneUtils .createMultiMaterialObject (cubeGeometry,[colorMaterial,cubeMaterial]); cube.children [1 ].scale .set (0.99 ,0.99 ,0.99 ); cube.castShadow = true ; cube.position .x = -60 +Math .round ((Math .random ()*100 )); cube.position .y = Math .round ((Math .random ()*10 )); cube.position .z = -100 +Math .round ((Math .random ()*150 )); scene.add (cube); } } gui = new dat.GUI (); gui.addColor (MDM ,'color' ); gui.add (MDM ,'removeCube' ); gui.add (MDM ,'addCube' ); gui.add (MDM ,'cameraNear' ,0 ,100 ).onChange (function (e ){ camera.near = e; camera.updateProjectionMatrix (); }); gui.add (MDM ,'cameraFar' ,0 ,200 ).onChange (function (e ){ camera.far = e; camera.updateProjectionMatrix (); }); gui.domElement .style .position = "absolute" ; gui.domElement .style .right = "500px" ; var i = 0 ; while (i < 10 ) { MDM .addCube (); i++; } } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ){ renderer.render (scene,camera); } function animate ( ){ render (); stats.update (); scene.traverse (function (e ){ if (e instanceof THREE .Group ){ e.rotation .x += 0.02 ; e.rotation .y += 0.02 ; e.rotation .z += 0.02 ; } }); requestAnimationFrame (animate); } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); initGUI (); initControls (); animate (); window .onresize = windowOnresize; }
THREE.MeshNormalMaterial 简介
使用法向向量计算物体的表面颜色
法向量在three.js中可以用来决定光的反射,有助于将纹理映射到三维模型,并提供有关如何计算光照、阴影和为表面像素着色的信息
属性
属性
描述
wireframe
该属性指定是否显示线框
wireframeLineWidth
该属性指定线框的宽度(这个属性支队THREE.CanvasRenderer有效)
案例 描述 选择sphere(球体)作为网格,当flatshading属性设置为true时,网格的每一个面渲染的颜色都不同,即使球体在旋转时,这些颜色也基本保持在原来的位置(每个面的颜色都是由从该面向外指的法向量计算得到的)。
结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,0.1 ,1000 ); camera.position .set (-20 ,50 ,40 ); camera.lookAt (new THREE .Vector3 (10 ,0 ,0 )); scene.add (camera); } var webGLRenderer,canvasRenderer,renderer;function initRenderer ( ) { webGLRenderer = new THREE .WebGLRenderer (); webGLRenderer.setClearColor (new THREE .Color (0x000000 )); webGLRenderer.setSize (window .innerWidth ,window .innerHeight ); webGLRenderer.shadowMap .enabled = true ; canvasRenderer = new THREE .CanvasRenderer (); canvasRenderer.setSize (window .innerWidth ,window .innerHeight ); renderer = webGLRenderer; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ){ stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function initLight ( ){ var ambientLight = new THREE .AmbientLight (0x0c0c0c ); scene.add (ambientLight); var spotLight = new THREE .SpotLight ("#ffffff" ); spotLight.position .set (-40 ,60 ,-10 ); spotLight.castShadow = true ; spotLight.shadow .mapSize = new THREE .Vector2 (1024 ,1024 ); spotLight.shadow .camera .far = 130 ; spotLight.shadow .camera .near = 40 ; scene.add (spotLight); } var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;function initModels ( ){ axes = new THREE .AxesHelper (50 ); scene.add (axes); meshMaterial = new THREE .MeshNormalMaterial () var groundGeometry = new THREE .PlaneGeometry (100 ,100 ,4 ,4 ); var groundMaterial = new THREE .MeshBasicMaterial ({ color :0x777777 , }) ground = new THREE .Mesh (groundGeometry,groundMaterial); ground.rotation .x = -Math .PI / 2 ; ground.position .y = -20 ; ground.receiveShadow = true ; scene.add (ground); var cubeGeometry = new THREE .CubeGeometry (15 ,15 ,15 ); cube = new THREE .Mesh (cubeGeometry,meshMaterial); cube.position .set (0 ,3 ,2 ); cube.castShadow = true ; scene.add (cube); var sphereGeometry = new THREE .SphereGeometry (14 ,20 ,20 ); sphere = new THREE .Mesh (sphereGeometry,meshMaterial); sphere.position .set (0 ,3 ,2 ); sphere.castShadow = true ; var planeGeometry = new THREE .PlaneGeometry (14 ,14 ,4 ,4 ); plane = new THREE .Mesh (planeGeometry,meshMaterial); plane.position = sphere.position ; selectedMesh = cube; } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ) { renderer.render (scene,camera); } function animate ( ){ stats.update (); selectedMesh.rotation .y += 0.02 ; render (); requestAnimationFrame (animate); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var MBM ,MaterialGui ,gui;function initMBM ( ){ MBM = cube.material ; MBM = new function ( ){ this .selectedMesh = "cube" ; }; gui = new dat.GUI (); addBasicMaterialSettings (gui,MBM ,meshMaterial); gui.add (MBM ,'selectedMesh' ,['cube' ,'sphere' ,'plane' ]).onChange (function (e ){ scene.remove (cube); scene.remove (sphere); scene.remove (plane); switch (e){ case 'cube' : scene.add (cube); selectedMesh = cube; break ; case 'sphere' : scene.add (sphere); selectedMesh = sphere; break ; case 'plane' : scene.add (plane); selectedMesh = plane; break ; } }); gui.domElement .style .position = 'absolute' ; gui.domElement .style .right = "500px" ; } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); initLight (); initModels (); initControls (); initMBM (); animate (); window .onresize = windowOnresize; }
在单几何体上使用多种材质
如果有一个方块,有12个面(注意,three.js中只作用于三角形)。你可以用这种材质给方块的每个面指定一种材质(例如不同的颜色)
案例 描述
先创建一个THREE.Mesh对象,用来保存所有的方块
创建一个数组,用来保存所有材质(12个面,只需6个材质,每一个材质应用于一整个面)
建立三个循环,以保证创建出正确数量的方块
在循环中,创建每一个方块,赋予材质和定位,并把他们添加到组中
结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,0.1 ,1000 ); camera.position .set (-50 ,40 ,50 ); camera.lookAt (scene.position ); scene.add (camera); } var renderer;function initRenderer ( ) { renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0x000000 )); renderer.setSize (window .innerWidth ,window .innerHeight ); renderer.shadowMap .enabled = true ; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ) { stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var group;function models ( ){ group = new THREE .Mesh (); var mats = []; mats.push (new THREE .MeshBasicMaterial ({color :0x009e60 })); mats.push (new THREE .MeshBasicMaterial ({color :0x0051ba })); mats.push (new THREE .MeshBasicMaterial ({color :0xffd500 })); mats.push (new THREE .MeshBasicMaterial ({color :0xff5800 })); mats.push (new THREE .MeshBasicMaterial ({color :0xC41E3A })); mats.push (new THREE .MeshBasicMaterial ({color :0xffffff })); for (var i=0 ; i<3 ; i++){ for (var j=0 ;j<3 ;j++){ for (var z=0 ;z<3 ; z++){ var geom = new THREE .BoxGeometry (2.9 ,2.9 ,2.9 ) var cube = new THREE .Mesh (geom,mats); cube.position .set (i * 3 - 3 , j * 3 - 3 , z * 3 - 3 ); group.add (cube); } } } group.scale .copy (new THREE .Vector3 (2 ,2 ,2 )); scene.add (group); } var MFM ,gui,rotationSpeed;function initGUI ( ){ MFM = new function ( ){ this .rotationSpeed = 0.02 ; } gui = new dat.GUI (); gui.add (MFM ,'rotationSpeed' ,0 ,2 ); gui.domElement .style .position = 'absolute' ; gui.domElement .style .right = "500px" ; } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ){ renderer.render (scene,camera); } function animate ( ){ render (); stats.update (); group.rotation .y += MFM .rotationSpeed ; group.rotation .x += MFM .rotationSpeed ; group.rotation .z += MFM .rotationSpeed ; requestAnimationFrame (animate); } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); models (); initGUI (); initControls (); animate (); window .onresize = windowOnresize; }
THREE.MeshLambertMaterial 简介
用来创建暗淡的并不光亮的表面
该材质非常易用,而且会对场景中的光源产生反应
支持线框绘制属性,可以绘制具有光照效果的线框物体
效果看起来比较暗淡
属性
名称
描述
color(颜色)
这是材质的环境光
emissive(自发光)
材质自发光的颜色。该材质虽然不会让使用它的物体变成光源,但会使物体的颜色不受其他光源的影响(即使在场景中没有光源的暗处,该物体表面的emissive颜色也可见,从而实现物体自发光。)该属性的默认值为黑色。
创建
var meshLambertMaterial = new THREE.MeshLambertMaterial({color:0xffffff});
案例 描述
通过改变标签可以自定义创建一个橘黄色,带有轻微自发光的效果的物体
结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,0.1 ,1000 ); camera.position .set (-20 ,50 ,40 ); camera.lookAt (new THREE .Vector3 (10 ,0 ,0 )); scene.add (camera); } var webGLRenderer,canvasRenderer,renderer;function initRenderer ( ) { webGLRenderer = new THREE .WebGLRenderer (); webGLRenderer.setClearColor (new THREE .Color (0x000000 )); webGLRenderer.setSize (window .innerWidth ,window .innerHeight ); webGLRenderer.shadowMap .enabled = true ; canvasRenderer = new THREE .CanvasRenderer (); canvasRenderer.setSize (window .innerWidth ,window .innerHeight ); renderer = webGLRenderer; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ){ stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function initLight ( ){ var ambientLight = new THREE .AmbientLight (0x0c0c0c ); scene.add (ambientLight); var spotLight = new THREE .SpotLight ("#ffffff" ); spotLight.position .set (-40 ,60 ,-10 ); spotLight.castShadow = true ; spotLight.shadow .mapSize = new THREE .Vector2 (1024 ,1024 ); spotLight.shadow .camera .far = 130 ; spotLight.shadow .camera .near = 40 ; scene.add (spotLight); } var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;function initModels ( ){ axes = new THREE .AxesHelper (50 ); scene.add (axes); meshMaterial = new THREE .MeshLambertMaterial ({ color :0x7777ff , }) var groundGeometry = new THREE .PlaneGeometry (100 ,100 ,4 ,4 ); var groundMaterial = new THREE .MeshBasicMaterial ({ color :0x777777 , }) ground = new THREE .Mesh (groundGeometry,groundMaterial); ground.rotation .x = -Math .PI / 2 ; ground.position .y = -20 ; ground.receiveShadow = true ; scene.add (ground); var cubeGeometry = new THREE .CubeGeometry (15 ,15 ,15 ); cube = new THREE .Mesh (cubeGeometry,meshMaterial); cube.position .set (0 ,3 ,2 ); cube.castShadow = true ; scene.add (cube); var sphereGeometry = new THREE .SphereGeometry (14 ,20 ,20 ); sphere = new THREE .Mesh (sphereGeometry,meshMaterial); sphere.position .set (0 ,3 ,2 ); sphere.castShadow = true ; var planeGeometry = new THREE .PlaneGeometry (14 ,14 ,4 ,4 ); plane = new THREE .Mesh (planeGeometry,meshMaterial); plane.position = sphere.position ; selectedMesh = cube; } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ) { renderer.render (scene,camera); } function animate ( ){ stats.update (); selectedMesh.rotation .y += 0.02 ; render (); requestAnimationFrame (animate); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var MLM ,gui;function initMBM ( ){ MLM = cube.material ; MLM = new function ( ){ this .selectedMesh = "cube" ; this .color = meshMaterial.color .getStyle (); this .emissive = meshMaterial.emissive .getHex (); this .wireframe = false ; this .wireframeLinewidth = 1 ; }; gui = new dat.GUI (); addBasicMaterialSettings (gui,MLM ,meshMaterial); var MLMGUI = gui.addFolder ("THREE.MeshLambertMaterial" ) MLMGUI .addColor (MLM ,'color' ).onChange (function (e ){ meshMaterial.color .setStyle (e); }); MLMGUI .addColor (MLM , 'emissive' ).onChange (function (e ) { meshMaterial.emissive = new THREE .Color (e); }); MLMGUI .add (MLM ,'wireframe' ).onChange (function (e ) { meshMaterial.wireframe = e; }); MLMGUI .add (MLM ,'wireframeLinewidth' ,0 ,20 ).onChange (function (e ) { meshMaterial.wireframeLinewidth = e; }); loadGopher (meshMaterial).then (function (gopher ){ gopher.scale .set (4 ,4 ,4 ); gui.add (MLM ,'selectedMesh' ,['cube' ,'sphere' ,'plane' ,'gopher' ]).onChange (function (e ){ scene.remove (cube); scene.remove (sphere); scene.remove (plane); scene.remove (gopher); switch (e){ case 'cube' : scene.add (cube); selectedMesh = cube; break ; case 'sphere' : scene.add (sphere); selectedMesh = sphere; break ; case 'plane' : scene.add (plane); selectedMesh = plane; break ; case 'gopher' : scene.add (gopher); selectedMesh = gopher; break ; } }); }); gui.domElement .style .position = 'absolute' ; gui.domElement .style .right = "500px" ; } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); initLight (); initModels (); initControls (); initMBM (); animate (); window .onresize = windowOnresize; }
THREE.MeshPhongMaterial 简介
创建一种光亮的材质
可以在物体表面实现高光效果
可以模拟塑料质感,也可以模拟金属质
THREE.js还提供了一个THREE.MeshPhongMaterial材质的扩展材质:THREE.MeshToonMaterial
属性
名称
描述
color(颜色)
材质的环境色。与环境光源一起使用。这个颜色会与环境光提供的颜色相乘。默认值为白色
emissive(自发光颜色)
材质自发光的颜色。该材质虽然不会让使用它的物体变成光源,但会使物体的颜色不受其他光源的影响(即使在场景中没有光源的暗处,该物体表面的emissive颜色也可见,从而实现物体自发光。)该属性的默认值为黑色。
specular(高光颜色)
该属性指定该材质的光亮程度及高光部分颜色。如果将它设置成与color属性相同的颜色,将会得到一个类似金属的材质,如果将它设置成灰色(grey),材质将会变得像塑料
shiness(高光度)
该属性指定物体表面镜面高光部分的轮廓的清晰程度,越光滑的表面,高光部分越清晰,反之越模糊。该属性的默认值为30
创建
var meshPhongMaterial = new THREE.MeshPhongMaterial({color: 0xdddddd});
案例 描述
创建THREE.MeshPhongMaterial材质
可以通过GUI控制界面实验这个材质,随意改变材质参数,实现金属材质感和塑料材质感
结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,0.1 ,1000 ); camera.position .set (-30 ,40 ,30 ); camera.lookAt (new THREE .Vector3 (0 ,0 ,0 )); scene.add (camera); } var webGLRenderer,canvasRenderer,renderer;function initRenderer ( ) { webGLRenderer = new THREE .WebGLRenderer (); webGLRenderer.setClearColor (new THREE .Color (0x000000 )); webGLRenderer.setSize (window .innerWidth ,window .innerHeight ); webGLRenderer.shadowMap .enabled = true ; canvasRenderer = new THREE .CanvasRenderer (); canvasRenderer.setSize (window .innerWidth ,window .innerHeight ); renderer = webGLRenderer; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ){ stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function initLight ( ){ var ambientLight = new THREE .AmbientLight (0xffffff ); scene.add (ambientLight); var spotLight = new THREE .SpotLight ("#ffffff" ); spotLight.position .set (-0 ,30 ,60 ); spotLight.castShadow = true ; spotLight.intensity = 0.6 spotLight.shadow .mapSize = new THREE .Vector2 (1024 ,1024 ); spotLight.shadow .camera .far = 130 ; spotLight.shadow .camera .near = 40 ; scene.add (spotLight); } var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;function initModels ( ){ axes = new THREE .AxesHelper (50 ); scene.add (axes); meshMaterial = new THREE .MeshStandardMaterial ({ color :0x7777ff , }) var groundGeometry = new THREE .PlaneGeometry (100 ,100 ,4 ,4 ); var groundMaterial = new THREE .MeshBasicMaterial ({ color :0x777777 , }) ground = new THREE .Mesh (groundGeometry,groundMaterial); ground.rotation .x = -Math .PI / 2 ; ground.position .y = -20 ; ground.receiveShadow = true ; scene.add (ground); var cubeGeometry = new THREE .CubeGeometry (15 ,15 ,15 ); cube = new THREE .Mesh (cubeGeometry,meshMaterial); cube.position .set (0 ,3 ,2 ); cube.castShadow = true ; scene.add (cube); var sphereGeometry = new THREE .SphereGeometry (14 ,20 ,20 ); sphere = new THREE .Mesh (sphereGeometry,meshMaterial); sphere.position .set (0 ,3 ,2 ); sphere.castShadow = true ; var planeGeometry = new THREE .PlaneGeometry (14 ,14 ,4 ,4 ); plane = new THREE .Mesh (planeGeometry,meshMaterial); plane.position = sphere.position ; selectedMesh = cube; } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ) { renderer.render (scene,camera); } function animate ( ){ stats.update (); selectedMesh.rotation .y += 0.02 ; render (); requestAnimationFrame (animate); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var MPM ,MaterialGui ,gui;function initMBM ( ){ MPM = meshMaterial; MPM = new function ( ){ this .selectedMesh = "cube" ; this .color = meshMaterial.color .getStyle (); this .emissive = meshMaterial.emissive .getStyle (); this .metalness = meshMaterial.metalness ; this .roughness = meshMaterial.roughness ; this .wireframe = false ; this .wireframeLinewidth = 1 ; }; gui = new dat.GUI (); addBasicMaterialSettings (gui,MPM ,meshMaterial); var MPMGUI = gui.addFolder ("THREE.MeshLambertMaterial" ) MPMGUI .addColor (MPM ,'color' ).onChange (function (e ){ meshMaterial.color .setStyle (e); }); MPMGUI .addColor (MPM , 'emissive' ).onChange (function (e ) { meshMaterial.emissive .setStyle (e); }); MPMGUI .add (MPM ,'metalness' ,0 ,1 ,0.01 ).onChange (function (e ) { meshMaterial.metalness = e; }); MPMGUI .add (MPM ,'roughness' ,0 ,1 ,0.01 ).onChange (function (e ){ meshMaterial.roughness = e; }); MPMGUI .add (MPM ,'wireframe' ).onChange (function (e ) { meshMaterial.wireframe = e; }); MPMGUI .add (MPM ,'wireframeLinewidth' ,0 ,20 ).onChange (function (e ) { meshMaterial.wireframeLinewidth = e; }); loadGopher (meshMaterial).then (function (gopher ){ gopher.scale .set (4 ,4 ,4 ); gui.add (MPM ,'selectedMesh' ,['cube' ,'sphere' ,'plane' ,'gopher' ]).onChange (function (e ){ scene.remove (cube); scene.remove (sphere); scene.remove (plane); scene.remove (gopher); switch (e){ case 'cube' : scene.add (cube); selectedMesh = cube; break ; case 'sphere' : scene.add (sphere); selectedMesh = sphere; break ; case 'plane' : scene.add (plane); selectedMesh = plane; break ; case 'gopher' : scene.add (gopher); selectedMesh = gopher; break ; } }); }); gui.domElement .style .position = 'absolute' ; gui.domElement .style .right = "500px" ; } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); initLight (); initModels (); initControls (); initMBM (); animate (); window .onresize = windowOnresize; }
THREE.MeshStandardMaterial 简介
这种材质使用更加正确的物理计算来决定物体表面如何与场景中的光源互动
能够更好的表现塑料质感和金属质感的表面
属性
名称
描述
metalness(金属感程度)
该属性控制物体表面的金属感程度。0代表完全塑料质感,1代表完全金属质感。默认值为0.5
roughness(粗糙程度)
该属性控制物体表面的粗糙程度。在视觉上,它决定表面对入射光的漫反射程度。默认值0.5。当值为0时会产生类似镜面的反射,为1时会产生完全的漫反射效果
案例 描述 结果 代码 THREE.MeshPhysicalMaterial 简介
该材质与THREE.MeshPhongMaterial材质非常相似
提供了对反光度的更多控制
该材质与上述MeshStandardMaterial材质,在不动手实验的情况下,很难确定什么样的参数值才能最符合特定需求。因此最佳的实践方法就是在程序里增加一个简单的UI,一边调节参数一边观察。
属性
名称
描述
clearCoat(清漆)
该属性控制物体表面清漆涂层效果的明显程度。该属性值越高,则清漆图层越厚,其结果是clearCoatRoughness属性带来的影响越明显。取值范围是0-1,默认值0
clearCoatRoughness(清漆粗糙程度)
该属性控制物体表面清漆涂层的粗糙程度。粗糙程度越高,漫反射越明显。该属性需要与clearCoat属性配合使用。取值范围0-1,默认值0
reflectivity(反光度)
该属性用于控制非金属表面的反光度,因此当metalness(金属感程度)为1或接近1时该属性的作用很小。取值范围时0-1,默认值0.5
案例 描述
与上述THREE.MeshStandardMaterial材质案例相似。
结果 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,0.1 ,1000 ); camera.position .set (-30 ,40 ,30 ); camera.lookAt (new THREE .Vector3 (0 ,0 ,0 )); scene.add (camera); } var webGLRenderer,canvasRenderer,renderer;function initRenderer ( ) { webGLRenderer = new THREE .WebGLRenderer (); webGLRenderer.setClearColor (new THREE .Color (0x000000 )); webGLRenderer.setSize (window .innerWidth ,window .innerHeight ); webGLRenderer.shadowMap .enabled = true ; canvasRenderer = new THREE .CanvasRenderer (); canvasRenderer.setSize (window .innerWidth ,window .innerHeight ); renderer = webGLRenderer; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var stats;function initStats ( ){ stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } function initLight ( ){ var ambientLight = new THREE .AmbientLight (0xffffff ); scene.add (ambientLight); var spotLight = new THREE .SpotLight ("#ffffff" ); spotLight.position .set (-0 ,30 ,60 ); spotLight.castShadow = true ; spotLight.intensity = 0.6 spotLight.shadow .mapSize = new THREE .Vector2 (1024 ,1024 ); spotLight.shadow .camera .far = 130 ; spotLight.shadow .camera .near = 40 ; scene.add (spotLight); } var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;function initModels ( ){ axes = new THREE .AxesHelper (50 ); scene.add (axes); meshMaterial = new THREE .MeshPhysicalMaterial ({ color :0x7777ff , }) var groundGeometry = new THREE .PlaneGeometry (100 ,100 ,4 ,4 ); var groundMaterial = new THREE .MeshBasicMaterial ({ color :0x777777 , }) ground = new THREE .Mesh (groundGeometry,groundMaterial); ground.rotation .x = -Math .PI / 2 ; ground.position .y = -20 ; ground.receiveShadow = true ; scene.add (ground); var cubeGeometry = new THREE .CubeGeometry (15 ,15 ,15 ); cube = new THREE .Mesh (cubeGeometry,meshMaterial); cube.position .set (0 ,3 ,2 ); cube.castShadow = true ; scene.add (cube); var sphereGeometry = new THREE .SphereGeometry (14 ,20 ,20 ); sphere = new THREE .Mesh (sphereGeometry,meshMaterial); sphere.position .set (0 ,3 ,2 ); sphere.castShadow = true ; var planeGeometry = new THREE .PlaneGeometry (14 ,14 ,4 ,4 ); plane = new THREE .Mesh (planeGeometry,meshMaterial); plane.position = sphere.position ; selectedMesh = cube; } var controls;function initControls ( ) { controls = new THREE .OrbitControls ( camera, renderer.domElement ); controls.enableDamping = true ; controls.enableZoom = true ; controls.autoRotate = true ; controls.minDistance = 1 ; controls.maxDistance = 200 ; controls.enablePan = true ; } function render ( ) { renderer.render (scene,camera); } function animate ( ){ stats.update (); selectedMesh.rotation .y += 0.02 ; render (); requestAnimationFrame (animate); } function windowOnresize ( ){ camera.aspect = window .innerWidth / window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); } var MPM ,MaterialGui ,gui;function initMBM ( ){ MPM = meshMaterial; MPM = new function ( ){ this .selectedMesh = "cube" ; this .color = meshMaterial.color .getStyle (); this .emissive = meshMaterial.emissive .getStyle (); this .metalness = meshMaterial.metalness ; this .roughness = meshMaterial.roughness ; this .clearCoat = meshMaterial.clearCoat ; this .clearCoatRoughness = meshMaterial.clearCoatRoughness ; this .reflectivity = meshMaterial.reflectivity ; this .wireframe = false ; this .wireframeLinewidth = 1 ; }; gui = new dat.GUI (); addBasicMaterialSettings (gui,MPM ,meshMaterial); var MPMGUI = gui.addFolder ("THREE.MeshLambertMaterial" ) MPMGUI .addColor (MPM ,'color' ).onChange (function (e ){ meshMaterial.color .setStyle (e); }); MPMGUI .addColor (MPM , 'emissive' ).onChange (function (e ) { meshMaterial.emissive .setStyle (e); }); MPMGUI .add (MPM ,'metalness' ,0 ,1 ,0.01 ).onChange (function (e ) { meshMaterial.metalness = e; }); MPMGUI .add (MPM ,'roughness' ,0 ,1 ,0.01 ).onChange (function (e ){ meshMaterial.roughness = e; }); MPMGUI .add (MPM ,'clearCoat' ,0 ,1 ,0.01 ).onChange (function (e ) { meshMaterial.clearCoat = e; }); MPMGUI .add (MPM ,'clearCoatRoughness' ,0 ,1 ,0.01 ).onChange (function (e ) { meshMaterial.clearCoatRoughness = e; }); MPMGUI .add (MPM ,'reflectivity' ,0 ,1 ,0.01 ).onChange (function (e ){ meshMaterial.reflectivity = e; }); MPMGUI .add (MPM ,'wireframe' ).onChange (function (e ) { meshMaterial.wireframe = e; }); MPMGUI .add (MPM ,'wireframeLinewidth' ,0 ,20 ).onChange (function (e ) { meshMaterial.wireframeLinewidth = e; }); loadGopher (meshMaterial).then (function (gopher ){ gopher.scale .set (4 ,4 ,4 ); gui.add (MPM ,'selectedMesh' ,['cube' ,'sphere' ,'plane' ,'gopher' ]).onChange (function (e ){ scene.remove (cube); scene.remove (sphere); scene.remove (plane); scene.remove (gopher); switch (e){ case 'cube' : scene.add (cube); selectedMesh = cube; break ; case 'sphere' : scene.add (sphere); selectedMesh = sphere; break ; case 'plane' : scene.add (plane); selectedMesh = plane; break ; case 'gopher' : scene.add (gopher); selectedMesh = gopher; break ; } }); }); gui.domElement .style .position = 'absolute' ; gui.domElement .style .right = "500px" ; } function draw ( ){ initScene (); initCamera (); initRenderer (); initStats (); initLight (); initModels (); initControls (); initMBM (); animate (); window .onresize = windowOnresize; }
粒子和精灵 理解粒子:
每一个粒子都是面向摄像机的二维平面。
粒子的作用:
可以非常容易的创建很多细小的物体,可以用来模拟雨滴,雪花,烟等有趣的效果
粒子的创建:
可以用THREE.Sprite(material)构造函数手工创建粒子。
var material = new THREE.Sprite(material);
material 属性可以是两种:new THREE.SpriteMaterial() 和 new THREE.SpriteCanvasMaterial()
将sprite添加到场景Scene中。如果没有给粒子指定任何属性,粒子将被渲染成二维的白色小方块。
💡对象THREE.Sprite和THREE.Mesh一样,THREE.Sprite对象也是THREE.Object3D对象的扩展。也就是说THREE.Mesh的大部分属性和函数都可以用于THREE.Sprite。THREE.Points 和 THREE.PointsMaterial
THREE.Points的构造函数接受两个属性:几何体和材质
var cloud = new THREE.Points(geometry,material);
🌞 材质用来给粒子着色和添加纹理;几何体用来指定单个粒子的位置案例代码(用THREE.PointsMaterial样式化粒子)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function createParticles (size,transparent,opacity,vertexColors,sizeAttenuation,colorValue,vertexColorValue ){ var geom = new THREE .Geometry (); var material = new THREE .PointsMaterial ({ size : size, transparent : transparent, opacity : opacity, vertexColors : vertexColors, sizeAttenuation : sizeAttenuation, color : new THREE .Color (colorValue), }); var range = 500 ; for (var i =0 ; i< 15000 ; i++){ var particle = new THREE .Vector3 ( Math .random () * range - range / 2 , Math .random () * range - range / 2 , Math .random () * range - range / 2 , ); geom.vertices .push (particle); var color = new THREE .Color (vertexColorValue); var asHSL = {}; color.getHSL (asHSL); color.setHSL (asHSL.h , asHSL.s , asHSL.l *Math .random ()); geom.colors .push (color); } var cloud = new THREE .Points (geom, material); cloud.name = "particles" ; scene.add (cloud); }
THREE.PointsMaterial对象中所有可设置的属性:
名称
描述
color
粒子系统中所有粒子的颜色。将vertexColors属性设置为true,并且通过颜色属性指定了几何体的颜色来覆盖该属性(更准确地说,顶点的颜色将乘以此值以确定最终颜色),默认值为:0xFFFFFF
map
通过这个属性可以在粒子上应用某种材质
size
指定粒子大小,默认值1
sizeAnnutation
如果该属性设置为false,那么所有粒子都将拥有相同的尺寸大小,无论距离摄像机有多远;如果设置为true,粒子的大小就取决于距离摄像机的距离远近。默认为true
vertexColors
通常,THREE.Points中所有粒子都拥有相同的颜色。如果该属性设置为THREE.VertexColors,并且几何体的颜色数组也有值,那就会使用颜色数组中的值。默认是THREE.NoColor
opacity
该属性与transparent属性一起使用,用来设置粒子的不透明度。默认值为1(完全无透明)
transparent
如果属性设置为true,那么粒子在渲染时会根据opacity属性的值来确定其透明度。默认值为false
blending
该属性指定渲染粒子时的融合模式.
fog
该属性决定粒子是否受雾化效果影响,默认为true
材质 Material 的 .blending 属性主要控制纹理融合的叠加方式,.blending 属性的默认值是 THREE.NormalBlending,其它值THREE.AdditiveBlending、THREE.SubtractiveBlending 等
THREE.NormalBlending:.blending 属性默认值
THREE.AdditiveBlending:加法融合模式
THREE.SubtractiveBlending:减法融合模式
THREE.MultiplyBlending:乘法融合模式
THREE.CustomBlending:自定义融合模式,与.blendSrc,.blendDst或.blendEquation属性组合使用
纹理样式化粒子
使用图像样式化粒子一种更直接的方式。使用THREE.TextureLoader().load()函数将图像加载为THREE.Texture对象,然后分配给材质的map属性
var texture = new THREE.TextureLoader().load(“../../ssf/df/raindrop-3.png”);
实现雨滴特效
加载雨滴纹理(纹理应该是正方形,尺寸最好是2的幂)
设置雨滴粒子材质属性
为每个粒子创建顶点
为粒子(THREE.Vector3)添加如何水平移动(velocityX)和下落速度(velocityY)(横向运动速度的范围-0.16+0.16,纵向速度的范围是0.10.3)
获取几何体所有粒子,对于每个粒子,用velocityX和velocityY改变它们的当前位置。
判断粒子范围,如果v.y的位置低于0,把粒子放回顶部,如果v.x的位置到达任何一条边界,就反转横向速度,让雨滴反弹。
💡depthWrite属性决定这个对象是否影响WebGL的深度缓存。设置为false,可以保证在各个位置上渲染的雨滴之间不会互相影响。如果不设置为false,那么当一个粒子在另一个THREE.Points对象的粒子前面时,会看到纹理的黑色背景。实例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 var scene;function initScene ( ){ scene = new THREE .Scene (); } var camera;function initCamera ( ){ camera = new THREE .PerspectiveCamera (45 ,window .innerWidth / window .innerHeight ,1 ,200 ); camera.position .set (0 ,20 ,100 ); camera.lookAt (new THREE .Vector3 (30 ,30 ,0 )); scene.add (camera); } var renderer;function initRenderer ( ) { renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0x000000 )); renderer.setSize (window .innerWidth ,window .innerHeight ); renderer.shadowMap .enabled = true ; document .body .appendChild (renderer.domElement ); } function initLight ( ) { scene.add (new THREE .AmbientLight (0x404040 )); light = new THREE .DirectionalLight (0xffffff ); light.position .set (1 , 1 , 1 ); scene.add (light); } var stats;function initStats ( ) { stats = new Stats (); document .body .appendChild (stats.domElement ); } var controls;var cloud;function initControls ( ){ controls = new function ( ){ this .size = 2 ; this .transparent = true ; this .opacity = 0.6 ; this .color = 0xffffff ; this .sizeAttenuation = true ; this .redraw = function ( ) { scene.remove (scene.getObjectByName ("particle1" )); createSprites (controls.size , controls.transparent , controls.opacity , controls.color ,controls.sizeAttenuation ); }; } var gui = new dat.GUI (); gui.add (controls,'size' ,0 ,20 ).onChange (controls.redraw ); gui.add (controls,'transparent' ).onChange (controls.redraw ); gui.add (controls,'opacity' ,0 ,1 ).onChange (controls.redraw ); gui.addColor (controls,'color' ).onChange (controls.redraw ); gui.add (controls,'sizeAttenuation' ).onChange (controls.redraw ); gui.domElement .style .position = 'absolute' ; gui.domElement .style .top = '0px' ; gui.domElement .style .right = '300px' ; controls.redraw (); } var helper;function initModels ( ) { helper = new THREE .AxesHelper (50 ); } function createSprites (size,transparent,opacity,color,sizeAttenuation ){ var texture = new THREE .TextureLoader ().load ("textures/particles/raindrop-3.png" ); var geom = new THREE .Geometry (); geom.scale (10 ,10 ,10 ); var material = new THREE .PointsMaterial ({ size :size, transparent :transparent, opacity :opacity, map :texture, blending : THREE .AdditiveBlending , sizeAttenuation :sizeAttenuation, color : color, depthWrite : false , }); var range = 150 ; for (var i=0 ;i<15000 ;i++){ var particle = new THREE .Vector3 ( Math .random ()*range-range/2 , Math .random ()*range*1.5 , 1 +(i/100 ) ); particle.velocityX = (Math .random () - 0.5 )/3 ; particle.velocityY = 0.1 +(Math .random () /5 ); geom.vertices .push (particle); } cloud = new THREE .Points (geom,material); cloud.name = "particle1" ; scene.add (cloud); } var controls2;function initControls2 ( ) { controls2 = new THREE .OrbitControls (camera, renderer); controls2.enableDamping = false ; controls2.enableZoom = true ; controls2.autoRotate = false ; controls2.minDistance = 1 ; controls2.enablePan = true ; } function render ( ){ renderer.render (scene,camera); } function animate ( ){ stats.update (); var vertices = cloud.geometry .vertices ; vertices.forEach (function (v ) { v.y = v.y - (v.velocityY ); v.x = v.x - (v.velocityX ); if (v.y <= 0 ) v.y = 60 ; if (v.x <= -20 || v.x >= 20 ) v.velocityX = v.velocityX * -1 ; }); cloud.geometry .verticesNeedUpdate = true ; requestAnimationFrame (animate); render (); } function windowOnchange ( ){ camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix (); renderer.setSize (innerWidth,innerHeight); } function draw ( ){ initStats (); initScene (); initModels () initLight (); initCamera (); initControls (); initControls2 (); initRenderer (); render (); animate (); window .onresize = windowOnchange; }
加载和使用纹理 UV贴图 纹理平铺(重复)的相关属性 在之前学习纹理对象Texture已经介绍过纹理的wrapS、wrapT、repeat属性,它们都是用来设置纹理重复的相关属性,在来简单说一下这几个属性wrapS
纹理在水平方向上纹理包裹方式,在UV映射中对应于U,默认THREE.ClampToEdgeWrapping,表示纹理边缘与网格的边缘贴合。中间部分等比缩放。还可以设置为:THREE.RepeatWrapping(重复平铺) 和 THREE.MirroredRepeatWrapping(先镜像再重复平铺)
wrapT
纹理贴图在垂直方向上的包裹方式,在UV映射中对应于V,默认也是THREE.ClampToEdgeWrapping,与wrapS属性一样也可以设置为:THREE.RepeatWrapping(重复平铺) 和 THREE.MirroredRepeatWrapping(先镜像再重复平铺)
repeat
用来设置纹理将在表面上,分别在U、V方向重复多少次。
repeat属性是Vector2类型,可以使用如下语句来为它赋值
this.cube.material.map.repeat.set(repeatX, repeatY)
repeatX 用来指定在x轴方向上多久重复一次,repeatY用来指定在y轴方向上多久重复一次这两个变量取值范围与对应效果如下
如果设置等于1,则纹理不会重复
如果设置为 大于0小于1 的值,纹理会被放大
如果设置为 小于0 的值,那么会产生纹理的镜像
如果设置为 大于1 的值,纹理会重复平铺
加载纹理并应用到网格 简介
纹理最基础的用法就是作为贴图被添加到材质上,当用这种方法创建网格时,网格的颜色就来源于纹理
UV贴图实质上就是指定模型上的哪一部分需要被映射到纹理的相应位置
可以用如下方法加载纹理
var textureLoader = new THREE.TexturenLoader();var texture = textureLoader.load(“../../assets/textures/sss.jpg”);
使用THREE.TextureLoader()从指定位置加载图片,图片格式可以是png,jpg或jpeg
纹理的加载是异步的:如果纹理加载较大,而程序在文件加载完成之前开始渲染场景,那么在最开始的瞬间会看到场景中一些物体没有贴图。如果希望等待纹理加载完成。可以为THREE.TextureLoader.load()添加回调函数。
var textureLoader = new THREE.TextureLoader();var texture = textureLoder.load(“../../assets/textures/sss.jpg”,onloadFunction,onProgressFunction,onErrorFunction);
onloadFunction在纹理加载完成时被调用,onProgressFunction可以随时汇报加载进度,onError在纹理加载出故障时被调用
用图片来作为纹理使用时,最好使用长宽为2的次方的正方形图片
纹理的放大和缩小。可以设置magFilter属性来指定纹理如何放大,minFilter来指定纹理如何缩小
这两个属性的属性值可以设置如下表所示的属性值
名称
描述
THREE.NearestFilter(最邻近过滤)
这个过滤器会将纹理上最近的像素颜色应用于面上。在放大时,会导致方块化,在缩小时,会丢失很多细节
THREE.LinearFilter(线性过滤)
这个过滤器最终的颜色是由周围四个像素值决定的。在缩小时仍会丢失一些细节,但是在放大时会平滑很多,方块化也比较少一些出现
也可以使用mipmap。mipmap是把纹理按照2的倍数进行缩小。mipmap纹理过滤模式如下表所示关于mipmap,https://blog.csdn.net/qq_42428486/article/details/118856697 💡 如果没有设置magFilter和minFilter属性的值。Three.js会将THREE.LinearFilter作为magFilter属性的默认值,将THREE.LinearMipMapLinearFilter作为minFilter属性的默认值。
案例 描述 结果 代码 自定义加载器 除了使用THREE.TextureLoader方法加载标准格式图片,Three.js还提供一些自定义的加载器,用来加载其他格式的 纹理文件。
加载器
描述
THREE.DDSLoader
该加载器可以加载DirectDraw Surface格式的纹理文件。首先要在HTML页面中引入DDSLoader.js文件,然后像下面这样加载纹理var textureLoader = new THREE.DDSLoader();var texture = textureLoader.load(“../../..”)
THREE.PVRLoader
Power VP也是一种私有版权的压缩纹理格式。Three.js支持Power VR 3.0 版本的文件。在HTML页面导入PVRLoader文件后,像下面这样加载纹理var textureLoader = new THREE.PVRLoader();var texture = textureLoader.load(“../../..”);💡注意不是所有设备上的浏览器都支持PVR格式。如果在桌面版Chrome浏览器中加载这种格式会在控制台看到错误信息:WEBGL_compressed_texture_pvrtc extension not supported但是这种格式在iOS系统上被广泛使用
THREE.TGALoader
Targa是一种在3D软件中仍被广泛使用的栅格图像格式。在HTML页面中引入TGALoader.js后,像下面这样加载纹理var textureLoader = new THREE.TGALoader();var texture = textureLoader.load(“../../..”);
THREE.KTXLoader
Khronos Texture(KTX)文件格式来自于Khronos工作组。该格式的初衷是提供一个压缩纹理格式,使其可以被WebGL直接使用,以便尽量降低处理它的额外开销。KTX格式拥有多种不同的编码,不同的硬件对编码的支持有可能不同。在HTML页面中引入KTXLoader.js文件后,像下面这样加载纹理if(renderer.extension.get(‘WEBGL_compressed_texture_astc’)!==null) return “astc”;if(renderer.extension.get(‘WEBGL_compressed_texture_etc1’)!==null) return “etc1”;if(renderer.extension.get(‘WEBGL_compressed_texture_s3tc’)!==null) return “s3tc”;if(renderer.extension.get(‘WEBGL_compressed_texture_pvrtc’)!==null) return “pvrtc”;var ktxTextureLoader = new THREE.KTXLoader();var texture;switch(determineFormat()){case “astc”:texture = ktxTextureLoader.load(“../../…ktx”);break;case “etc1”:texture = ktxTextureLoader.load(“../../…ktx”);break;case “s3tc”:texture = ktxTextureLoader.load(“../../…ktx”);break;case “pvrtc”:texture = ktxTextureLoader.load(“../../…ktx”);break;}上述代码先查询WebGL当前支持的KTX编码,然后选择加载特定编码的KTX文件
上面的纹理都是直接存储或者压缩存储的普通图片。除了这些普通图片之外,Three.js还支持HDR图像(高动态范围图像)。相比普通图片,HDR图像包含了更高的亮度范围。它的亮度范围更接近人眼的光学特征。由于HDR图像所包含的亮度范围大于屏幕所能够支持的范围,可以尝试微调Three.js对HDR图像的渲染参数,并观察渲染效果的变化。这种参数调节可以通过设置THREE.WebGLRenderer类的属性来实现。下表列出了相关属性
加载器
描述
toneMapping(色调映射)
该属性控制Three.js如何将HDR色彩域映射到屏幕所支持的色彩域上,由如下选项可用:
- THREE.NoToneMapping
- THREE.LinearToneMapping
- THREE.ReinhardToneMapping
- THREE.Uncharted2ToneMapping
- THREE.CineonToneMapping
默认映射为THREE.LinearToneMapping
toneMappingExposure
该属性控制色调映射的曝光级别,可用于微调渲染场景中纹理贴图的色彩
toneMappingWhitePoint
该属性设置色调映射中的白点值
Three.js还支持EXR和RGBE格式
加载器
描述
THREE.EXRLoader
EXR文件是为存储HDR图像而开发的图像文件格式。在HTML页面中引引入EXRLoader.js文件后,像下面这样加载纹理var textureLoader = new THREE.EXRloader();var texture = textureLoader.load(“../../..”);
THREE.RGBELoader
RGBE文件是Radiance渲染系统的图像文件格式。在HTML页面中引入RGBELoader.js文件后,像下面这样加载纹理var hdrTextureLoader = new THREE.RGBEloader();hdrTextureLoader.load(“../../..”,function(texture,metadata){texture.encoding = THREE.RGBEEncoding;texture.flipY = true;… and use the texture})加载RGBE纹理时,必须将加载器返回的纹理对象的encoding属性设定为THREE.RGBEEncoding,否则将无法正确渲染。此外还需将flipY属性设置为true,否则图像会上下颠倒。
使用凹凸贴图创建褶皱 简介
凹凸贴图用于为材质添加厚度
与左侧墙相比右侧有更多的细节
创建 可以通过为材质设置额外的纹理(凹凸贴图)来实现
1 2 3 4 5 6 7 8 9 10 var textureLoader = new THREE .TextureLoader ();var cubeMaterial = new THREE .MeshStandardMaterial ({ map : textureLoader.load ("../../assets/textures/stone/stone.jpg" ), bumpMap : textureLoader.load ("../../assets/textures/stone/stone-bump.jpg" ), metalness : 0.2 , roughness : 0.07 , });
💡注意像素的密集程度定义的是凹凸的高度,但是凹凸图只包含像素的相对高度,没有任何倾斜的方向信息。所以使用凹凸图所能表达的深度信息是有限的,要想实现更多细节可以使用法向贴图
完整代码 使用法向贴图创建更加细致的凹凸和褶皱 简介
发现贴图保存的不是高度信息,而是法向量的方向。
使用法向贴图只需要使用很少的顶点和面就可以创建出细节很丰富的模型
右侧的图像细节更加丰富。当光源围绕方块移动时,你会看到纹理对光源做出很自然的反应,这样的模型更加真实
纹理(左图),法向贴图(右图)
使用法向贴图最大问题是它们难以创建,需要使用比如Blender和Photoshop这样的特殊工具。这些工具可以将高分辨率的效果图或者纹理作为输入来创建法向图。
使用法向贴图和凹凸贴图来增加物体表面细节时,不需要改变模型的实际形状;所有顶点都保持在原始位置不变。这些贴图利用场景中的光照来制造伪深度和细节。
创建 1 2 3 4 5 6 7 8 9 10 11 var textureLoader = new THREE .TextureLoader ();var cubeMaterial = new THREE .MeshStandardMaterial ({ map : textureLoader.load ("../../assets/textures/general/plaster.jpg" ), normalMap : textureLoader.load ("../../assets/textures/general/plaster-normal.jpg" ), metalness : 0.2 , roughness : 0.07 , });
完整代码 使用移位贴图来改变顶点位置 简介
移位贴图能够根据贴图的内容,修改模型顶点,真正改变模型的形状。
图中越亮的颜色会使顶点移位越远。
除了给displacementMap属性指定纹理对象之外,displacementScale和displacementBias两个属性也可以用来控制顶点的移位程度。
使用移位贴图的模型必须具有大量顶点。否则其顶点移位效果看起来会与移位贴图并不相像。因为顶点过少的模型没有足够的顶点可以移动。
创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var textureLoader = new THREE .TextureLoader ();var sphereGeometry = new THREE .SphereGeometry (4 ,20 ,20 );var sphereMaterial = new THREE .MeshStandardMaterial ({ map : textureLoader.load ("../assets/textures/w_c.jpg" ), displacementMap : texture.load ("../assets/textures/w_d.jpg" ), metalness : 0.2 , roughness : 0.07 , color : 0xffffff , });
完整代码 用环境光遮挡贴图实现细节阴影 简介
场景中总是有静止不动的物体和光源,所以投射到物体上的阴影也不会变化,因此如果能够计算一此阴影数据并在渲染时重复使用,为了做到这一点,three.js提供了两种贴图:环境光遮挡贴图和光照贴图
环境光遮挡技术用于决定模型的哪一部分暴露于环境光之中 。
在Blender或类似的软件中,环境光通常被当作半球光源或者平行光源来考虑
虽然模型的大部分表面都能接收到环境光,但是它们接受光线的多少仍然有差别(以一个站立的人物模型为例,头顶往往接收的环境光更多,而胳膊下侧则接收更少)。
这种光照的差异可以被渲染(又称烘培)到一张纹理贴图上,然后与颜色贴图混合在一起应用到模型上。这样一来可以避免在渲染循环中重复计算光照差异。
环境光遮挡贴图
环境遮挡贴图的作用是指出模型上哪些部分处于阴影中,应该从环境光中接受较少的光照。与环境光遮挡贴图作用相反的是光照贴图。该贴图用来决定模型的哪些部分需要补充更多的光照。
创建 1 2 3 4 5 6 7 8 9 10 11 var textureLoader = new THREE .TextureLoader ();var meterial = new THREE .MeshStardardMaterial ({ aoMap : textureLoader.load ("../assets/models/baymax/ambient.png" ), aoMapIntensity : 2 , color : 0xffffff , metalness : 0 , roughtness : 1 , });
💡请记住,在使用aoMap属性或者lightMap属性时,对于光照贴图来说,需要使用faceVertexUvs[1]。
geometry.faceVertexUvs.push(geometry.faceVertexUvs[0]);
完整代码 用光照贴图产生假阴影 简介
光照贴图里面的信息用于指出一个模型的特定部分应该从场景中接收多少光照,换句话说,模型的光照信息被预先烘培到了纹理贴图中。
有很多3D图形软件可以用于烘培光照贴图(比如Blender)
下图用于渲染地面。从图中可以看出,大部分面被白色光照亮,但有一处较暗的阴影部分。
(这是因为要在地面上放置一个物体,它会在其地面上遮挡部分光照)
烘培到纹理贴图中的阴影、光照以及环境光遮挡信息只能用于静态场景,或者场景中的静态物体。一旦光源或物体发生移动或者改变,就不得不实时计算阴影了
创建 1 2 3 4 5 6 plane.geometry .faceVertexUvs .push (plane.geometry .faceVertexUvs [0 ]); plane.material = new THREE .MeshBasicMaterial ({ map : textureLoader.load ("../assets/textures/general/floor-wood.jpg" ), lightMap : textureLoader.load ("../assets/textures/lightmap/lightmap.png" ); });
完整代码 金属光泽度贴图和粗糙度贴图 简介
MeshStandardMaterial材质中可以用metalness和roughness两个属性来设置大部分物体的表面质感。除了可以直接用数值来设置这两个属性值之外,还可以通过纹理贴图来设置。
如果希望在一个表面粗糙的物体上指定一些闪亮的局部,则可以为metalnessMap属性设置一张金属质感贴图。
若希望在一个光滑的物体上设置一些粗糙的局部,则可以在roughnessMap属性上使用纹理贴图来实现。
当使用纹理贴图来设置两个属性时,在模型的具体位置上,metalness和roughness两个属性的实际值等于属性值本身与响应贴图中的值的乘积。
在环境贴图的基础上,金属光泽度属性值越高的物体,表面的反射越清晰,粗糙度值越高的物体,反射越浑浊。
如下图所示,画面中的左图在roughness属性上设置了粗糙度贴图,从而使小球锈迹斑斑。右边的小球在metalness属性上设置了金属光泽度贴图,整个球面使粗糙的,只有一些区域被磨的光亮,显示出金属表面特有的镜面反射效果。
💡注意:金属光泽度属性metalness的值会先与来自metalnessMap贴图中的值相乘,粗糙度属性roughness的值则会与先来自roughnessMap贴图中的值相乘,然后再应用于物体渲染中。
创建 1 2 met1.metalnessMap = textureLoader.load ("../assets/textures/engraved/roughness-map.jpg" ); mat1.roughnessMap = textureLoader.load ("../assets/textures/engraved/roughness-map.jpg" );
完整代码 Alpha贴图 简介
Alpha贴图用于控制物体表面的透明度
贴图中的纯黑部分代表该部分表面完全透明,纯白色部分则代表完全不透明
创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var sphereMaterial = new THREE .MeshStandardMaterial ({ alphaMap : textureLoader.load ("../assets/textures/alpha/partial-transparenxy.png" ), envMap : alternativeMap, metalness : 0.02 , roughness : 0.07 , color : 0xffffff , alphaTest : 0.5 , }); sphereMaterial.alphaMap .wrapS = THREE .RepeatWrapping ; sphereMaterial.alphaMap .wrapT = THREE .RepeatWrapping ; sphereMaterial.alphaMap .repeat .set (8 ,8 );
完整代码 自发光贴图 简介
自发光贴图是一个控制模型表面实现自发光效果的纹理贴图
自发光物体本身看起来闪耀光芒,但是对周围环境毫无影响。也就是说自发光特性只能单独影响物体本身,不能使该物体变成光源
💡注意:由于来自emissiveMap的颜色由emissive属性值调制,因此需要将该属性值设置为非0才能看见自发光贴图效果。
创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var cubeMaterial = new THREE .MeshStandardMaterial ({ emissive : 0xffffff , emissiveMap : textureLoad.load ("../assets/textures/emissive/lava.png" ), normalMap : textureLoad.load ("../assets/textures/emissive/lava-normals.png" ), metalnessMap : textureLoad.load ("../assets/textures/emissive/lava-smoothness.png" ), metalness : 1 , roughness : 0.4 , normalScal : new THREE .Vector2 (4 ,4 ), });
完整案例 高光贴图 简介
一般情况下,在新版的three.js中THREE.MeshStandardMaterial材质是最佳选择,可以通过调节其各种属性来生成现实世界中的各种真实材质
若要使用高光贴图,需要使用MeshPhongMaterial材质。
高光贴图用于指定物体表面中哪些部分比较闪亮,或者哪部分相对暗淡。与THREE.MeshStandardMaterial材质的金属光泽度贴图和粗糙度贴图的作用有些接近
创建 1 2 3 4 5 6 7 8 9 var earthMaterial = new THREE .MeshPhongMaterial ({ map : textureLoader.load ("../assets/textures/earth/Earth.png" ), normalMap : textureLoader.load ("../assets/textures/earth/EarthNormal.png" ), specularMap : textureLoader.load ("../assets/textures/earth/EarthSpec.png" ), normalScale : new THREE .Vertor2 (6 ,6 ), });
完整案例 渲染一个地球,其中使用高光贴图来生成相对闪亮的海面和相对暗淡的陆地表面
使用环境贴图创建伪镜面效果 简介
计算镜面反射效果对CPU的消耗是非常大的,而且通常会使用光线追踪算法
在three.js中可以通过创建一个对象所处环境的纹理来伪装镜面反射,并将它应用到指定的对象上。
创建 案例完整代码 自定义UV映射 重复纹理 简介
需要保证将纹理的包裹属性设置为THREE.RepeatWrapping
cube.material.map.wrapS = THREE.RepeatWrapping; cube.material.map.wrapT = THREE.RepeatWrapping;
wrapS属性定义的是纹理沿x轴方向的行为,wrapT是沿y轴方向的行为。three.js为这些属性提供了如下两个选项
THREE.RepeatWrapping 允许纹理重复自己
THREE.ClampToEdgeWrapping 是属性的默认值,纹理的整体不会重复,只会重复纹理边缘的像素来填满剩下的空间。
如果设置了THREE.RepeatWrapping,可以通过以下代码设置repeat属性
cube.material.map.repeat.set(repeatX,repeatY);
变量repeatX用来指定纹理在x轴方向多久重复一次
变量repeatY用来指定纹理在y轴方向多久重复一次
如果变量的值为1,那么纹理不会重复
如果变量的值大于1,纹理开始重复
如果变量的值小于1,纹理被放大
如果变量的值为负数,会产生一个纹理的镜像
当修改repeat属性时,three.js会自动更新纹理,并使用新的值来进行渲染
如果将值从THREE.RepeatWrapping改为THREE.ClampToEdgeWrapping,需要明确的更新纹理
cube.material.map.needsUpdate = true;
在画布上绘制图案并作为纹理 将画布作为纹理 如何在画布上创建简单纹理,并应用到网格
简介
需要使用Literally库(http://literallycanvas.com/)来创建一个交互式画布
创建一个画布元素
1 2 3 4 5 6 7 <div class ="fs-container" > <div id ="canvas-output" > </div > </div> ... var canvas = document .createElement ("canvas" );document .getElementById ("canvas-output" ).appendChild (canvas);$("#canvas-output" ).literallycanvas ({imageURLPrefix :"../libs/other/literally/img" })
将画布上绘制的结果作为输入创建一个纹理(在创建纹理时将画布元素的引用作为参数传递给纹理对象的构造函数new THREE.Texture(canvas))。
1 var texture = new THREE .Texture (canvas);
最后需要做的是在渲染时更新材质,这样画布上的内容就会显示在物体上。
(为了告知three.js我们需要更新材质,需要将纹理的needsUpdate属性设置为true)
1 2 3 4 5 function render ( ){ renderer.render (scene,camera); cube.material .map .needsUpdate = true ; }
完整案例代码 将画布用作凹凸贴图 将视频输出作为纹理 创建动画和移动摄像机 简单动画 简介
可以通过改变物体的旋转,位置,缩放,材质等属性来实现动画
以下案例实现简单动画(方块旋转动画,小球弹跳动画,圆柱缩放动画)
案例完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 var scene;function initScene ( ) { scene = new THREE .Scene (); } var camera;function initCamera ( ) { camera = new THREE .PerspectiveCamera (45 ,window .innerWidth /window .innerHeight ,0.1 ,1000 ); camera.position .set (30 ,40 ,30 ); camera.lookAt (new THREE .Vector3 (0 ,0 ,0 )); scene.add (camera); } var renderer;function initRenderer ( ) { renderer = new THREE .WebGLRenderer (); renderer.setClearColor (new THREE .Color (0x000000 )); renderer.setSize (window .innerWidth ,window .innerHeight ); renderer.shadowMap .enabled = true ; document .getElementById ("webgl-output" ).appendChild (renderer.domElement ); } var ambientLight,spotLight;function initLight ( ){ ambientLight = new THREE .AmbientLight (0x444444 ); scene.add (ambientLight); spotLight = new THREE .SpotLight (0xffffff ); spotLight.position .set (20 ,30 ,40 ); spotLight.shadow .mapSize .width = 2048 ; spotLight.shadow .mapSize .height = 2048 ; spotLight.shadow .camera .fov = 150 ; spotLight.castShadow = true ; scene.add (spotLight); } var axes,plane,cube,sphere,cylinder;function initModels ( ){ axes = new THREE .AxesHelper (50 ); var planeGeometry = new THREE .PlaneGeometry (20 ,60 ); var planeMaterial = new THREE .MeshLambertMaterial ({ color : 0xffffff , }); var plane = new THREE .Mesh (planeGeometry,planeMaterial); plane.rotation .x = -0.5 *Math .PI ; plane.position .set (0 ,0 ,0 ); plane.receiveShadow = true ; scene.add (plane); var cubeGeometry = new THREE .CubeGeometry (4 ,4 ,4 ); var cubeMaterial = new THREE .MeshLambertMaterial ({ color : 0xff0000 , }); cube = new THREE .Mesh (cubeGeometry,cubeMaterial); cube.castShadow = true ; cube.position .set (0 ,3 ,20 ); scene.add (cube); var sphereGeometry = new THREE .SphereGeometry (4 ,20 ,20 ); var sphereMaterial = new THREE .MeshStandardMaterial ({ color : 0x0000ff , metalness : 0.05 , roughness : 0.07 , }); sphere = new THREE .Mesh (sphereGeometry,sphereMaterial); sphere.position .set (0 ,4 ,-20 ); sphere.castShadow = true ; scene.add (sphere); var cylinderGeometry = new THREE .CylinderGeometry (2 ,2 ,15 ); var cylinderMaterial = new THREE .MeshLambertMaterial ({ color : 0x00ff00 , }); cylinder = new THREE .Mesh (cylinderGeometry,cylinderMaterial); cylinder.position .set (0 ,0 ,10 ); cylinder.castShadow = true ; scene.add (cylinder); } var animation;function initGUI ( ){ animation = new function ( ){ this .rotationSpeed = 0.02 ; this .bouncingSpeed = 0.03 ; this .scalingSpeed = 0.03 ; } var gui = new dat.GUI (); gui.add (animation,'rotationSpeed' ,0 ,0.5 ,0.001 ).onChange (function (e ){ animation.rotationSpeed = e; }); gui.add (animation,'bouncingSpeed' ,0 ,0.5 ,0.001 ).onChange (function (e ){ animation.bouncingSpeed = e; }); gui.add (animation,'scalingSpeed' ,0 ,0.5 ,0.001 ).onChange (function (e ){ animation.scalingSpeed = e; }); } var controls;function initControls ( ){ controls = new THREE .OrbitControls (camera,renderer.domElement ); controls.enablePan = true ; controls.enableZoom = true ; } function windowOnresize ( ){ camera.aspect = window .innerWidth /window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerWidth ); } var stats;function initStats ( ){ stats = new Stats (); document .getElementById ("webgl-output" ).appendChild (stats.domElement ); } var step = 0 ,scalingStep = 0 ;function render ( ){ stats.update (); cube.rotation .x += animation.rotationSpeed ; cube.rotation .y += animation.rotationSpeed ; cube.rotation .z += animation.rotationSpeed ; step += animation.bouncingSpeed ; sphere.position .z = -10 +10 *Math .cos (step); sphere.position .y = 4 +10 *Math .abs (Math .sin (step)); scalingStep += animation.scalingSpeed ; var scaleX = Math .abs (Math .sin (scalingStep/4 )); var scaleY = Math .abs (Math .cos (scalingStep/5 )); var scaleZ = Math .abs (Math .sin (scalingStep/7 )); cylinder.scale .set (scaleX,scaleY,scaleZ); requestAnimationFrame (render); renderer.render (scene,camera); } function draw ( ){ initScene (); initStats (); initCamera (); initRenderer (); initLight (); initModels (); initControls (); initGUI (); render (); window .onresize = windowOnresize; }
如何使用鼠标选择场景中的对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var projector = new THREE .Projector ();function onDocumentMouseDown (event ){ var vector = new THREE .Vector3 ((event.clientX / window .innerWidth )*2 -1 ,-(event.clientY / window .innerHeight )*2 +1 ,0.5 ); vector = vector.unproject (camera); var raycaster = new THREE .Raycaster (camera.position ,vector.sub (camera.position ).normalize ()); var intersects = raycaster.intersectObjects ([sphere,cylinder,cube]); if (intersects.length > 0 ){ console .log (intersects[0 ]); intersects[0 ].object .material .transparent = true ; intersects[0 ].object .material .opacity = 0.1 ; } } window .addEventListener ("mousdown" ,onDocumentMouseDown,false );
使用Tween.js实现动画 简介
Tween.js是一个轻量级的JavaScript库,可以从http://github.com/sole/tween.js/下载
通过这个库可以很容易地实现某个属性在两个值之间进行过度,起始值和结束值的所有中间值都会自动计算出来,tweening补间。
例如,你可以使用这个值将网格的x轴坐标值在10秒内从10递减到3
var tween = new THREE.Tween({x:10}).to({x:3},1000) .easing(TWEEN.Easing.Elastic.Inout) .onUpdate(function(){ //update the mesh })
摄像机控件
名称
描述
第一视角控制器(FirstPersonControls)
该控制器的行为类似第一视角射击游戏中的摄像机,使用键盘移动,使用鼠标转动
飞行控制器(FlyControls)
飞行模拟控制器,用键盘和鼠标控制摄像机的移动
翻滚控制器(RollControls)
该控制器是飞行控制器的简化版,允许绕着z轴旋转
轨迹球控制器(TrackBallControls)
最常用的控制器,可以使用鼠标或者控制球来移动,平移和缩放场景。如果你使用的是OrtographicCamera,可以使用OrtographicTrack Ballcontrols,它是这个摄像机类型专用的
轨道控制器(OrbitControls)
该控件可以在特定的场景中模拟轨道中的卫星,可以使用鼠标和键盘在场景中游走
轨迹球控制器(TrackBallControls)
使用TrackBallControls控制器时,先在HTML页面引入对应的JavaScript文件
创建控制器,并绑定到摄像机上
var trackballControls = new THREE.TrackballControls(camera); trackballControls.rotateSpeed = 1.0; //旋转速度,默认值1.0 trackballControls.zoomSpedd = 1.0; //缩放速度,默认值1.2 trackballControls.panSpeed = 1.0; //平移速度,默认值是0.3
摄像机的位置更新
var clock = new THREE.Clock(); function render(){ var delta = clock.getDelta(); //调用clock.getDelta()方法可以精确的计算出此次调用距离上次调用的时间间隔 trackballControls.update(delta); //更新控制器,常被用在动画循环中。 requestAnimationFrame(render); webGLRender.render(scene,camera); }
飞行控制器(FlyControls)
使用飞行控制器可以像飞行模拟器一样在场景中飞行
首先需要在HTML页面中加载对于的JavaScript文件
创建控制器并绑定到摄像机上
1 2 3 4 5 6 7 var flyControls = new THREE .FlyControls (camera);flyControls.movementSpeed = 25 ; flyControls.domElement = document .querySelector ('#webgl-output' ); flyControls.rollSpeed = Math .PI /24 ; flyControls.autoForward = true ; flyControls.dragToLook = false ;
摄像机的位置更新
第一视角控制器(FirstPersonControls)
通过第一视角控制器可以像第一视角射击游戏那样控制摄像机。鼠标用于控制视角,键盘用于控制移动角色
同样先引入对应的JavaScript文件,然后创建控制器并绑定摄像机
1 2 3 4 5 6 7 8 9 var fpControls = new THREE .FirstPersonControls (camera);fpControls.lookSpeed = 0.4 ; fpControls.movementSpeed = 20 ; fpControls.lookVertical = true ; fpControls.constrainVertical = true ; fpControls.verticalMin = 1.0 ; fpControls.verticalMax = 2.0 ; fpControls.lon = -150 ; fpControls.lat = 120 ;
摄像机的位置更新
轨道控制器(OrbitControls)
轨道控制器可以用于控制场景中的对象围绕场景中心旋转和平移。1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script type="text/javaScript" asrc="../assets/libs/three/controls/OrbitControls" > </script> ... var orbitControls = new THREE .OrbitControls (camera);orbitControls.autoRotate = true ; ... var clock = new THREE .Clock ();function render ( ){ orbitControls.update (clock.getDelta ()); }
变形动画和骨骼动画
变形动画
使用变形动画,需要定义网格变形之后的状态,或者是关键位置
对于变形目标,所有顶点位置都会被存储下来。
所需要做的事将所有顶点从一个位置移动到另一个定义好的关键位置,并重复该过程
骨骼动画
使用骨骼动画时需要定义骨骼,也就是网格的骨头,并把顶点绑定到特定的骨头上。
当你移动一块骨头时,任何与其相连的骨头都会做相应的移动,同时骨头上绑定的顶点也会随之移动
网格的变形也是基于骨头的位置、移动和缩放实现的
用变形目标创建动画 变形目标时实现动画的最直接方式。可以为所有顶点定义一系列的关键位置(也称关键帧),然后让Three.js将这些顶点动一个位置移动到另一个位置。这种方法对大型网格和大型动画来说,模型文件会变得非常大,因为在每个关键帧的位置,所有顶点的位置都需要重复存储一遍。
使用混合器和变形目标创建动画 Three.js的三个核心动画类
THREE.AnimationClip(动画片段):
当具有动画数据的模型被加载的时候,这个模型对象往往具有一个名为animations的成员对象。该对象包含了一个THREE.AnimationClip对象集合
一个模型所包含的THREE.AnimationClip对象通常保存有某种特定类型的动画数据,或者时该模型能够执行的某种动作。
比如当加载一个鸟模型时,它可能包含了两个THREE.AnimationClip对象,一个保存了拍打翅膀的作用,另一个保存了张嘴闭嘴的作用。
THREE.AnimationMixer(动画混合器):
THREE.AnimationMixer对象用于控制多个THREE.AnimationClip对象,确保这些动画在适当的时间发生,使动画同步或者控制从一个动画过度到另一个动画。
主要方法为:
mixer = new THREE.AnimationMixer(mesh);//通过mesh获取到AnimationMixer对象action = mixer.clipAction(clip);//用clipAction方法生成可以控制执行动画的实例
THREE.AnimationAction(动画行为):
当向THREE.AnimationMixer对象添加一个THREE.AnimationClip对象时,调用者会获得一个THREE.AnimationAction。
很多动画控制功能是通过THREE.AnimationAction 调用的,而THREE.AnimationMixer并没有提供很全面的控制接口。
实例(马的奔跑运动)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 var loader = new THREE .JSON Loader();var mixer = new THREE .AnimationMixer ();var mesh,clipAction,frameMesh;function initModels ( ){ loader.load ('../assets/models/horse/horse.js' , function (geometry, mat ) { geometry.computeVertexNormals (); geometry.computeMorphNormals (); var mat = new THREE .MeshLambertMaterial ({ morphTargets :true , vertexColors :THREE .FaceColors , }); mesh = new THREE .Mesh (geometry,mat); mesh.scale .set (0.15 ,0.15 ,0.15 ); mesh.translateY (-10 ); mesh.translateX (20 ); mesh.rotation .y = -0.3 *Math .PI ; mixer = new THREE .AnimationMixer (mesh); var animationClip = geometry.animations [0 ]; clipAction = mixer.clipAction (animationClip).play (); mixer.timeScale = 10 ; clipAction.timeScale = 10 ; clipAction.effectiveTimeScale = 10 ; clipAction.setLoop (THREE .LoopRepeat ); scene.add (mesh); }); }
最后需要更新mixer
1 2 3 4 5 6 7 8 9 10 11 12 var clock = new THREE .Clock ();function render ( ){ stats.update (); controls.update (clock.getDelta ()); requestAnimationFrame (render); renderer.render (scene,camera); if (mixer){ mixer.update (clock.getDelta ()); } }
💡必须用mixer.update(delta)的形式去调用update函数,以便告诉混合器此次渲染和上次渲染的时间差
使用多个THREE.AnimationClip对象 实例(立方体的放大与缩小动画)手动创建两个动画,第一段动画:将立方体的尺寸从(2,2,2)变为(2,20,2)第二段动画:将立方体尺寸从(2,2,2)变为(40,2,2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 var loader = new THREE .JSON Loader();var mixer = new THREE .AnimationMixer ();var mesh,clipAction1,clipAction2,animationClip1,animationClip2;function initModels ( ){ var cubeGeometry = new THREE .BoxGeometry (2 ,2 ,2 ); var cubeMaterial = new THREE .MeshLambertMaterial ({ morphTargets :true , color : 0xff0000 , }); var cubeTarget1 = new THREE .BoxGeometry (2 ,20 ,2 ); var cubeTarget2 = new THREE .BoxGeometry (40 ,2 ,2 ); cubeGeometry.morphTargets [0 ] = {name :'t1' ,vertices :cubeGeometry.vertices }; cubeGeometry.morphTargets [1 ] = {name :'t2' ,vertices :cubeTarget1.vertices }; cubeGeometry.morphTargets [2 ] = {name :'t3' ,vertices :cubeTarget2.vertices }; cubeGeometry.computeMorphNormals (); var cube = new THREE .Mesh (cubeGeometry,cubeMaterial); cube.position .set (-10 ,0 ,0 ); scene.add (cube); mixer = new THREE .AnimationMixer (cube); animationClip1 = THREE .AnimationClip .CreateFromMorphTargetSequence ( 'first' , [cubeGeometry.morphTargets [0 ],cubeGeometry.morphTargets [1 ]], 1 ); animationClip2 = THREE .AnimationClip .CreateFromMorphTargetSequence ( 'second' , [cubeGeometry.morphTargets [0 ],cubeGeometry.morphTargets [2 ]], 1 ); clipAction1 = mixer.clipAction (animationClip1).play (); clipAction2 = mixer.clipAction (animationClip2).play (); mixer.timeScale = 10 ; clipAction1.timeScale = 10 ; }
用骨骼和蒙皮创建动画
当使用骨骼创建动画时,移动骨骼,three.js需要决定如何相应的移动相应的皮肤(一系列顶点)
案例一个手模型,上面有几块骨头,通过移动这几块骨头,就可以让整个模型动起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var mesh;function initModels ( ){ loader = new THREE .JSON Loader(); loader.load ("../assets/models/hand/hand-1.js" ,function (geometry,amt ){ var mat = new THREE .MeshLambertMaterial ({ color :0xF0C8C9 , skinning :true , }); mesh = new THREE .SkinnedMesh (geometry,mat); mesh.scale .set (15 ,15 ,15 ); mesh.position .x = -5 ; mesh.rotateX (0.5 *Math .PI ); mesh.rotateZ (0.3 *Math .PI ); scene.add (mesh); startAnimation (); }); } var onUpdate = function ( ){ var pos = this .pos ; mesh.skeleton .bones [5 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [6 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [10 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [11 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [15 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [16 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [20 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [21 ].rotation .set (0 ,0 ,pos); mesh.skeleton .bones [1 ].rotation .set (pos,0 ,0 ); } var tween;function startAnimation ( ){ tween = new TWEEN .Tween ({pos :-1.5 }) .to ({pos :0 },3000 ) .easing (TWEEN .Easing .Cubic .InOut ) .yoyo (true ) .repeat (Infinity ) .onUpdate (onUpdate) tween.start (); }
使用外部模型创建动画 使用Blender创建骨骼动画 为了能够让动画运行起来,还需要创建THREE.SkinnedMesh对象,共同使用THREE.AnimationMixer、THREE.AnimationClip和THREE.ClipAction告诉three.js如何运行导入的动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 var mesh,animationClip,clipAction,mixer;function initModels ( ){ loader = new THREE .JSON Loader(); loader.load ("../assets/models/hand/hand-8.json" ,function (geometry,amt ){ var mat = new THREE .MeshNormalMaterial ({ color :0xF0C8C9 , skinning :true , }); mesh = new THREE .SkinnedMesh (geometry,mat); mesh.scale .set (18 ,18 ,18 ); scene.add (mesh); mixer = new THREE .AnimationMixer (mesh); animationClip = mesh.geometry .animations [0 ]; clipAction = mixer.clipAction (animationClip).play (); animationClip = clipAction.getClip (); }); } var clock = new THREE .Clock ();function render ( ){ stats.update (); requestAnimationFrame (render); renderer.render (scene,camera); if (mixer){ mixer.update (clock.getDelta ()); } }
从Collada模型加载动画
第一步需要先引入对应的JavaScript文件(ColladaLoader.js)
由于普通的Collada模型是不压缩的,因此文件非常大。three.js还有一个KMZLoader加载器,用于加载KMZ(Keyhole Markup language Zipped)模型,该模型基本上就是压缩过的Collada模型。如需加载此类模型,只需要将ColladaLoader替换为KMZLoader即可
第二部创建一个加载器,加载模型文件。
Collada模型不仅可以包含模型,还可以保存包含摄像机、光源和动画等的场景。使用Collada模型最好的方式是将loader.load方法的调用结果输出在控制台,然后决定使用哪些组件。
第三步set up the mixer1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var mesh,animationClip,clipAction,mixer;function initModels ( ){ loader = new THREE .ColladaLoader (); loader.load ("../assets/models/monster/monster.dae" ,function (result ) { scene.add (result.scene ); result.scene .rotateZ (-0.2 *Math .PI ); result.scene .translateX (-20 ); result.scene .translateY (-20 ); mixer = new THREE .AnimationMixer (result.scene ); animationClip = result.animations [0 ]; clipAction = mixer.clipAction (animationClip).play (); animationClip = clipAction.getClip (); enableControls (); }); }
从雷神之锤模型中加载动画
加载MD2文件的方式与前面介绍的其他文件方式非常相似,但是由于MD2文件只存储几何体,所以在加载MD2文件时,需要创建材质对象,并自行为其加载纹理资源。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function initModels ( ){ var textureLoader = new THREE .TextureLoader (); loader = new THREE .MD2Loader (); loader.load ("../assets/models/ogre/ogro.md2" ,function (result ) { var mat = new THREE .MeshStandardMaterial ({ color : 0xffffff , morphTargets : true , metalness : 0 , map : textureLoader.load ("../assets/models/ogre/skins/skin.jpg" ) }) var mat2 = new THREE .MeshNormalMaterial (); mesh = new THREE .Mesh (result,mat); scene.add (mesh); mixer = new THREE .AnimationMixer (mesh); animationClip1 = result.animations [7 ]; clipAction1 = mixer.clipAction (animationClip1).play (); animationClip2 = result.animations [9 ]; clipAction2 = mixer.clipAction (animationClip2) animationClip3 = result.animations [10 ]; clipAction3 = mixer.clipAction (animationClip3) }); }
使用gltfLoader
gltf格式本身侧重于优化文件尺寸以及提高资源使用效率
这种格式的新版加载器名为THREE.GLTFLoader,它支持2.0版(这是目前的gltf标准格式)以及更高版本的glTF文件。
首先在HTML页面引入对应的Javascript文件
然后按照下面方式使用glTFLoader加载器1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function initModels ( ){ loader = new THREE .GLTFLoader (); loader.load ("../assets/models/CesiumMan/CesiumMan.gltf" ,function (result ) { scene.add (result.scene ); result.scene .scale .set (20 ,20 ,20 ); result.scene .rotateY (-0.4 *Math .PI ); mixer = new THREE .AnimationMixer (result.scene ); animationClip = result.animations [0 ]; clipAction = mixer.clipAction (animationClip).play (); animationClip = clipAction.getClip (); enableControls (); }); }
利用fbxLoader显示动作捕捉模型动画
Autodesk的FBX格式是一种非常易于使用的格式。https://www.mixamo.com/ 提供了约2500个动画模型可供下载和使用1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function initModels ( ){ loader = new THREE .FBXLoader (); loader.load ("../assets/models/salsa/salsa.fbx" ,function (result ) { scene.add (result); result.scale .set (0.2 ,0.2 ,0.2 ); mixer = new THREE .AnimationMixer (result); animationClip = result.animations [0 ]; clipAction = mixer.clipAction (animationClip).play (); animationClip = clipAction.getClip (); enableControls (); }); }
通过xLoader加载古老的DirectX模型
这种文件格式将模型和动画分开存储。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function initModels ( ){ var manager = new THREE .LoadingManager (); var textureLoader = new THREE .TextureLoader (); var loader = new THREE .XLoader (manager,textureLoader); var animLoader = new THREE .XLoader (manager,textureLoader); loader.load (["../assets/models/x/SSR06_model.x" ],function (result ){ var mesh = result.models [0 ]; animLoader.load (["../assets/models/x/stand.x" ,{putPos :false ,putScl :false }],function (anim ){ animLoader.assignAnimation (mesh); mixer = mesh.animationMixer ; clipAction = mixer.clipAction ("stand" ).play (); clip = clipAction.getClip (); mesh.translateY (-6 ); mesh.rotateY (-0.7 *Math .PI ); scene.add (mesh); enableControls (); }) }) }
利用BVHLoader显示骨骼动画
BVHLoader的特殊之处在于该加载器不返回具有动画的网格或者几何体,它只返回骨骼和动画。
为了可视化,在程序中使用了THREE.SkeletonHelper1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function initModels ( ){ var loader = new THREE .BVHLoader (); loader.load ("../assets/models/amelia-dance/DanceNightClub7_t1.bvh" ,function (result,mat ){ skeletonHelper = new THREE .SkeletonHelper (result.skeleton .bones [0 ]); skeletonHelper.skeleton = result.skeleton ; var boneContainer = new THREE .Object3D (); boneContainer.translateY (-70 ); boneContainer.translateX (-100 ); boneContainer.add (result.skeleton .bones [0 ]); scene.add (skeletonHelper); scene.add (boneContainer); mixer = new THREE .AnimationMixer (skeletonHelper); clipAction = mixer.clipAction (result.clip ).setEffectiveWeight (1.0 ).play (); enableControls (); }) }
如何重用SEA3D模型
SEA3D是一个开源软件项目,它的功能很丰富,通常可以用于制作游戏、创建模型、添加动画等
在创建THREE.SEA3D加载器对象的时候,就要同时向它提供场景容器THREE.Scene的对象。
加载状态回调函数也不是在调用load函数时提供,而是直接向onComplete属性提供回调函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function initModels ( ){ var sceneContainer = new THREE .Scene (); var loader = new THREE .SEA3D ({ container :sceneContainer, }); loader.load ("../assets/models/mascot/mascot.sea" ); loader.onComplete = function (e ){ var skinnedMesh = sceneContainer.children [0 ]; skinnedMesh.scale .set (0.1 ,0.1 ,0.1 ); skinnedMesh.translateX (-40 ); skinnedMesh.translateY (-20 ); skinnedMesh.rotateY (-0.2 *Math .PI ); scene.add (skinnedMesh); mixer = new THREE .AnimationMixer (skinnedMesh); animationClip = skinnedMesh.animations [0 ].clip ; clipAction = mixer.clipAction (animationClip).play (); animationClip = clipAction.getClip (); enableControls (); } }
绘制直线和曲线 直线 方法一: tips var p1 = new THREE.Vector3(5,6,7);var p2 = new THREE.Vector3(50,60,4);var geometry = new THREE.Geometry();geometry.vertices.push(p1,p2); var material = new THREE.LineBasicMaterial({color:0xffffff});var line = new THREE.Line(geometry,material);scene.add(line);
方法二: tips var p3 = new THREE.Vector3(20,4,50);var p4 = new THREE.Vector3(1,40,8);var line2 = new THREE.LineCurve3(p3,p4);let points = line2.getPoints(100);geometry.setFromPoints(points);
曲线(自定义弧线) 通过添加点,绘制样条曲线
创建一个平面作为画板
1 2 3 4 5 6 7 8 9 var planeGeometry = new THREE .PlaneGeomrtry (60 ,60 );var planeMaterial = new THREE .MeshBasicMaterial ({ color :0xCCCCCC ; }); var plane = new THREE .Mesh (planeGeometry,planeMaterial);plane.rotatoX (-0.5 *Math .PI ); scene.add (plane);
创建点击事件,获取平面上的点坐标,并创建圆点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var raycaster = new THREE .Raycaster ();var mouse = new THREE .Vector2 ();window .addEventListener ("mousedown" ,mousedown);function mousedown (e ){ if (e.button == 0 ){ mouse.x = e.clientX /renderer.domElement .clientWidth *2 -1 ; mouse.y = -(e.clientY /renderer.domElement .clientHeight *2 )+1 ; raycaster.setFromCamera (mouse,camera); var intersects = raycaster.intersectObjects (scene.children ); if (intersects.length ){ selected = intersects[0 ]; } } }
绘制曲线(实时更新曲线——删除上一个曲线)
线的那些事 1. 线的材质
LineBasicMaterial tips const material = new THREE.LineBasicMaterial({color:0xffffff, //颜色linewidth:1, //线宽,一般线宽宽度总是1,不可设置linecap:’round’, //线端点类型,可以是:butt\round\square,默认是roundlinejoin:’round’, //线连接点类型,可以是:bevel\round\miter,默认是round})
LineDashedMaterial
虚线材质 tips const material = new THREE.LineDashedMaterial({color:0xffffff, //颜色linewidth:1, //线宽scale:1, //虚线的比例大小 ,默认1dashSize:3, //虚线的点的大小,默认3gapSize:1, //点之间的间距大小,默认1})
2. 线的几何 tips const points = [];points.push(new THREE.Vector3(-10,0,0));points.push(new THREE.Vector3(0,10,0));points.push(new THREE.Vector3(10,0,0));
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry,material);
3. LineCurve Raycaster
raycaster(光线投射),用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体),物体选中
Raycaster( origin, direction, near, far )
origin–射线的起点向量
direction–射线的方向向量,应该归一标准化
near–所有返回的结果应该比near远,near不能为负,默认0
far–所有返回的结果应该比far近,far不能小于near,默认值为无穷大1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const raycaster = new THREE .Raycaster ();const mouse = new THREE .Vector2 ();function onDocumentMouseDown (e ){ mouse.x = (event.clientX / window .innerWidth )*2 - 1 ; mouse.y = -(event.clientY / window .innerHeight )*2 + 1 ; raycaster.setFromCamera (mouse,camera); var intersects = raycaster.intersectObjects (objects); if (intersects.length >0 ){ var intersect = intersects[0 ].object ; } }
注意点 阴影问题(无法渲染正确阴影):
首先确认渲染器有无开启阴影贴图(默认是false)
renderer.shadowMap.enabled = true;
确认物体材质是否可受光照影响(MeshBasicMaterial材质不受光照影响)
确认光源是否可以产生阴影(PointLight、SpotLight、DirectionalLight可以有阴影)
确认光源是否开启阴影
spotLight.castShadow = true;
确认物体是否渲染阴影贴图和是否接收阴影
//地面接收阴影 ground.receiveShadow = true; //方块产生阴影 cube.castShaow = true;
页面响应式布局
给页面添加一个监听事件
设置摄像机的长宽比,手动更新相机的投影矩阵
设置渲染器的宽高1 2 3 4 5 6 7 8 9 10 11 window .addEventListener (resize,onResize,false );function onResize ( ){ camera.aspect = window .innerWidth /window .innerHeight ; camera.updateProjectionMatrix (); renderer.setSize (window .innerWidth ,window .innerHeight ); }