ThreeJS学习笔记(五)——二维几何体元素及穿梭动画
二维几何体
ThreeJS可以创建三种二维几何体,包括CircleGeometry(圆形),PlaneGeometry(矩形),ShapeGeometry(自定义形状)。
创建二维几何体和创建三维几何体差不多,同样由形状和材质两个参数,拥有的属性也和三维几何体一样。
new THREE.Mesh(new THREE.PlaneGeometry(width, height, 1, 1 ),new THREE.MeshBasicMaterial(MaterialParam ));
需要注意的是,由于贴图的尺寸必须是(2的幂数)X (2的幂数),如:1024*512,所以为了防止贴图变形,平面的宽度比例需要与贴图的比例一致。
代码示例如下:
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 | function createPlane(options){ // options={ // width:0, // height:0, // pic:"", // transparent:true, // opacity:1 // blending:false // } if ( typeof options.pic== "string" ){ //传入的材质是图片路径,使用 textureloader加载图片作为材质 var loader = new THREE.TextureLoader(); loader.setCrossOrigin( this .crossOrigin ); var texture = loader.load( options.pic, function () {}, undefined, function (){}); } else {传入的材质是canvas var texture= new THREE.CanvasTexture( options.pic ) } var MaterParam={ //材质的参数 map:texture, overdraw: true , side: THREE.FrontSide, // blending: THREE.AdditiveBlending, transparent: options.transparent, //needsUpdate:true, //premultipliedAlpha: true, opacity:options.opacity } if (options.blending){ MaterParam.blending=THREE.AdditiveBlending //使用饱和度叠加渲染 } var plane = new THREE.Mesh( new THREE.PlaneGeometry( options.width, options.height, 1, 1 ), new THREE.MeshBasicMaterial(MaterParam ) ); return plane; } |
在3维空间中还原平面元素在设计稿上的大小和位置
设计稿如下图,要求三维空间中,A元素和背景处于不同的深度,A元素位于背景前方
那么在三维空间中,与相机所在位置的相对空间位置如下图所示,
A在该平面元素在三维空间中的位置如上图所示,面B为A平面最终渲染在屏幕上的区域,此区域应该与设计稿上的A的大小一致,那必然就有一个问题,在三维空间中,A元素的大小和坐标要如何设置,才能使A在屏幕上的投影B能与设计稿上的一致?
这里我们需要做一些计算,计算中会用到以下已知数值:
- fov:创建相机时设置的垂直方向的夹角,
- W:canvas的width,这里以1920为示例
- H:canvas的height,这里以1080为示例
- D:相机与屏幕所在平面的距离,
- d:相机与元素A的距离,
- aw: 元素A在设计稿上的宽度
- ah: 元素A在设计稿上的高度
- ax: 元素A中点心在设计稿上的中心点X轴上的偏移值
- ay: 元素A中点心在设计稿上的中心点Y轴上的偏移值
其中D可以视为在屏幕上投影面积为1920(W)*1080(H)的平面元素离相机的距离
计算公式为:D=H/(2*Math.tan(fov/2));
平面A在三维空间中尺寸与投影大小(即设计稿上的尺寸)的缩放倍数为
zoom=d/D=d*2*Math.tan(fov/2)/H
由此可得出平面元素A在三维空间中的大小设置为
w=aw*d*2*Math.tan(fov/2)/H;
h=ah*d*2*Math.tan(fov/2)/H;
位置坐标为
x=ax*d*2*Math.tan(fov/2)/H;
y=ay*d*2*Math.tan(fov/2)/H;
以下创建多个平面的代码示例:
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 | var fov=45; var camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 1, 300000 ); camera.position.set(0, 0, 1500); var planes=[ [ "ship1" , "../resource/part1/ship1.png" ,1024,1024,700,-400,0,1], //name,pic,w,h,x,y,z,透明度 [ "plan1" , "../resource/part1/plan_1.png" ,1024,256,-170,50,-2000,1], [ "air" , "../resource/part1/airplane_1.png" ,512,512,150,100,-3000,1], [ "star1" , "../resource/part1/star_1.png" ,2317,979,150,0,-4500,1], [ "star2" , "../resource/part1/star_2.png" ,256,128,500,-100,-6000,1], [ "light" , "../resource/part1/light.png" ,1152,1024,200,50,-7500,1], [ "part1_bg" , "../resource/part1/part1_bg.jpg" ,2048*1.5,1024*1.5,0,0,-18000,1] ] planes.forEach( function (planeSet){ var scale=(camera.position.z-planeSet[6])*2*Math.tan(fov/2*Math.PI/180)/1080; //此处的1080为设计稿的高度 var plane=createPlane({ width:parseInt(planeSet[2]*scale), height:parseInt(planeSet[3]*scale), pic:planeSet[1], transparent: true , opacity:1 //planeSet[7] }) plane.position.set(parseInt(planeSet[4]*scale),parseInt(planeSet[5]*scale),planeSet[6]); plane.name=planeSet[0]; scene.add(plane); }) |
穿梭动画
穿梭动画是通过修改camera.position.z坐标来实现的。
需要说明的是,要改变position.z的时候还要注意camera.lookAt的坐标点,若camera.position.z超过了lookAt.z,画面会以180度翻转过来,所以要保持在一个纵向深度可穿越的话,需要将camera.lookAt的z坐标设置比所有可见元素的Z坐标还要大一点。
同时,还要注意camera的最大可视深度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | window.addEventListener( 'mousewheel' , onMouseWheel, false ); function onMouseWheel(event){ if (event.wheelDelta>0){ new TWEEN.Tween( camera.position ) .to( {z:camera.position.z+2500}, 500 ) .start(); } if (event.wheelDelta<0){ new TWEEN.Tween( camera.position ) .to( {z:camera.position.z-2500}, 500 ) .start(); } } function render() { camera.position.x += ( mouseX - camera.position.x ) ; camera.position.y += ( mouseY - camera.position.y ) ; camera.lookAt( new THREE.Vector3(0,0,-20000) ); //此处-20000要设置得比最远的平面元素更大一些。 renderer.render( scene, camera ); } |
本章DEMO
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 | <! DOCTYPE html> < html > < head > < meta charset = "UTF-8" > < title ></ title > </ head > < body > < div id = "space" ></ div > < script src = "https://code.jquery.com/jquery-1.12.4.min.js" integrity = "sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin = "anonymous" ></ script > < script src = "../js/lib/threejs/three.js" ></ script > < script src = "../js/lib/Tween.js" ></ script > < script > var container, stats; var camera, scene, renderer; var mouseX = 0, mouseY = 0; var windowHalfX = window.innerWidth / 2; var windowHalfY = window.innerHeight / 2; var planes=[ ["ship1","../resource/part1/ship1.png",1024,1024,700,-400,0,1],//name,pic,w,h,x,y,z,透明度 ["plan1","../resource/part1/plan_1.png",1024,256,-170,50,-2000,1], ["air","../resource/part1/airplane_1.png",512,512,150,100,-3000,1], ["star1","../resource/part1/star_1.png",2317,979,150,0,-4500,1], ["star2","../resource/part1/star_2.png",256,128,500,-100,-6000,1], ["light","../resource/part1/light.png",1152,1024,200,50,-7500,1], ["part1_bg","../resource/part1/part1_bg.jpg",2048*1.5,1024*1.5,0,0,-18000,1] ] init(); animate(); var mesh; function createPlane(options){ // options={ // width:0, // height:0, // pic:"", // transparent:true, // opacity:1 // blending:false // } if(typeof options.pic=="string"){ var loader = new THREE.TextureLoader(); loader.setCrossOrigin( this.crossOrigin ); var texture = loader.load( options.pic, function() {}, undefined, function(){}); }else{ var texture= new THREE.CanvasTexture( options.pic ) } var MaterParam={ map:texture, overdraw: true, side: THREE.FrontSide, // blending: THREE.AdditiveBlending, transparent: options.transparent, //needsUpdate:true, //premultipliedAlpha: true, opacity:options.opacity } if(options.blending){ MaterParam.blending=THREE.AdditiveBlending } var plane = new THREE.Mesh( new THREE.PlaneGeometry( options.width, options.height, 1, 1 ), new THREE.MeshBasicMaterial(MaterParam ) ); return plane; } function addPlanes(){ planes.forEach(function(planeSet){ var scale=(1500-planeSet[6])*2*Math.tan(22.5*Math.PI/180)/1080; var plane=createPlane({ width:parseInt(planeSet[2]*scale), height:parseInt(planeSet[3]*scale), pic:planeSet[1], transparent:true, opacity:1//planeSet[7] }) plane.position.set(parseInt(planeSet[4]*scale),parseInt(planeSet[5]*scale),planeSet[6]); plane.name=planeSet[0]; //plane.visible=false; scene.add(plane); }) } function init() { container = document.getElementById("space") camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500000 ); camera.position.set(0, 0, 1500); scene = new THREE.Scene(); var ambient = new THREE.AmbientLight( 0xffffff ); scene.add( ambient ); var directionalLight = new THREE.DirectionalLight( 0xffffff ); directionalLight.position.set( -5, 5, 5).normalize(); scene.add( directionalLight ); addPlanes(); renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); container.appendChild( renderer.domElement ); document.addEventListener( 'mousemove', onDocumentMouseMove, false ); window.addEventListener( 'resize', onWindowResize, false ); window.addEventListener( 'mousewheel', onMouseWheel, false ); } function onWindowResize() { windowHalfX = window.innerWidth / 2; windowHalfY = window.innerHeight / 2; camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } function onMouseWheel(event){ if(event.wheelDelta>0){ new TWEEN.Tween( camera.position ) .to( {z:camera.position.z+2500}, 1500 ) .start(); } if(event.wheelDelta< 0 ){ new TWEEN.Tween( camera.position ) .to( {z:camera.position.z-2500}, 1500 ) .start(); } } function onDocumentMouseMove( event ) { mouseX = ( event.clientX - windowHalfX ) / 2; mouseY = ( event.clientY - windowHalfY ) / 2; } // function animate() { requestAnimationFrame( animate ); render(); TWEEN.update(); } function render() { camera.position.x += ( mouseX - camera.position.x ) ; camera.position.y += ( mouseY - camera.position.y ) ; camera.lookAt( new THREE.Vector3(0,0,-20000) ); renderer.render( scene, camera ); } </script> </ body > </ html > |
手机阅读请扫描下方二维码:
1

1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

555
1

1

1

1
1

1

1

1

1

1

1

1

1

1

1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1