threejs
# 01. 简介
three.js 是使用javascript来写3D程序。 在浏览器端,WebGL 是一个底层的标准,在这些标准被定义后,Chrome、Firefox 之类的浏览器 实现了这些标准。然后,就能通过javascript 代码,在网页上实现 三维图形的渲染。 ThreeJS则是封装了底层了图形接口,更容易来实现3D程序。
# 组成
scene => (点point 线line 面mesh) 和light
3D Model组成是由:点(1),线(3) 面(3) 共七种
mesh是由 geometry(几何体) 和material(材质) 组成。
# 核心
一个 典型的three.js 程序 至少需要包括 渲染器(Renderer),场景(Scene),照相机(Camera),以及你在场景中创建的物体。
# 渲染器
渲染器将和canvas 元素 进行绑定,如果在HTML中手动定义了canvas元素,那么Renderer 可以这样写
var renderer = new THREE.WebGLRenderer({canvas: document.getElementById('#mainCanvas')});
如果想在Three.js生成Canvas元素,在HTML中就不需要定义canvas元素,在javascript代码中可以这样写
var renderer = new THREE.WebGLRenderer();
render.setSize(400, 400);
# 场景
在Three.js 中添加的物体 都是添加到场景中的,因此它相当于一个大容器。在程序最开始的时候进行实例化,然后将物体添加到场景中。
var scene = new THREE.Scene();
也就是说 ,场景是光源,相机和所有物体的父容器,通过:scene.children
可以访问到这些子物体。这些物体在创建的时候没有名字,可以通过name
属性指定名字,这样就可以通过:scene.getChildByMName(name)
来访问具体夫人子物体。
scene.traverse(funtion)
可以访问 该父场景中的所有子物体来执行回调函数。
# 相机
WebGL 和Three.js 使用的坐标系 是右手坐标系,即右手伸开,拇指为X,四指为Y,手心为Z。
相机就像是人的眼睛一样,人站在不同的位置,抬头或者低头都能够看到不同的景色。在Threejs中有多种相机,透视相机(THREE.PerspectiveCamera
)用的最多。
定义:
var camera = new THRRR.PerspectiveCamera(45, 4 / 3, 1, 1000);
注意: 相机也需要添加到场景中。
# 总结
Three.js 中的场景是一个物体的容器,开发者将需要的物体放入场景中。相机的作用就是指向场景,在场景中取一个合适的景,把它拍下来。 渲染器的作用就是 将相机拍摄下来的照片 放到浏览器显示。在定义了场景中的物体,设置好的照相机以后,渲染器就知道 如何让渲染出二维的结果。这个时候,只需要调用渲染器的渲染函数,就能使其渲染一次。
renderer.render(scene, camera);
# 照相机
根据投影方式的不同,照相机又分为正交投影相机 和透视投影相机。使用透视投影照相机 获得的结果 是类似人眼看到的 有 ‘近大远小’的效果,而使用正交投影照相机 得到的结果就像是平面画3D的效果,在三维空间内平行的线,投影到二维空间中也一定是平行的。
# 正交投影照相机
正交投影照相机的构造函数是:
THREE.OrthographicCamera(left, right, top, bottom, near, far)
这六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,其为视景体(Frustum)。只有在视景体内部的物体才可能显示在屏幕上,而视景体外的物体 会在显示之前 被裁减掉。 为了保持照相机的横竖比例,需要保持(right - left)和(top - bottom)的比例与Canvas 宽度和高度的比例一致。
var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 1000);
camera.position.set(0, 0, 5);
scene.add(camera);
其中,第二句是设定照相机的位置。照相机默认都是沿 z 轴负方向观察的,可以通过 lookAt 函数指定它看着其他方向:
camera.lookAt(new THREE.Vector3(0, 0, 0));
这样就改变照相机观察方向由当前位置指向原点。注意, lookAt 函数接受的是一个 THREE.Vector3 的实例。
# 透视投影相机
透视投影是更符合人眼视觉的投影,构造函数是:
PerspectiveCamera( fov, aspect, near, far )
fov: 为视角的大小。如果设置为0,相当你闭上眼睛了,所以什么也看不到,如果为180,那么可以认为你的视界很广阔,但是在180度的时候,往往物体很小,因为他在你的整个可视区域中的比例变小了。
aspect: 为实际窗口的纵横比,即宽度除以高度。通常设为Canvas 的横纵比例。
near: 摄像机视锥体近端面
far: 摄像机视锥体远端面
# 形状
threejs封装了一些常见的几何形状,在使用时,就只需要定义threejs设定好需要的值即可,如果想要自定义形状,就需要手动创造顶点和面。
# 立方体
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的立方体。
物体的几何中心默认在原点的位置。若设置了分段,会对六个面 进行分段,而不是对立方体进行分段,因此在立方体的中间是不分段的,只有六个侧面被分段。
# 平面
THREE.PlanGeometry(width, height, widthSegments, heightSegments)
width 是x方向上的长度,height 是y方向的长度, 后两个参数 同样 表示分段数。
# 球体
THREE.SphereGeometry(radius, segmentsWidth, segmentsHeight, phiStart, phiLength, thetaStart, thetaLength)
- radius 是半径
- segmentsWidth 表示经度上的切片数
- segmentsHeight 表示 纬度上的切片数
- phiStart 表示 经度开始的弧度
- phiLength 表示 经度跨过的弧度;
- thetaStart 表示纬度开始的弧度
- thetaLength 表示纬度跨国的弧度。
使用
var sphere = new THREE.SphereGeometry(3, 8, 6)
可以创建一个 半径为3, 经度划分为8份,纬度划分为6份的球体。
segmentsWidth 相当于经度被切成了几瓣,而 segmentsHeight 相当于纬度被切成了几层。对于球 体而言,当这两个值较大的时候,形成的多面体就可以近似看做是球体了。
# 圆形
THREE.CircleGemetry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
其中, radiusTop 与radiusBottom 分别是顶面与底面的半径,由此可知,当这两个 参数设置为不同的值时,实际上创建的是一个圆台,height是圆柱体的高度,radiusSegments 与hegithSegments 可类比球体中的分段,openEnded是一个布尔值,表示是否咩有顶点和底面,默认为false,表示有顶面和底面。
# 圆环
THREE.TorusGemetry(radius, tube, radialSegments, tubularSegments, arc)
其中, radius 是圆环半径;tube 是管道半径; radialSegments 与 tubularSegments 分 别是两个分段数; arc 是圆环面的弧度,默认为 Math.PI * 2 。
# 材质
材质(Material)是与渲染相关的属性。通过设置材质可以改变物体的颜色、纹理贴图、光照模式等。材质的共有属性包括:
- 基础属性
id: 用来标识材质
name:赋予材质名称
opaticy:定义物体透明度,取值范围:0 ~ 1
side:设定在几何体的哪个面应用材质,默认值为THREE.FrontSide,即外面。也可以设置为THREE.BackSide(内面) 或者THREE.DoubleSide(双面)
# 基本材质
用基本材质(BasicMaterial) 的物体渲染后的颜色适中为该材质的颜色,不会由于光照 产生明暗,阴影的效果。如果没有指定材质的颜色,则颜色是随机的。
THREE.MeshBasicMaterial({color: 0xffff00, opacity: 0.75})
其中,opt 为包括各属性的值。如新建一个不透明的0.75的黄色材质
new THREE.MeshBasicMaterial({color: 0xffff00, opacity: 0.75})
常用的属性包括:
- visible:是否可见,默认为true
- side:渲染面片正面或是反面,默认为正面THREE.FrontSide, 可设置为反面THREE.BackSide, 或双面THREE.DoubleSide
- wireframe: 是否渲染线而非面,默认为false
- color:十六进制RGB颜色,如红色:0xffff00
- map:使用纹理贴图
# Lambert 材质
Lambert 光照模型的主要特点是只考虑漫反射而不考虑镜面反射的效果,因而对于金属,镜子等需要镜面反射效果的物体就不适应,对于其他物体等我的漫反射效果都是适用的。
new THREE.MeshLamberMaterial(opt)
color 是用来表现材质对散射光的反射能力,也是最常用来设置材质颜色的属性。除此之外,还可以用ambient和emissive 控制材质的颜色。
ambient 表示对环境光的反射能力,只有当设置了AmbientLight后,该值才是有效的,材质对环境光的反射能力与环境光强相乘后的到材质实际表现的颜色。
emissive是材质的自发光颜色,可以用来表现光源的颜色。
new THREE.MeshLamberMaterial({color: 0xfffff00, emissive: 0xff0000})
# Phong 材质
Phone 模型开绿了镜面反射的效果,因此对于金属,镜面反射的表现尤为合适。
new THREE.MeshPhoneMaterial(opt)
可以通过shininess 属性控制光照模型中的n值,当shininess 值越大时,高光的光斑越小,默认值为30
使用黄色的镜面光,红色的散射光
new THREE.MeshPhoneMaterial({color: 0xff0000, specular: 0xffff00, shininess: 100})
# Depth 材质
这种材质的特点在于,不控制物体的渲染效果,外光根据物体到相机的距离变化,一般与其他材质结合形成远处逐渐消失的效果。
# 联合材质
var depthMaterial = new THREE.MeshDepthMaterial;
var bacsicMaterial = new THREE.MeshBasicMaterial(opt);
var cube = new THREE.SceneUtils.createMaterialObject(cubeGeometry, [depthMaterial, basicMaterial]);
cube.children[1].scale.set(0.99, 0.99, 0.99);
对于要融合的材质,需要添加属性transparent: true 开启融合模式。 createMaterialObject() 创建网格时,几何体会被复制,返回一个网格组,内部的网格完全一样。渲染时 画面回闪烁。所以需要最后一行代码来缩小带有depth材质的网格,避免出现闪烁。
# 物体
使用几何形状和材质就能创建物体了。最常见的一种物体就是网格(Mesh),网格是由顶点,边,面等组成的物体,其他物体包括线段(Line),骨骼(Bone),粒子系统(ParticleSystem)等。 创建物体需要指定几何形状和材质,其中,几何形状决定了物体的顶点位置等信息,材质决定了物体的颜色,和纹理等信息。
# 创建网格
Mesh(geometry, material)
创建网格要把几何形状与材质传入其构造函数。
var material = new THREE.MeshLamberMaterial({color: oxffff00});
var geometry = new THREE.CubeGeometry(1, 2, 3);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
若不设置属性material, 则每次会随机分配一种wireframe 为true的材质,每次刷新页面后的颜色时不同的。除了在构造函数中指定材质,在网格被创建后,也能对材质进行修改。
# 几何变换
平移,缩放,旋转时物体三个常用的属性,即为translate,scale,rotate三个属性。
平移 mesh.translateX(100) // 沿着x轴正方向平移距离100 var axis = new THREE.Vector(0, 1, 0); // 创建一个三维向量,xyz分量分别为0, 1, 0 mesh.translateOnAxis(axis, 100); // 沿着向量axis 方向平移 100
缩放 mesh.scale.x = 2.0 // x轴方向放大2倍 mesh.scale.set(0.5, 0.5, 0.5) // 缩小为原来的0.5倍
# 加载几何模型
THREE.js 有一系列导入外部文件的辅助函数,是在three.js 之外的,使用前需要额外下载。 .obj 是最常用的模型格式,导入.obj 需要OBJLoader.js; 导入带.mtl材质的.obj 文件需要MTLLoader.js 以及 OBJMTLLoaderjs.
# 无材质模型
创建loader变量,用于导入模型:
var loader = new THREE.OBJLoader();
接收两个参数: 第一个:模型路径 第二个:表示完成导入后的回调函数,一般我们需要在这个回调函数中将导入的模型添加到场景中。
loader.load('../lib/port.obj', function(obj){mesh = obj; // 储存到全局变量中scene.add(obj)})
默认的情况下,只有正面的面片被绘制,模型中部分可能穿模。而如果需要双面绘制,需要这样设置:
var loader = new THREE.OBJLoader();
loader.load('../lib/port.obj', function(obj){
obj.traverse(function(child){
if (child instanceof THREE.Mesh) {
child.material.side = THREE.DoubleSide;
}
});
mesh = obj;
scene.add(obj)
})
# 光线
# 环境光
环境光是指 场景整体的光照效果,环境光没有明确的光源位置,在各处形成的亮度 也是一致的。
THREE.AmbientLight(hex)
在设置环境光时,只需要指定光的颜色。使用环境光渲染时,环境光不在乎物体材质的color属性,而是ambient属性。ambient属性的默认值时0xffffff。当环境光使用的颜色比较明亮,渲染的颜色往往会过饱和。因此,环境光通常使用白色或者灰色,作为整体光照的基础。
# 点光源
点光源是一种单点发光,照射所有方向的光源。点光源照到不同物体表面的亮度是线性递减的
THREE.PointLight(hex, intensity, distance)
其中,hex是光源16进制的颜色值,intensity是亮度,默认值为1,表示100%亮度,distance是光源最远照射的距离,默认值为0。
var light = new THREE.PointLight(oxffffff, 0, 100);
light.position.set(0, 1.5, 2);
scene.add(light);
# 聚光灯
聚光灯是一种特殊的点光源,他能够朝着一个方向投射光线。 聚光灯投射出的是类似圆锥形的光线, 与现实中看到的聚光灯是一致的。
THREE.SpotLight(hex, intensity, distance, angle, exponent)
- angle 是 聚光灯的张角,默认值是Math.PI/3, 最大值是Math.PI/2
- exponent 是光强在偏离target(target 需要在之后定义,默认值为(0,0,0,))的衰减指数。默认值为10 在调用构造函数之后, 除了设置光源本身的位置,一般还需要设置目标点target
light.position.set(x1, y1, z1);
light.target.position.set(x2, y2, z2);
除了设置light.target.position的方法外,如果想让聚光灯跟着某一物体移动(真正的聚光灯),可以target指定该物体。
# 平行光
对于任意平行的平面,平行光照射的亮度都是相同的,而与平面所在位置无关。
THREE.DirectionalLight(hex, intensity);
对于平行光而言,设置光源位置尤其重要。
var light = new THREE.DirectionalLight();
light.position.set(2, 5, 3);
scene.add(light);
注意:这里设置光源位置并不意味着所有光从(2,5,3)点射出,如果是,就成了点光源。 而是意味着,平行光将以矢量(-2, -5, -3)的方向照射到所有平面。因此,平面亮度与平面的位置无关,而只与平面的法向量相关。只要平面是平行的,那么得到的光照也一定是相同的。
# 阴影
阴影的形成,也就是因为比周围获得的光照更少。因此,要形成阴影,光源必不可少。
在Three.js中,能形成阴影的光源只有平行光THREE.DirectionLight与聚光灯 THREE.SpotLight,相对地,能表现阴影效果的材质只有 THREE.LamerMaterial 和 THREE.PhongMaterial.
# 初始化
首先要告诉渲染器渲染阴影:
renderer.shadowMapEnabled = true;
然后, 对于光源以及所有要产生阴影的物体调用:
xxx.castShadow = true;
对于接收阴影的物体调用:
xxx.receiveShadow = true;
这就是产生阴影效果的前置条件。
# 创建阴影
为了看到阴影照相机的位置,通常可以在调试时开启light.shadowCameraVisible = true; 对于聚光灯,需要设置shadowCameraNear,shadowCameraFar,shadowCameraFov 三个值, 与透视投影照相机相同,只有介于shadowCameraNear 与shadowCameraFar 之间的物体将产生阴影, shadowCameraFov表示张角。
对于平行光,需要设置shadowCameraNear, shadowCameraFar, shadowCameraLeft, shadowCameraRight, shadowCameraTop 以及 shadowCameraBottom 六个值,相当于正交投影照相机的六个面。同样,只有在这个六个面围城的长方体内的物体才会产生阴影效果。
light.castShadow = true;
light.shadow.camera.top = 180;
shaow.camera.bottom = -100;
light.shadow.camera.left = - 120;
light.shadow.camera.right = 120;
# 动作渲染
原理: 对于THREE.js 程序而言,动作渲染的实现是通过在秒中多次重绘画面实现的。FPS(Frames Per Second) 指 每秒画面重绘的次数。FPS越大,则渲染效果越平滑,当FPS小于20时,一般就能明显感受到画面的卡滞现象。当FPS足够大,(比如达到60),再增加帧数,人眼也不会感受到明显的变化,反而相应的就要消耗更多资源。
# setInterval 方法
如果要设置特定的FPS, 可以使用该方法:
setInterval(func, msec);
其中,func 是每过msec毫秒执行的函数,如果将func定位为重绘画面的函数。,就能实现动画效果。
var id = setInterval(draw, 20);
function draw() {
mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
}
这样,每20毫秒就会调用一次draw函数,改变长方体的旋转值,然后进行重绘。 最终得到的效果就是FPS为50的旋转长方体。
# requestAnimationFrame 方法
不在意多久重绘一次,就适用requestAnimationFrame 方法。 它告诉浏览器在合适的时候调用指定函数,通常可能达到60FPS。 requestAnimationFrame 同样有对应的cancelAnimationFrame 取消动画,使用方法类似clearInterval。 由于 requestAnimationFrame只请求一帧画面,与settimeout很相似。因此,除了在inti函数中需要调用,在被其调用的函数中需要再次调用 requestAnimationFrame:
function draw() {
mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
var id = requestAnimationFrame(draw);
}
可以使用 render.setAnimationLoop(callback)来代替requestAnimationFrame(); callback为每个可用帧 都会调用的函数。如果传入 null ,所有正在进行的动画都会停止。对于webVR项目,必须使用此函数。
# 相机控件
threeJs 包括多个相机控件,每个控件都必须加载对应的控制器插件才能使用。
# 控制器要生效必须在renderer中使用代码更新
var clock = new THREE.Clock();
function render() {
var delte = clock.getDelta();
trackballControls,uodate(delta);
requestAnimationFrame(render);
webGLRender.render(scene, camera);
}
其中,THREE.Clock()对象 可以获取一次渲染耗费的时间, 调用clock.getDelta() 会返回此次调用和上次调用之间的时间。
# OrbitControls
可以使得相机围绕目标进行轨道运动。以舞台中心为中点,左右拖动屏幕会让镜头围绕着中心点渲染,镜头会看着中心点。
orbitControls(object: Camera, domElement: HTMLDOMElement)
object: (必须)将要被控制的相机。 该相机不允许是其他任何对象的子级,除非该对象是场景自身。 domElement:(可选)用于事件监听的HTML元素,其默认值为整个文档,但是如果你只是希望在特定的元素上,(例如canvas)进行控制,在这里进行指定。
# TrackballControls
以模型或者点为中心,围绕中心来展示。拖动模型后,模型位置会变化,但是摄像机LookAt的位置不会变化,导致再次旋转模型将不再以模型为中心点。 注意: 使用相机控件,会导致相机lookAt() 失效,需要设置OrbitContriols.target为目标向量,比如:
controls.target = new THREE.Vector(0, -1000, 0);
# 渲染
# WebGLRender
使用WebGLRender对象能调动计算机显卡,计算指定相机角度下的scene样子进行渲染。
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xEEEEEE);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
调用setClearColor(0xEEEEEE)将渲染器的背景色设置为白色,调用setSiz() 来控制渲染器渲染scene的范围。
# 渲染机制
threejs的渲染器是基于webGL的。它的渲染机制是根据物体离照相机的距离来控制和进行渲染的。也就是说,它根据物体的空间顺序进行排序,然后根据这个顺序来渲染物体。对于透明的物体,是按照从最远到最近的顺序进行渲染的。
# 控制渲染顺序
- 设置
renderer.sortObjects = false;
这样,物体的渲染顺序将会由他们添加到场景中的顺序所决定。
- 设置
renderer.sortObjects = true;
并且给指定的物体设置object.rendererOrder 指定它的渲染顺序。默认renderOrder = 0;
- 遍历设置
# 坐标轴AxesHelper
用于简单模拟3个坐标轴的对象。 红色代表x轴,绿色代表y轴,蓝色代表z轴
var axesHelper = new THREE.AxesHelper(legnth);
scene.addd(axesHelper);
length(可选): 表示代表轴的线段长度,默认为1.
# 性能检测stats:
检测当前场景的渲染帧率和显存占用情况:
stats = new Stats();
stats.donElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
stats.domElement.style.left = '0px';
document.body.appendChild(_stats.domElement);
使用时,需要添加入渲染函数内:
function Animate() {
requestAnimationnFrame(Animate);
render()
}
function render() {
stats.update();
render.render(scene,camera)
}
# 动画
# 动画混合器AnimationMixer
动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
AnimationMixer(Object3D)
Object3D为混合气播放的动画所属对象。
# 属性
- time : Number全局的混合器时间(单位秒; 混合器创建的时刻记作0时刻)
- timeScale : Number全局时间(mixer time)的比例因子 说明: 将混合器的时间比例设为0/1,可以暂停/取消暂停由该混合器控制的所有动作。
# 方法
clipAction (clip : AnimationClip, optionalRoot : Object3D) : AnimationAction//返回传入AnimationActionAnimationaction = mixer.clipAction(clip);
clip是动画剪辑(AnimationClip)对象或者动画剪辑的名称(导入的模型的动作信息保存在object.animations[]数组中)。根对象Object3D可选,默认值为混合气的默认根对象。
如果不存在符合传入的剪辑和根对象这两个参数的动作,该方法将会创建一个。
AnimationActions 用来调度存储再AnimationClips中的动画。 AnimationClip是一个可重用的关键帧轨道集,它代表动画。 AnimationAction的大多数方法都可以链式调用。
# 点击交互
原理
浏览器是一个2D视口,在里面显示THREEjs的内容是3D场景,从浏览器观测3D场景时,眼睛就相当于是threejs内的摄像机点,鼠标在屏幕的点击位置是另一个点。这两个点会在threejs内连接城一条直线raycaster。直线穿过的threejs内的物体就是鼠标所点击的物体。
实现:
Raycaster( origin : Vector3, direction : Vector3, near : Float, far : Float )
- origin —— 光线投射的原点向量。
- direction —— 向射线提供方向的方向向量,应当被标准化。
- near —— 返回的所有结果比near远。near不能为负值,其默认值为0。
- far —— 返回的所有结果都比far近。far不能小于near,其默认值为Infinity(正无穷)。
方法:
.setFromCamera(coords: Vector2, camera: Camera): null
coords - 在标准化设备坐标中鼠标的二维坐标。 camera - 射线所来源的摄像机。
使用一个新的原点和方向来更新射线。
intersectObjects(objects: Array, recursive: Boolean, optionalTarget: Array): Array
objects - 检测和射线相交的一组物体
recursive - 若为true,则同事也会检测所有物体的后代,否则将只会检测对象本身的相交部分。默认为false。
optionalTarget - 可选 设置结果的目标数组,如果不设置该值,则一个新的Array会被实例话,如果设置了这个值,则在每次调用之前必须清空这个数组(例如: array.length = 0)