这是本人在CSDN上的第一篇文章,因为刚刚写了一个three.js的Demo,就传上来了,参考的是国外Karim Maaloul这位大佬的飞机游戏,给了我比较大的启发,和小伙伴们分享下。
three.js
three.js是JavaScript编写的WebGL第三方库。提供了非常多的3D显示功能。 —— [ 百度百科 ]
个人感觉这个库还是比较给力的,运用了图形建模的许多思想,一些在3D建模里的相机、灯光、贴图都被运用到three里,他的引用库文件可以在GitHub上面找到并下载,格式是zip的,解压后在build文件夹的就是,一个压缩,一个没压缩,在练习阶段建议用没压缩的,点进去它的结构一步了然,方便理解原理.
-
Demo图
-
HTML
页面的HTML部分需要引入three.js的库文件:
<script type="text/javascript" src="js/three.js"></script>
下面是HTML部分完整的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>漂浮立方</title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
#world{
width:100%;
height: 100%;
position: absolute;
overflow: hidden;
background: linear-gradient(#A4D3EE,#AEEEEE);
}
</style>
</head>
<body>
<div id="world"></div>
</body>
<script type="text/javascript" src="js/three.min.js"></script>
</html>
上面的代码在body里嵌套了一层div,three会在这个div里自动添加canvas,我们所有效果就在这里面,在css样式中,使用了两种背景色,作为渐变效果
JavaScript
注意three.js有四大核心要素:场景、相机、渲染器、模型对象,这四样要素缺一不可
- 场景
场景可以理解为舞台,是所有元素运作的环境,所有事物添加到场景中最终才能被我们看到,新建一个场景代码如下:
var scene = new THREE.Scene();
在创建好每个模型后往往用如下方法将模型置入场景中:
scene.add(e);
- 相机
相机有正交、透视、通用三种,可以理解为我们的眼睛,这里我们使用透视相机,代码如下:
var camera = new THREE.PerspectiveCamera(a1,a2,a3,a4);
四个参数:
视角:setFov(fov)设置视角的角度
宽高比:相机所看到的宽和高的比例
near:相机的近视角
far:表示相机的远视角
- 渲染器
三维空间里的物体映射到二维平面的过程被称为三维渲染。 一般来说我们都把进行渲染操作的软件叫做渲染器,渲染器需要加载到DOM节点中,创建渲染器代码如下:
//新建渲染器
var renderer = new THREE.WebGLRenderer();
//body中加载渲染引擎
document.body.appendChild(renderer.domElement);
- 模型对象
对象模型是一种事物的组合:几何体、材质、网格,three使用网格系统来建立几何模型,我们创建一个物体通常先创建相应的几何体,再设置材质,把他们添加到网格中,形成物体,在下面会具体讲解
漂浮立方
首先我们创建一个初始化init()函数,它使得冗长的代码能够以清晰的结构呈现出来,我们主要的函数都会在init里加载
window.addEventListener('load',init,false);
function init(){
//场景布局
createScene();
//灯光
createLight();
//场中的对象
//海
createEarth();
//带云的天空
createSky();
//循环函数
loop();
}
- 场景布局
场景,相机,渲染器都是在createScene函数中创建,这是我们着手构造“世界”的准备工作,代码如下:
var scene,renderer,camera;
function createScene(){
//新建场景
scene = new THREE.Scene();
//新建渲染器(允许透明度、抗锯齿、允许投影)
renderer = new THREE.WebGLRenderer({alpha: true,antialias:true,shadowMapEnabled:true});
//重置画板大小
renderer.setSize(window.innerWidth,window.innerHeight);
//相机(60度角,满屏宽高,视区1到10000)
camera = new THREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,1,10000);
camera.position.set(0,300,600);
camera.lookAt(Sea);
camera.lookAt(scene.position);
scene.add(camera);
//在场景中添加雾效果
scene.fog = new THREE.Fog(0xf7d9aa, 100, 950);
//world中加载渲染引擎
var world = document.getElementById('world');
world.appendChild(renderer.domElement);
//屏幕监听,在调整视窗时更新渲染器及相机
window.addEventListener('resize',handleWindowResize,false);
}
我们通过一个handleWindowResize函数来使得渲染器自适应窗口的大小,在改变视窗时渲染器更新渲染,代码如下:
function handleWindowResize(){
renderer.setSize(window.innerWidth,window.innerHeight);
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
}
- 灯光
灯光使得我们的物体能被看见,同样使用createLight函数来创建灯光,代码如下:
var hemisphereLight,shadowLight;
function createLight(){
//创建半球灯对象
hemisphereLight = new THREE.HemisphereLight(0xaaaaaa,0x000000,0.9);
//创建平行光对象
shadowLight = new THREE.DirectionalLight(0xffffff,0.9);
shadowLight.position.set(150,350,350);
shadowLight.castShadow = true;
scene.add(hemisphereLight);
scene.add(shadowLight);
}
记得将创建好的组件都添加到场景scene中,有时候我们在检查效果时,往往发现组件并没有被使用,相机的位置与视角也是影响效果的重要因素,我们事先都需要设置好
- 场中的对象
我们使用构造函数Earth来构造出场景中的地球类,现在我们开始构造,代码如下:
Earth = function(){
//创建圆(上下半径600、高800、半径数40、垂直段数10)
var geom = new THREE.SphereGeometry(600,80,60);
//在x轴上旋转圆柱
geom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
//创造材质
var mat = new THREE.MeshPhongMaterial({
color:'#104E8B',
transparent:true,
opacity:0.8,
shading:THREE.FlatShading
});
//创建网格
this.mesh = new THREE.Mesh(geom,mat);
//允许接受阴影
this.mesh.receiveShadow = true;
//通过合并定点保证波的连续性
geom.mergeVertices();
//得到顶点数量
var l = geom.vertices.length;
//建立一个数组储存与每个顶点相关的新数据
this.waves = [];
for (var i = 0;i<l;i++){
//得到每个顶点
var v = geom.vertices[i];
//存储与之相关的数据
this.waves.push({
x: v.x,
y: v.y,
z: v.z,
//随机角
ang:Math.random()*Math.PI*2,
//随机距离
amp:5 + Math.random()*15,
//0.016到0.048间速度
speed:0.016 + Math.random*0.032
});
}
}
为了使得地球看上去更加有意思一点我们使它的的表面凹凸不平,所以我们为它增加一个方法moveWaves,这里用到了原型继承的知识,代码如下:
Earth.prototype.moveWaves = function(){
//得到顶点
var verts = this.mesh.geometry.vertices;
var l = verts.length;
for (var i=0; i<l; i++){
var v = verts[i];
//获取相关的波数据
var vprops = this.waves[i];
//更新顶点的位置
v.x = vprops.x + Math.cos(vprops.ang)*vprops.amp;
v.y = vprops.y + Math.sin(vprops.ang)*vprops.amp;
//增加下一帧的角度
vprops.ang += vprops.speed;
}
earth.mesh.rotation.z += 0.0005;
}
moveWaves方法会在循环函数中调用,一次来对每一帧进行渲染,就能模拟出地球表面凹凸不平的效果
下面我们使用createEarth函数来实例化地球类,并设置其位置,代码如下:
var earth;
function createEarth(){
earth = new Earth();
//让海洋圆柱在屏幕下方位置
earth.mesh.position.y = -600;
earth.mesh.position.z = 500;
scene.add(earth.mesh);
}
我们用简单的随机立方体来模拟漂浮在天空中的云朵,接下来我们创建Cloud的构造函数,来创建云朵类,代码如下:
Cloud = function(){
//创建一个3D对象用作容器
this.mesh = new THREE.Object3D();
//创建立体几何,用来复制云
var geom = new THREE.BoxGeometry(20,20,20);
//材质
var mat = new THREE.MeshPhongMaterial({
color:'#FFFFFF'
});
//复制几何,次数随机,造云
//最大6、最小3
var nBlocs = 3 + Math.floor(Math.random()*3);
for (var i = 0;i<nBlocs;i++){
//复制网格,用来创建几何图形
var m = new THREE.Mesh(geom,mat);
//设置每个方块的位置和旋转量,随机
m.position.x = i*15;
m.position.y = Math.random()*10;
m.position.z = Math.random()*10;
m.rotation.z = Math.random()*Math.PI*2;
m.rotation.y = Math.random()*Math.PI*2;
//设置方块大小,随机
var s = 0.2 + Math.random()*0.3;
m.scale.set(s,s,s);
//允许每个方块投影并接受阴影
m.castShadow = true;
m.receiveShadow = true;
this.mesh.add(m);
}
}
云朵在天空中是看似随机有规律的的运动着的,我们还需要创建一个天空的构造函数来对云进行分布,代码如下:
Sky = function(){
//3D容器
this.mesh = new THREE.Object3D();
//在天空中选择一些云
this.nClouds = 20;
//规定统一角度放置云
var stepAngle = Math.PI*2 / this.nClouds;
//创建云
for (var i = 0;i<this.nClouds;i++){
//实例化一个云对象
var c = new Cloud();
//设置每片云的位置和旋转
//利用简单的三角学知识
var a = stepAngle*i;//云的最后一个角度
var h = 650 + Math.random()*100;//轴中心和云的距离(云高),700到950之间
//运用三角函数知识(以轴心为原点r边为h,y = sin(x轴夹角度)*h)
//把极坐标转换成笛卡尔坐标
c.mesh.position.y = Math.sin(a)*h;
c.mesh.position.x = Math.cos(a)*h;
//根据位置旋转云
c.mesh.rotation.z = a + Math.PI/2;
//随机云的深度
c.mesh.position.z = -400 - Math.random()*400;
//为每片云设置随机规模比例
var s = 1 + Math.random()*2;
c.mesh.scale.set(s,s,s);
this.mesh.add(c.mesh);
}
}
从上面的代码中我们使用了三角函数的知识,这需要我们有良好的数学基础,实际上我们在编写的算法就是数学运算,如果在这方面有所欠缺,应该要补一补啦,但建议缺哪里补哪里
下面对天空的构造函数进行实例化,使我们的漂浮立方呈现出来,代码如下:
function createSky(){
sky = new Sky();
sky.mesh.position.y = -600;
sky.mesh.position.z = 700;
scene.add(sky.mesh);
}
写到这里我们的工作基本完成的差不多了,现在我们的立方还不是真正意义上的“漂浮”,我们需要场景里的对象运动起来,所以我们要编写一个loop函数来使得渲染器循环渲染我们的每一帧,我们要是用一个requestAnimationFrame函数来调用渲染器的循环渲染,相当于递归,代码如下:
function loop(){
earth.moveWaves();
sky.mesh.rotation.z += 0.0008;
//渲染
renderer.render(scene,camera);
//调用循环渲染函数
requestAnimationFrame(loop);
}
现在我们的所有的代码已经完成了,具体的效果点击链接可以查看>漂浮立方
这是我在CSDN里的第一篇微博文章,不知道有什么地方写的不好,有很多细节可能都没有写清楚,不过难懂的地方都加了注释,也可以留言我,其实写这篇微博体现最多的就是我们在编写three的效果时的一些实现思路和结构思路,个人认为这是非常重要的事情,这能让我在编写实现时不会发生思维混乱,以至于写着写着就不知道写到哪里,结构化的编程思路是我们需要学习的一种思维,这会让我在理解实现上受益不菲。