Skip to content
32 changes: 16 additions & 16 deletions chapter-01/02-first-scene.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
// once everything is loaded, we run our Three.js stuff.
function init() {

// create a scene, that will hold all our elements such as objects, cameras and lights.
// create a scene场景画面, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();

// create a camera, which defines where we're looking at.
Expand All @@ -34,20 +34,20 @@
// create a render and set the size
var renderer = new THREE.WebGLRenderer();
renderer.setClearColorHex();
renderer.setClearColor(new THREE.Color(0xEEEEEE));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0xEEEEEE));//设置场景的背景色
renderer.setSize(window.innerWidth, window.innerHeight);//window.innerWidth和window.innerHeight将整个页面窗口指定为渲染区域

// show axes in the screen
var axes = new THREE.AxisHelper(20);
scene.add(axes);
var axes = new THREE.AxisHelper(20);//坐标轴轴线粗细为20
scene.add(axes);//将轴添加到场景中

// create the ground plane
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
var planeGeometry = new THREE.PlaneGeometry(60, 20);//平面大小宽60, 长20
var planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc});//创建基本材质来设置平面外观
var plane = new THREE.Mesh(planeGeometry, planeMaterial);//将大小和外观组合进Mesh对象并赋值给平面变量

// rotate and position the plane
plane.rotation.x = -0.5 * Math.PI;
//在将平面添加到场景之前,还需要设置平面的位置.
plane.rotation.x = -0.5 * Math.PI;//先将平面围绕x轴旋转90度,然后使用position属性来定义其在场景中的位置
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;
Expand All @@ -57,7 +57,7 @@

// create a cube
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});//将线框(wireframe)属性设置为true,这样物体就不会被渲染为实体物体
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

// position the cube
Expand All @@ -81,20 +81,20 @@
// add the sphere to the scene
scene.add(sphere);

// position and point the camera to the center of the scene
//摄像机将决定哪些东西会被渲染到场景中 position and point the camera to the center of the scene
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);
camera.lookAt(scene.position);//用lookAt方法指向场景的中心,默认状态下摄像机是指向(0,0,0)位置

// add the output of the renderer to the html element
//将渲染的结果添加到HTML框架的<div>元素中。我们使用JavaScript来选择需要正确输出的元素并使用appendChild方法将结果添加到div元素中。add the output of the renderer to the html element
document.getElementById("WebGL-output").appendChild(renderer.domElement);

// render the scene
// render the scene最后告诉渲染器使用指定的摄像机来渲染场景。
renderer.render(scene, camera);
}
window.onload = init;

</script>
</body>
</html>
</html>
15 changes: 10 additions & 5 deletions chapter-01/04-materials-light-animation.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/*
如果希望我们的场景动起来,那么首先需要解决的问题是如何在特定的时间间隔重新渲染场景,requestAnimationFrame函数为稳定而连续的渲染场景提供了良好的解决方案。通过这个函数,你可以向浏览器提供一个回调函数。你无须定义回调间隔,浏览器将自行决定最佳回调时机。
*/
<!DOCTYPE html>

<html>
Expand All @@ -6,7 +9,7 @@
<title>Example 01.04 - Materials, light and animation</title>
<script type="text/javascript" src="../libs/three.js"></script>

<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>//在动画运行时,该库可以在一个图片中显示画面每秒传输帧数。
<style>
body {
/* set margin to 0 and overflow to hidden, to go fullscreen */
Expand Down Expand Up @@ -108,7 +111,8 @@
// call the render function
var step = 0;
renderScene();


//在renderScene函数中每渲染完一帧后,调用stats.update函数更新统计。
function renderScene() {
stats.update();
// rotate the cube around its axes
Expand All @@ -122,17 +126,18 @@
sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));

// render using requestAnimationFrame
requestAnimationFrame(renderScene);
requestAnimationFrame(renderScene);//在renderScene()方法中,requestAnimationFrame()方法又一次被调用了,这样做的目的是保证动画能够持续运行。
renderer.render(scene, camera);
}


function initStats() {

var stats = new Stats();

stats.setMode(0); // 0: fps, 1: ms

// Align top-left
// 统计图形将会显示在浏览器左上方Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
Expand All @@ -146,4 +151,4 @@

</script>
</body>
</html>
</html>
21 changes: 18 additions & 3 deletions chapter-01/05-control-gui.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
\\使用data.GUI库为我们的示例添加用户操作界面
<style>
body {
/* set margin to 0 and overflow to hidden, to go fullscreen */
Expand Down Expand Up @@ -102,23 +103,34 @@
spotLight.castShadow = true;
scene.add(spotLight);


////////######################################///////////

// add the output of the renderer to the html element
document.getElementById("WebGL-output").appendChild(renderer.domElement);
//本章开头时创建的框架页面代码中,我们引用了TrackballControl.js文件(?)该文件用于实现利用鼠标移动摄像机,以便以不同角度观察场景,由于它需要响应文档对象模型(DOM)元素的事件,它的初始化代码必须出现在上面代码中的appendChild函数调用之后。可以通过按下鼠标左键并移动鼠标来转动摄像机,以便从不同角度观察场景。此外,按S键可以拉近或拉远摄像机,按D键可以平移摄像机。代码补充如下:
var trackballControls = initTrackballControls(camera, renderer);
var clock = new THREE.Clock();

//initTrackballControls函数也定义于util.js文件中,它的具体实现将在后面章节中详细介绍。


// call the render function
var step = 0;


//接下来我们需要定义一个JavaScript对象,该对象将保存希望通过dat.GUI改变的属性
var controls = new function () {
this.rotationSpeed = 0.02;
this.bouncingSpeed = 0.03;
};

//在这个JavaScript对象中,我们定义了两个属性——this.rotationSpeed和this.bouncing Speed,以及它们的默认值。接下来需要将这个JavaScript对象传递给data.GUI对象,并设置这两个属性的取值范围
var gui = new dat.GUI();
gui.add(controls, 'rotationSpeed', 0, 0.5);
gui.add(controls, 'bouncingSpeed', 0, 0.5);

render();

//立方体旋转速度(rotationSpeed)和球体弹跳速度(bouncingSpeed)的取值范围为0~0.5。现在需要做的就是在render()中直接引用这两个属性,这样当我们在dat.GUI中修改这两个属性的值时,就可以影响相应物体的旋转速度和弹跳速度
function render() {
stats.update();
// rotate the cube around its axes
Expand All @@ -131,6 +143,9 @@
sphere.position.x = 20 + ( 10 * (Math.cos(step)));
sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));

//与帧数统计模块相似,TrackballControl.js库也需要在render函数渲染完一帧后更新这里略了
//trackballControls .update (clock.getDelta());

// render using requestAnimationFrame
requestAnimationFrame(render);
renderer.render(scene, camera);
Expand All @@ -156,4 +171,4 @@

</script>
</body>
</html>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<html>

<head>
<title>Example 01.06 - Screen size change</title>
<title>Example 01.06 - 场景对浏览器的自适应 </title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
Expand All @@ -29,7 +29,8 @@
var camera;
var scene;
var renderer;

//将变量camera、renderer和scene的定义移到init()方法的外面,这样其他的方法(如onResize()方法)也可以访问它们。

// once everything is loaded, we run our Three.js stuff.
function init() {

Expand Down Expand Up @@ -156,18 +157,24 @@
return stats;
}
}



///////////////////////#################################//////////////////////////////////////

//每当浏览器尺寸改变时onResize()方法就会被执行。在onRaise()方法中需要更新摄像机和渲染器
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

//对于摄像机,需要更新它的aspect属性,这个属性表示屏幕的长宽比;对于渲染器,需要改变它的尺寸。最后我们需要将变量camera、renderer和scene的定义移到init()方法的外面,这样其他的方法(如onResize()方法)也可以访问它们。

window.onload = init;

// listen to the resize events
// 为浏览器注册一个事件监听器listen to the resize events
window.addEventListener('resize', onResize, false);

</script>
</body>
</html>
</html>
46 changes: 37 additions & 9 deletions chapter-02/01-basic-scene.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
//在第1章中我们介绍了Three.js库的基础知识。通过示例展示了Three.js是如何工作的,然后创建了第一个完整的Three.js应用。在本章中我们将会深入了解Three.js库,介绍构建Three.js应用的基本组件

//当我们在浏览器中打开这个示例的时候,其效果大致如图2.1所示。请记住除了鼠标之外,键盘上的A、S和D键也可用于在渲染场景中转向、缩放和平移。


<!DOCTYPE html>

<html>

<head>
<title>Example 02.01 - Basic Scene</title>
<title>Example 02.01 - Basic Scene创建场景 </title>
<script type="text/javascript" src="../libs/three.js"></script>

<script type="text/javascript" src="../libs/stats.js"></script>
Expand Down Expand Up @@ -32,6 +37,7 @@

var stats = initStats();

//我们使用THREE.Scene对象的scene.add(object)方法添加了一个THREE.Mesh对象(你看到的平面)、一个THREE.SpotLight对象(聚光灯光源)和一个THREE.AmbientLight对象(环境光)。渲染场景的时候,THREE.Camera对象会自动地被Three.js添加进来。但是我们手动添加它会是一个更好的实践,尤其是当你需要处理多个摄像机的时候。
// create a scene, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();

Expand Down Expand Up @@ -85,26 +91,33 @@

var controls = new function () {
this.rotationSpeed = 0.02;
this.numberOfObjects = scene.children.length;
this.numberOfObjects = scene.children.length;//用numberOfObjects变量来显示场景中对象数量

//点击removeCube按钮,将会移除最后一个添加到场景中的方块。
this.removeCube = function () {
var allChildren = scene.children;
//由于Three.js将子对象保存在数组中(最新的对象保存在数组的最后),所以我们可以使用THREE.Scene对象的children属性来获取最后一个添加到场景中的对象,children属性将场景中的所有对象存储为数组
var lastObject = allChildren[allChildren.length - 1];

//children属性将场景中的所有对象存储为数组。在移除对象时,我们还需要检查该对象是不是THREE.Mesh对象,这样做的原因是避免移除摄像机和光源。
if (lastObject instanceof THREE.Mesh) {
scene.remove(lastObject);
//当我们移除对象之后,需要再次更新控制界面中表示场景中对象数量的属性numberOfObjects。
this.numberOfObjects = scene.children.length;
}
};

//当你点击addCube按钮的时候,一个新的THREE.BoxGeometry对象就会被创建,它的长、宽和高都是一个从1到3之间的随机数。除了尺寸是随机的,这个方块的颜色和位置也是随机的。
this.addCube = function () {

var cubeSize = Math.ceil((Math.random() * 3));
var cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
var cubeMaterial = new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
cube.name = "cube-" + scene.children.length;

cube.name = "cube-" + scene.children.length;//方块的名字是在cube后面加上当前场景中对象的数量(即scene.children.length)

//给对象命名在调试的时候是很有用的,而且还可以直接通过名字来获取场景中的对象。如果使用Three.Scene.getObjectByName(name)方法,可以直接获取场景中名为name的对象,然后可以执行一些比如改变位置的操作。

// position the cube randomly in the scene

Expand All @@ -114,14 +127,28 @@

// add the cube to the scene
scene.add(cube);
//我们的控制界面是使用numberOfObjects变量来显示场景中对象数量的。所以,无论什么时候添加或者删除对象,我们都要将该变量设置为更新后的数量。
this.numberOfObjects = scene.children.length;
};

//这个按钮的功能是在浏览器的控制台中打印出场景中的所有对象信息
this.outputObjects = function () {
console.log(scene.children);
console.log(scene.children);//我们使用的是内置的console对象在浏览器控制台日志中输出对象信息,这样做对于代码调试非常有用,尤其是当你为对象命名时,它对查找某个特定对象相关的问题非常有用。
}
};

//例如,对象cube-17(如果你已经知道对象的名字,就可以使用console.log(scene.getObjectByName("cube-17"))来输出对应的信息


/* 总结
THREE.Scene.Add:用于向场景中添加对象
THREE.Scene.Remove:用于移除场景中的对象
THREE.Scene.children:用于获取场景中所有的子对象列表
THREE.Scene.getObjectByName:利用name属性,用于获取场景中特定的对象
*/




var gui = new dat.GUI();
gui.add(controls, 'rotationSpeed', 0, 0.5);
gui.add(controls, 'addCube');
Expand All @@ -131,13 +158,14 @@

render();

//使用render循环来渲染场景
function render() {
stats.update();

// rotate the cubes around its axes
// rotate the cubes around its axes在这里,我们使用了THREE.Scene.traverse()方法。我们可以将一个方法作为参数传递给traverse()方法,这个传递来的方法将会在每一个子对象上执行。由于THREE.Scene对象存储的是对象树,所以如果子对象本身还有子对象,traverse()方法会在所有的子对象上执行,直到遍历完场景树中的所有对象为止。
scene.traverse(function (e) {
if (e instanceof THREE.Mesh && e != plane) {

//我们使用render()方法来更新每个方块的旋转状态(我们特意忽略了表示地面的plane对象)。我们还可以使用for循环或者forEach来遍历children属性数组来达到同样的目的,因为只向THREE.Scene增加对象且没有创建嵌套结构。
e.rotation.x += controls.rotationSpeed;
e.rotation.y += controls.rotationSpeed;
e.rotation.z += controls.rotationSpeed;
Expand Down Expand Up @@ -170,4 +198,4 @@

</script>
</body>
</html>
</html>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>

//当设置了overrideMaterial属性后,场景中所有的物体都会使用该属性指向的材质,即使物体本身也设置了材质。当某一个场景中所有物体都共享同一个材质时,使用该属性可以通过减少Three.js管理的材质数量来提高运行效率,但是实际应用中,该属性通常并不非常实用。
<html>

<head>
Expand Down Expand Up @@ -167,4 +167,4 @@

</script>
</body>
</html>
</html>
Loading