摘要:一般说来,对于制图建模软通常使正交投影,这样不会因为投影而改变物体比例而对于其他大多数应用,通常使用透视投影,因为这更接近人眼的观察效果。
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 下载地址three.js 下载地址
1.3.2 目录结构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之前,首先在部分,需要引入外部文件Three.js。
WebGL 的渲染是需要HTML5 中的Canvas元素的,你可以手动在HTML的部分中使用canvas标签,或者让Three.js帮你生成。这两种选择,一般没有多大差别。我们先手动定义一个canvas标签:
在js里面定义一个函数,将所有执行的代码放在函数里,在html加载完成后,执行该函数
function init{ // 所有需要执行的代码 }
一个典型的Three.js程序,至少应该包括四个部分:渲染器(renderer)、场景(scene)、相机(camera)、以及场景中创建的物体。
2.1 渲染器(renderer)渲染器决定了渲染的结果应该画在页面的什么元素上面,并且以怎样的方式来绘制。渲染器将会和canvas元素进行绑定,如果之前标签中,定义了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轴的方向就是屏幕由里往外的方向
这里我们定义一个透视相机(相机也需要添加到场景中):
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 完整代码
长方体
效果图
canvas元素的默认宽高为300/150
下面介绍下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))。
一般说来,对于制图、建模软通常使正交投影,这样不会因为投影而改变物体比例;而对于其他大多数应用,通常使用 透视投影,因为这更接近人眼的观察效果。当然,照相机的选择并没有对错之分,你可以更具应用的特性,选择一个效果更佳的照相机。
4.3 正交投影照相机 4.3.1 参数介绍正交投影照相机(Orthographic Camera)
THREE.OrthographicCamera(left, right, top, bottom, near, far)
这六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,这六个面围成一个长方体,我们称其视景体(Frustum)。只有在视景体内部(下图中的灰色部分)的物体才可能显示在屏幕上,而视景体外的物体会在显示之前被裁减掉。
为了保持照相机的横竖比例,需要保证(right - left)与(top - bottom)的比例与Canvas宽度与高度的比例(800/600)一致。
// [2-(-2)] / [1.5-(-1.5)] = canvas.width/canvas.height var 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(-1, 1, 1.5, -1.5, 1, 10);
效果图(此时水平方向的距离就被拉长了):
照相机位置
接下来,我们来看看照相机位置对渲染结果的影响。在之前的例子中,我们将照相机设置在(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);
效果图(与之前相机向右的效果是一样的)
换个角度
到目前为止,我们使用照相机,都是沿着Z轴负方向观察的,因此看到的都是一个正方形,现在我们尝试一下仰望这个正方体,改变照相机的位置:
// x轴:4; y轴:-3; z轴:5 camera.position.set(4, -3, 5);
照相机默认是沿着z轴的负方向观察的,因此观察不到正方体,只看到一片黑。我们可以通过lookAt函数指定它看着原点方向:
camera.lookAt(new THREE.Vector3(0, 0, 0));
效果图:
注意:lookAt函数接收的是一个THREE.Vector3的实例千万不能写成camera.lookAt(0,0,0)。
4.4 透视投影照相机 4.4.1 参数介绍透视投影照相机(Perspective Camera)
THREE.PerspectiveCamera(fov, aspect, near, far)
让我们通过一张透视照相机投影的图来了解这些参数。
透视图中,灰色的部分是视景体,是可能被渲染的物体所在的区域。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);
效果图:
对比正交透视照相机下正方形的效果,透视投影可以看到全部的12条边,而且有近大远小的效果,这也就是与正交投影的区别。
竖直张角
接下来,我们来看下fov的改变对渲染效果的影响。我们将原来的45改为60:
var camera = new THREE.PerspectiveCamera(60, 800 / 600, 1, 10); camera.position.set(0, 0, 5); scene.add(camera);
效果图:
为什么正方体显得更小了呢?我们从下面的侧视图来看,虽然正方体的实际大小并未改变,但是将照相机的竖直张角设置得更大时,视景体变大了,因而正方体相对于整个视景体的大小就变小了,看起来正方形就显得变小了。
注意,改变fov并不会引起画面横竖比例的变化,而改变aspect则会改变横竖比例。
5. 点、线、面 5.1 3D世界的组成在计算机世界里,3D世界由点组成,两个点能组成一条直线,三个不在一条直线上的点,就能组成一个三角面,无数的三角面就能组成各种各样的物体,如下图:
我们通常把这种网络模型叫做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、整体代码:
效果图:
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、完整代码
效果图:
6. 几何形状在创建物体时,需要传入两个参数,一个是几何形状(Geometry),另一个是材质(Material),这一章将着重介绍几何形状的创建,第6章介绍材质,第7章介绍如何使用两者创建网格。
几何形状(Geometry)最主要的功能是储存了一个物体的顶点信息。WebGL需要程序员指定每个顶点的位置,而在Three.js中,可以通过指定一些特征来创建几何形状,例如使用半径创建一个球体,从而省去程序员一个个指定顶点的工作量。
本章节将分别介绍立方体、平面、球体、圆柱体、四面体、八面体等几何形状,以及以三维文字作为几何形状的方法。本节还会介绍通过手动定义 顶点位置和面片信息组成几何形状。
6.1 基本几何形状 6.1.1 立方体虽然这形状的名字叫做立方体(CubeGeometry),但其实是长方体,也就是长宽高可以设置不同的值:
new THREE.CubeGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)
这里,width是x方向上的长度;height是y方向上的长度;depth是z方向上的长度;后三个参数分别是在三个方向上的分段数,如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);
在设置材质,并添加到场景之后具体的效果是:
物体的默认位置是原点,对于立方体而言,是其几何中心在原点的位置。
分段
根据THREE.CubeGeometry(width, height, depth, widthSegments, heightSegments, depthSegments),的后三个参数,为这个长方体分段:
// x轴分两段 y轴分两段 z轴分三段 new THREE.CubeGeometry(1, 2, 3, 2, 2, 3)
效果图:
注意这个分段是对六个面进行分段,而不是对立方体的体素分段,因此在立方体的中间是不分段的,只有六个侧面被分段。
6.1.2 平面这里的平面(PlaneGeometry)其实是一个长方形,而并非是数学意义上无限大的平面:
new THREE.PlaneGeometry(width, height, widthSegments, heightSegments)
其中,width是x方向上的长度;height是y方向上的长度;后两个参数同样表示分段。
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);
效果图:
如果需要创建的平面在x轴和z轴所在的平面内,可以通过物体的旋转来实现,具体的做法将在下面章节介绍到。
6.1.3 球体球体(SphereGeometry)的构造函数是:
new THREE.SphereGeometry(radius, segmentsWidth, segmentsHeight, phiStart, phiLength, thetaStart, thetaLength)
其中,radius是半径;segmentsWidth表示经度上的切片数;segmentsHeight表示纬度上的切片数;phiStart表示经度开始的弧度;phiLength表示经度跨过的弧度;thetaStart表示纬度开始的弧度;thetaLength表示纬度跨过的弧度。
分段
首先,我们来理解下segmentsWidth和segmentsHeight。使用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);
效果图:
new THREE.SphereGeometry(2, 8, 16)的效果如图:
new THREE.SphereGeometry(3, 18, 12)的效果如图:
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);
效果图:
值得注意的是,这里的SegmentsWidth为8意味着对于经度从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);
效果图:
我们再来看一个经度纬度都改变了起始位置和跨度的例子: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);
效果图:
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);
效果图:
6.1.5 圆柱体圆柱体(CylinderGeometry)的构造函数是:
new THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
其中,radiusTop与radiusBottom分别是顶面和底面的半径,由此可知,当这两个参数设置为不同的值时,实际上创建的是一个圆台;height是圆柱体的高度;radiusSegments与heightSegments可类比球体中的分段,一个表示底面、顶面的分段,另一个表示环面的分段;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);
效果图:
圆台
顶面、底面半径不一致的时候,即是一个圆台。将底面半径设为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);
效果图:
无底面、顶面
openEnded为true的时候,将无底面、顶面。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);
效果图:
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是管道半径;radialSegments与tubularSegments分别是两个分段数,详见上图;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);
效果图:
6.1.8 圆环结如果说圆环面是甜甜圈,那么圆环结(TorusKnotGeometry)就是打了结的甜甜圈,其构造参数为:
new THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments, p, q, heightScale)
前四个参数在圆环面中已经有所介绍,p和q是控制其样式的参数,一般可以缺省,如果需要详细了解,请学习圆环结的相关知识;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);
效果图:
6.2 文字形状文字形状(TextGeometry)可以用来创建三维的文字形状。6.2.1 下载使用
使用文字前,需要下载和引用额外的字体库。字体库在three.js Github master/examples/fonts目录下,下载里面的json文件,放在你的目录下,然后加载。
这里,我们就以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})
hello
效果图:
我们可以改变材质和添加光照来改变显示效果(灯光、材质不必深究,后面会细讲)
// 将材质改为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));
效果图:
总结:
需要注意的是,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 });
示例代码:
基本材质