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元素位于背景前方
那么在三维空间中,与相机所在位置的相对空间位置如下图所示,
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>
手机阅读请扫描下方二维码:
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