three.js学习笔记(一)

三大组建

在Three.js中,要渲染物体到网页中,我们需要3个组建:场景(scene)、相机(camera)和渲染器(renderer)。有了这三样东西,才能将物体渲染到网页中去。

想要渲染的物体需要添加在scene变量中,camera变量定义了能够在渲染好的scene里看到什么,renderer则负责计算制定相机角度下,浏览器中scene的样子。

1、创建这三要素的代码如下:

1
2
3
4
var scene = new THREE.Scene(); // 场景
var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);// 透视相机
var renderer = new THREE.WebGLRenderer(); // 渲染器
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器的大小为窗口的内宽度,也就是内容区的宽度

2、创建好三要素后,只是一个空白的画面,还需要添加需要渲染的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var axes = new THREE.AxisHelper(20);
scene.add(axes);
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;
scene.add(plane);

【解读】

  • 首先创建了一个axes(坐标轴)对象,并使用scene.add0函数将这些坐标轴
    添加到我们的场景中。
  • 用THREE.PlaneGeometry(x,y)定义平面的尺寸。
  • 使用MeshBasicMaterialO方法创建一个基本的材质,且颜色是Oxcccccc。
  • 接下来把定义的平面尺寸与材质对象合并到一个名为plane (平面)的Mesh(网格)对象中。
  • 之后将它放置到合适的位置
  • 最后,我们要把这个plane添加到scene。
    可以依照同样的方法添加cube(方块)和sphere(球体),但是要把wireframe(线框)属性设为true。

3、添加立方体小栗子
在上面代码的基础上,再添加一个立方体到场景中

1
2
3
4
5
6
7
8
9
10
11
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
// position the cube
cube.position.x = -4;
cube.position.y = 3;
cube.position.z = 0;
// add the cube to the scene
scene.add(cube);

4、使用renderer通过相机渲染scene

1
2
3
4
5
6
7
8
9
10
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.getElementById("WebGL-output").appendChild(renderer.domElement);
// render the scene
renderer.render(scene, camera);

【解释】
使用x、 y和z这三个position属性指定相机的位置,即悬挂在场景的上方。 为确保相机能够拍摄到这些物体,使用lookAt()函数指向场景的中心。 之后将renderer的输出挂接到HTML页面框架中的<div>元素;并告诉renderer用设置好的相机来渲染scene。

【完整栗子】

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
<!DOCTYPE html>
<html>
<head>
<title>First Scene</title>
<script type="text/javascript" src="three.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="WebGL-output">
</div>
<script type="text/javascript">
function init() {
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColorHex();
renderer.setClearColor(new THREE.Color(0xEEEEEE));
renderer.setSize(window.innerWidth, window.innerHeight);
var axes = new THREE.AxisHelper(20);
scene.add(axes);
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;
scene.add(plane);
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = -4;
cube.position.y = 3;
cube.position.z = 0;
scene.add(cube);
var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff, wireframe: true});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.x = 20;
sphere.position.y = 4;
sphere.position.z = 2;
scene.add(sphere);
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);
document.getElementById("WebGL-output").appendChild(renderer.domElement);
renderer.render(scene, camera);
}
window.onload = init;
</script>
</body>
</html>

[效果图]
1.png

添加材质、灯光和阴影

基础材质(调用MeshBasicMaterial()方法)不会对场景中的光源产生反应,只会以指定的颜色渲染物体。

【改变材质】

1
2
3
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);

three.js有两种材质可以对光源产生反应:MeshLambertMaterial,MeshPhongMaterial。

【添加阴影】

1
2
3
4
5
6
7
8
9
//告诉renderer我们需要阴影
renderer.shadowMapEnabled = true;
//定义哪个物体接受阴影
plane.receiveShadow = true;
//定义哪个物体投射阴影
cube.castShadow = true;
sphere.castShadow = true;

【添加光源】

1
2
3
4
5
//添加光源
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(-40, 60, -10);
spotLight.castShadow = true;
scene.add(spotLight);

[效果图]
2.png

添加动画

加入动画,需要使得场景可以以一定的时间间隔渲染,之前使用的方式是setInterval方法,但是它并不考虑浏览器中发生的事情,且没有跟显示器的重画同步,CPU使用率较高,降低系统效率。

目前就则引入了requestAnimationFrame()方法,可以按照浏览器定义的时间间隔调用,且绘画效果平滑、高校。

1
2
3
4
5
6
function renderScene() {
requestAnimationFrame(renderScene);//让动画持续运行
renderer.render(scene, camera);
}
renderScene();//启动动画

让方块绕所有轴旋转:

1
2
3
4
5
6
7
8
9
function renderScene() {
cube.rotation.x += 0.02;
cube.rotation.y += 0.02;
cube.rotation.z += 0.02;
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
}

弹跳球:

1
2
3
4
5
6
7
8
9
var step = 0;
function renderScene() {
step += 0.01;//定义弹跳速度
sphere.position.x = 20 + ( 10 * (Math.cos(step)));//用cossin使弹跳轨迹光滑
sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
}

[效果图]
4.png

监测动画运行帧频(FPS)

three.js的作者还提供了一个小的辅助库stats.js,这个辅助坑可以检测动画运行的帧频。

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
//引入库
<script type="text/javascript" src="../libs/stats.js"></script>
//增加div元素用于显示统计图形
<div id="Stats-output"></div>
<script type="text/javascript">
//该函数用于初始化统计对象
function initStats() {
var stats = new Stats();
stats.setMode(0); // 如果设置为0: fps, 1: ms(渲染时间)
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
//调用该函数,使开始拥有统计功能。
var stats = initStats();
function renderScene() {
//在渲染函数里告诉stats对象每次渲染开始的时间。
stats.update();
//其他code
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
}
renderScene();
</script>

[效果图]
3.png

使用dat.GUI库简化试验

在该库的帮助下,很容易创建一个简单的界面组建,用以修改代码中的变量。

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
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script>
var controls = new function () {
this.rotationSpeed = 0.02;
this.bouncingSpeed = 0.03;
};
var gui = new dat.GUI();
gui.add(controls, 'rotationSpeed', 0, 0.5);
gui.add(controls, 'bouncingSpeed', 0, 0.5);
function render() {
stats.update();
// rotate the cube around its axes
cube.rotation.x += controls.rotationSpeed;
cube.rotation.y += controls.rotationSpeed;
cube.rotation.z += controls.rotationSpeed;
// bounce the sphere up and down
step += controls.bouncingSpeed;
sphere.position.x = 20 + ( 10 * (Math.cos(step)));
sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
// render using requestAnimationFrame
requestAnimationFrame(render);
renderer.render(scene, camera);
}
<script>

[效果图]
5.png


参考
http://www.hewebgl.com/article/getarticle/50
《three.js开发指南》