Three.js
2017-09-16 本文已影响957人
就想叫菜鸟
Three.js
1. 概述
1.1 什么是Three.js
- Three.js是一个3D javascript库。Three.js封装了底层的图形接口,使得程序员能够在无需掌握繁冗的图形学知识的情况下,也能用简单的代码实现三维场景的渲染。
1.2 使用Three.js
- Three.js是一个JavaScript库
- 在其<head>部分引用和jQuery相似,在GitHub上下载其源码,并引用
<script type = "text/javascript" src = "three.js"></script>
//这样就可以通过全局变量THREE访问到所有的属性和方法。
- 一个典型的Three.js的程序至少包括渲染器(Renderer),场景(Scene),照相机(Camera)
- [ ] 渲染器将与canvas元素进行绑定,如果之前之前定义了id为mainCanvas的canvas元素,那么Renderer可以这样写:
var renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('mainCanvas')
});
- [ ] 而如果想要Three.js生成Canvas元素,在HTML中就不需要定义Canvas元素,在JavaScript代码中可以这样写:
var renderer = new THREE.WebGLRenderer();
renderer.setSize(400, 300);
document.getElementByTagName('body')[0].appendChild(renderer.domElement);
//document.body.appendChild(renderer.domElement);
//这里是将canvas元素添加到body中
renderer.setClearColor(0x000000);
- [ ] 场景(scene)
- 在Three.js中添加的物体都是添加到场景中,因此他相当于一个大容器。一般来说,场景来没有很复杂的操作,在程序最开始的时候进行实例化,然后将物体添加到场景中即可。
var scene = new Three.Scene();
- [ ] 照相机(Camera)
- 在介绍照相机设置前,我们先来简单了解下坐标系。WebGL和Three.js使用的坐标系是右手坐标系。
var camera = new THREE.PerspectiveCamera(45, 4/3, 1, 1000);
camera.position.set(0, 0, 5);
scene.add(camera);
//这里的照相机也需要被添加到场景中。
- [ ] 创建一个长方体
var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3),
new THREE.MeshBasicMaterial({
color: 0xff0000
});
);
scene.add(cube);
- [ ] 渲染
- 在定义了场景中物体,设置好的照相机之后,渲染器就知道如何渲染出二维的结果了。这时候,我们只需要调用渲染器的渲染效果,就能使其渲染一次了。
renderer.render(scene, camera);
2. 照相机
2.1 什么是照相机
- 我们使用Three.js创建的场景是三维的,在通常情况下显示屏是二维的,那么三维的场景如何显示到二维的显示屏呢?照相机就是这样一个抽象,它定义了三维空间到二维屏幕的投影方式。
- 针对投影方式的不同,照相机又分为正交投影照相机与透视投影照相机。
2.2 正交投影和透视投影
- 透视投影获得的结果就是类似于人眼在真是世界中看到的有“近大远小”的效果;而使用正交投影照相机获得的结果就像几何学课上老师教我们画的效果图。
2.3 正交投影照相机
- 正交投影照相机(Orthographic Camera)设置起来比较直观,他的构造函数是:
THREE.OrthographicCamera(left, right, top, bottom, near, far);
//这六个面围成一个长方体,我们将其称为视景体(Frustum)。
效果图为:
此处输入图片的描述
- 为了保持照相机的横竖比例,需要保证(right-left)和(top-bottom)的比例和Canvas宽度和高度的比例一致。near和far都是指到照相机位置在深度平面的位置,而照相机不应该拍摄到其后方的物体,因此这两个值都应该是正值。
var camera = THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10);
camera.position.set(0, 0, 5);
scene.add(camera);
- 这里有几个问题:
1. 为了试验长度比例变化时的效果,我们将照相机水平方向的距离减小为2:
var camera = new THREE.OrthographicCamera(-1, 1, 1.5, -1.5, 1, 10);
//这样的结果是水平方向被拉长了。
2. 若是改变照相机的位置,将照相机像右移动1个单位:
camera.position.set(1, 0, 5);
//得到的效果是物体看上去向左移动了
3. 那么正交投影照相机在设置时,是否需要保证left和right是相反数呢,如果不是,那么会产生什么效果呢:
var camera = new THREE.Orthographic(-1, 3, 1.5, -1.5, 1, 10);
camera.position.set(0, 0, 5);
//这里和把照相机右移得到的效果相同
- 换个角度看:
camera.position.set(4, -3, -5);
camera.lookAt(new THREE.Vactor3(0, 0, 0));
//这里是指将照相机的视角拉到原点
- 所有实例代码
window.onload = init;
function init() {
//初始化一个renderer;
var renderer = new THREE.WebGLRenderer({
canvas: document.getElementById("mainCanvas")
//这里是相当于对象的形式存储的,所以不要分号
});
renderer.setSize(400, 300);
renderer.setClearColor("black");
//要将renderer添加进body里面
document.getElementsByTagName('body')[0].appendChild(renderer.domElement);
//初始化一个scene
var scene = new THREE.Scene();
//初始化一个camera
//var camera = new THREE.PerspectiveCamera(45, 3/4, 1, 1000);
var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10);
//定义camera的位置
camera.position.set(4, -3, 5);
camera.lookAt(new THREE.Vector3(0, 0, 0));
//这里的lookAt函数是将视角指定为看原点
//将camera添加到scene中
scene.add(camera);
//这里是创建一个长方形
var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({
color: "green",
wireframe: true
//这里指不使用实心材料,所以为true
})
);
//这里要将这个长方形加入这个scene中
scene.add(cube);
renderer.render(scene, camera);
}
2.4 透视照相机
-
[ ] 效果图:
此处输入图片的描述
- [ ] 构造函数
var camera = new PerspectiveCamera(fov, aspect, near, far);
- 透视图中,灰色的部分是视景图,是可能被渲染的物体所在的区域。
- fov是视景图竖直方向上的张角(是角度制而非弧度制)
- aspect等于width/height,是照相机水平方向和竖直方向长度的比例,通常设为canvas的横纵比例。
- near和far分别是照相机最近最远的距离。均为正值。
- 设置透视投影照相机, 这里canvas长400px,宽为300px,所以aspect设为400/300;
var camera = new THREE.PerspectiveCamera(45, 4/3, 1, 10);
camera.position.set(0, 0, 5);
scene.add(camera);
//得到的结果是一个近大远小的图, 可以看到正方体12条边。
- 如果改变fov值,即张角,则当fov值越大时,视野就越大,则正方体相对就变小。改变aspect则会引起跟之前相同的变化。
第三章 几何形状
- 在创建物体时,需要传入两个参数,一个是几何形状(Geometry),另一个是材料(Meterial)。
- 几何形状(Geometry)最主要的功能是储存了一个物体的顶点信息。
3.1 基本几何形状
- [ ] 立方体(CubeGeometry)
- 虽然这一形状的名字为立方体,实际它的长宽高可以设为不同的值
new THREE.CubeGeometry(width, height, depth, widthSegments, heightSegments, depthSegments);
- 后三个参数分别是在三个方向的分段数,如widthSegments为3的话,则代表x方向上水平分为三份。
- [ ] 平面(PlaneGeometry)
- 这里的平面其实是一个长方形。
- 构造函数为:
new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);
//后面两个参数也可以不要
- 如果需要创建的平面在X轴和Z轴所在的平面内,可以通过物体的旋转来实现
- [ ] 球体:
new THREE.SphereGeometry(radius, segmentWidth, segmentHeight, phiStart, phiLength, thetaStart, thetaLehgth);
//radius是半径,segmentsWidth表示经度上的切片数,segmentsHeight表示纬度上的切片数,phiStart表示经度上开始的弧度,phiLength表示经度跨越的弧度,thetrStart表示纬度开始的弧度,thetaLength表示纬度跨越的弧度。
new THREE.SphereGeometry(3, 8,6);
//可以创建一个半径为3,经度划分为8份,纬度划分为6份的球体。
- [ ] 圆形:
new CircleGeometry(radius, segment, thetaStart, thetaLength);
//这四个参数与球体中的意义一样。
var sphere = new THREE.Mesh(new THREE.SphereGeometry(1, 8, 6),
new THREE.MeshBasicMaterial({
color: "green",
wireframe: true
})
);
//完整代码
- [ ] 圆柱体:
THREE.CylinderGeometry(radiusTop, radiusBottom, height, raiusSegments, heightSegments, openEnded);
- 其中radiusTop和radiusBottom分别是顶面和底面的半径,由此可知,这两个参数设置为不同值时是一个圆台。height是圆柱体的高度,radiusSegments和heightSegments可类比球体中的分段,openEnded是一个布尔值,表示是否没有顶面和底面,缺省值为false, 表示有顶面和底面。
- [ ] 正多面体:
- 正四面体,正八面体,正二十面体等的构造函数类似:
THREE.TetrahedronGeometry(radius, detail);
THREE.OctahedronGeometry(radius, detail);
THREE.IcosahedronGeometry(radius, detail);
//radius是半径,detail是细节层次的层数。
- [ ] 圆环面:
THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc)
-
效果为:
此处输入图片的描述
- radius为圆环半径,tube为管道半径,radiusSegments和tubularSegments为两个方面上的分段数,arc为圆弧面的弧度,缺省值为Math.PI*2。
new THREE.TorusGeometry(3, 1, 4, 8);//一个粗糙的圆面
new THREE.TorusGeometry(3, 1, 12, 18);//一个精细的圆面
new THREE.TorusGeometry(3, 1, 4, 8, Math.PI/3*2);
//创建部分圆环
- [ ] 圆环结(TorusKnotGeometry)
THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments, p, q, heightScale) ;
- p, q控制其样式,一般可以缺省。heightScale是在Z轴方向上的缩放。
3.2 文字形状
- 文字形状(TextGeometry)可以用来创建三维的文字形状。
new THREE.TextGeometry(text, parameter);
- 完整代码:
function init() {
var renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('mainCanvas')
});
renderer.setClearColor(0x000000);
var scene = new THREE.Scene();
// camera
var camera = new THREE.OrthographicCamera(-2.5, 2.5, 1.875, -1.875, 0.1, 100);
camera.position.set(5, 5, 20);
camera.lookAt(new THREE.Vector3(1, 0, 0));
scene.add(camera);
// var material = new THREE.MeshBasicMaterial({
// color: 0xffff00,
// wireframe: true
// });
//金属发亮物体
var material = new THREE.MeshPhongMaterial({
color: 0xffff00,
specular:0xffff00,
//指定该材质的光亮程度及其高光部分的颜色,如果设置成和color属性相同的颜色,则会得到另一个更加类似金属的材质,如果设置成grey灰色,则看起来像塑料
shininess:0
//指定高光部分的亮度,默认值为30
});
//方向光
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(-5, 10, 5);
scene.add(light);
// load font
var loader = new THREE.FontLoader();
loader.load('js/helvetiker_regular.typeface.js', function(font) {
var mesh = new THREE.Mesh(newTHREE.TextGeometry('Hello', {
font: font,
size: 1,
height: 1
}), material);
scene.add(mesh);
// render
renderer.render(scene, camera);
});
}
3.3 自定义形状:
- 自定义形状的构造函数为:
var geometry = new THREE.Geometry();
第四章: 材质
- 材质是独立于物体顶点信息之外的与渲染效果相关的属性,通过设置材质可以改变物体的颜色,纹理贴图,光照模式。
4.1 基本材质
- 使用基本材质的物体,渲染后物体的颜色始终为该材质的颜色。而不会由于光照产生明暗,阴影的效果。如果没有指定材质的颜色,则颜色是随机的。
- 其构造函数为:
new THREE.MeshBasicMaterial(opt);
- opt可以缺省,或者为包含各属性的值,如新建一个不透明度为0.75的黄色材质:
new THREE.MeshBasicMaterial({
color: 0xffff00,
opacity: 0.75
})
- visible: 是否可见, 默认为true
- side:渲染面片正面或者反面,默认为正面
- wireframe: 渲染成线而非面,即物体为空心的,默认为false
- color:十六进制颜色
- map: 设置纹理贴图
4.2 Lambert材质
- Lambert材质(MeshLamberMaterial)是符合Lambert光照模型的材质。Lambert光照模型的主要特点是指考虑漫反射而不考虑镜面反射的效果,因而对于金属,镜子等需要镜面反射效果的物体就不适应,对于其他大部分物体的漫反射效果还是适应的。
- 构造函数为:
new THREE.MeshLamberMaterial({
color: 0xffff00
});
- color是用来表现材质对散射光的反射能力,也是最常用来设置材质颜色的属性。除此之外,还可以用ambient和emissive控制材料的颜色。
- ambient表示对环境光的反射能力,只有当设置了AmbientLight后,该值才是有效的
- emissive是材料自发光的颜色,可以用来表现光源的颜色。
4.3 Phong材质
- Phong材质(MeshPhongMaterial)是符合Phong光照模型的材质。与Lambert不同的是,Phong模型考虑了镜面反射的效果,因此对于金属,镜面的表现尤为适合。
- 漫反射部分和Lambert光照模型是相同的,因此,如果不指定镜面反射系数,而只设定漫反射,其效果与Lambert是相同的。
new THREE.MeshPhongMaterial({
color: 0xffff00
});
- 这里也可以指定emissive和ambient值,下面就specular值指定镜面反射系数作说明。首先,我们只使用镜面反射
var material = new THREE.MeshPhongMaterial({
specular: 0xff0000
});
var mesh = new THREE.Mesh(new THREE.SphereGeometry(3, 10, 8), material);
- 可以通过shininess属性控制光照模型中的值,当shininess值越大时,高光的光斑越小,默认为30.
new THREE.MeshPhongMaterial({
specular: 0xff0000,
shininess: 1000
});
4.4 法向材质
- 法向材质可以将材质的颜色设置为其法向量的方向,有时候对于调试很有帮助,法向材质的设定也很简单:
new THREE.MeshNorthMaterial();
- 材料的颜色与照相机与该物体的角度相关,下面我们只改变照相机位置,观察两个角度的颜色变化。
4.5 材质的纹理贴图
- 在此之前,我们使用的材质都是单一颜色的,有时候,我们却希望使用图像作为材料。
- 首先,我们选择一张长宽为128像素的图像:
var texture = THREE.ImageUtils.loadTexture('../img/0.png');
var material = new THREE.MeshLamberMaterial({
map: texture;
});
- 这样就完成了将图片应用于材质的基本步骤,但是由于现在我们还没使用动画,画面只被渲染了一次,而在导入纹理之前,已经完成了这次渲染,因此只看到一片黑,所以,在完成导入纹理的步骤后,重新绘制画面
var texture = THREE.ImageUtils.loadTextures('../img/0.png', {}, function(){
renderer.render(scene, camera);
});
var material = new THREE.MeshLamberMaterial({
map: texture
});
- 完整代码示例:
function init() {
var renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('mainCanvas')
});
renderer.setClearColor(0x000000);
var scene = new THREE.Scene();
// camera
var camera = new THREE.OrthographicCamera(-10, 10, 7.5, -7.5, 0.1, 100);
camera.position.set(25, 25, 25);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);
// light
var light = new THREE.PointLight(0xffffff, 1, 1000);
light.position.set(10, 15, 20);
scene.add(light);
var texture = THREE.ImageUtils.loadTexture('img/0.png', {}, function() {
renderer.render(scene, camera);
});
var material = new THREE.MeshLambertMaterial({
map: texture
});
// var cube = new THREE.Mesh(new THREE.CubeGeometry(5, 5, 5), material);
// scene.add(cube);
var sphere = new THREE.Mesh(new THREE.SphereGeometry(5, 25, 15), material);
scene.add(sphere);
renderer.render(scene, camera);
}
- 当希望长方体的六面体各种的贴图都不同。首先要准备几张不同的图片,
var materials = [];
for(var i=0; i<6; ++i) {
material.push(new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('img/'+i+'.png', {}, function(){
renderer.render(scene, camera);
}),
overdraw: true
}));
}
var cube = new THREE.Mesh(new CubeGeometry(5, 5, 5),
new THREE.MeshFaceMaterial(materials));
scene.add(cube);
- 棋盘格
var texture = THREE.ImageUtils.loadTexture('img/0.png', {}, function(){
renderer.render(scene, camera);
});
texture.wrapS = texture.wrapT = THREE.RepeatsWrapping;
texture.repeat.set(4, 4);
- 完整代码:
window.onload = init;
function init() {
var renderer = new THREE.WebGLRenderer({
canvas: document.getElementById("mainCanvas")
});
renderer.setClearColor(0x000000);
renderer.setSize(400, 300);
document.getElementsByTagName('body')[0].appendChild(renderer.domElement);
//scene
var scene = new THREE.Scene();
//camera
var camera = new THREE.OrthographicCamera(-10, 10, 7.5, -7.5, 0.1, 100);
//建立一个位置
camera.position.set(0, 0, 25);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);
// camera
//var camera = new THREE.OrthographicCamera(-10, 10, 7.5, -7.5, 0.1, 100);
//camera.position.set(25, 25, 25);
//camera.lookAt(new THREE.Vector3(0, 0, 0));
//scene.add(camera);
var light = new THREE.PointLight(0xffffff, 1, 1000);
light.position.set(10, 15, 20);
scene.add(light);
var texture = THREE.ImageUtils.loadTexture('img/1.png', {}, function(){
renderer.render(scene, camera);
});
var material = new THREE.MeshLambertMaterial({
map: texture
});
texture.wrapS = texture.wrapT = texture.RepeatWrapping;
texture.repeat.set(4, 4);
// var sphere = new THREE.Mesh(new THREE.CubeGeometry(5, 5, 5), material);
// scene.add(sphere);
var plane = new THREE.Mesh(new THREE.PlaneGeometry(12, 12), material);
scene.add(plane);
renderer.render(scene, camera);
}
第五章:网格
- 在学习了几何形状和材质之后,我们就可以使用它们来创建物体。最常用的一种物体就是网格(Mesh),网格是由定点,边,面等组成的物体,其他的物体包括线段(Line),骨骼(Bone),粒子系统(ParticleSystem)等。
5.1 创建网格
- 创建物体需要指定几何形状和材质。其中,几何形状决定了物体的定点位置等信息,材质决定了物体的颜色,纹理等信息。
- 网格的创建:只需要将几何形状和材质传入其构造函数。最常用的物体时网络:
Mesh(geometry, material);
var material = new THREE.MeshLambertMaterial({
color: 0xff0000
});
var geometry = new THREE.CubeGeometry(1, 2, 3);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
//如果之后这些不复用,则可以直接写在一起
var mesh = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3),
new THREE.MeshLambertMaterial({
color: 0xff0000
})
);
scene.add(mesh);
5.2 修改属性
- 除了在构造函数中修改属性,当网格被创建后,也能对材质进行修改。
var material = new THREE.MeshLambertMaterial({
color: 0xffff00
});
var geometry = new THREE.CubeGeometry(1, 2, 3);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
mesh.material = new THREE.MeshLambertMaterial({
color: 0xff0000
});
- 位置,缩放,旋转: 位置,缩放,旋转是物体三个常用属性,由于THREE.Mesh基础自THREE.Object3D, 因此包含scale,rotation,position三个属性。它们都是THREE.Vector3实例,因此修改其值的方法是相同的,这里以位置为例。
- THREE.Vector3有x, y, z三个属性,如果只设置其中一个属性,则可以用一下方法:
mesh.position.z = 1;
- 如果需要同时设置多个属性,可以使用以下的方法:
mesh.position.set(1.5, -0.5, 0);
mesh.position = new THREE.Vector(1.5, -0.5, 0);
第六章: 动画
- 在本章之前,所有画面都是静止的,本章将介绍如何使用Three.js进行动态画面的渲染。此外,将会介绍一个Three.js作者写的另外一个库,用来观测每秒帧数。
6.1 实现动画原理
- 这里我们将动态页面简称为动画(animation)。而对于Three.js而言,动画的实现也是通过在每秒中多次重绘画面实现的。
- 为了衡量画面切换速度,引入了每秒帧数FPS的概念,是指每秒画面重绘的次数,是指每秒画面重绘的次数。FPS越大,则动画效果越平滑。
- [ ] setInterval方法
- 如果要设置特定的FPS,可以使用JavaScript DOM定义的方法:
setInterval(func, mesc);
//其中,func每通过mesc毫秒执行的函数,如果将func定义为重绘画面的函数,就能实现动画效果。setInterval函数返回一个id,如果需要停止重绘,需要使用clearInterval方法,并传入该id
- 示例:
- 首先在init函数中定义每20毫秒执行draw函数的setInterval, 返回值记录在全局变量id中:
id = setInterval(draw, 20);
- 在draw函数中,我们首先设定在每帧中的变化,这里我们让场景中的长方体绕y轴转动。
function draw() {
mesh.rotation.y = (mesh.rotation.y+0.01)%(Math.PI*2);
renderer.render(camera, scene);
}
- 我们在HTML中添加一个按钮,按下后停止动画:
<button id = 'stopBtn' onclick = 'stop()'>Stop</button>
- 对应的Stop函数:
function stop() {
if(id!=null) {
clearInterval(id);
id == null;
}
}
- 完整代码:
window.onload = init;
var mesh = null;
var scene = null;
var camera = null;
var renderer = null;
// var mesh = null;
var id = null;
function init() {
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('mainCanvas')
})
renderer.setClearColor(0x000000);
renderer.setSize(400, 300);
document.getElementsByTagName('body')[0].appendChild(renderer.domElement);
//scene
scene = new THREE.Scene();
camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);
camera.position.set(5, 5, 20);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);
light = new THREE.DirectionalLight(0xffffff);
light.position.set(20, 10, 5);
scene.add(light);
//mesh
mesh = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3),
new THREE.MeshLambertMaterial({
color: 0xffff00
})
);
scene.add(mesh);
id = setInterval(draw, 20);
}
function draw() {
mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
}
- [ ] requestAnimationFrame方法
- 大多数时候,我们并不在意多久重绘一次,这时候就适合用requestAnimationFrame方法,它告诉浏览器在合适的时候调用指定函数,通常可以达到60FPS.
- 示例:
- 与requestAnimationFrame相对应的有clearAnimationFrame来取消动画:
function stop() {
if(id!=null) {
clearAnimationFrame(id);
id=null;
}
}
- 与setInterval不同的是,由于requestAnimationFrame只请求一帧画面。因此,除了在init函数中需要调用,在被其调用的函数中需要再次调用:
function draw() {
mesh.rotation.y = (mesh.rotation.y+0.01)%(Math.PI*2);
renderer.render(scene, camera);
id = requestAnimationFrame(draw);
}
- 因为这个出现的时间较短,所以一些老的浏览器使用的名字不是这个,所以要检验兼容性。
var requestAnimationFrame = window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame
|| window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
- [ ] 如何取舍
- 两个方法的差别较为微妙,requestAnimationFrame适用于对于事假较为敏感的环境,而setInterval则可以保证程序的运算不至于导致延迟的情况下提供更简洁的逻辑。
6.2 使用stat.js记录FPS
- stat.js是Three.js的作者的另外一个有用的JavaScript库,很多情况下,我们希望知道实时的FPS信息,从而更好地检测动画效果,这时候,stat.js就能提供一个很好的帮助。他的效果为:单击后显示每帧的渲染时间。
- 我们要下载stat.js文件,下载后,将其放在项目文件夹中,然后在HTML中引用:
<script type = 'text/javascript' src = 'stat.js'></script>
- 在页面刚开始时,我们要将其添加到页面的一角
var stat = null;
function init() {
stat = new Stats();
stat.domElement.style.position = 'absolute';
stat.domElement.style.right = '0px';
stat.domElement.style.top = '0px';
document.body.appendChild(stat.domElement);
// Three.js init ...
}
- 在上一节介绍的动画重绘函数draw中调用stat.begin(), stat.end(),分别表示一帧的开始和结束:
function draw() {
stat.begin();
mesh.rotation.y = (mesh.rotation.y+0.01)%(Math.PI*2);
renderer.render(scene, camera);
stat.end();
}
第七章 外部模型
- 我们了解到,使用Three.js创建常见几何体是十分方便的,但是对于人或者动物这样非常复杂的模型使用几何体组合就非常麻烦了。因此,Three.js允许用户导入由3ds Max等工具制作的三维模型,并添加到场景中。
7.1 支持格式
- Three.js有一系列导入外部文件的辅助函数,是在three.js之外的,使用前需要额外下载,在
https://github.com/mrdoob/three.js/tree/master/examples/js/loaders
可以找到。
7.2 无材质的模型
- 本节中,我们将使用3ds Max创建一个茶壶模型,并将导出的没有材质的模型使用three.js导入场景中。
- 首先,下载OBJLoader.js并在HTML的<head>中使用:
<script type = 'text/javascript' src = 'OBJLoader.js'></script>
- 然后我们需要准备*.obj模型,可以使用建模软件导出,也可以在网上下载。这里,我们在3ds Max中创建一个茶壶,将其放置在原点处。设置其半径为3,这一单位与我们的three.js场景的单位是一致的。
- 导出成port.obj,在选项中,如果选择了Export materials,会生成一个同名的*.mtl文件。在init函数中,创建loader变量,用于导入模型:
var loader = new THREE.OBJLoader();
- loader导入模型的时候,接受两个参数,第一个表示模型路径,第二个表示完成导入后的回调函数,一般我们需要在这个回调函数中将导入的模型添加到场景中。
loader.load('../obj/port.obj', function(obj) {
mesh=obj;//存储在全局变量中。
scene.add(obj);
});
- 我们可以在重绘函数让该茶壶旋转:
function draw() {
renderer.render(scene, camera);
mesh.rotation.y += 0.01;
if(mesh.rotation.y>Math.PI*2) {
mesh.rotation.y -= Math.PI*2;
}
}
//这里出现的画面只有正面的面片被绘制,所以会看见背面的画面有缺口。
- 如果想要双面绘制则要重新设置:
var loader = new THREE.OBJLoader();
loader.load('../lib/port.obj', function(obj){
obj.traverse(function(child) {
if(child instanceof THREE.Mesh) {
child.material.side = THREE.DoubleSide;
}
});
mesh = obj;
scene.add(obj);
})
7.3 有材质的模型
- 模型的材质有两种定义方式:一种是在代码中导入模型后设置材质,另一种是在建模软件中导出材质信息。
- 代码中设置材质:
var loader = new THREE.OBJLoader();
loader.load('../lib/port.obj', function(obj){
obj.traverse(function(child){
//如果obj文件代表的三维对象是由多个子模型构成的模型组合,我们可以调用object.traverse(function(child){})来对每个子模型进行处理。
if(child instanceof THREE.Mesh) {
child.material = new THREE.MeshLambertMaterial({
color: 0xffff00,
side:THREE.DoubleSide
});
}
});
mesh = obj;
scene.add(obj);
})
- 建模软件中设置材质
- 使用上一节相似的方法导出模型,在选项中选中Export materials,最终导出port.obj模型文件以及port.mtl文件。现在,我们不再使用OBJLoder.js, 而是使用MTLLoder.js与OBJMTLLoader.js,并且要按照该顺序引用。
<script src = "MTLLoader.js"></script>
<script src = "OBJMTLLoader.js"></script>
- 调用的方法也有所不同:
var loader = new THREE.OBJMTLLoader();
loader.addEventListener("load", function(event){
var obj = event.content;
mesh = obj;
scene.add(obj);
});
loader.load('../lib/port.obj', '../lib/port.mtl');