Blogger Information
Blog 143
fans 1
comment 0
visits 440335
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
three.js 入门详解(一)
弘德誉曦的博客
Original
1372 people have browsed it

three.js 入门详解(一)

Levi丶

image

1. 概述

1.1 什么是WebGL?

WebGL是在浏览器中实现三维效果的一套规范
  • 想要使用WebGL原生的API来写3D效果的话,很吃力。three.js是WebGL的一个开源框架,它省去了很多麻烦的细节。

1.2 初识three.js

什么是threejs,很简单,你将它理解成three+js就可以了。three表示3D的意思js表示javascript的意思。那么合起来,three.js就是使用javascript 来写3D程序的意思。
  • Javascript是运行在网页端的脚本语言,那么毫无疑问Three.js也是运行在浏览器上的。

1.3 前期准备

1.3.1 下载地址

1.3.2 目录结构

image

  • Build目录: 包含两个文件,three.js 和three.min.js 。这是three.js最终被引用的文件。一个已经压缩,一个没有压缩的js文件。
  • Docs目录: 这里是three.js的帮助文档,里面是各个函数的api,可惜并没有详细的解释。试图用这些文档来学会three.js是不可能的。
  • Editor目录: 一个类似3D-max的简单编辑程序,它能创建一些三维物体。
  • Examples目录: 一些很有趣的例子demo,可惜没有文档介绍。对图像学理解不深入的同学,学习成本非常高。
  • Src目录: 源代码目录,里面是所有源代码。
  • Test目录: 一些测试代码,基本没用。
  • Utils目录: 存放一些脚本,python文件的工具目录。例如将3D-Max格式的模型转换为three.js特有的json模型。
  • .gitignore文件: git工具的过滤规则文件,没有用。
  • CONTRIBUTING.md文件: 一个怎么报bug,怎么获得帮助的说明文档。
  • LICENSE文件: 版权信息。
  • README.md文件: 介绍three.js的一个文件,里面还包含了各个版本的更新内容列表。

1.3.3 配置开发环境

  • 浏览器: 推荐使用高版本的浏览器,谷歌、火狐、360等,对于前端开发者来说,chrome是不二的选择
  • js 开发工具: VS-code、Webstorm 都可以,为了方便下面的学习,这里使用Webstorm
  • Three.js 调试: 利用谷歌浏览器的调试窗口,使用断点调试的方法

2. 开始使用Three.js

使用Three.js之前,首先在<head>部分,需要引入外部文件Three.js
<head>
    <script type="text/javascript" src="three.js"></script></head>
WebGL 的渲染是需要HTML5 中的Canvas元素的,你可以手动在HTML的<body>部分中使用canvas标签,或者让Three.js帮你生成。这两种选择,一般没有多大差别。我们先手动定义一个canvas标签:
<body onload="init();">
    <canvas id="canvasId" width="800" height="600"></canvas></body>
在js里面定义一个函数,将所有执行的代码放在函数里,在html加载完成后,执行该函数
function init{    // 所有需要执行的代码}

一个典型的Three.js程序,至少应该包括四个部分:渲染器(renderer)场景(scene)相机(camera)以及场景中创建的物体

2.1 渲染器(renderer)

渲染器决定了渲染的结果应该画在页面的什么元素上面,并且以怎样的方式来绘制

渲染器将会和canvas元素进行绑定,如果之前<html>标签中,定义了id为canvasId的canvas标签,那么renderer可以这样写:

var renderer = new THREE.WebGLRenderer({    canvas : document.getElementById('canvasId');
});
如果想要Three.js生成Canvas元素的时候,在html中就不需要在定义一个canvas标签了,直接在javascript代码中写道:
var renderer = new THREE.WebGLRenderer();
renderer.setSize = (800,600);document.body.appendChild(renderer.domElement);
  • 上面的代码setSize是为canvas元素设置宽高,document.body.appendChild(renderer.domElement)是将渲染器对应的Canvas元素添加到body中。
我们可以使用下面的代码(用于清除画面的颜色)将背景色设置为黑色:
renderer.setClearColor(0x000000);

2.2 场景(scene)

在Three.js中添加物体都是添加到场景中的,因此它相当于一个大容器。一般说,场景里没有很复杂的操作,只要new一个对象就可以了,然后将物体添加到场景中即可。
var scene = new THREE.Scene();

2.3 照相机(camera)

在介绍照相机之前,我们先来介绍一下坐标系。
  • three.js中使用的是右手坐标系,X轴水平向右,y轴垂直向上,Z轴的方向就是屏幕由里往外的方向

image

这里我们定义一个透视相机(相机也需要添加到场景中):
var camera = new THREE.PerspectiveCamera(45, 4 / 3, 1, 1000);// 设置相机的位置camera.position.set(0,0,5);// 将相机添加到场景中scene.add(camera);

2.4 创建一个物体

这里我们先介绍一个长方体,创建一个x、y、z方向长度分别为1、2、3的长方体,并设置为红色。
var geometry = new THREE.CubeGeometry(1,2,3);var material = new THREE.MeshBasicMaterial({    color: 0xff0000;
});var cube = new THREE.Mesh(geometry,material);
scene.add(cube);
  • new THREE.CubeGeometry(); 表示调用一个几何体

    • Cube : 立方体 Geometry : 几何;
    • CubeGeometry是一个正方体或者长方体,究竟是什么,由它的3个参数所决定
CubeGeometry(width, height, depth, segmentsWidth, segmentsHeight, segmentsDepth, materials, sides)
width:立方体x轴的长度

height:立方体y轴的长度

depth:立方体z轴的深度,也就是长度

想一想大家就明白,以上3个参数就能够确定一个立方体。

剩下的几个参数就要费解和复杂一些了,不过后面我们会自己来写一个立方体,到时候,你会更明白这些参数的意义,这里你可以将这些参数省略。
  • new THREE.MeshBasicMaterial(); 表示的是物体的材质

    • 你可以在里面设置物体的颜色
var material = new THREE.MeshBasicMaterial({    color: 0xff0000;
});
  • 一定不要忘了,将物体添加到场景

2.5 渲染

在定义了场景中的物体,设置好的照相机之后,渲染器就知道如何渲染出二维的结果了。这时候,我们只需要调用渲染器的渲染函数,就能使其渲染一次了。
renderer.render(scene, camera);

2.6 完整代码

<html lang="en"><head>
    <meta charset="UTF-8">
    <title>长方体</title>
    <script src="three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();            // canvas元素设置宽高
            renderer.setSize = (800, 600);            // 渲染器对应的Canvas元素添加到<body>中。
            document.body.appendChild(renderer.domElement);            // 清除画面的颜色
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            // 相机
            // 定义一个透视相机
            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);
        }    </script></body></html>
  • 效果图

image
canvas元素的默认宽高为300/150

3. Three.js功能概览

下面介绍下Three.js官网文档中的一些重要的对象,在你需要寻求帮助时,就能够知道关键词是什么。
  • Cameras(照相机,控制投影方式)

    • Camera
    • OrthographicCamera
    • PerspectiveCamera
  • Core(核心对象)

    • BufferGeometry
    • Clock(用来记录时间)
    • EventDispatcher
    • Face3
    • Face4
    • Geometry
    • Object3D
    • Projector
    • Raycaster(计算鼠标拾取物体时很有用的对象)
  • Lights(光照)

    • Light
    • AmbientLight
    • AreaLight
    • DirectionalLight
    • HemisphereLight
    • PointLight
    • SpotLight
  • Loaders(加载器,用来加载特定文件)

    • Loader
    • BinaryLoader
    • GeometryLoader
    • ImageLoader
    • JSONLoader
    • LoadingMonitor
    • SceneLoader
    • TextureLoader
  • Materials(材质,控制物体的颜色、纹理等)

    • Material
    • LineBasicMaterial
    • LineDashedMaterial
    • MeshBasicMaterial
    • MeshDepthMaterial
    • MeshFaceMaterial
    • MeshLambertMaterial
    • MeshNormalMaterial
    • MeshPhongMaterial
    • ParticleBasicMaterial
    • ParticleCanvasMaterial
    • ParticleDOMMaterial
    • ShaderMaterial
    • SpriteMaterial
  • Math(和数学相关的对象)

    • Box2
    • Box3
    • Color
    • Frustum
    • Math
    • Matrix3
    • Matrix4
    • Plane
    • Quaternion
    • Ray
    • Sphere
    • Spline
    • Triangle
    • Vector2
    • Vector3
    • Vector4
  • Objects(物体)

    • Bone
    • Line
    • LOD
    • Mesh(网格,最常用的物体)
    • MorphAnimMesh
    • Particle
    • ParticleSystem
    • Ribbon
    • SkinnedMesh
    • Sprite
  • Renderers(渲染器,可以渲染到不同对象上)

    • CanvasRenderer
    • WebGLRenderer(使用WebGL渲染,这是本书中最常用的方式)
    • WebGLRenderTarget
    • WebGLRenderTargetCube
    • WebGLShaders(着色器,在最后一章作介绍)
  • Renderers / Renderables

    • RenderableFace3
    • RenderableFace4
    • RenderableLine
    • RenderableObject
    • RenderableParticle
    • RenderableVertex
  • Scenes(场景)

    • Fog
    • FogExp2
    • Scene
  • Textures(纹理)

    • CompressedTexture
    • DataTexture
    • Texture
  • Extras

    • FontUtils
    • GeometryUtils
    • ImageUtils
    • SceneUtils
  • Extras / Animation

    • Animation
    • AnimationHandler
    • AnimationMorphTarget
    • KeyFrameAnimation
  • Extras / Cameras

    • CombinedCamera
    • CubeCamera
  • Extras / Core

    • Curve
    • CurvePath
    • Gyroscope
    • Path
    • Shape
  • Extras / Geometries(几何形状)

    • CircleGeometry
    • ConvexGeometry
    • CubeGeometry
    • CylinderGeometry
    • ExtrudeGeometry
    • IcosahedronGeometry
    • LatheGeometry
    • OctahedronGeometry
    • ParametricGeometry
    • PlaneGeometry
    • PolyhedronGeometry
    • ShapeGeometry
    • SphereGeometry
    • TetrahedronGeometry
    • TextGeometry
    • TorusGeometry
    • TorusKnotGeometry
    • TubeGeometry
  • Extras / Helpers

    • ArrowHelper
    • AxisHelper
    • CameraHelper
    • DirectionalLightHelper
    • HemisphereLightHelper
    • PointLightHelper
    • SpotLightHelper
  • Extras / Objects

    • ImmediateRenderObject
    • LensFlare
    • MorphBlendMesh
  • Extras / Renderers / Plugins

    • DepthPassPlugin
    • LensFlarePlugin
    • ShadowMapPlugin
    • SpritePlugin
  • Extras / Shaders

    • ShaderFlares
    • ShaderSprite

我们看到,Three.js功能是十分丰富的,一时间想全部掌握有些困难。在接下来的章节中,我们将会先详细介绍照相机、几何形状、材质、物体等入门级知识;然后介绍使用动画、模型导入、加入光照等功能;最后,对于学有余力的读者,我们将介绍着色器,用于更高级的图形渲染。

4. 照相机

本章将介绍照相机的概念,以及如何使用Three.js设置相应的参数。

4.1 什么是照相机?

在图形学中,照相机可没有生活中的照相机那么简单
  • 我们使用的Three.js创建的场景是三维的,而通常情况下显示器是二维的,那么三维的场景怎么在二维的显示器上显示呢?照相机就是一个抽象,它定义了三维空间到二维屏幕投影的方式,用“照相机”这样一个类比,可以使我们直观地理解这一投影方式。
  • 而针对投影方式的不同,照相机又分为正交投影照相机透视投影照相机。我们需要为自己的程序选择合适的照相机。这两者分别是什么,以及两者有何差异,我们将在下节中作介绍。

4.2 正交投影和透视投影

举个简单的例子来说明正交投影与透视投影照相机的区别。使用透视投影照相机获得的结果是类似人眼在真实世界中看到的有“近大远小”的效果(如下图中的(a));而使用正交投影照相机获得的结果就像我们在数学几何学课上老师教我们画的效果,对于三维空间内平行的线,投影到二维空间中也一定是平行的(如下图中的(b))。

image

一般说来,对于制图、建模软通常使正交投影,这样不会因为投影而改变物体比例;而对于其他大多数应用,通常使用 透视投影,因为这更接近人眼的观察效果。当然,照相机的选择并没有对错之分,你可以更具应用的特性,选择一个效果更佳的照相机。

4.3 正交投影照相机

4.3.1 参数介绍

正交投影照相机(Orthographic Camera)
THREE.OrthographicCamera(left, right, top, bottom, near, far)
这六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,这六个面围成一个长方体,我们称其视景体(Frustum)。只有在视景体内部(下图中的灰色部分)的物体才可能显示在屏幕上,而视景体外的物体会在显示之前被裁减掉。

image

为了保持照相机的横竖比例,需要保证(right - left)与(top - bottom)的比例与Canvas宽度与高度的比例(800/600)一致。
// [2-(-2)] / [1.5-(-1.5)] = canvas.width/canvas.heightvar camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10) // left right top bottom near far

near与far都是指到照相机位置在深度平面的位置,而照相机不应该拍摄到其后方的物体,因此这两个值应该均为正值。为了保证场景中的物体不会因为太近或太远而被照相机忽略,一般near的值设置得较小far的值设置得较大,具体值视场景中物体的位置等决定。

4.3.2 示例代码

下面我们通过一个具体的例子来了解正交投影照相机的设置

基本设置

  • 设置照相机:
var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10);
camera.poaition.set(0,0,5);
scene.add(camera);
  • 在原点处创建一个边长为1的正方体,为了和透视效果做对比,这里我们使用wireframe而不是实心的材质,以便看到正方体后方的边:
var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), 
    new THREE.MeshBasicMaterial({        color: 0xff0000,        wireframe: true
    })
);
scene.add(cube);
  • 效果图:

  • 我们看到正交投影的结果是一个正方形,后面的边与前面完全重合了,这也就是正交投影与透视投影的区别所在。

长宽比例

这里,我们的Canvas宽度是800px,高度是600px,照相机水平方向距离4,垂直方向距离3,因此长宽比例保持不变。为了试验长宽比例变化时的效果,我们将照相机水平方向的距离减小为2(right-left = 2):
var camera = new THREE.OrthographicCamera(-111.5, -1.5110);
  • 效果图(此时水平方向的距离就被拉长了):

照相机位置

接下来,我们来看看照相机位置对渲染结果的影响。在之前的例子中,我们将照相机设置在(0, 0, 5)位置,而由于照相机默认是面向z轴负方向放置的,所以能看到在原点处的正方体。现在,如果我们将照相机向右移动1个单位:
var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10);// 向右移动一个单位的位置camera.position.set(1, 0, 5);
  • 效果图(物体看上去向左移动了)

  • 其实照相机就好比人的眼睛,当我们身体往右移动的时候,看到的物体就好像向左移了。
正交投影摄像机在设置时,是否需要保证left 和 right 互为相反数呢?
  • 下面,我们将原本的参数(-2, 2, 1.5, -1.5, 1, 10)改为(-1, 1, 1.5, -1.5, 1, 10),即,将视景体设置得更靠右:
var camera = new THREE.OrthographicCamera(-1, 3, 1.5, -1.5, 1, 10);
camera.position.set(0, 0, 5);
  • 效果图(与之前相机向右的效果是一样的)

image

换个角度

到目前为止,我们使用照相机,都是沿着Z轴负方向观察的,因此看到的都是一个正方形,现在我们尝试一下仰望这个正方体,改变照相机的位置:
// x轴:4;  y轴:-3;  z轴:5camera.position.set(4, -3, 5);
照相机默认是沿着z轴的负方向观察的,因此观察不到正方体,只看到一片黑。我们可以通过lookAt函数指定它看着原点方向

image

camera.lookAt(new THREE.Vector3(000));
  • 效果图:

image

  • 注意:lookAt函数接收的是一个THREE.Vector3的实例千万不能写成camera.lookAt(0,0,0)

4.4 透视投影照相机

4.4.1 参数介绍

透视投影照相机(Perspective Camera)
THREE.PerspectiveCamera(fov, aspect, near, far)
让我们通过一张透视照相机投影的图来了解这些参数。

image

  • 透视图中,灰色的部分是视景体,是可能被渲染的物体所在的区域。fov是视景体竖直方向上张角(是角度制而非弧度制),如侧视图所示。
  • aspect等于width / height,是照相机水平方向和竖直方向长度的比值,通常设为Canvas的横纵比例
  • near和far分别是照相机到视景体 最近、最远的距离,均为正值,且far应大于near

4.4.2 示例代码

下面我们通过一个例子来学习透视投影照相机

基本设置

  • 设置透视投影照相机,这里Canvas长800px,宽600px,所以aspect设为800 / 600
var camera = new THREE.PerspectiveCamera(45, 800 / 600, 1, 10);
camera.position.set(0, 0, 5);
scene.add(camera);
  • 设置一个在原点处的边长为1的正方体:
var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1),        new THREE.MeshBasicMaterial({            color: 0xff0000,            wireframe: true
        })
);
scene.add(cube);
  • 效果图:

image

  • 对比正交透视照相机下正方形的效果,透视投影可以看到全部的12条边,而且有近大远小的效果,这也就是与正交投影的区别。

竖直张角

  • 接下来,我们来看下fov的改变对渲染效果的影响。我们将原来的45改为60
var camera = new THREE.PerspectiveCamera(60, 800 / 600, 1, 10);
camera.position.set(0, 0, 5);
scene.add(camera);
  • 效果图:

image

  • 为什么正方体显得更小了呢?我们从下面的侧视图来看,虽然正方体的实际大小并未改变,但是将照相机的竖直张角设置得更大时,视景体变大了,因而正方体相对于整个视景体的大小就变小了,看起来正方形就显得变小了。

image

  • 注意,改变fov不会起画面横竖比例的变化,而改变aspect改变横竖比例。

5. 点、线、面

5.1 3D世界的组成

在计算机世界里,3D世界由点组成,两个点能组成一条直线,三个不在一条直线上的点,就能组成一个三角面,无数的三角面就能组成各种各样的物体,如下图:

image

  • 我们通常把这种网络模型叫做Mesh模型。给物体贴上皮肤,或者专业点就叫做纹理,那么这个物体就活灵活现了。最后无数的物体就组成了我们的3D世界。

5.2 在Three.js中定义一个点

在三维空间中的某一个点可以用一个坐标点来表示。一个坐标点由x,y,z三个分量构成。在three.js中,点可以在右手坐标系中表示:

空间几何中,点可以用一个向量来表示,在Three.js中也是用一个向量来表示的

THREE.Vector3 = function ( x, y, z ) {this.x = x || 0;this.y = y || 0;this.z = z || 0;

};
  • 我们来分析这段代码:前面我们已经知道了THREE是Three.js引擎的一个全局变量。只要你想用它,就可以在任何地方用它。
  • 那么THREE.Vector3呢,就是表示Vector3是定义在THREE下面的一个。以后要用Vector3,就必须要加THREE前缀。当然Three.js的设计者,也可以不加THREE这个前缀,但是他们预见到,Three.js引擎中会有很多类型,最好给这些类型加一个前缀,以免与开发者的代码产生冲突。
  • THREE.Vector3被赋值为一个函数。这个函数有3个参数,分别代表x坐标y坐标z坐标的分量。函数体内的代码将他们分别赋值给成员变量x,y,z。看看上面的代码,中间使用了一个“||”(或)运算符,就是当x=null或者undefine时,this.x的值应该取0

5.3 点的操作

在3D世界中可以用THREE.Vector3D来表示。
  • 现在来看看怎么定义个点,假设有一个点x=4,y=8,z=9。你可以这样定义它:
var point1 = new THREE.Vecotr3(4,8,9);
  • 另外你也可以使用set方法,代码如下:
var point1 = new THREE.Vector3();

point1.set(4,8,9);

5.4 绘制一条线段

两个不重合的点能够决定一条直线。在three.js中,也可以通过定义两个点,来画一条直线。
  • 1、首先,声明一个几何体geometry
  • 几何体里面有个vertices变量,可以用来存放点
var geometry = new THREE.Geometry();// 几何体里面有个vertices变量,可以用来存放点
  • 2、定义一种线条的材质,使用THREE.LineBasicMaterial类型来定义,它接受一个集合作为参数,其原型如下:
THREE.LineBasicMaterial(parameters);
  • parameters 是定义材质外观的对象,它包含多个属性来定义材质,这些属性是:

    • Color 线条的颜色,用16进制表示,默认都是白色
    • Linewidth 线条的宽度,默认是1个单位宽度
    • Linecap 线条两端的外观,默认是圆角端点,当线条较粗的时候才能看到效果
    • Linejoin 两个线条的连接点处的外观,默认是“round”,表示圆角。
    • VertexColors 定义线条材质是否使用顶点颜色,这是一个boolean值。意思是,线条各部分的颜色会根据顶点的颜色来进行插值。
    • Fog 定义材质的颜色是否受全局雾效的影响。
  • 我们这里使用了顶点颜色 vertexColors: THREE.VertexColors,就是线条的颜色会根据顶点来计算。
var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );
  • 注意: 关于线宽的坑,WebGLRender渲染方式是不之持绘制线宽的,要想支持,需要将渲染方式设置为CanvasRenderer
  • 3、接下来,定义两种颜色,分别表示线条两个端点的颜色,
var color1 = new THREE.Color( 0x444444 ),
    color2 = new THREE.Color( 0xFF0000 );
  • 4、定义2个顶点的位置,并放到geometry中,代码如下:
var p1 = new THREE.Vector3(-100,0,100);var p2 = new THREE.Vector3(100,0,-100);

geometry.vertices.push(p1);
geometry.vertices.push(p2);
  • 5、为4中定义的2个顶点,设置不同的颜色,代码如下所示:
geometry.colors.push( color1, color2 );
  • geometry中colors表示顶点的颜色,必须材质中vertexColors等于THREE.VertexColors时,颜色才有效,如果vertexColors等于THREE.NoColors时,颜色就没有效果了。那么就会去取材质中color的值,这个很重要。
  • 6、定义一条线。
  • 定义线条,使用THREE.Line类,代码如下所示:
var line = new THREE.Line( geometry, material, THREE.LinePieces );
  • 第一个参数是几何体geometry,里面包含了2个顶点和顶点的颜色
  • 第二个参数是线条的材质,或者是线条的属性,表示线条以哪种方式取色。
  • 第三个参数是一组点的连接方式。
  • 7、然后,将这条线加入到场景中,代码如下:
scene.add(line);
  • 8、整体代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html><head>
    <script type="text/javascript" src="three.js"></script>

    <script type="text/javascript">
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();            // 设定渲染器尺寸
            renderer.setSize(800, 600);            // 添加到dom
            document.body.appendChild(renderer.domElement);            // 重绘时颜色
            renderer.setClearColor(0xffffff);            // 场景
            var scene = new THREE.Scene();            // 相机
            var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);            // 设定相机位置
            camera.position.set(0, -25, 0);            // 相机看向
            camera.lookAt(new THREE.Vector3(0, 0, 0));
            scene.add(camera);            // 定义一个物体
            // new 一个模型
            var geometry = new THREE.Geometry();            // 定义模型的类型是线段  并且设置其材质
            var material = new THREE.LineBasicMaterial({                // 使用顶点颜色
                vertexColors: true
            });            // 定义两个颜色
            var color1 = new THREE.Color(0x444444),
                color2 = new THREE.Color(0xff0000);            // 新建两个点
            var p1 = new THREE.Vector3(-1, 0, 1);            var p2 = new THREE.Vector3(1, 0, -1);            // 将新建的两个点添加到几何体中
            geometry.vertices.push(p1);
            geometry.vertices.push(p2);            // 将两个颜色添加到几何体中
            geometry.colors.push(color1, color2);            // new 一条线
            var line = new THREE.Line(geometry, material, THREE.LinePieces);
            scene.add(line);            // 渲染
            renderer.render(scene, camera);

        }    </script></head><body onload="init()"></body></html>
  • 效果图:

image

5.5 线条的深度理解

在Threejs中,一条线由点,材质和颜色组成。
  • 点由THREE.Vector3表示,Threejs中没有提供单独画点的函数,它必须被放到一个THREE.Geometry形状中,这个结构中包含一个数组vertices,这个vertices就是存放无数的点(THREE.Vector3)的数组
  • 1、为了绘制一条直线,首先我们需要定义两个点
var p1 = new THREE.Vector3( -1, 0, 1 );var p2 = new THREE.Vector3( 1, 0, -1 );
  • 2、声明一个THREE.Geometry,并把点加进去
var geometry = new THREE.Geometry();
geometry.vertices.push(p1);
geometry.vertices.push(p2);
  • geometry.vertices的能够使用push方法,是因为geometry.vertices是一个数组。这样geometry中就有了2个点了。
  • 3、然后我们需要给线加一种材质,THREE.LineBasicMaterial
var material = new THREE.LineBasicMaterial();
  • 4、最终我们通过THREE.Line绘制了一条线:
var line = new THREE.Line( geometry, material, THREE.LinePieces );

5.6 绘制网格线

我们要画一个网格的坐标,那么我们就应该找到线的点。把网格虚拟成正方形,在正方形边界上找到几个等分点,用这些点两两连接,就能够画出整个网格来。
  • 1、定义两个点
// 在x轴上定义两个点p1(-500,0,0),p2(500,0,0)。geometry.vertices.push( new THREE.Vector3( - 500, 0, 0 ));
geometry.vertices.push( new THREE.Vector3( 500, 0, 0 ));
  • 2、算法

    • 这两个点决定了x轴上的一条线段,将这条线段复制20次,分别平行移动到z轴的不同位置,就能够形成一组平行的线段。
    • 同理,将p1p2这条线先围绕y轴旋转90度,然后再复制20份,平行于z轴移动到不同的位置,也能形成一组平行线。
for ( var i = 0; i <-= 20; i ++ ) {    var line = new THREE.Line( geometry, new THREE.LineBasicMaterial({ color: 0x000000, opacity: 0.2 }));
    line.position.z = ( i * 50 ) - 500;
    scene.add( line );    var line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.2 } ));
    line.position.x = ( i * 50 ) - 500;
    line.rotation.y = 90 * Math.PI / 180;   //  旋转90度
    scene.add( line );

}
  • 3、完整代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html><head>
    <script type="text/javascript" src="three.js"></script>

    <script type="text/javascript">
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();            // 设定渲染器尺寸
            renderer.setSize(800, 600);            // 添加到dom
            document.body.appendChild(renderer.domElement);            // 重绘时颜色
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            // 相机
            var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);            // 设定相机位置
            camera.position.set(0, -25, 0);            // 相机看向
            camera.lookAt(new THREE.Vector3(0, 0, 0));
            scene.add(camera);            // 定义一个几何体
            var geometry = new THREE.Geometry();
            geometry.vertices.push(new THREE.Vector3(-2, 0, 0));
            geometry.vertices.push(new THREE.Vector3(2, 0, 0));            // for循环出来六条线段
            for (var i = 0; i <= 5; i++) {                // 定义竖着的线段
                var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({                    color: 0xffffff,
                }));                // 每条线段之间的间隔为0.8,-2是为了达到田字格的效果
                line.position.z = (i * 0.8) - 2;
                scene.add(line);                // 定义横着的线段
                var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({                    color: 0xffffff,                    opacity: 0.2
                }));
                line.position.x = (i * 0.8) - 2;
                line.rotation.y = 90 * Math.PI / 180;
                scene.add(line);                // 渲染
                renderer.render(scene, camera);

            }
        }    </script></head><body onload="init()"></body></html>
  • 效果图:

image

6. 几何形状

在创建物体时,需要传入两个参数,一个是几何形状(Geometry),另一个是材质(Material),这一章将着重介绍几何形状的创建,第6章介绍材质,第7章介绍如何使用两者创建网格。
  • 几何形状(Geometry)最主要的功能是储存了一个物体的顶点信息。WebGL需要程序员指定每个顶点的位置,而在Three.js中,可以通过指定一些特征来创建几何形状,例如使用半径创建一个球体,从而省去程序员一个个指定顶点的工作量。
  • 本章节将分别介绍立方体、平面、球体、圆柱体、四面体、八面体等几何形状,以及以三维文字作为几何形状的方法。本节还会介绍通过手动定义 顶点位置面片信息组成几何形状。

6.1 基本几何形状

6.1.1 立方体

虽然这形状的名字叫做立方体(CubeGeometry),但其实是长方体,也就是长宽高可以设置不同的值:
new THREE.CubeGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)
  • 这里,widthx方向上的长度;heighty方向上的长度;depthz方向上的长度;后三个参数分别是在三个方向上的分段数,如widthSegments为3的话,代表x方向上水平分为三份。一般情况下不需要分段的话,可以不设置后三个参数,后三个参数的缺省值为1。其他几何形状中的分段也是类似的,下面不做说明。

长宽高

  • 创建立方体直观简单,如:new THREE.CubeGeometry(1, 2, 3);可以创建一个x方向长度为1,y方向长度为2,z方向长度为3的立方体。
// 调用渲染器var renderer = new THREE.WebGLRenderer();
renderer.setSize(800, 600);document.body.appendChild(renderer.domElement);
renderer.setClearColor(0x000000);// 调用场景var scene = new THREE.Scene();// 调用相机var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);
camera.position.set(25, 25, 25);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);// 新建一个几何体(长方体)var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3), new THREE.MeshBasicMaterial({    color: 0xffff00,    wireframe: true}));

scene.add(cube);
  • 为了更好地表现参数效果,我们在场景中用长度为3红、绿、蓝线段分别表示x、y、z三个轴(这里不需要深究,后面会详细介绍):
// 封装一个坐标系函数function drawAxes(scene) {    // x-axis
    var xGeo = new THREE.Geometry();
    xGeo.vertices.push(new THREE.Vector3(0, 0, 0));
    xGeo.vertices.push(new THREE.Vector3(3, 0, 0));    var xMat = new THREE.LineBasicMaterial({        color: 0xff0000
    });    var xAxis = new THREE.Line(xGeo, xMat);
    scene.add(xAxis);    // y-axis
    var yGeo = new THREE.Geometry();
    yGeo.vertices.push(new THREE.Vector3(0, 0, 0));
    yGeo.vertices.push(new THREE.Vector3(0, 3, 0));    var yMat = new THREE.LineBasicMaterial({        color: 0x00ff00
    });    var yAxis = new THREE.Line(yGeo, yMat);
    scene.add(yAxis);    // z-axis
    var zGeo = new THREE.Geometry();
    zGeo.vertices.push(new THREE.Vector3(0, 0, 0));
    zGeo.vertices.push(new THREE.Vector3(0, 0, 3));    var zMat = new THREE.LineBasicMaterial({        color: 0x00ccff
    });    var zAxis = new THREE.Line(zGeo, zMat);
    scene.add(zAxis);
}// 在init 函数里调用这个函数 即可在屏幕上显示一个坐标系了drawAxes(scene);
  • 在设置材质,并添加到场景之后具体的效果是:

image

  • 物体的默认位置原点,对于立方体而言,是其几何中心在原点的位置。

分段

  • 根据THREE.CubeGeometry(width, height, depth, widthSegments, heightSegments, depthSegments),的后三个参数,为这个长方体分段:
// x轴分两段  y轴分两段 z轴分三段new THREE.CubeGeometry(1, 2, 3, 2, 2, 3)
  • 效果图:

image

  • 注意这个分段是对六个面进行分段,而不是对立方体的体素分段,因此在立方体的中间是不分段的,只有六个侧面被分段。

6.1.2 平面

这里的平面(PlaneGeometry)其实是一个长方形,而并非是数学意义上无限大的平面:
new THREE.PlaneGeometry(width, height, widthSegments, heightSegments)
  • 其中,widthx方向上的长度;heighty方向上的长度;后两个参数同样表示分段
  • new THREE.PlaneGeometry(2, 4);创建的平面在x轴和y轴所在平面内:
var plane = new THREE.Mesh(    new THREE.PlaneGeometry(2, 4), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(plane);
  • 效果图:

image

  • 如果需要创建的平面在x轴和z轴所在的平面内,可以通过物体的旋转来实现,具体的做法将在下面章节介绍到。

6.1.3 球体

球体(SphereGeometry)的构造函数是:
new THREE.SphereGeometry(radius, segmentsWidth, segmentsHeight, phiStart, phiLength, thetaStart, thetaLength)
  • 其中,radius半径segmentsWidth表示经度上的切片数segmentsHeight表示纬度上的切片数phiStart表示经度开始的弧度phiLength表示经度跨过的弧度thetaStart表示纬度开始的弧度thetaLength表示纬度跨过的弧度

分段

  • 首先,我们来理解下segmentsWidthsegmentsHeight。使用var sphere = new THREE.SphereGeometry(2, 8, 6)可以创建一个半径为2经度划分成8份,纬度划分成6份的球体
var sphere = new THREE.Mesh(    new THREE.SphereGeometry(2, 8, 6), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(sphere);
  • 效果图:

image

  • new THREE.SphereGeometry(2, 8, 16)的效果如图:

image

  • new THREE.SphereGeometry(3, 18, 12)的效果如图:

image

  • segmentsWidth相当于经度被切成了几瓣,而segmentsHeight相当于纬度被切成了几层。因为在图形底层的实现中,并没有曲线的概念,曲线都是由多个折线近似构成的。对于球体而言,当这两个值较大的时候,形成的多面体就可以近似看做是球体了。

经度弧度

  • new THREE.SphereGeometry(2, 8, 6, Math.PI / 2, Math.PI / 3)表示起始经度为Math.PI / 6,经度跨度为Math.PI / 3
var sphere = new THREE.Mesh(    new THREE.SphereGeometry(2, 8, 6, Math.PI / 2, Math.PI / 3), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(sphere);
  • 效果图:

image

  • 值得注意的是,这里的SegmentsWidth8意味着对于经度从Math.PI / 2跨过Math.PI / 3区域内划分为8块,而不是整个球体的经度划分成8块后再判断在此经度范围内的部分。

纬度弧度

  • 理解了经度之后,纬度可以同理理解。new THREE.SphereGeometry(2, 8, 6, 0, Math.PI * 2, Math.PI / 6, Math.PI / 3)意味着纬度从Math.PI / 6跨过Math.PI / 3
var sphere = new THREE.Mesh(    // 经度起始弧度为0度,经度跨度为 180*2
    new THREE.SphereGeometry(2, 8, 6, 0, Math.PI * 2, Math.PI / 6, Math.PI / 3), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(sphere);
  • 效果图:

image

  • 我们再来看一个经度纬度都改变了起始位置和跨度的例子:new THREE.SphereGeometry(2, 8, 6, Math.PI / 2, Math.PI, Math.PI / 6, Math.PI / 2)
var sphere = new THREE.Mesh(    new THREE.SphereGeometry(2, 8, 6, Math.PI / 2, Math.PI, Math.PI / 6, Math.PI / 2), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(sphere);
  • 效果图:

image

6.1.4 圆形

圆形(CircleGeometry)可以创建圆形或者扇形,其构造函数是:
new THREE.CircleGeometry(radius, segments, thetaStart, thetaLength)
  • 这里的参数跟绘制圆是一样的,我们再来熟悉一下。radius是半径;segments表示切片数;thetaStart表示纬度开始的弧度thetaLength表示纬度跨过的弧度
  • 看个例子: new THREE.CircleGeometry(3, 18, Math.PI / 3, Math.PI / 3 * 4)可以创建一个在x轴和y轴所在平面的三分之二圆的扇形:
var circle = new THREE.Mesh(    new THREE.CircleGeometry(2, 18, Math.PI / 3, Math.PI / 3 * 4), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(circle);
  • 效果图:

image

6.1.5 圆柱体

圆柱体(CylinderGeometry)的构造函数是:
new THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
  • 其中,radiusTopradiusBottom分别是顶面和底面的半径,由此可知,当这两个参数设置为不同的值时,实际上创建的是一个圆台;height是圆柱体的高度radiusSegmentsheightSegments可类比球体中的分段,一个表示底面、顶面的分段,另一个表示环面的分段;openEnded是一个布尔值,表示是否没有顶面和底面,缺省值为false,表示有顶面和底面。

标准圆柱体

  • new THREE.CylinderGeometry(1.5, 1.5, 3, 18, 3)创建一个顶面与底面半径都为2,高度为4的圆柱体:
var cylinder = new THREE.Mesh(    new THREE.CylinderGeometry(1.5, 1.5, 3, 18, 3), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(cylinder);
  • 效果图:

image

圆台

  • 顶面、底面半径不一致的时候,即是一个圆台。将底面半径设为2创建一个圆台:new THREE.CylinderGeometry(1.5, 2, 3, 18, 3)
var cylinder = new THREE.Mesh(    new THREE.CylinderGeometry(1.5, 2, 3, 18, 3), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(cylinder);
  • 效果图:

image

无底面、顶面

  • openEndedtrue的时候,将无底面、顶面。new THREE.CylinderGeometry(1.5, 1.5, 3, 18, 3, true)将创建一个没有顶面与底面的圆柱:
var cylinder = new THREE.Mesh(    new THREE.CylinderGeometry(1.5, 1.5, 3, 18, 3, true), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(cylinder);
  • 效果图:

image

6.1.6 正四面体、正八面体、正二十面体

正四面体(TetrahedronGeometry)、正八面体(OctahedronGeometry)、正二十面体(IcosahedronGeometry)的构造函数较为类似,分别为:
// 正四面体new THREE.TetrahedronGeometry(radius, detail)// 正八面体new THREE.OctahedronGeometry(radius, detail)// 正二十面体new THREE.IcosahedronGeometry(radius, detail)
  • 其中,radius是半径;detail是细节层次(Level of Detail)的层数,对于大面片数模型,可以控制在视角靠近物体时,显示面片数多的精细模型,而在离物体较远时,显示面片数较少的粗略模型。这里我们不对detail多作展开,一般可以对这个值缺省。

正四面体

  • new THREE.TetrahedronGeometry(2.5)创建一个半径2.5的正四面体:
var tetrahedron = new THREE.Mesh(    new THREE.TetrahedronGeometry(2.5), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(tetrahedron);
  • 效果图:

正八面体

  • new THREE.OctahedronGeometry(2.5)创建一个半径为2.5的正八面体:
var octahedron = new THREE.Mesh(    new THREE.OctahedronGeometry(2.5), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(octahedron);
  • 效果图:

正二十面体

  • new THREE.IcosahedronGeometry(2.5)创建一个半径为2.5的正二十面体:
var icosahedron = new THREE.Mesh(    new THREE.IcosahedronGeometry(2.5), 
    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(icosahedron);
  • 效果图:

6.1.7 圆环面

圆环面(TorusGeometry)就是甜甜圈的形状,其构造函数是:
new THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc)

  • 其中,radius是圆环半径;tube是管道半径;radialSegmentstubularSegments分别是两个分段数,详见上图;arc是圆环面的弧度,缺省值为Math.PI * 2

粗糙圆环面

  • new THREE.TorusGeometry(2, 0.7, 4, 8)创建一个粗糙的圆环面:
var torus = new THREE.Mesh(    new THREE.TorusGeometry(2, 0.7, 4, 8),    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(torus);
  • 效果图:

精细圆环面

  • new THREE.TorusGeometry(2, 0.7, 12, 18)创建一个较为精细的圆环面:
var torus = new THREE.Mesh(    new THREE.TorusGeometry(2, 0.7, 12, 18),    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(torus);
  • 效果图:

部分圆环面

  • new THREE.TorusGeometry(2, 0.7, 4, 8, Math.PI / 3 * 2)创建部分圆环面:
var torus = new THREE.Mesh(    new THREE.TorusGeometry(2, 0.7, 4, 8, Math.PI / 3 * 2),    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(torus);
  • 效果图:

image

6.1.8 圆环结

如果说圆环面是甜甜圈,那么圆环结(TorusKnotGeometry)就是打了结的甜甜圈,其构造参数为:
new THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments, p, q, heightScale)
  • 前四个参数在圆环面中已经有所介绍,pq是控制其样式的参数,一般可以缺省,如果需要详细了解,请学习圆环结的相关知识heightScale是在z轴方向上的缩放。
  • new THREE.TorusKnotGeometry(2, 0.5, 32, 8) 默认样式的圆环结:
var torus = new THREE.Mesh(    new THREE.TorusKnotGeometry(1.6, 0.4, 32, 8),    new THREE.MeshBasicMaterial({        color: 0xffff00,        wireframe: true
    })
);
scene.add(torus);
  • 效果图:

image

6.2 文字形状

文字形状(TextGeometry)可以用来创建三维的文字形状。

6.2.1 下载使用

使用文字前,需要下载和引用额外的字体库。字体库在three.js Github master/examples/fonts目录下,下载里面的json文件,放在你的目录下,然后加载。

image

  • 这里,我们就以helvetiker字体为例。我们在刚刚的字体库目录下,下载helvetiker_regular.typeface.json文件放在你的目录下,然后用以下方法加载:
// 调用一个字体加载函数var loader = new THREE.FontLoader();
loader.load('helvetiker_regular.typeface.json', function(font) {    var mesh = new THREE.Mesh(        new THREE.TextGeometry('Hello', {            font: font,            size: 1,            height: 1
        }), 
        new THREE.MeshBasicMaterial({            color: 0xffff00,            wireframe: true
        })
    );
    scene.add(mesh);    // 写在loader函数里面 否则不显示
    renderer.render(scene,camera);
});
  • 注意:

    • 之前用的73dev版本的three.js,执行代码的时候发现报错,可能是还没有添加这个功能,所以建议去下载最新版本的three.js
    • json配置文件,需要在本地服务器打开,推荐使用webstorm编辑器,因为它打开html文件时,就是以本地服务器的方式打开的。或者在cmd命令行中输入live-server,但需要配置,具体方法请点这里

6.2.2 参数介绍

创建文字形状的流程和之前介绍的基本几何形状是类似的,其构造函数是:
new THREE.TextGeometry(text, parameters)
  • 其中,text是要显示的文字字符串,parameters是以下参数组成的对象:

    • size:字号大小,一般为大写字母的高度
    • height:文字的厚度
    • curveSegments:弧线分段数,使得文字的曲线更加光滑
    • font:字体,默认是'helvetiker',需对应引用的字体文件
    • weight:值为'normal''bold',表示是否加粗
    • style:值为'normal''italics',表示是否斜体
    • bevelEnabled:布尔值,是否使用倒角,意为在边缘处斜切
    • bevelThickness:倒角厚度
    • bevelSize:倒角宽度

6.2.3 示例代码

创建一个三维文字new THREE.TextGeometry("hello", {size: 1, height: 1})
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>hello</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init()">
    <script>
        function init() {            // 调用一个渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 调用场景
            var scene = new THREE.Scene();            // 调用相机
            var camera = new THREE.OrthographicCamera(-4, 4, 3, -3, 0.1, 100);
            camera.position.set(5, 5, 20);
            camera.lookAt(new THREE.Vector3(1.1, 0, 0));
            scene.add(camera);            // 定义材质
            var material = new THREE.MeshBasicMaterial({                color: 0xffff00,                wireframe: true
            });            // 加载文字模板
            var loader = new THREE.FontLoader();
            loader.load('helvetiker_regular.typeface.json', function(font) {                // 中文字符不能解析
                var mesh = new THREE.Mesh(new THREE.TextGeometry('hello', {                    font: font,                    size: 1,                    height: 1
                }), material);
                scene.add(mesh);                // 渲染一定要在load函数里面
                renderer.render(scene, camera);
            })
        }    </script></body></html>
  • 效果图:

  • 我们可以改变材质和添加光照来改变显示效果(灯光、材质不必深究,后面会细讲)
// 将材质改为lambert材质var material = new THREE.MeshLambertMaterial({    color: 0xffff00});// 加上一束方向光var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 0, 0.5);
scene.add(light);
  • 效果图:

  • 这里只是给大家看了一个效果,具体材质、灯光的原理不要去深究,直接跳过,看下面的知识点。

6.3 自定义形状

对于Three.js没有提供的形状,可以通过自定义形状来创建。
  • 由于自定义形状需要手动指定每个顶点位置,以及顶点连接情况,如果该形状非常复杂,程序员计算量就会比较大。这种情况,建议使用建模工具,创建好之后,再通过three.js导入到场景中,这样会十分高效、方便。
  • 自定义形状使用的是Geometry类,它是其他如CubeGeometry、SphereGeometry等几何形状的父类,其构造函数是:
new THREE.Geometry()
  • 我们以创建一个梯台为例,首先,初始化一个几何形状,然后设置顶点位置以及顶点连接情况。

    • 顶面创建4个点,底面创建4个点,按照顺时针的顺序逐个创建
    • geometry创建点的时候都是push到数组vertices里面的
    • 所以这8个点,按照顺序都有一个对应的索引值
    • 利用Face3的方法将3点连成一个三角面

  • 看代码
// 初始化几何形状var geometry = new THREE.Geometry();// 设置顶点的位置 // 顶部4个点geometry.vertices.push(new THREE.Vector3(-1, 2, -1));
geometry.vertices.push(new THREE.Vector3(1, 2, -1));
geometry.vertices.push(new THREE.Vector3(1, 2, 1));
geometry.vertices.push(new THREE.Vector3(-1, 2, 1));// 底部4顶点geometry.vertices.push(new THREE.Vector3(-2, 0, -2));
geometry.vertices.push(new THREE.Vector3(2, 0, -2));
geometry.vertices.push(new THREE.Vector3(2, 0, 2));
geometry.vertices.push(new THREE.Vector3(-2, 0, 2));// 设置顶点连接情况// 顶面geometry.faces.push(new THREE.Face3(0, 1, 3));
geometry.faces.push(new THREE.Face3(1, 2, 3));// 底面geometry.faces.push(new THREE.Face3(4, 5, 6));
geometry.faces.push(new THREE.Face3(5, 6, 7));// 四个侧面geometry.faces.push(new THREE.Face3(1, 5, 6));
geometry.faces.push(new THREE.Face3(6, 2, 1));
geometry.faces.push(new THREE.Face3(2, 6, 7));
geometry.faces.push(new THREE.Face3(7, 3, 2));
geometry.faces.push(new THREE.Face3(3, 7, 0));
geometry.faces.push(new THREE.Face3(7, 4, 0));
geometry.faces.push(new THREE.Face3(0, 4, 5));
geometry.faces.push(new THREE.Face3(0, 5, 1));
  • 效果图:

image

  • 总结:

    • 需要注意的是,new THREE.Vector3(-1, 2, -1)创建一个矢量,作为顶点位置追加到geometry.vertices数组中。
    • 而由new THREE.Face3(0, 1, 3)创建一个三个顶点组成的面片,追加到geometry.faces数组中。三个参数分别是四个顶点在geometry.vertices中的序号。

7. 材质

材质(material),是独立于物体顶点信息之外的与渲染效果相关的属性。通过设置材质可以改变物体颜色、纹理贴图、光照模式等。
  • 下面将会为大家介绍基本材质两种基于光照模型材质法向量作为材质、 图像作为材质

7.1 基本材质

使用基本材质(BasicMaterial)的物体,渲染后物体的颜色,始终为该材质的颜色,不会由于光照产生明暗、阴影效果。如果没有指定材质的颜色,则颜色是随机的,构造函数如下:
new THREE.MeshBasicMaterial(opt)
  • 其中参数opt可以缺省,或者为包含各属性的值。如,为一个黄色正方体添加一个1不透明度 (opacity)
new THREE.MeshBasicMaterial({    color: 0xffff00,    opacity: 0.75});
  • 示例代码:
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>基本材质</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            
            // 相机
            var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 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, 100);
            light.position.set(10, 15, 5);
            scene.add(light);            
            // 材质
            var material = new THREE.MeshBasicMaterial({                color: 0xffff00,                opacity: 0.75
            });            // 几何体
            var cube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), material);
            scene.add(cube);            
            // 渲染
            renderer.render(scene, camera);
        }    </script></body></html>
  • 效果图:

  • 下面,介绍几个常用的属性

    • visible:是否可见,默认为true
    • side:渲染面片正面或是反面,默认为正面THREE.FrontSide,可设置为反面THREE.BackSide,或双面THREE.DoubleSide
    • wireframe:是否渲染线而非面,默认为false
    • color:十六进制RGB颜色,如红色表示为0xff0000
    • map:使用纹理贴图(下面会着重讲解)
  • 对于基本材质,即使改变场景中的光源,使用该材质的物体也始终为颜色处处相同的效果。当然,这不是很具有真实感,因此,接下来我们将介绍更为真实的光照模型:Lambert光照模型以及Phong光照模型。

7.2 Lambert 材质

Lambert材质(MeshLambertMaterial)是符合Lambert光照模型的材质。Lambert光照模型的主要特点是只考虑漫反射而不考虑镜面反射的效果,因而对于金属、镜子等需要镜面反射效果的物体就不适应,对于其他大部分物体的漫反射效果都是适用的。
  • 它的光照模型的公式为:
Idiffuse = Kd * Id * cos(theta)
  • 其中,Idiffuse是漫反射光强,Kd是物体表面的漫反射属性,Id是光强,theta是光的入射角弧度。
  • 当然,对于使用Three.js的Lambert材质,不需要了解以上公式就可以直接使用。创建Lambert材质的构造函数如下:
new THREE.MeshLambertMaterial()
  • 示例代码(创建一个黄色并使用光照的立方体):

    • 光照这里不细讲,后面会着重讲解,这里大家只需要知道是干什么用的就行
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>Lambert材质</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            
            // 相机
            var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 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, 100);
            light.position.set(10, 15, 5);
            scene.add(light);            // Lambert材质
            var material = new THREE.MeshLambertMaterial({                color: 0xffff00,
            });            
            // 几何体
            var cube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), material);
            scene.add(cube);            
            // 渲染
            renderer.render(scene, camera);
        }    </script></body></html>
  • 效果图:

  • 下面,介绍几个常用的属性:

    • color是用来表现材质对散射光的反射能力,也是最常用来设置材质颜色的属性。除此之外,还可以用ambientemissive控制材质的颜色。
    • ambient表示对环境光反射能力,只有当设置了AmbientLight后,该值才是有效的,材质对环境光的反射能力与环境光强相乘后得到材质实际表现的颜色。
    • emissive是材质的自发光颜色,可以用来表现光源的颜色。
  • 单独使用红色自发光:
var material = new THREE.MeshLambertMaterial({    emissive: 0xff0000})
  • 效果图:

  • 如果同时使用红色的自发光与黄色的散射光:
var material = new THREE.MeshLambertMaterial({    color: 0xffff00,    emissive: 0xff0000})
  • 效果图:

  • 这样就会出现一个渐变色的效果,我们可以新建一个球体:
var material = new THREE.MeshLambertMaterial({    color: 0xffff00,    emissive: 0xff0000});var sphere = new THREE.Mesh(new THREE.SphereGeometry(1.6, 40, 16), material);
scene.add(sphere);
  • 效果图:

7.3 Phong材质

Phong材质(MeshPhongMaterial)是符合Phong光照模型的材质。和Lambert不同的是,Phong模型考虑了镜面反射的效果,因此对于金属、镜面的表现尤为适合。
  • 漫反射部分和Lambert光照模型是相同的,镜面反射模型的公式为:
Ispecular = Ks * Is * (cos(alpha)) ^ n
  • 其中,Ispecular是镜面反射的光强Ks是材质表面镜面反射系数Is是光源强度alpha是反射光与视线的夹角n高光指数,越大则高光光斑越小。
  • 由于漫反射部分与Lambert模型是一致的,因此,如果不指定镜面反射系数,而只设定漫反射,其效果与Lambert是相同的:
new THREE.MeshPhongMaterial({    color: 0xffff00});
  • 完整代码:
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>Phong材质</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            
            // 相机
            var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 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, 200);
            light.position.set(10, 15, 25);
            scene.add(light);            
            // 材质
            var material = new THREE.MeshPhongMaterial({                color: 0xffff00,                //specular: 0xffff00,
                //shininess: 1000
            });            // 几何体
            var cube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), material);
            scene.add(cube);            //var sphere = new THREE.Mesh(new THREE.SphereGeometry(1.6, 40, 16), material);
            //scene.add(sphere);
            
            // 渲染
            renderer.render(scene, camera);
        }    </script></body></html>
  • 效果图:

  • 下面,介绍几个常用的属性:

    • 同样地,可以指定emissiveambient值,这里不再说明。
    • 下面就specular值指定镜面反射系数作说明。
  • 首先,我们只使用镜面反射(specular),将高光设为红色,应用于一个球体:
var material = new THREE.MeshPhongMaterial({    specular: 0xff0000});var sphere = new THREE.Mesh(new THREE.SphereGeometry(1.6, 40, 16), material);
scene.add(sphere);
  • 效果图:

  • 可以通过shininess属性控制光照模型中的n值*(高光指数,光斑),当shininess值越大时,高光的光斑越小,默认值为30。我们将其设置为1000时:
var material = new THREE.MeshPhongMaterial({    specular: 0xff0000,    shininess: 1000});
  • 效果图:

  • 使用黄色的镜面光,红色的散射光:
material = new THREE.MeshPhongMaterial({    color: 0xff0000,    specular: 0xffff00,    shininess: 1000});
  • 效果图:

7.4 法向材质

法向材质可以将材质的颜色设置为其法向量的方向,有时候对于调试很有帮助。
  • 法向材质的设定很简单,不需要设置参数。构造函数如下:
new THREE.MeshNormalMaterial()

材质的颜色与照相机与该物体的角度相关,下面我们只改变照相机位置,观察两个角度的颜色变化:

  • 示例代码:
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>法向材质</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            // 相机
            var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 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, 200);
            light.position.set(10, 15, 25);
            scene.add(light);            // 材质
            var material = new THREE.MeshNormalMaterial();            // 几何体
            var cube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), material);
            scene.add(cube);            // 渲染
            renderer.render(scene, camera);
        }    </script></body></html>
  • camera.position.set(5, 25, 25);的效果图:

  • camera.position.set(25, 25, 25);的效果图:

  • 我们观察的是同样的三个面,但是由于观察的角度不同,物体的颜色就不同了。因此,在调试时,要知道物体的法向量,使用法向材质就很有效。

7.5 材质的纹理贴图

在此之前,我们使用的材质都是单一颜色的,有时候,我们却希望使用图像作为材质。这时候,就需要导入图像作为纹理贴图,并添加到相应的材质中。

7.5.1 单张图像应用于长方体

  • 首先,我们需要选择一张长宽均为128像素的图像:

  • 将其导入到纹理texture中:
var texture = THREE.ImageUtils.loadTexture('images/01.jpg');
  • 然后,将材质的map属性设置为texture
var material = new THREE.MeshLambertMaterial({    map: texture
});
  • 这样就完成了将图片应用于材质的基本步骤。但是由于现在我们还没使用动画,画面只被渲染了一次,而在导入纹理之前,已经完成了这次渲染,因此看到的只是一片黑。所以,如果没有重绘函数(将在下一章介绍),就需要在完成导入纹理的步骤后,重新绘制画面,这是在回调函数中实现的:
var texture = THREE.ImageUtils.loadTexture('images/01.jpg', {}, function() {
    renderer.render(scene, camera);
});var material = new THREE.MeshLambertMaterial({    map: texture
});
  • 注意:需要在本地服务器运行。
  • 完整代码:
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>纹理贴图</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            // 相机
            var camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 1000);
            camera.position.set(25, 25, 25);
            camera.lookAt(new THREE.Vector3(0, 0, 0));
            scene.add(camera);            // 光照
            var light = new THREE.PointLight(0xffffff, 1, 200);
            light.position.set(10, 15, 25);
            scene.add(light);            // 纹理(需要重绘函数)
            var texture = THREE.ImageUtils.loadTexture('images/01.jpg', {}, function() {
                renderer.render(scene, camera);
            });            // 材质
            var material = new THREE.MeshLambertMaterial({                map: texture
            });            // 几何体
            var cube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), material);
            scene.add(cube);            // var sphere = new THREE.Mesh(new THREE.SphereGeometry(1.6, 40, 16), material);
            // scene.add(sphere);

            // 渲染
            renderer.render(scene, camera);
        }    </script></body></html>
  • 现在,就能看到这样的效果了:

  • 类似地,如果将其应用于球体,将会把整个球体应用该图像:

7.5.2 六张图像应用于长方体

有时候,我们希望长方体的六面各种的贴图都不同。因此,我们首先准备了六张颜色各异的图像,分别写了数字01到06。然后,分别导入图像到六个纹理,并设置到六个材质中:
var materials = [];for (var i = 1; i < 7; ++i) {
    materials.push(new THREE.MeshBasicMaterial({        map: THREE.ImageUtils.loadTexture('images/0' + i + '.jpg', {}, function() {
            renderer.render(scene, camera);
        }),        overdraw: true
    }));
}var cube = new THREE.Mesh(    new THREE.CubeGeometry(2, 2, 2),    new THREE.MeshFaceMaterial(materials));
scene.add(cube);
  • 效果为:

7.5.3 棋盘

用黑白相间的图片绘制一副棋盘

image

  • 实现代码:
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>棋盘</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0xffffff);            // 场景
            var scene = new THREE.Scene();            // 相机
            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);            // 光照
            var light = new THREE.PointLight(0xffffff, 1, 1000);
            light.position.set(10, 15, 25);
            scene.add(light);            var texture = THREE.ImageUtils.loadTexture('images/chess.png', {}, function() {
                renderer.render(scene, camera);
            });            // texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
            // texture.repeat.set(4, 4);
            var material = new THREE.MeshLambertMaterial({                map: texture
            });            
            // 平面
            var plane = new THREE.Mesh(new THREE.PlaneGeometry(12, 12), material);
            scene.add(plane);            // 渲染
            renderer.render(scene, camera);
        }    </script></body></html>
  • 效果图:

可是,棋盘格是8横8纵64个小方格组成的,那应该怎么办呢?

  • 首先,我们需要指定重复方式为两个方向(wrapSwrapT)都重复:
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
  • 然后,设置两个方向上都重复4次,由于我们的图像本来是有2行2列,所以重复4次即为8行8列:
texture.repeat.set(44);
  • 效果图:

image

8. 网格

在学习了几何形状和材质之后,我们就能使用他们来创建物体了。最常用的一种物体就是网格(Mesh),网格是由顶点、边、面等组成的物体;其他物体包括线段(Line)、骨骼(Bone)、粒子系统(ParticleSystem)等。创建物体需要指定几何形状和材质,其中,几何形状决定了物体的顶点位置等信息,材质决定了物体的颜色、纹理等信息。
  • 本章将介绍创建较为常用的物体:网格,然后介绍如何修改物体的属性。

8.1 创建网格

在上两节中,我们学习了如何创建几何形状与材质,而网格的创建非常简单,只要把几何形状与材质传入其构造函数。最常用的物体是网格(Mesh),它代表包含点、线、面的几何体,其构造函数是:
// geometry : 定义的几何体// material : 材质new THREE.Mesh(geometry,material)
  • 下面,让我们通过一个具体的例子了解如何创建网格:
// 几何体(长方体)var geometry = new THREE.CubeGeometry(0.6, 1.2, 1.8);// 材质var material = new THREE.MeshLambertMaterial({    color: 0xffff00});// 网格var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
  • 如果materialgeometry之后不会复用的话,也可以合在一起写为:
var mesh = new THREE.Mesh(new THREE.CubeGeometry(0.6, 1.2, 1.8), 
    new THREE.MeshLambertMaterial({        color: 0xffff00
    })
);
scene.add(mesh);
  • 完整代码:
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>网格</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            // 相机
            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(0, 0, 0));
            scene.add(camera);            // 光照 
            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(20, 10, 5);
            scene.add(light);            // 材质
            var material = new THREE.MeshLambertMaterial({                color: 0xffff00
            });            // 几何体
            var geometry = new THREE.CubeGeometry(0.6, 1.2, 1.8);            // 网格
            var mesh = new THREE.Mesh(geometry, material);
            scene.add(mesh);            // render
            renderer.render(scene, camera);
        }    </script></body></html>
  • 效果图:

8.2 修改属性

8.2.1 材质

除了在构造函数中指定材质,在网格被创建后,也能对材质进行修改:
  • 示例代码:
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});
  • 最终会显示红色,原因很简单,在js语言预解析中,下面材质重新赋值后,就覆盖了上面定义的材质。
  • 效果图

image

8.2.2 位置、缩放、旋转

位置、缩放、旋转是物体三个常用属性。由于THREE.Mesh基础自THREE.Object3D,因此包含scale、rotation、position三个属性。它们都是THREE.Vector3实例,因此修改其值的方法是相同的,这里以位置为例。
  • THREE.Vector3x、y、z三个属性,如果只设置其中一个属性,则可以用以下方法:
mesh.position.z = 1;
  • 如果需要同时设置多个属性,可以使用以下两种方法:
mesh.position.set(1.5, -0.50);
mesh.position = new THREE.Vector3(1.5, -0.50);
  • 示例代码(修改位置):
<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>修改位置</title>
    <script type="text/javascript" src="js/three.js"></script></head><body onload="init();">
    <script>
        function init() {            // 渲染器
            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(800, 600);            document.body.appendChild(renderer.domElement);
            renderer.setClearColor(0x000000);            // 场景
            var scene = new THREE.Scene();            // 相机
            var camera = new THREE.OrthographicCamera(-2.5, 2.5, 1.875, -1.875, 0.1, 100);
            camera.position.set(5, 5, 10);
            camera.lookAt(new THREE.Vector3(0, 0, 0));
            scene.add(camera);            var material = new THREE.MeshLambertMaterial({                color: 0xffff00
            });            var geometry = new THREE.CubeGeometry(0.6, 1.2, 1.8);            var mesh = new THREE.Mesh(geometry, material);
            scene.add(mesh);            // 修改位置属性
            mesh.position.set(1, 0, 0);            // mesh.position = new THREE.Vector3(1.5, -0.5, 0);
            // mesh.position.x = 1;

            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(20, 10, 5);
            scene.add(light);            // 坐标轴
            drawAxes(scene);            // 渲染
            renderer.render(scene, camera);            function drawAxes(scene) {                // x-axis
                var xGeo = new THREE.Geometry();
                xGeo.vertices.push(new THREE.Vector3(0, 0, 0));
                xGeo.vertices.push(new THREE.Vector3(3, 0, 0));                var xMat = new THREE.LineBasicMaterial({                    color: 0xff0000
                });                var xAxis = new THREE.Line(xGeo, xMat);
                scene.add(xAxis);                // y-axis
                var yGeo = new THREE.Geometry();
                yGeo.vertices.push(new THREE.Vector3(0, 0, 0));
                yGeo.vertices.push(new THREE.Vector3(0, 3, 0));                var yMat = new THREE.LineBasicMaterial({                    color: 0x00ff00
                });                var yAxis = new THREE.Line(yGeo, yMat);
                scene.add(yAxis);                // z-axis
                var zGeo = new THREE.Geometry();
                zGeo.vertices.push(new THREE.Vector3(0, 0, 0));
                zGeo.vertices.push(new THREE.Vector3(0, 0, 3));                var zMat = new THREE.LineBasicMaterial({                    color: 0x00ccff
                });                var zAxis = new THREE.Line(zGeo, zMat);
                scene.add(zAxis);
            }
        }    </script></body></html>
  • 效果图

image

  • 缩放对应的属性是scale旋转对应的属性是rotation,具体方法与上例相同,分别表示沿x、y、z三轴缩放或旋转。
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post