做有态度的前端团队

网易FEG前端团队

ThreeJS学习笔记(五)——二维几何体元素及穿梭动画

二维几何体

ThreeJS可以创建三种二维几何体,包括CircleGeometry(圆形),PlaneGeometry(矩形),ShapeGeometry(自定义形状)。

创建二维几何体和创建三维几何体差不多,同样由形状和材质两个参数,拥有的属性也和三维几何体一样。
new THREE.Mesh(new THREE.PlaneGeometry(width, height, 1, 1 ),new THREE.MeshBasicMaterial(MaterialParam ));

需要注意的是,由于贴图的尺寸必须是(2的幂数)X (2的幂数),如:1024*512,所以为了防止贴图变形,平面的宽度比例需要与贴图的比例一致。
代码示例如下:

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元素位于背景前方

1472558137624.png

那么在三维空间中,与相机所在位置的相对空间位置如下图所示,

1472558264171.png

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;

以下创建多个平面的代码示例:

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的最大可视深度。

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

<!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>



手机阅读请扫描下方二维码: