官网链接:three.js docs
这个文章是记录了博主学习three的一些磕磕绊绊,查阅了大量资料后的总结。
下载:
three更新迭代得很快,很多方法在各种版本里的使用方法不太一样或者已经被废弃,所以下载正确的版本是很重要的,想下载最新版本的只需将@后面的版本去掉即可
npm install three@对应版本 --save
下载后的three文件如下
在该目录下examples/jsm 放置了很多three的拓展库,如果我们在核心库里找不到想要的方法,可以进这里找一下
其中
引入:
不管是vue还是react,他们的引入方式是相同的
核心库的引用:
import * as THREE from 'three';
扩展库:
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
如上,前期准备已经完成。
要想正式开始学习three,我们首先要认识three的三元素:
场景Scene、相机Camera、渲染器Renderer
打个比方,我们深处在三维世界,那么我们所处的空间就是一个场景,我们的眼睛就是相机,渲染器就是能让我们加载到这个空间的载体。如果再将我们可见的各种物品(花草、树等)看作一个个模型和几何体加入到场景中,那么一个three的实例就产生了。
要想正确加载,那么三元素缺一不可
使用Scence来创建一个场景
let scence = new THREE.Scence()
一个场景想要显示任何东西需要三种类型的组件
场景并没有很多特殊的选项和属性,它最重要的功能是允许添加对象,移除对象以及处理场景的children属性
scence自带方法属性:
fog雾化是个有趣的方法,通过 fog 属性可以为整个场景添 效果 一个物体离得越远,就越模糊
scene.fog = new THREE.Fog('pink',0.5,30) // (颜色,雾的显示近处的浓度,far远处)
overrideMaterial能够强制将场景中的物体变为同一材质(材质会在后面讲),不受定义的材质影响
scene.overrideMaterial = new THREE.MeshLambertMaterial({
color:'pink'
})
一个合适的相机能够让我们的场景变得更加具有趣味,适当的曲率或者视角能让使用者的代入感更加强烈。
以人眼距作为类比,相机有几种属性与人眼类似
1.“看”的方向 : .lookAt(x, y, z)
相机正对的画布的位置(焦点)
camera.lookAt(0, 0, 0);
2. 相机(眼睛)所在的位置:.position.set(x, y, z)
这个属性决定了相机所在的位置,我们可以动态控制相机的位置达到在场景中移动的效果,就像我们带着眼睛在空间中走路,改变的只是我们的xyz坐标、
camera.position.set(800, 800, 800);
)
也叫正交相机,使用正投影相机,所有方块渲染出来的尺寸都是一样的,对象与相机之间的距离不会影响渲染的结果,通常应用在二维游戏中。
正投影不关心使用的什么样的长宽比,或者以什么样的视角观察场景
使用方法:
var camera = new THREE.OrthographicCamer(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(3, 1, 2)
OrthographicCamera ( left, right, top, bottom, near, far )
对应参数
透视图展示:
可用方法:
setViewOffset(fullWidth : Float, fullHeight : Float, x : Float, y : Float, width : Float, height : Float )
fullWidth — 多视图的全宽设置 fullHeight — 多视图的全高设置 x — 副摄像机的水平偏移 y — 副摄像机的垂直偏移 width — 副摄像机的宽度 height — 副摄像机的高度
.clearViewOffset ()-----清除任何由setViewOffset设置的偏移量
.updateProjectionMatrix()---------更新摄像机投影矩阵,在任何参数改变以后必须被调用
.toJSON()---使用json格式来返回摄像机数据
使用透视相机更贴近真实世界,透视相机被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。
const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
scene.add( camera );
PerspectiveCamera ( fov,aspect,near,far )
示意图:
双透视摄像机(立体相机)常被用于创建3D立体影像或者Parallax Barrier(视差效果)
正如官网所说,ArrayCamera 用于更加高效地使用一组已经预定义的摄像机来渲染一个场景。这将能够更好地提升VR场景的渲染性能。
const renderer = new THREE.WebGLRenderer()
renderer.setSize(3 / 4 * window.innerWidth, 3 / 4 * window.innerHeight)//对渲染实例设置宽高
renderer.setClearColor('pink', 1)
renderer.render(scene, camera)
补充:如果你希望保持你的应用程序的尺寸,但是以较低的分辨率来渲染,你可以在调用setSize时,给updateStyle(第三个参数)传入false。例如,假设你的
如上,三元素的加载已经完成,但是如果仅仅如此还不能加载出场景,需要将三元素相连,也是因为生成的场景中可能会有动态的几何体动画,就相当于我们所说的帧率,利用浏览器的刷新率来加载
requestAnimationFrame()调用一个函数不是立即调用而是向浏览器发起一个执行某函数的请求, 什么时候会执行由浏览器决定,一般默认保持60FPS的频率,大约每16.7ms调用一次requestAnimationFrame()方法指定的函数,
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate()
至此,一个正确可见的场景就完成了,完整代码:
其中生成了一个几何体以便更直观得看到场景
import React, { useEffect, useState } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
let camera = null; //相机
let scene = null; //场景
let renderer = null; //渲染器
let container = null; //three挂载对象
let controls = null; //控件
function ThreeDemo() {
//初始加载场景
const createtHREE = () => {
//新建场景
scene = new THREE.Scene();
//新建一个相机(相当于眼睛,只有有了眼睛才能视物)
camera = new THREE.PerspectiveCamera(
75,
((3 / 4) * window.innerWidth) / ((3 / 4) * window.innerHeight),
0.1,
1000
);
//将相机放入合适的位置
camera.position.set(30, 30, 50)
camera.lookAt(0,0,0)
// 生成渲染器
renderer = new THREE.WebGLRenderer();
renderer.setSize((3 / 4) * window.innerWidth, (3 / 4) * window.innerHeight);
renderer.setClearColor('#888', 0.5); //背景颜色
container = document.getElementById('container');
container.appendChild(renderer.domElement); //生成的渲染的实例, 这个要放到对应的dom容器里面
controls = new OrbitControls(camera, renderer.domElement);
addGeo();
animate();
};
//生成几何体
const addGeo = () => {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
};
//
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
useEffect(() => {
createtHREE();
}, []);
return (
);
}
export default ThreeDemo;
那么恭喜你!已经迈进了three学习的门槛,加载成功的图形如下:
神说要有光!那就给我们场景加入一道光。有时候觉得玩这个就像是上帝造物,结合实际才能更好的学习不是hhh扯远了,three提供的光源有多种,每种光都有不同的适用场景
学习光之前,我们先在上部分的场景中新建一个平面,来接收光源,可以更加直观得看到光的区分
Three. js 里有两种材质可以对光源产生反应: MeshLambertMaterial 与MesbPhongMaterial
该平面的材质是漫反射网格材质受光影响,所以没有光的话会呈现一片黑色,而几何体的材质不受光的影响,正常显示。
//生成平面
const addPlant = () => {
let plantGeo = new THREE.PlaneGeometry(60, 60, 1, 1);
const plantMaterial = new THREE.MeshLambertMaterial({
color: 'pink',
});
const plane = new THREE.Mesh(plantGeo, plantMaterial);
plane.receiveShadow = true;
// 设置平面位置
plane.rotation.x = -0.5 * Math.PI;
plane.position.set(15, 0, 0);
scene.add(plane);
};
AmbienLight光源的颜色会影响整个场景,光线没有特定的来源而且这个光源也不会影响阴影的生成,
在使用其他光源(例如 Spoight DirectionalLight )的同时使用 AmbientLight ,可以是弱化阴影或添加一些过渡颜色。是个很神奇很基础的光效。
加入很简单,只需一行代码:
//加入光源
const addLight = () => {
const light = new THREE.AmbientLight(0xfafafa);
scene.add(light);
};
此时能很明显得看到平面的颜色改变了,是环境光所导致的(为了更明显的区分,博主加深了场景颜色,又向场景中加入了一个跟平面一样材质的几何体)
Three.js 库中的 PointLight (点光源)是一种单点发光,照射所有方向的光源
点光源不会产生阴影,原因是点光源会向所有的方向发射光线,在这种情况下计算阴影对cpu来说是一个沉重的负担
注意:threejs 不稳定,点光源在某些版本不生效,解决办法--将版本退回到155之前的版本
加入点光源:
const pointLight = new THREE.PointLight('red');
pointLight.position.set(30, 20, 0);
pointLight.intensity = 0.5; //光照强度
pointLight.dispose = 10;//光源照射距离
pointLight.castShadow = true;//生成阴影(阴影的形成是双向的,需要物体加入接受阴影的属性)
scene.add(pointLight);
可以加入点光源的辅助工具,以便更好得定位到点光源的位置
const heapler = new THREE.PointLightHelper(pointLight)
scene.add(heapler);
SpotLight (聚光灯光源)大概会是你最常用到的光源(特别是当你想要生成阴影时)
SpotLight 光源具有一种锥形效果
聚光灯光源可以使用点光源的属性,还有一些额外的属性如下
const spotLight = new THREE.SpotLight('pink');
spotLight.position.set(30, 20, 0);
scene.add(spotLight);
const spotLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLightHelper);
既然说到阴影了,three默认生成的几何体们不产生阴影,因为计算阴影需要耗费大量的计算资源,所以如果想要展示阴影的话需要额外多几步操作
1.首先我们需要告诉renderer(渲染器)我们需要阴影
renderer.shadowMap.enabled = true;
2.明确定义哪个物体投射投影,哪个物体接受投影
投射投影对象.castShadow = true
接受投影对象.receiveShadow = true
到我们例子里,就是正方体是接受投影的对象,而聚光灯是产生投影的对象。
生成正方体时,我们需要将这两个属性都设置为true,单独设置一个阴影是不会呈现的
const geometry2 = new THREE.BoxGeometry(10, 10, 10);
const material2 = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
const cube2 = new THREE.Mesh(geometry2, material2);
cube2.receiveShadow = true;
cube2.castShadow = true;
cube2.position.set(20, 5, 0);
scene.add(cube2);
3.定义场景中哪个光源可以产生阴影,(不是所有的光源都能产生阴影)
以聚光灯举例
const spotLight = new THREE.SpotLight('pink');
spotLight.position.set(30, 20, 0);
spotLight.castShadow = true;
scene.add(spotLight);
const spotLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLightHelper);
此时。画面中的阴影如下:
感觉并不是很好看,阴影有几个属性可以调节形状与映射大小
light.shadow.mapSize
阴影贴图尺寸属性(提升边缘渲染效果)
light.shadow.radius
弱化模糊阴影边缘
设置mapSize后:
spotLight.shadow.mapSize.width = 100;
spotLight.shadow.mapSize.height = 100;
设置radius
spotLight.shadow.radius = 5;
通过光源的阴影相机属性.shadow.camera可以
控制阴影的渲染范围
构造摄像机时,以透视相机举例:
PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
fov — 摄像机视锥体垂直视野角度,从视图的底部到顶部,以角度来表示。默认值是50
aspect — 摄像机视锥体长宽比,通常是使用画布的宽/画布的高
near — 摄像机视锥体近端面,其有效值范围是0到当前摄像机 far远端面)的值之间
far — 摄像机视锥体远端面,该值必须大于near plane(摄像机视锥体近端面)的值。
通过调整以上数值,形成一个长方形的盒子,即阴影的形成范围,可以做出漂亮的符合实际的阴影了,注意:far的值必须大于near,如果你设置的阴影不显示的话,不妨看一下是不是值设置错误的问题。
好看的阴影是需要调整的,阴影的属性、光源的位置、光源的类型都会影响到阴影的形成,所以有点耐心啦~
spotLight.shadow.camera.near = 20;
spotLight.shadow.camera.far = 4000;
spotLight.shadow.camera.fov = 40;
接下来继续回到光源:
方向光光源可以看做是距离很远的光源,这个光源发出的所有光线都是相互平行的。
与聚光灯光源差别:
方向光不像聚焦光一样离目标越远越暗淡,被方向光光源照亮的整个区域接收到的光强是一样的
属性与聚光灯属性相像
包围对象的空间定义得越紧密,其投影的效果越好
const directionaLight = new THREE.DirectionalLight('pink', 1);
directionaLight.position.set(100, 20, 10);
directionaLight.castShadow = true;
scene.add(directionaLight);
使用半球光光源,可以创建出更加贴近自然的光照效果 如果不使用这种光源,要模拟室外光照,可以添
加一个方向光光源来模拟太阳,或者再添加一个环境光光源,为场景提供基础色 但是,这样看上去不怎么自然 你在室外的时候,并不是所有的光照都来自上方;很多是来自空气的散射 地面的反射,以及其他物体的反射
示意图:(图片来自网络)
你只要指定来自上方(光线) 的颜色 (接收自天空的颜色)、接收自地面的颜色,以及它们的光照强度 之后如果想修改这些属性,可以使用如下属性:
HemisphereLight(groundColor ,Color ,intensity )
let hemiLight = new THREE.HemisphereLight('pink', 'blue', 0.3);
hemiLight.position.set(300, 500, 100);
scene.add(hemiLight);
可以看到,半球光是不产生阴影的,它可以取代环境光提亮场景,类似于一个简易的环境球
使用平面光光源,可以定义一个发光的矩形
如果你要使用平面光光掘,那么就不能再用之前我们一直在示例中使用的 THREE.WebGLRenderer 对象了 因为平面光光源是 种非常复杂的光源;它会对 THREE WebGLRenderer 对象造成非常严重的性能损失 THREE.WebGLDeferredRendererebGL 的延迟渲染器)对象在渲染场景时使用了一种不同的方法,可以处理复杂的光照(或者光源很多的情况)
有点复杂,待续吧...
前面已经用到过材质了,材质就是生成几何体的时候,我们用一种什么样材料的载体来承载它,three给我们提供了多种材质
常用的材质如下:
在学习材质之前,我们要了解到材质的共有属性,所有的材质都是从Material基类衍生来的
控制物体不透明度、是否可用、自定义名称或者ID等
控制物体如何与背景融合
控制底层WEBGL上下文对象渲染物体的方式,一般来讲不会用到
MeshBasicMaterial 是一种非常简单的材质,这种材质不考虑光照的影响使用这种材质的网格格会被渲染成一些简单的多边形
属性描述:
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.receiveShadow = true;
cube.castShadow = true;
cube.position.set(20, 5, 0);
scene.add(cube);
我们一开始的例子生成的几何体就是用MeshBasicMaterial材质来生成的,可以很明显看到他只有我们初始设置的颜色,不会手各种光源的影响。
使用这种材质的物体,其外观不是由光照或某个材质属性决定的,而是由物体导向及的距离决定的。当几何体使用该材质后,会基于摄像机的距离为物体上色,深度基于相机远近平面。白色最近,黑色最远。可以将这种材质与其他材质结合,创造出之间消失得效果,这种材质只有两个控制线框的属性。
THREE.MashNormalMaterial 是一种法向量材质,该材质拥有的属性与THREE.MeshDepthMaterial 相同
用于暗淡不光亮表面的材质,会对光源产生反应。
这种材质的物品会根据光的方向和亮度做出相应反应
创建一种光亮的材质,基本属性与MeshLambertMaterial属性一一致,其余可使用属性:
THREE.ShaderMaterial是three库中功能最丰富最复杂的一种材质
着色器可以将three中的JavaScript对象转化为屏幕上的像素,通过这些自定义的着色器,可以明确指定对象如何渲染和遮盖,或者修改three默认值
要使用ShaderMaterial材质,必须要传入两个不同的着色器
vertexShader:vertexShader会在几何体的每一个顶点上执行,可以用这个着色器通过改变顶点的位置来对几何体进行变换
framentShader:framentShader还在几何体的每一个像素上执行,在vertexShader里,会返回这个特定像素应该显示的颜色
该分类有两种材质,这两种材质只能用于特定的几何体:THREE.Line(线段),线段上只有两个顶点,不包含任何面
通过线段基础材质可以设置线段的颜色、宽度、端点、和连接点属性
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff });
const points = [];
points.push(new THREE.Vector3(-10, 0, 0));
points.push(new THREE.Vector3(0, 10, 0));
points.push(new THREE.Vector3(10, 0, 0));
const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(lineGeometry, lineMaterial);
scene.add(line);
这个材质有跟LineBasicMaterial一样的属性外还有几个额外属性
const lineMaterial = new THREE.LineDashedMaterial({
color: 0x0000ff,
dashSize: 0.1,
gapSize: 0.1,
scale: 0.1,
});
const points = [];
points.push(new THREE.Vector3(-10, 0, 0));
points.push(new THREE.Vector3(0, 10, 0));
points.push(new THREE.Vector3(10, 0, 0));
const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(lineGeometry, lineMaterial);
line.computeLineDistances();//显示线段为虚线
scene.add(line);
材质注意事项:
1.如果要创造一种透明的材质,仅仅设置opacity是不够的,还要将transparent属性设置为true
3.可以为几何体赋予多种材质,但这么做会复制几何体,从而创造出多个网格
4.THREE.Line几何体不可以用普通材质覆盖,只能用LineBasicMaterial和 LineDashedMaterial
几何体种类:
PlaneGeometry(平面)
CircleGeometry(圆形)
ShapeGeometry(塑性)
BoxGeometry(立方体)
SphereGeometry(球体)
CylinderGeometry(圆柱)
TorusGeometry(圆环)
TorusKnotGeometry(环面扭结)
PolyhedronGeometry(多面体)
IcosahedronGeometry(二十面体)
OctahedronGeometry(八面体)
TetraHedronGeometry(四面体)
复杂几何体:
ConvexGeometry(凸面体)
LatheGeometry(扫描面)
ExtrudeGeometry(拉伸几何体)
TubeGeometry(管状体)
ParametricGeometry(参数几何体)
TextGeometry(文本几何体)
注意:
创建二维几何体时,z 轴没有考虑 ,如果想拥有一个水平的 二维图形,那么必须将这个网格绕 X轴旋转-0.5pi
如果你要旋转一个二维图形,或者一个开放的三维图形(例如圆柱或者管子),记住
要将材质设置成 side:THREE.DoubleSide 如果你不这么做,那么该几何体的内侧或背面
将会不可见
为了更直观得看到几何体在空间中的位置,可在场景中加入一个工具:AxesHelper,
var axisHelper = new THREE.AxesHelper(20);//(xyz轴的长度)
scene.add(axisHelper);
前面用以接收阴影的平面就是用PlaneGeometry创建的
new THREE.PlaneGeometry(width height, widthSegments, heightSegments);
(宽、高、宽度段数《指定矩形的宽度应该划分成几段》、高度段数)
平面创建后,我们可以看到这个平面只有一面可见,这是因为我们在初始材质生成的时候,没有指定材质的side属性,这个属性默认值显示正面,我们可以指定他只显示反面或者正反一样的材质
创建二维圆
new THREE.CircleGeometry(radius,segments,thetaStart,thetaLength)
用这个我们可以画出各种不一样的圆,半圆、多边的圆等
ShapeGeometry可以创建一个自定义的二维图形
借助THREE.Shape()绘图函数
let _material = new THREE.MeshNormalMaterial({
color: 'pink',
side: THREE.DoubleSide,
});
let _geomery = '';
const shape = new THREE.Shape();
// .lineTo(100, 0)绘制直线线段,线段起点:.currentPoint,线段结束点:(100,0)
shape.lineTo(10, 0);
shape.lineTo(10, 10);
shape.lineTo(0, 10);
const path1 = new THREE.Path(); // 圆孔1
path1.absarc(2, 2, 1);
const path2 = new THREE.Path(); // 圆孔2
path2.absarc(8, 2, 1);
const path3 = new THREE.Path(); // 方形孔
path3.moveTo(5, 5);//将画笔移动到指定位置
path3.lineTo(8, 5);//开始画线
path3.lineTo(8, 8);
path3.lineTo(5, 8);
shape.holes.push(path1, path2,path3);//将三个洞加入图形
_geomery = new THREE.ShapeGeometry(shape);
let thisGeometry = new THREE.Mesh(_geomery, _material);
thisGeometry.receiveShadow = true;
thisGeometry.castShadow = true;
thisGeometry.position.set(20, 20, 20);
scene.add(thisGeometry);
在材质章节已经用到了这个几何体,生成长方体。
new THREE.BoxGeometry(10 ,10,10) ;
他除了常用的长宽高,还有一些非必须的属性如下
球缓冲几何体
new THREE.SphereGeometry(10,10,10)
圆柱和类似圆柱的物体
new THREE.CylinderGeometry(radiusTop, radiusBottom,height, segmentsX, segmentsY)
圆环
圆环没有放在three的默认导出中,如果使用需要额外引入
import { TorusGeometry } from 'three/src/geometries/TorusGeometry';
new TorusGeometry(radius, tube,radialSegments , tubularSegments , arc )
环面扭结,看上去就像是一根管子绕着他自己旋转了几圈
这个集合题也需要单独引入
import { TorusKnotGeometry } from 'three/src/geometries/TorusKnotGeometry';
new TorusKnotGeometry(radius, tube,radialSegments , tubularSegments , p,q,heightScale)
创造多面体,多面体是只有平面和直边的几何体
要创建一个PolyhedronGemotry对象,我们需要传入vertices(定点)、face(面)、radius(半径)和detail参数
响应的字体生成类与字体库也需要引入:
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
import fontJSON from 'three/examples/fonts/gentilis_regular.typeface.json';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
几何体生成如下
var loader = new FontLoader();
let font = loader.parse(fontJSON);
_geomery = new TextGeometry('hello,hello', {
font: font, //THREE.Font实例对象
size: 30, //字体大小,默认100
height: 5, //挤出文本的厚度,默认50
curveSegments: 12, //曲线上点的数量,默认12
bevelEnabled: true, //是否开启斜角,默认FALSE
bevelThickness: 10, //文本上斜角的深度,默认20
bevelSize: 8, //斜角与原始文本轮廓之间的延伸距离,默认8
bevelSegments: 6, //斜角的分段数,默认3
});
是面片、线或点几何体的有效表述。包括顶点位置,面片索引、法相量、颜色值、UV 坐标和自定义缓存属性值。使用 BufferGeometry 可以有效减少向 GPU 传输上述数据所需的开销。
这个类用于存储与BufferGeometry相关联的 attribute(例如顶点位置向量,面片索引,法向量,颜色值,UV坐标以及任何自定义 attribute )。 利用 BufferAttribute,可以更高效的向GPU传递数据。
BufferAttribute( array : 缓存中的数据, itemSize : 队列中与顶点相关的数据值的大小, normalized : 指明缓存中的数据如何与GLSL代码中的数据对应-boolean)
Float32Array创建的顶点数据 每3个为一组,组成一个平面,
new THREE.BufferAttribute(vertices, 3),第二个参数3--代表顶点数组每三个为一组
_geomery = new THREE.BufferGeometry(); //创建一个Buffer类型几何体对象
let vertices = new Float32Array([
//类型数组创建顶点数据
0, 0, 0,
2, 0, 0,
0, 1, 0,
0, 0, 0,
0, 0, 1,
2, 0, 0,
]);
// 创建属性缓冲区对象,目的是为了创建各种各样顶点数据
let attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组,表示一个顶点的xyz坐标
_geomery.attributes.position = attribue;