0%

8

自动摘要: 正交投影摄像机和透视投影摄像机 1.区别 正交投影摄像机:所有物体被渲染出来的尺寸是一样的,没有近大远小之分。因为对象相对于摄像机的距离对渲染的结果是没有影响的。常用于二维游戏中。透视投影摄 ……..

正交投影摄像机和透视投影摄像机

  1. 区别

正交投影摄像机:所有物体被渲染出来的尺寸是一样的,没有近大远小之分。因为对象相对于摄像机的距离对渲染的结果是没有影响的。常用于二维游戏中。透视投影摄像机:透视试图,近大远小。更贴近真实世界。

  1. 属性

正交投影摄像机Orthographic Camera:
构造函数:

THREE.OrthographicCamera(left, right, top, bottom, near, far)

参数 描述
left(左边界) 可视范围的左平面,渲染部分的左边界。
right(右边界) 可渲染区域的另一边界
top(上边界) 可渲染区域的最上面
bottom(下边界) 可渲染区域的最下面
near(近面距离) 基于摄像机所处的位置,从这一点开始渲染
far(远面距离) 基于摄像机所处的位置,渲染场景到这一点结束
zoom(变焦) 放大和缩小场景(默认值为1,小于1—缩小,大于1—放大,负数场景会上下颠倒)
透视投影摄像机PerspectiveCamera:![](/images/1666056384697-d28eb3a7-2764-4c7b-ad98-6c6a8021a4f7.jpeg)

构造函数:

THREE.PerspectiveCamera(fov,aspect,near,far,zoom)

参数 描述
fov 视场。摄像机能够看到的那部分场景。一般计算机不能完全显示能够看到的景象,所以一般会选择较小的区域。推荐默认值:50
aspect(长宽比) 渲染结果的横向尺寸和纵向尺寸的比值。推荐默认值:window.innerWidth/window.innerHeight
near(近面距离) 定义从距离摄像机多近的距离开始渲染。推荐默认值:0.1
far(远面距离) 定义了摄像机从它所处的位置能够看多远。推荐默认值:100
zoom(变焦) 放大和缩小场景(默认值为1,小于1—缩小,大于1—放大,负数场景会上下颠倒)
  1. 实例:



需求:在Three.js中改变摄像机,通过点击按钮SwitchCamera时,透视摄像机变成正交摄像机。过程:a. 构建基本场景(scene,camera,renderer,plane,spotLight,ambientLight)b. 添加立方体(平铺整个平面)c. 添加初始化性能插件、dat.GUI简化实验流程d. 添加用户交互插件e. 窗口触发函数(页面响应式布局)代码:HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name=viewport
content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,minimal-ui">
<script type="text/javascript" charset="UTF-8" src="libs/three/three.js"></script>
<script type="text/javascript" charset="UTF-8" src="libs/three/controls/TrackballControls.js"></script>
<script type="text/javascript" charset="UTF-8" src="libs/three/geometries/ConvexGeometry.js"></script>
<script type="text/javascript" charset="UTF-8" src="libs/three/geometries/QuickHull.js"></script>
<script type="text/javascript" charset="UTF-8" src="libs/three/geometries/ParametricGeometries.js"></script>
<script type="text/javascript" src="libs/util/Stats.js"></script>
<script type="text/javascript" src="libs/util/dat.gui.js"></script>
<script type="text/javascript" src="libs/three/controls/OrbitControls.js"></script>
<script type="text/javascript" src="js/util.js"></script>
<script type="text/javascript" src="04-01.js"></script>
<link rel="stylesheet" href="css/default.css">
<title>Title</title>
<style>
html, body {
margin: 0;
height: 100%;
}
</style>
<script></script>
</head>
<body onload="draw()">
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}
//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
camera.position.set(120,60,180);
camera.lookAt(scene.position);
}

//渲染器
var renderer;
function initRender(){
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);
}

//平面
var plane;
var planeGeometry = new THREE.PlaneGeometry(180,180);
function initPlane(){
var planeMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff,
});
plane = new THREE.Mesh(planeGeometry, planeMaterial);

plane.rotation.x = -0.5*Math.PI;
plane.position.set(0,0,0);
scene.add(plane);
}

//光
var light;
function initLight(){
scene.add(new THREE.AmbientLight(0x292929));

light = new THREE.DirectionalLight(0xffffff,0.7);
light.position.set(-20,40,60);
scene.add(light);
}

//方块
var cube;
function initCube(){
var cubeGeometry = new THREE.BoxGeometry(4,4,4);
for(var j=0;j<(planeGeometry.parameters.height/5);j++){
for(var i=0;i<planeGeometry.parameters.width/5;i++){
var rnd = Math.random()*0.75+0.25;
var cubeMaterial = new THREE.MeshLambertMaterial();
cubeMaterial.color = new THREE.Color(rnd,0,0);
cube = new THREE.Mesh(cubeGeometry,cubeMaterial);

cube.position.set(
-((planeGeometry.parameters.width)/2)+2+(i*5),
2,
-((planeGeometry.parameters.height)/2)+2+(j*5));

scene.add(cube);
}
}
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.body.appendChild(stats.domElement);
}
//使用dat.GUI简化实验流程
var trackballControls;
var controls;
function initControls(){
controls = new function(){
this.perspective = "Perspective";
this.switchCamera = function (){
if(camera instanceof THREE.PerspectiveCamera){
camera = new THREE.OrthographicCamera(window.innerWidth/-16, window.innerWidth/16, window.innerHeight/16, window.innerHeight/-16, -200,500);
camera.position.set(120,60,180);
camera.lookAt(scene.position);

trackballControls = initTrackballControls(camera,renderer);
this.perspective = "Orthographic";
}
else{
camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);
camera.position.set(120,60,180);

camera.lookAt(scene.position);
trackballControls = initTrackballControls(camera,renderer);
this.perspective = "Perspective";
}
};
};
var gui = new dat.GUI();
gui.add(controls,'switchCamera');
gui.add(controls,'perspective').listen();
gui.domElement.style.position = 'absolute';
gui.domElement.style.right = '300px';
gui.domElement.style.top = 0;
}

function render(){
renderer.render(scene,camera);
}
//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var interaction;
function initInteraction() {

interaction = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
interaction.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
interaction.enableZoom = true;
//是否自动旋转
interaction.autoRotate = true;
//设置相机距离原点的最远距离
interaction.minDistance = 200;
//设置相机距离原点的最远距离
interaction.maxDistance = 600;
//是否开启右键拖拽
interaction.enablePan = true;
}

//窗口触发函数
function onWindowResize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}


function animate(){
//更新控制器
render();
//更新性能插件
stats.update();
requestAnimationFrame(animate);
}
function draw(){
initScene();
initCamera();
initRender();
initPlane();
initLight();
initCube();
initControls();
initInteraction();
initStats();

animate();
window.onresize = onWindowResize;
}

总结:正交投影摄像机和透视投影摄像机的切换,首先用instanceof判断目前摄像机是否是透视摄像机,如果是透视投影摄像机,则用camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far)设置为正交投影摄像机,反之亦然。后续可以继续设置摄像机的位置,对准场景中心。

从外部资源加载几何体

格式 描述
JSON
OBJ或MTL OBJ是一种简单的三维文件格式,由Wavefront Technologies创建。它是使用最广泛的三维文件格式,用来定义对象的几何体。MTL文件常同OBJ文件一起使用。Three.js还有一个可定制的OBJ导出器,叫做OBJExporter.js,可以用来将Three.js中的模型导出一个OBJ文件。
STL STL是STLereoLithography(立体成型术)的缩写,广泛用于快速成型。例如三维打印机的模型文件通常是STL文件。Three.js还有一个可定制的STL导出器,叫作OBJExporter.js,可以用来将Three.js中的模型导出到一个OBJ文件。
PLY 这种格式的全程是多边形(polygon)文件格式,通常用来保存三维扫描仪的信息。

以牙齿模型ply格式为例:首先:引入script加载器

1
<script type="text/javascript" src="libs/three/loaders/PLYLoader.js"></script>

然后实例化加载对象,加载模型

1
2
3
4
5
6
7
8
9
10
11
12
13
var loader = new THREE.PLYLoader();
loader.load('tooth-models/arch_lower_aligned.ply',function (loadedMesh){
var material = new THREE.MeshBasicMaterial({
color: 0x7777ff,
});

var group = new THREE.Mesh(loadedMesh, material);

group.scale.set(0.1,0.1,0.1);
setModelPosition(group);
console.log(group);
scene.add(group);
});

案列完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//场景
var scene;
function initScene(){
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera(){
camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);
camera.position.set(0,0,0);
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
}

//渲染器
var renderer;
function initRender(){
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);
}
//光源
var light;
function initLight() {
scene.add(new THREE.AmbientLight(0x444444));

light = new THREE.PointLight(0xffffff);
light.position.set(0,0,100);

//告诉平行光需要开启阴影投射
light.castShadow = true;

scene.add(light);
}
//模型强制居中处理
function setModelPosition(object) {
object.updateMatrixWorld();
// 获得包围盒得min和max
const box = new THREE.Box3().setFromObject(object);
// 返回包围盒的宽度,高度,和深度
const boxSize = box.getSize();
// 返回包围盒的中心点
const center = box.getCenter(new THREE.Vector3());
object.position.x += object.position.x - center.x;
object.position.y += object.position.y - center.y;
object.position.z += object.position.z - center.z;
}
function initModel(){
//坐标轴
var helper = new THREE .AxesHelper();
scene.add(helper);
//从外部加载PLY格式文件
var loader = new THREE.PLYLoader();
loader.load('tooth-models/arch_lower_aligned.ply',function (loadedMesh){
var material = new THREE.MeshBasicMaterial({
color: 0x7777ff,
});

var group = new THREE.Mesh(loadedMesh, material);
setModelPosition(group);
group.scale.set(0.1,0.1,0.1);
console.log(group);
scene.add(group);
});
}
//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}
var stats;
function initStats(){
stats =new Stats();
document.body.appendChild(stats.domElement);
}

function render(){
renderer.render(scene, camera);
}

function animate(){
render();

stats.update();

requestAnimationFrame(animate);
}

function draw(){
initScene();
initCamera();
initLight();
initRender();
initModel();
initStats();
initControls();

animate();
}

注: 牙齿模型位置有问题(已通过手动修改模型位置解决)、牙齿材质问题(学习完材质之后解决)、移除模型问题(通过移除最后一个添加到场景的对象)

问题:

  1. JSON文件加载问题:文件加载不出
  2. ply文件加载问题:模型的材质无法正确加载。(ply格式中rgb颜色附加到顶点信息中无法正确编写代码);


可以自定义材质,但是无法正确加载原模型的材质颜色

  1. obj格式模型完整加载:mtl文件中保存了material信息,obj文件中保存了geometry信息

光源

💡没有光源,渲染的场景将不可见(除非使用基础材质或线框材质)

不同种类的光源

名字 描述
简单基础光源 THREE.AmbientLight 基础光源。该光源的颜色会叠加到场景现有物体的颜色上
THREE.PointLight 点光源,从空间的一点向所有方向发射光线。点光源能用来创建阴影
THREE.SpotLight 有聚光效果,类似台灯、手电筒。该光源可以投射阴影
THREE.DirectionalLight 无限光,光线可以看作是平行线,就像太阳光。可以用来创建阴影
特殊 THREE.HemisphereLight 特殊光源,可以通过模拟反光面和光线微弱的天空来创建更加自然的室外光线。无阴影
THREE.AreaLight 使用这种光源可以指定散发光线的平面,而不是一个点。无阴影
THREE.lensFlare 这不是一种光源。可以为场景中的光源增加镜头光晕效果

THREE.AmbientLight

作用描述:

  1. 颜色会应用到全局
  2. 光源没有特别的来源方向
  3. 不会产生阴影,会将所有物体渲染为相同的颜色
  4. 最好在使用其他光源的同时使用它,目的是弱化阴影,或给场景添加一些额外的颜色

注意事项:

  1. 使用这种光源时,用色应该尽量保守。如果指定的颜色过于明亮,会发现画面的颜色过于饱和。
  2. 除了颜色之外,还可以设置强度值。

创建THREE.AmbientLight光源

  1. 首先添加实例化THREE.AmbientLight对象

var ambientLight = new THREE.AmbientLight(“#606008”);

  1. 将对象添加到场景

scene.add(ambientLight);

案例

结果:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera = initCamera();
// function initCamera() {
// camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1,1000);
// camera.position.set(30, 30, 30);
// camera.lookAt(new THREE.Vector3(0, 0, 0));
// scene.add(camera);
// }

//渲染器
var renderer;
function initRender(){
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0xffffff));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
}
//灯光
var ambientLight;
function initAmbientLight(){
ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
}

//点光源
var spotLight;
function initSpotLight(){
spotLight = new THREE.SpotLight(0xffffff, 1, 180, Math.PI / 4);
spotLight.shadow.mapSize.set(2048,2048);
spotLight.position.set(-30,40,50);
spotLight.castShadow = true;
scene.add(spotLight);
}

var controls;
function initControls() {
controls = new function(){
this.intensity = ambientLight.intensity;
this.ambientColor = ambientLight.color.getStyle();
this.disableSpotlight = false;
}
var gui = new dat.GUI();
gui.add(controls, 'intensity', 0, 3,0.1).onChange(function(e){
ambientLight.color = new THREE.Color(controls.ambientColor);
ambientLight.intensity = controls.intensity;
});
gui.addColor(controls,'ambientColor').onChange(function(e){
spotLight.color = new THREE.Color(controls.ambientColor);
spotLight.intensity = controls.intensity;
});
gui.add(controls,'disableSpotlight').onChange(function(e){
spotLight.visible = !e;
});
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.body.appendChild(stats.domElement);
}
//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls2;
function initControls2() {

controls2 = new THREE.OrbitControls(camera, renderer);0

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls2.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls2.enableZoom = true;
//是否自动旋转
controls2.autoRotate = false;
//设置相机距离原点的最远距离
controls2.minDistance = 50;
//设置相机距离原点的最远距离
controls2.maxDistance = 200;
//是否开启右键拖拽
controls2.enablePan = true;
}
function render(){
renderer.render(scene,camera);
}

function animate(){
stats.update;

requestAnimationFrame(animate);
render();
}
//页面窗口响应式布局
function windowOnchange(){
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

function draw(){
initScene();
initCamera();
initStats();
initControls2();
initRender();
initAmbientLight();
initSpotLight();
addHouseAndTree(scene)
initControls();


animate();
window.onresize = windowOnchange;
}

THREE.SpotLight

作用描述

  1. 聚光灯光源,从特定的一点以锥形发射光线
  2. 该光源产生的光具有方向和角度
  3. THREE.SpotLight所有属性
    属性 描述
    angle(角度) 光源发射出的光束的宽度,单位是弧度,默认值为Math.PI/3
    castShadow(投影) 如果设置为true,这个光源就会生成阴影
    color(颜色) 光源颜色
    decay(衰减) 光源强度随着离开光源的距离而衰减的速度。该值为2时更接近现实世界中的效果,默认值为1。只有当WebGLReader的属性physicallyCorrectLights(物理正确光源)被设置为启用时,decay属性才有效
    distance(距离) 光源照射的距离。默认值为0,这意味着光线强度不会随着距离的增加而减弱。
    intensity(强度) 光源照射的强度。默认值为1
    penumbra(半影区) 该属性设置聚光灯的锥形照明区域在其区域边缘附近的平滑衰减速度。取值范围在0到1之间,默认值为0
    position(位置) 光源在场景中的位置
    power(功率) 当物理正确模式启用时(WebGLReader的属性physicallyCorrectLights被设置为启用时),该属性指定光源的功率,以流明为单位,默认值为4*Math.PI
    target(目标) 使用THREE.SpotLight光源时,它的指向很重要。使用target属性,可以将THREE.SpotLight光源指向场景中的特定对象或特定位置。注意,此属性需要一个THREE.Object3D对象(如THREE.Mesh)
    visible(是否可见) 光源是否可见。如果属性设置为true,该光源就会打开;如果设置为false,光源就会关闭

当THREE.SpotLight的shadow属性为enable时,可以用以下属性调节阴影特性

属性 描述
shadow.bias(阴影偏移) 用来偏置阴影的位置。当用非常薄的对象时,可以使用它来解决一些奇怪的效果。如果看到一些奇怪的阴影效果,可以将该属性设置为很小的值(比如0.01)通常可以解决问题。默认值为0
shadow.camera.far(投影远点) 到距离光源的哪一个位置可以生成阴影。默认值为5000.
shadow.camera.fov(投影视场) 用于生成阴影的视场大小。默认值为50
shadow.camera.near(投影近点) 从距离光源的哪一个位置开始生成阴影。默认值为50
shadow.mapSize.width和shadow.mapSize.height(阴影映射宽度和阴影映射高度) 决定了有多少像素用来生成阴影。当阴影具有锯齿状边缘或看起来不光滑时,可以增加这个值。在场景渲染之后无法更改。两者的默认值均为512
shadow.radius(半径) 当该值大于1时,阴影的边缘将有平滑效果。该属性在THREE.WebGLRenderer的shadowMap.type属性为THREE.BasicShadowMap时无效。

案列结果代码

THREE.PointLight

  1. 点光源是一种单点发光,照射所有方向的光源。
  2. 点光源可以像聚光灯一样启用阴影并设置其属性
    属性 描述
    color(颜色) 光源颜色
    distance(距离) 光源照射的距离。默认值为0,这意味着光的强度不会随着距离增加而减少
    intensity(强度) 光源照射的强度。默认值为1
    position(位置) 光源在场景中的位置
    visible(是否可见) 该属性设置为true,光源就会打开,false——关闭
    decay(衰减) 光源强度随着离开光源的距离而衰减的速度。该值为2时更接近现实世界中的效果,默认值为1。只有当WebGLReader的属性physicallyCorrectLights(物理正确光源)被设置为启用时,decay属性才有效
    power(功率) 当物理正确模式启用时(WebGLReader的属性physicallyCorrectLights被设置为启用时),该属性指定光源的功率,以流明为单位,默认值为4*Math.PI

案例结果代码

THREE.DirectionalLight

  1. 平行光,发出的所有光线都是互相平行的
  2. 平行光的一个范例就是太阳光
  3. 被平行光照亮的物体的整个区域接收到的光强是一样的

案例结果代码

THREE.HemisphereLight

  1. 可以创建出更贴近自然的户外光照效果

  2. 创建一个半球光光源

    1
    2
    3
    var hemisphereLight = new THREE.HemisphereLight(0xffffff,0x00ff00,0.6);
    hemisphereLight.position.set(0,500,0);
    scene.add(hemisphereLight);
  3. 属性

    属性 描述
    groundColor 从地面发出的光线的颜色
    color 从天空发出的光线的颜色
    intensity 光线照射的强度

案例结果代码

THREE.AreaLight

  1. 使用THREE.AreaLight光源,可以定义一个长方形的发光区域

  2. 使用THREE.AreaLight光源,需要现在HTML中导入库RectAreaLightUniformsLib.js

  3. 添加THREE.AreaLight光源

    1
    2
    3
    4
    //光源颜色为0xffffff,光强为500,宽为4,高为10
    var areaLight = new THREE.AreaLight(0xffffff,500,4,10);
    areaLight.position.set(0,20,30);
    scene.add(areaLight);
  4. 在创建THREE.AreaLight光源时,创建出一个垂直平面

  5. 不能看到光源本身,只能看到光源发射出的光,而且只有当光照射到某个物体上时才能看见。可以在光源位置增加平面对象来模拟光线照射区域。

案例结果代码

镜头光晕(Lens Flare)

  1. 对于游戏和三维图像来说,可以更具真实感
  2. THREE.LensFlare对象接受如下参数

flare = new THREE.LensFlare(texture,size,distance,blending,color,opacity);

参数 描述
texture(纹理) 纹理——图片,用来决定光晕的形状
size(尺寸) 可以指定光晕大小,单位是像素。如果设置为-1,将使用纹理本身的大小
distance(距离) 从光源(0)到摄像机(1)的距离。使用这个参数将镜头光晕放置正确的位置
blending(混合) 可以为光晕提供多种材质,混合模式决定了它们如何混合在一起。默认混合方式是THREE.AdditiveBlending
color(颜色) 光晕的颜色
opacity(不透明度) 定义光晕的不透明度,0完全透明,1完全不透明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//加载一个纹理
var textureFlare0 = THREE.ImageUtils.loadTexture("../../..");
//定义镜头光晕颜色
var flareColor = new THREE.Color(0xffffff);
//创建THREE.LensFlare对象
var lensFlare = new THREE.LensFlare(textureFlare0,350,0.0,THREE.AdditiveBlending,flareColor);
//设置镜头光晕的位置
lensFlare.position.copy(spotLight.position);
//添加至场景中
scene.add(lensFlare);

//加载纹理
var textureFlare3 = THREE.ImageUtils.loadTexture("../../..");
//用THREE.LensFlare的add方法添加纹理,尺寸,距离,混合模式
lensFlare.add(textureFlare3,60,0.6,THREE.AdditiveBlending);

案例结果代码

材质(texture)

所有材质:

名称 描述
MeshBasicMaterial(网格基础材质) 基础材质,用于给几何体赋予一种简单的颜色,或者显示几何体的线框
MeshDepthMaterial(网格深度材质) 这个材质的使用从摄像机到网格的距离来决定如何给网格上色
MeshNormalMaterial(网格法向材质) 使用法向向量计算物体的表面颜色
MeshLambertMaterial(网格Lambert材质) 这是一种考虑光照影响的材质,用于创建暗淡,不光亮的物体
MeshPhongMaterial(网格Phong材质) 这是一种考虑光照影响的材质,用于创建光亮的物体
MeshStandardMaterial(网格标准材质) 这种标准材质采用“基于物理的渲染(PBR)”算法来绘制物体表面。能计算出表面与光线的正确互动关系,从而使渲染出来的物体更真实。
MeshPhysicalMaterial(网格物理材质) 这是MeshStandardMaterial(网格标准材质)的扩展材质,为光线反射计算模型提供了更多的控制
MeshToonMaterial(网格卡通材质) 这是MeshPhongMaterial(网格Phong材质)的扩展材质,使得物体渲染卡通化
ShadowMaterial(阴影材质) 专门用于接收用于阴影图的特殊材质。在该材质中只有阴影图像,非阴影部分为完全透明的区域
shaderMaterial(着色器材质) 允许使用自定义的着色器程序,直接控制顶点的放置位置和像素的着色方式
LineBasicMaterial(直线基础材质) 用于THREE.line(直线)几何体,用于创建着色的直线
LineDashMaterial(虚线材质) 这种材质和LineBasicMaterial(直线基础材质)一样,但是允许创建一种虚线的效果。

材质的共有属性

基础属性

属性 描述
id(标识符) 用于识别材质,并在创建材质时赋值。从0开始往上增加
uuid(通用唯一识别符) 这是生成的唯一ID,在内部使用
name(名称) 可以通过这个属性赋予材质名称,用于调试的目的
opacity(不透明度) 定义物体的透明度。与transparent属性一起使用。该属性的赋值范围从0到1
transparent(是否透明) 如果设置为true,Three.js会使用指定的不透明度渲染物体,如果设置为false,这个物体就不透明。如果使用alpha(透明度)通道的纹理,这个值应该设置为true。
overdraw(过度描绘) 当使用THREE.CanvasRender时,多边形会被渲染的稍微大一点。当这个渲染器渲染的物体有间隙时,可以将这个属性设置为true。
visible(是否可见) 定义该材质是否可见。如果设置为false,该物体在场景中就不可见
side(侧面) 可以定义哪一面应用材质。默认值是THREE.FontSide(前面)。
needsUpdate(是否更新) 对于材质的某些修改,是否告诉THREE.js材质已经改变,如果设置为true,Three.js会使用新的材质属性更新它的缓存
colorWrite(是否输出颜色) 如果属性值设置为false,则具有该材质的物体不会被真正的绘制到场景中,实际效果是该物体本身是不可见的,但其他物体被它挡住的部分也不可见
flatShading(平面着色) 该属性控制物体表面法线的生成方式,从而影响光线效果。属性值为true时,在两个相邻但不共面的三角形之间,光照会因为生硬过度而产生棱角,为false时则会产生平滑的过度效果。
lights 该属性值为布尔值。控制物体表面是否接受光照。默认值为true
premultipliedAlpha(预计算Alpha混合) 该属性控制半透明表面的混合方式。默认值为false
dithering(抖动) 该属性控制是否启用颜色抖动模式,在一定程度上可以减轻颜色不均的问题。默认值为false
shadowSide(投影面) 控制哪个面会投射阴影。默认值为null。当属性值为null时。投射阴影的面按照如下原则推定:当side为THREE.FrontSide时,side为后面;当side为THREE.BackSide时,side为前面当side为THREE.DoubleSide时,side为前面双侧
vertexColors(顶点颜色) 可以为物体的每一个顶点指定特有的颜色。默认值是THREE.NoColors。如果设置属性值为THREE.VertexColors,则渲染时将使用THREE.Face3 vertexColors数组指定的颜色,为每一个顶点设定颜色。如果该属性值为THREE.FaceColors,则会使用每一个面自己的颜色属性来设定面的颜色。CanvasRenderer不支持该属性,但WebGLRenderer能够支持
fog(雾) 控制材质是否受雾的影响

融合属性

  • 融合属性决定了我们渲染的颜色如何与它们后面的颜色交互
    属性 描述
    blending(融合) 该属性决定了物体上的材质如何与背景融合。一般的融合模式是THREE.NormalBlending,在这种模式下只显示材质的上层
    blendSrc(融合源) 除了标准融合模式之外,还可以通过设置blendSrc、blendDst和blendEquation来创建自定义的融合模式。这个属性定义了物体(源)如何与背景(目标)相融合。默认值为THREE.SrcAlphaFactor,即使用alpha(透明度通道)进行融合。
    blendSrcAlpha(融合源透明度) 该属性为blendSrc指定透明度,默认值为null
    blendDst(融合目标) 定义了融合时如何使用背景(目标),默认值为THREE.OneMinusSrcAlphaFactor,其含义是目标也使用源的alpha通道进行融合,只是使用的值是1(源的alpha通道值)
    blendDstAlpha(融合目标透明度) 该属性为blendDst指定透明度,默认值为null
    blendEquation(融合公式) 定义如何使用不blendSrc和blendDst的值。默认值为使它们相加(AddEquation)。

高级属性

THREE.MeshBasicMaterial

简介

  1. 一种简单的材质,不考虑场景中光照的影响
  2. 使用这种材质网格会被渲染成简单的平面多边形
  3. 可以显示几何体的线框

属性

名称 描述
color(颜色) 设置材质的颜色
wireframe(线框) 设置这个属性可以将材质渲染成线框,适用于调试
wireframeLinewidth(线框线宽) 如果打开了wireframe,这个属性定义线框中线的宽度
wireframeLinecap(线框线段端点) 这个属性定义了线框模式下顶点间线段的端点如何显示。可选的值包括butt(平)、round(圆)和square(方)。默认值是round。在实际使用中,这个属性的修改结果很难看出来。WebGLRenderer对象不支持该属性。
wireframeLinejoin(线框线段连接点) 定义了线段连接点如何显示。可选的值有round(圆)、bevel(斜角)、miter(尖角)。默认值为round。WebGLRenderer对象不支持该属性。

案例

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
camera.position.set(-20,50,40);
camera.lookAt(new THREE.Vector3(10,0,0));
scene.add(camera);
}
//渲染器
var webGLRenderer,canvasRenderer,renderer;
function initRenderer() {
webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000000));
webGLRenderer.setSize(window.innerWidth,window.innerHeight);
webGLRenderer.shadowMap.enabled = true;

canvasRenderer = new THREE.CanvasRenderer();
canvasRenderer.setSize(window.innerWidth,window.innerHeight);

renderer = webGLRenderer;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}
//光源
function initLight(){
var ambientLight = new THREE.AmbientLight(0x0c0c0c);
scene.add(ambientLight);

var spotLight = new THREE.SpotLight("#ffffff");
spotLight.position.set(-40,60,-10);
spotLight.castShadow = true;
spotLight.shadow.mapSize = new THREE.Vector2(1024,1024);
spotLight.shadow.camera.far = 130;
spotLight.shadow.camera.near = 40;
scene.add(spotLight);
}

//模型
var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;
function initModels(){
//坐标轴
axes = new THREE.AxesHelper(50);
scene.add(axes);
//材质
meshMaterial = new THREE.MeshBasicMaterial({
color:0x7777ff,
name: "Basic Material",
flatShading: true,
});
//地面
var groundGeometry = new THREE.PlaneGeometry(100,100,4,4);
var groundMaterial = new THREE.MeshBasicMaterial({
color:0x777777,
})
ground = new THREE.Mesh(groundGeometry,groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -20;
ground.receiveShadow = true;
scene.add(ground);

//立方体
var cubeGeometry = new THREE.CubeGeometry(15,15,15);
cube = new THREE.Mesh(cubeGeometry,meshMaterial);
cube.position.set(0,3,2);
cube.castShadow = true;
scene.add(cube);

//球
var sphereGeometry = new THREE.SphereGeometry(14,20,20);
sphere = new THREE.Mesh(sphereGeometry,meshMaterial);
sphere.position.set(0,3,2);
sphere.castShadow = true;
// scene.add(sphere);

//平面
var planeGeometry = new THREE.PlaneGeometry(14,14,4,4);
plane = new THREE.Mesh(planeGeometry,meshMaterial);
plane.position = sphere.position;

selectedMesh = cube;

}

//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}

function render() {
renderer.render(scene,camera);
}

function animate(){
stats.update();

selectedMesh.rotation.y +=0.02;
render();

requestAnimationFrame(animate);
}

//窗口响应式布局
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var MBM,MaterialGui,gui;
function initMBM(){
MBM = cube.material;
MBM = new function(){
this.id = cube.id;
this.uuid = cube.uuid;
this.name = "Basic Material";
this.opacity = meshMaterial.opacity;
this.transparent = meshMaterial.transparent;
this.overdraw = meshMaterial.overdraw;
this.visible = meshMaterial.visible;
this.side = "FrontSide";
this.colorWrite = meshMaterial.colorWrite;
this.flatShading = meshMaterial.flatShading;
this.premultipliedAlpha = meshMaterial.premultipliedAlpha;
this.dithering = meshMaterial.dithering;
this.shadowSide = "";
this.vertexColors = "NoColors";
this.fog = true;

this.color = meshMaterial.color.getStyle();
this.wireframe = meshMaterial.wireframe;
this.wireframeLinewidth = 6.9;
this.wireframeLinecap = "round";
this.wireframeLinejoin = "round";

this.switchRenderer = function () {
if (renderer instanceof THREE.WebGLRenderer) {
renderer = canvasRenderer;
document.getElementById("webgl-output").innerHTML = '';
document.getElementById("webgl-output").appendChild(renderer.domElement);
} else {
renderer = webGLRenderer;
document.getElementById("webgl-output").innerHTML = '';
document.getElementById("webgl-output").appendChild(renderer.domElement);
}
}
this.selectedMesh = "cube";
};
gui = new dat.GUI();
MaterialGui = gui.addFolder("THREE.Material");
MaterialGui.add(MBM,'id');
MaterialGui.add(MBM,'uuid');
MaterialGui.add(MBM,'name');
MaterialGui.add(MBM,'opacity',0,1,0.01).onChange(function (e){
meshMaterial.opacity = e;
});
MaterialGui.add(MBM,'transparent').onChange(function (e){
meshMaterial.transparent = e;
});
MaterialGui.add(MBM,'overdraw',0,1,0.01).onChange(function (e){
meshMaterial.overdraw = e;
});
MaterialGui.add(MBM,'visible').onChange(function (e){
meshMaterial.visible = e;
});
MaterialGui.add(MBM,'side',{FrontSide:0,BackSide:1,BothSides:2}).onChange(function (e){
meshMaterial.side = parseInt(e);
});
MaterialGui.add(MBM,'colorWrite').onChange(function (e){
meshMaterial.colorWrite = e;
});
MaterialGui.add(MBM,'flatShading').onChange(function (e){
meshMaterial.flatShading = e;
meshMaterial.needsUpdate = true;
});
MaterialGui.add(MBM,'premultipliedAlpha').onChange(function (e){
meshMaterial.premultipliedAlpha = e;
});
MaterialGui.add(MBM,'dithering').onChange(function (e){
meshMaterial.dithering = e;
});
MaterialGui.add(MBM,'shadowSide',{FrontSide:0,BackSide:1,BothSide:2}).onChange(function (e){
meshMaterial.shadowSide = parseInt(e);
});
MaterialGui.add(MBM,'vertexColors', {NoColors: THREE.NoColors, FaceColors: THREE.FaceColors,
VertexColors: THREE.VertexColors}).onChange(function (vertexColors) {
meshMaterial.vertexColors = parseInt(vertexColors);
});
MaterialGui.add(MBM,'fog');
//THREE.MeshBasicMaterialGUI
var MeshBasicMaterialGUI = gui.addFolder("THREE.MeshBasicMaterial");

MeshBasicMaterialGUI.addColor(MBM,'color').onChange(function (e) {
meshMaterial.color.getStyle(e);
});
MeshBasicMaterialGUI.add(MBM,'wireframe').onChange(function (e) {
meshMaterial.wireframe = e;
});
MeshBasicMaterialGUI.add(MBM,'wireframeLinewidth',0,20).onChange(function (e){
meshMaterial.wireframeLinewidth = e;
});
MeshBasicMaterialGUI.add(MBM,'wireframeLinecap',['butt','round','square']).onChange(function (e) {
meshMaterial.wireframeLinecap = e;
});
MeshBasicMaterialGUI.add(MBM,'wireframeLinejoin',['round','bevel','miter']).onChange(function (e) {
meshMaterial.wireframeLinejoin = e;
});

gui.add(MBM,'switchRenderer');

gui.add(MBM,'selectedMesh',['cube','sphere','plane']).onChange(function (e){
scene.remove(cube);
scene.remove(sphere);
scene.remove(plane);

switch (e){
case 'cube':
scene.add(cube);
selectedMesh = cube;
break;
case 'sphere':
scene.add(sphere);
selectedMesh = sphere;
break;
case 'plane':
scene.add(plane);
selectedMesh = plane;
break;
}
});
gui.domElement.style.position = 'absolute';
gui.domElement.style.right = "500px";
}


function draw(){
initScene();
initCamera();
initRenderer();
initStats();
initLight();
initModels();
initControls();
initMBM();

animate();
window.onresize = windowOnresize;
}

💡这个例子里可以设置side属性,通过这个属性可以指定THREE.Geometry对象的哪个面应用材质。可以通过选择plane(平面)网格验证该属性。side属性设置为FrontSide或者BackSide时,平面旋转时会有一半的时间看不见。side属性设置为BothSides时,这个平面始终都能看见(因为几何体两面都有材质)。设置为BothSides时,由于两面都有材质,所以渲染需要做更多的工作,对场景的性能会有影响。

THREE.MeshDepthMaterial

简介

  1. 这个材质使用摄像机到网格的距离来决定如何给网格上色
  2. 可以将这种材质和其他材质结合,容易创建出逐渐消失的效果。
  3. 这种材质只有两个控制线框显示的属性

属性

属性 描述
wireframe 该属性指定是否显示线框
wireframeLineWidth 该属性指定线框的宽度(这个属性支队THREE.CanvasRenderer有效)

案例

描述:用THREE.MeshDepthMaterial材质渲染方块,基于摄像机的距离上色,点击addCube添加方块、removeCube移除方块(从最后一个添加进场景的方块开始移除)、自定义方块旋转速度、控制摄像机的近面距离和远面距离。

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
//overrideMaterial设置所有物体材质的属性
scene.overrideMaterial = new THREE.MeshDepthMaterial();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,50,110);
camera.position.set(-50,40,50);
camera.lookAt(scene.position);
scene.add(camera);
}

//渲染器
var renderer;
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}

//初始化性能
var stats;
function initStats() {
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}

//窗口响应式布局
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var MDM,gui,rotationSpeed;
function initGUI(){
MDM = new function(){
this.rotationSpeed = 0.02;
this.cameraNear = camera.near;
this.cameraFar = camera.far;

this.removeCube = function (){
var allChildren = scene.children;
var lastChild = allChildren[allChildren.length - 1];
if(lastChild instanceof THREE.Mesh){
scene.remove(lastChild);
}
}

this.addCube = function () {
var cubeSize = Math.ceil(3+(Math.random()*3));
var cubeGeometry = new THREE.BoxGeometry(cubeSize,cubeSize,cubeSize);
var cubeMaterial = new THREE.MeshLambertMaterial({
color:Math.random()*0xffffff
});
var cube = new THREE.Mesh(cubeGeometry,cubeMaterial);
cube.castShadow = true;
cube.position.x = -60+Math.round((Math.random()*100));
cube.position.y = Math.round((Math.random()*10));
cube.position.z = -100+Math.round((Math.random()*150));

scene.add(cube);
}
}

gui = new dat.GUI();
gui.add(MDM,'removeCube');
gui.add(MDM,'addCube');
gui.add(MDM,'cameraNear',0,100).onChange(function (e){
camera.near = e;
camera.updateProjectionMatrix();
});
gui.add(MDM,'cameraFar',0,200).onChange(function(e){
camera.far = e;
camera.updateProjectionMatrix();
});
gui.domElement.style.position = "absolute";
gui.domElement.style.right = "500px";

var i = 0;
while (i < 10) {
MDM.addCube();
i++;
}

}

function initModel() {

//辅助工具
var helper = new THREE.AxisHelper(10);
scene.add(helper);

var s = 25;

var cube = new THREE.CubeGeometry(s, s, s);
var material = new THREE.MeshDepthMaterial();

for (var i = 0; i < 3000; i++) {

var mesh = new THREE.Mesh(cube, material);

mesh.position.x = 800 * ( 2.0 * Math.random() - 1.0 );
mesh.position.y = 800 * ( 2.0 * Math.random() - 1.0 );
mesh.position.z = 800 * ( 2.0 * Math.random() - 1.0 );

mesh.rotation.x = Math.random() * Math.PI;
mesh.rotation.y = Math.random() * Math.PI;
mesh.rotation.z = Math.random() * Math.PI;

mesh.updateMatrix();

scene.add(mesh);

}

}
//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}
function render(){
renderer.render(scene,camera);
}

function animate(){
render();

stats.update();

//遍历场景中mesh对象。
scene.traverse(function(e){
if(e instanceof THREE.Mesh){
e.rotation.x += 0.02;
e.rotation.y += 0.02;
e.rotation.z += 0.02;
}
});

requestAnimationFrame(animate);
}

function draw(){
initScene();
initCamera();
initRenderer();
initStats();
initGUI();
initControls();

animate();
window.onresize = windowOnresize;
}

联合材质

简介

  1. THREE.MeshDepthMaterial材质没有用来设置颜色的属性,一切都是由材质的默认属性决定
  2. THREE.js库可以通过联合材质创建出可以拥有颜色,同时也是THREE.MeshDepthMaterial材质的物体
  3. 如何联合材质?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //创建两种材质
    var cubeMaterial = new THREE.MeshDepthMaterial();
    //对于THREE.MeshBasicMaterial材质,需要设置transparent为true,并且指定一个融合模式
    //如果transparent没有设置为true,只会出现纯绿色物体,因为three.js不会执行任何融合模式
    var colorMaterial = new THREE.MeshBasicMaterial({
    color:0xffffff,
    transparent:true,
    //设置THREE.MeshBasicMaterial如何与背景(THREE.MeshDepthMaterial材质渲染的方块)互相作用
    //THREE.MultiplyBlending对象,这种融合模式会把前景色和背景色相乘
    blending:THREE.MultiplyBlending
    });

    var cube = new THREE.SceneUtils.createMultiMaterialObject(cubeGeometry,[colorMaterial,cubeMaterial]);
    cube.children[1].scale.set(0.99,0.99,0.99);
    //当调用THREE.SceneUtils.createMultiMaterialObject()方法创建一个网格时,几何体会被复制,返回一个网格组
    //如果没有最后一行,渲染的时候画面会有闪烁
    //通过缩小THREE.MeshDepthMaterial材质的网格,可以避免闪烁现象
    这些方块从MeshDepthMaterial对象获得了亮度,从MeshBasicMaterial对象获得了颜色

案例

描述

在上述THREE.MeshDepthMaterial案例中,使用联合材质方法,给方块添加颜色

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,50,110);
camera.position.set(-50,40,50);
camera.lookAt(scene.position);
scene.add(camera);
}
//渲染器
var renderer;
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}

var stats;
function initStats() {
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}

//窗口响应式布局
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var MDM,gui,rotationSpeed;
function initGUI(){
MDM = new function(){
this.rotationSpeed = 0.02;
this.cameraNear = camera.near;
this.cameraFar = camera.far;
this.color = 0x00ff00;

this.removeCube = function (){
var allChildren = scene.children;
var lastChild = allChildren[allChildren.length - 1];
//判断时不再是Mesh对象,而是判断是否是group对象
if(lastChild instanceof THREE.Group){
scene.remove(lastChild);
}
}

this.addCube = function () {
//随机大小
var cubeSize = Math.ceil(3+(Math.random()*3));
//定义立方体几何
var cubeGeometry = new THREE.BoxGeometry(cubeSize,cubeSize,cubeSize);
//定义一种材质MeshDepthMaterial
var cubeMaterial = new THREE.MeshDepthMaterial();
//定义第二种材质MeshBasicMaterial
var colorMaterial = new THREE.MeshBasicMaterial({
// 颜色
color: MDM.color,
// 是否透明
transparent: true,
// 融合模式THREE.MultiplyBlending模式会把前景色和背景色融合
blending: THREE.MultiplyBlending
});
var cube = new THREE.SceneUtils.createMultiMaterialObject(cubeGeometry,[colorMaterial,cubeMaterial]);
cube.children[1].scale.set(0.99,0.99,0.99);
cube.castShadow = true;
cube.position.x = -60+Math.round((Math.random()*100));
cube.position.y = Math.round((Math.random()*10));
cube.position.z = -100+Math.round((Math.random()*150));

scene.add(cube);
}
}

gui = new dat.GUI();
gui.addColor(MDM,'color');
gui.add(MDM,'removeCube');
gui.add(MDM,'addCube');
gui.add(MDM,'cameraNear',0,100).onChange(function (e){
camera.near = e;
camera.updateProjectionMatrix();
});
gui.add(MDM,'cameraFar',0,200).onChange(function(e){
camera.far = e;
camera.updateProjectionMatrix();
});
gui.domElement.style.position = "absolute";
gui.domElement.style.right = "500px";
//初始页面时先增加几个立方体
var i = 0;
while (i < 10) {
MDM.addCube();
i++;
}

}

//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}
function render(){
renderer.render(scene,camera);
}

function animate(){
render();

stats.update();

//遍历场景中Group对象。旋转立方体
scene.traverse(function(e){
if(e instanceof THREE.Group){
e.rotation.x += 0.02;
e.rotation.y += 0.02;
e.rotation.z += 0.02;
}
});

requestAnimationFrame(animate);
}

function draw(){
initScene();
initCamera();
initRenderer();
initStats();
initGUI();
initControls();

animate();
window.onresize = windowOnresize;
}

THREE.MeshNormalMaterial

简介

  1. 使用法向向量计算物体的表面颜色
  2. 法向量在three.js中可以用来决定光的反射,有助于将纹理映射到三维模型,并提供有关如何计算光照、阴影和为表面像素着色的信息

属性

属性 描述
wireframe 该属性指定是否显示线框
wireframeLineWidth 该属性指定线框的宽度(这个属性支队THREE.CanvasRenderer有效)

案例

描述

选择sphere(球体)作为网格,当flatshading属性设置为true时,网格的每一个面渲染的颜色都不同,即使球体在旋转时,这些颜色也基本保持在原来的位置(每个面的颜色都是由从该面向外指的法向量计算得到的)。

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
camera.position.set(-20,50,40);
camera.lookAt(new THREE.Vector3(10,0,0));
scene.add(camera);
}
//渲染器
var webGLRenderer,canvasRenderer,renderer;
function initRenderer() {
webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000000));
webGLRenderer.setSize(window.innerWidth,window.innerHeight);
webGLRenderer.shadowMap.enabled = true;

canvasRenderer = new THREE.CanvasRenderer();
canvasRenderer.setSize(window.innerWidth,window.innerHeight);

renderer = webGLRenderer;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}
//光源
function initLight(){
var ambientLight = new THREE.AmbientLight(0x0c0c0c);
scene.add(ambientLight);

var spotLight = new THREE.SpotLight("#ffffff");
spotLight.position.set(-40,60,-10);
spotLight.castShadow = true;
spotLight.shadow.mapSize = new THREE.Vector2(1024,1024);
spotLight.shadow.camera.far = 130;
spotLight.shadow.camera.near = 40;
scene.add(spotLight);
}

//模型
var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;
function initModels(){
//坐标轴
axes = new THREE.AxesHelper(50);
scene.add(axes);
//材质
meshMaterial = new THREE.MeshNormalMaterial()
//地面
var groundGeometry = new THREE.PlaneGeometry(100,100,4,4);
var groundMaterial = new THREE.MeshBasicMaterial({
color:0x777777,
})
ground = new THREE.Mesh(groundGeometry,groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -20;
ground.receiveShadow = true;
scene.add(ground);

//立方体
var cubeGeometry = new THREE.CubeGeometry(15,15,15);
cube = new THREE.Mesh(cubeGeometry,meshMaterial);
cube.position.set(0,3,2);
cube.castShadow = true;
scene.add(cube);

//球
var sphereGeometry = new THREE.SphereGeometry(14,20,20);
sphere = new THREE.Mesh(sphereGeometry,meshMaterial);
sphere.position.set(0,3,2);
sphere.castShadow = true;
// scene.add(sphere);

//平面
var planeGeometry = new THREE.PlaneGeometry(14,14,4,4);
plane = new THREE.Mesh(planeGeometry,meshMaterial);
plane.position = sphere.position;

selectedMesh = cube;

}

//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}

function render() {
renderer.render(scene,camera);
}

function animate(){
stats.update();

selectedMesh.rotation.y += 0.02;

render();
requestAnimationFrame(animate);
}

//窗口自适应
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var MBM,MaterialGui,gui;
function initMBM(){
MBM = cube.material;
MBM = new function(){
this.selectedMesh = "cube";
};

gui = new dat.GUI();
addBasicMaterialSettings(gui,MBM,meshMaterial);

gui.add(MBM,'selectedMesh',['cube','sphere','plane']).onChange(function (e){
scene.remove(cube);
scene.remove(sphere);
scene.remove(plane);

switch (e){
case 'cube':
scene.add(cube);
selectedMesh = cube;
break;
case 'sphere':
scene.add(sphere);
selectedMesh = sphere;
break;
case 'plane':
scene.add(plane);
selectedMesh = plane;
break;
}
});
gui.domElement.style.position = 'absolute';
gui.domElement.style.right = "500px";
}


function draw(){
initScene();
initCamera();
initRenderer();
initStats();
initLight();
initModels();
initControls();
initMBM();

animate();
window.onresize = windowOnresize;
}

在单几何体上使用多种材质

  1. 如果有一个方块,有12个面(注意,three.js中只作用于三角形)。你可以用这种材质给方块的每个面指定一种材质(例如不同的颜色)

案例

描述

  1. 先创建一个THREE.Mesh对象,用来保存所有的方块
  2. 创建一个数组,用来保存所有材质(12个面,只需6个材质,每一个材质应用于一整个面)
  3. 建立三个循环,以保证创建出正确数量的方块
  4. 在循环中,创建每一个方块,赋予材质和定位,并把他们添加到组中

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
camera.position.set(-50,40,50);
camera.lookAt(scene.position);
scene.add(camera);
}
//渲染器
var renderer;
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}

var stats;
function initStats() {
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}

//窗口响应式布局
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var group;
function models(){
//创建一个THREE.Mesh对象,用来保存所有方块
group = new THREE.Mesh();
//创建一个数组
var mats = [];
//为每个面创建一个材质,并将它们保存在数组中
mats.push(new THREE.MeshBasicMaterial({color:0x009e60}));
mats.push(new THREE.MeshBasicMaterial({color:0x0051ba}));
mats.push(new THREE.MeshBasicMaterial({color:0xffd500}));
mats.push(new THREE.MeshBasicMaterial({color:0xff5800}));
mats.push(new THREE.MeshBasicMaterial({color:0xC41E3A}));
mats.push(new THREE.MeshBasicMaterial({color:0xffffff}));
//建立三个循环,以保证创建出正确数量的方块
for(var i=0; i<3; i++){
for(var j=0;j<3;j++){
for (var z=0;z<3; z++){
//创建方块几何
var geom = new THREE.BoxGeometry(2.9,2.9,2.9)
//创建方块
var cube = new THREE.Mesh(geom,mats);
//设置方块位置定位
cube.position.set(i * 3 - 3, j * 3 - 3, z * 3 - 3);
//将方块添加到THREE.Mesh对象中
group.add(cube);
}
}
}
//THREE.Mesh对象的放大
group.scale.copy(new THREE.Vector3(2,2,2));
scene.add(group);
}

var MFM,gui,rotationSpeed;
function initGUI(){
MFM = new function(){
this.rotationSpeed = 0.02;
}
gui = new dat.GUI();
gui.add(MFM,'rotationSpeed',0,2);
gui.domElement.style.position = 'absolute';
gui.domElement.style.right = "500px";
}

//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}
function render(){
renderer.render(scene,camera);
}

function animate(){
render();

stats.update();

group.rotation.y += MFM.rotationSpeed;
group.rotation.x += MFM.rotationSpeed;
group.rotation.z += MFM.rotationSpeed;

requestAnimationFrame(animate);
}

function draw(){
initScene();
initCamera();
initRenderer();
initStats();
models();
initGUI();
initControls();

animate();
window.onresize = windowOnresize;
}

THREE.MeshLambertMaterial

简介

  1. 用来创建暗淡的并不光亮的表面
  2. 该材质非常易用,而且会对场景中的光源产生反应
  3. 支持线框绘制属性,可以绘制具有光照效果的线框物体
  4. 效果看起来比较暗淡

属性

名称 描述
color(颜色) 这是材质的环境光
emissive(自发光) 材质自发光的颜色。该材质虽然不会让使用它的物体变成光源,但会使物体的颜色不受其他光源的影响(即使在场景中没有光源的暗处,该物体表面的emissive颜色也可见,从而实现物体自发光。)该属性的默认值为黑色。

创建

var meshLambertMaterial = new THREE.MeshLambertMaterial({color:0xffffff});

案例

描述

  1. 通过改变标签可以自定义创建一个橘黄色,带有轻微自发光的效果的物体

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
camera.position.set(-20,50,40);
camera.lookAt(new THREE.Vector3(10,0,0));
scene.add(camera);
}
//渲染器
var webGLRenderer,canvasRenderer,renderer;
function initRenderer() {
webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000000));
webGLRenderer.setSize(window.innerWidth,window.innerHeight);
webGLRenderer.shadowMap.enabled = true;

canvasRenderer = new THREE.CanvasRenderer();
canvasRenderer.setSize(window.innerWidth,window.innerHeight);

renderer = webGLRenderer;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}
//光源
function initLight(){
//环境光
var ambientLight = new THREE.AmbientLight(0x0c0c0c);
scene.add(ambientLight);
//据光源
var spotLight = new THREE.SpotLight("#ffffff");
spotLight.position.set(-40,60,-10);
spotLight.castShadow = true;
spotLight.shadow.mapSize = new THREE.Vector2(1024,1024);
spotLight.shadow.camera.far = 130;
spotLight.shadow.camera.near = 40;
scene.add(spotLight);
}

//模型
var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;
function initModels(){
//坐标轴
axes = new THREE.AxesHelper(50);
scene.add(axes);
//创建THREE.MeshLambertMaterial材质
meshMaterial = new THREE.MeshLambertMaterial({
color:0x7777ff,
})
//地面
var groundGeometry = new THREE.PlaneGeometry(100,100,4,4);
var groundMaterial = new THREE.MeshBasicMaterial({
color:0x777777,
})
ground = new THREE.Mesh(groundGeometry,groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -20;
ground.receiveShadow = true;
scene.add(ground);

//立方体
var cubeGeometry = new THREE.CubeGeometry(15,15,15);
cube = new THREE.Mesh(cubeGeometry,meshMaterial);
cube.position.set(0,3,2);
cube.castShadow = true;
scene.add(cube);

//球
var sphereGeometry = new THREE.SphereGeometry(14,20,20);
sphere = new THREE.Mesh(sphereGeometry,meshMaterial);
sphere.position.set(0,3,2);
sphere.castShadow = true;
// scene.add(sphere);

//平面
var planeGeometry = new THREE.PlaneGeometry(14,14,4,4);
plane = new THREE.Mesh(planeGeometry,meshMaterial);
plane.position = sphere.position;

selectedMesh = cube;

}

//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}

function render() {
renderer.render(scene,camera);
}

function animate(){
stats.update();

selectedMesh.rotation.y += 0.02;

render();
requestAnimationFrame(animate);
}

//窗口自适应
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var MLM,gui;
function initMBM(){
MLM = cube.material;
MLM = new function(){
this.selectedMesh = "cube";
//材质颜色
this.color = meshMaterial.color.getStyle();
//材质自发光颜色
this.emissive = meshMaterial.emissive.getHex();
//材质是否显示线框模式
this.wireframe = false;
//材质线框的宽度,在WebGLRenderer下无反应
this.wireframeLinewidth = 1;
};

gui = new dat.GUI();
addBasicMaterialSettings(gui,MLM,meshMaterial);

var MLMGUI = gui.addFolder("THREE.MeshLambertMaterial")
MLMGUI.addColor(MLM,'color').onChange(function(e){
meshMaterial.color.setStyle(e);
});
MLMGUI.addColor(MLM, 'emissive').onChange(function (e) {
meshMaterial.emissive = new THREE.Color(e);
});
MLMGUI.add(MLM,'wireframe').onChange(function (e) {
meshMaterial.wireframe = e;
});
MLMGUI.add(MLM,'wireframeLinewidth',0,20).onChange(function (e) {
meshMaterial.wireframeLinewidth = e;
});
loadGopher(meshMaterial).then(function(gopher){
gopher.scale.set(4,4,4);
gui.add(MLM,'selectedMesh',['cube','sphere','plane','gopher']).onChange(function (e){
scene.remove(cube);
scene.remove(sphere);
scene.remove(plane);
scene.remove(gopher);

switch (e){
case 'cube':
scene.add(cube);
selectedMesh = cube;
break;
case 'sphere':
scene.add(sphere);
selectedMesh = sphere;
break;
case 'plane':
scene.add(plane);
selectedMesh = plane;
break;
case 'gopher':
scene.add(gopher);
selectedMesh = gopher;
break;
}
});
});

gui.domElement.style.position = 'absolute';
gui.domElement.style.right = "500px";
}


function draw(){
initScene();
initCamera();
initRenderer();
initStats();
initLight();
initModels();
initControls();
initMBM();

animate();
window.onresize = windowOnresize;
}

THREE.MeshPhongMaterial

简介

  1. 创建一种光亮的材质
  2. 可以在物体表面实现高光效果
  3. 可以模拟塑料质感,也可以模拟金属质
  4. THREE.js还提供了一个THREE.MeshPhongMaterial材质的扩展材质:THREE.MeshToonMaterial

属性

名称 描述
color(颜色) 材质的环境色。与环境光源一起使用。这个颜色会与环境光提供的颜色相乘。默认值为白色
emissive(自发光颜色) 材质自发光的颜色。该材质虽然不会让使用它的物体变成光源,但会使物体的颜色不受其他光源的影响(即使在场景中没有光源的暗处,该物体表面的emissive颜色也可见,从而实现物体自发光。)该属性的默认值为黑色。
specular(高光颜色) 该属性指定该材质的光亮程度及高光部分颜色。如果将它设置成与color属性相同的颜色,将会得到一个类似金属的材质,如果将它设置成灰色(grey),材质将会变得像塑料
shiness(高光度) 该属性指定物体表面镜面高光部分的轮廓的清晰程度,越光滑的表面,高光部分越清晰,反之越模糊。该属性的默认值为30

创建

var meshPhongMaterial = new THREE.MeshPhongMaterial({color: 0xdddddd});

案例

描述

  1. 创建THREE.MeshPhongMaterial材质
  2. 可以通过GUI控制界面实验这个材质,随意改变材质参数,实现金属材质感和塑料材质感

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
camera.position.set(-30,40,30);
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
}
//渲染器
var webGLRenderer,canvasRenderer,renderer;
function initRenderer() {
webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000000));
webGLRenderer.setSize(window.innerWidth,window.innerHeight);
webGLRenderer.shadowMap.enabled = true;

canvasRenderer = new THREE.CanvasRenderer();
canvasRenderer.setSize(window.innerWidth,window.innerHeight);

renderer = webGLRenderer;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}
//光源
function initLight(){
//环境光
var ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
//聚光源
var spotLight = new THREE.SpotLight("#ffffff");
//光源位置
spotLight.position.set(-0,30,60);
//是否启用光源阴影
spotLight.castShadow = true;
//光源强度
spotLight.intensity = 0.6
//光源阴影大小
spotLight.shadow.mapSize = new THREE.Vector2(1024,1024);
//光源阴影投射远点
spotLight.shadow.camera.far = 130;
//光源阴影投射近点
spotLight.shadow.camera.near = 40;
//将光源添加进场景
scene.add(spotLight);
}

//模型
var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;
function initModels(){
//坐标轴
axes = new THREE.AxesHelper(50);
scene.add(axes);
//创建MeshStandardMaterial材质
meshMaterial = new THREE.MeshStandardMaterial({
color:0x7777ff,
})
//地面
var groundGeometry = new THREE.PlaneGeometry(100,100,4,4);
var groundMaterial = new THREE.MeshBasicMaterial({
color:0x777777,
})
ground = new THREE.Mesh(groundGeometry,groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -20;
ground.receiveShadow = true;
scene.add(ground);

//立方体
var cubeGeometry = new THREE.CubeGeometry(15,15,15);
cube = new THREE.Mesh(cubeGeometry,meshMaterial);
cube.position.set(0,3,2);
cube.castShadow = true;
scene.add(cube);

//球
var sphereGeometry = new THREE.SphereGeometry(14,20,20);
sphere = new THREE.Mesh(sphereGeometry,meshMaterial);
sphere.position.set(0,3,2);
sphere.castShadow = true;
// scene.add(sphere);

//平面
var planeGeometry = new THREE.PlaneGeometry(14,14,4,4);
plane = new THREE.Mesh(planeGeometry,meshMaterial);
plane.position = sphere.position;

selectedMesh = cube;

}

//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}

function render() {
renderer.render(scene,camera);
}

function animate(){
stats.update();

selectedMesh.rotation.y += 0.02;

render();
requestAnimationFrame(animate);
}

//窗口自适应
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var MPM,MaterialGui,gui;
function initMBM(){
MPM = meshMaterial;
MPM = new function(){
this.selectedMesh = "cube";
//材质颜色
this.color = meshMaterial.color.getStyle();
//材质自发光颜色
this.emissive = meshMaterial.emissive.getStyle();
//材质金属感程度
this.metalness = meshMaterial.metalness;
//材质粗糙程度
this.roughness = meshMaterial.roughness;
//线框模式
this.wireframe = false;
//线框宽度,WebGLRenderer下不支持
this.wireframeLinewidth = 1;
};

gui = new dat.GUI();
addBasicMaterialSettings(gui,MPM,meshMaterial);

var MPMGUI = gui.addFolder("THREE.MeshLambertMaterial")
MPMGUI.addColor(MPM,'color').onChange(function(e){
meshMaterial.color.setStyle(e);
});
MPMGUI.addColor(MPM, 'emissive').onChange(function (e) {
meshMaterial.emissive.setStyle(e);
});
MPMGUI.add(MPM,'metalness',0,1,0.01).onChange(function (e) {
meshMaterial.metalness = e;
});
MPMGUI.add(MPM,'roughness',0,1,0.01).onChange(function (e){
meshMaterial.roughness = e;
});
MPMGUI.add(MPM,'wireframe').onChange(function (e) {
meshMaterial.wireframe = e;
});
MPMGUI.add(MPM,'wireframeLinewidth',0,20).onChange(function (e) {
meshMaterial.wireframeLinewidth = e;
});
loadGopher(meshMaterial).then(function(gopher){
gopher.scale.set(4,4,4);
gui.add(MPM,'selectedMesh',['cube','sphere','plane','gopher']).onChange(function (e){
scene.remove(cube);
scene.remove(sphere);
scene.remove(plane);
scene.remove(gopher);

switch (e){
case 'cube':
scene.add(cube);
selectedMesh = cube;
break;
case 'sphere':
scene.add(sphere);
selectedMesh = sphere;
break;
case 'plane':
scene.add(plane);
selectedMesh = plane;
break;
case 'gopher':
scene.add(gopher);
selectedMesh = gopher;
break;
}
});
});

gui.domElement.style.position = 'absolute';
gui.domElement.style.right = "500px";
}


function draw(){
initScene();
initCamera();
initRenderer();
initStats();
initLight();
initModels();
initControls();
initMBM();

animate();
window.onresize = windowOnresize;
}

THREE.MeshStandardMaterial

简介

  1. 这种材质使用更加正确的物理计算来决定物体表面如何与场景中的光源互动
  2. 能够更好的表现塑料质感和金属质感的表面

属性

名称 描述
metalness(金属感程度) 该属性控制物体表面的金属感程度。0代表完全塑料质感,1代表完全金属质感。默认值为0.5
roughness(粗糙程度) 该属性控制物体表面的粗糙程度。在视觉上,它决定表面对入射光的漫反射程度。默认值0.5。当值为0时会产生类似镜面的反射,为1时会产生完全的漫反射效果

案例

描述

结果

代码

THREE.MeshPhysicalMaterial

简介

  1. 该材质与THREE.MeshPhongMaterial材质非常相似
  2. 提供了对反光度的更多控制
  3. 该材质与上述MeshStandardMaterial材质,在不动手实验的情况下,很难确定什么样的参数值才能最符合特定需求。因此最佳的实践方法就是在程序里增加一个简单的UI,一边调节参数一边观察。

属性

名称 描述
clearCoat(清漆) 该属性控制物体表面清漆涂层效果的明显程度。该属性值越高,则清漆图层越厚,其结果是clearCoatRoughness属性带来的影响越明显。取值范围是0-1,默认值0
clearCoatRoughness(清漆粗糙程度) 该属性控制物体表面清漆涂层的粗糙程度。粗糙程度越高,漫反射越明显。该属性需要与clearCoat属性配合使用。取值范围0-1,默认值0
reflectivity(反光度) 该属性用于控制非金属表面的反光度,因此当metalness(金属感程度)为1或接近1时该属性的作用很小。取值范围时0-1,默认值0.5

案例

描述

  1. 与上述THREE.MeshStandardMaterial材质案例相似。

结果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
camera.position.set(-30,40,30);
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
}
//渲染器
var webGLRenderer,canvasRenderer,renderer;
function initRenderer() {
webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000000));
webGLRenderer.setSize(window.innerWidth,window.innerHeight);
webGLRenderer.shadowMap.enabled = true;

canvasRenderer = new THREE.CanvasRenderer();
canvasRenderer.setSize(window.innerWidth,window.innerHeight);

renderer = webGLRenderer;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}
//光源
function initLight(){
var ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);

var spotLight = new THREE.SpotLight("#ffffff");
spotLight.position.set(-0,30,60);
spotLight.castShadow = true;
spotLight.intensity = 0.6
spotLight.shadow.mapSize = new THREE.Vector2(1024,1024);
spotLight.shadow.camera.far = 130;
spotLight.shadow.camera.near = 40;
scene.add(spotLight);
}

//模型
var axes,cube,plane,meshMaterial,sphere,ground,selectedMesh;
function initModels(){
//坐标轴
axes = new THREE.AxesHelper(50);
scene.add(axes);
//材质
meshMaterial = new THREE.MeshPhysicalMaterial({
color:0x7777ff,
})
//地面
var groundGeometry = new THREE.PlaneGeometry(100,100,4,4);
var groundMaterial = new THREE.MeshBasicMaterial({
color:0x777777,
})
ground = new THREE.Mesh(groundGeometry,groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -20;
ground.receiveShadow = true;
scene.add(ground);

//立方体
var cubeGeometry = new THREE.CubeGeometry(15,15,15);
cube = new THREE.Mesh(cubeGeometry,meshMaterial);
cube.position.set(0,3,2);
cube.castShadow = true;
scene.add(cube);

//球
var sphereGeometry = new THREE.SphereGeometry(14,20,20);
sphere = new THREE.Mesh(sphereGeometry,meshMaterial);
sphere.position.set(0,3,2);
sphere.castShadow = true;
// scene.add(sphere);

//平面
var planeGeometry = new THREE.PlaneGeometry(14,14,4,4);
plane = new THREE.Mesh(planeGeometry,meshMaterial);
plane.position = sphere.position;

selectedMesh = cube;

}

//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls;
function initControls() {

controls = new THREE.OrbitControls( camera, renderer.domElement );

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
}

function render() {
renderer.render(scene,camera);
}

function animate(){
stats.update();

selectedMesh.rotation.y += 0.02;

render();
requestAnimationFrame(animate);
}

//窗口自适应
function windowOnresize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}

var MPM,MaterialGui,gui;
function initMBM(){
MPM = meshMaterial;
MPM = new function(){
this.selectedMesh = "cube";

this.color = meshMaterial.color.getStyle();
this.emissive = meshMaterial.emissive.getStyle();
this.metalness = meshMaterial.metalness;
this.roughness = meshMaterial.roughness;
this.clearCoat = meshMaterial.clearCoat;
this.clearCoatRoughness = meshMaterial.clearCoatRoughness;
this.reflectivity = meshMaterial.reflectivity;
this.wireframe = false;
this.wireframeLinewidth = 1;
};

gui = new dat.GUI();
addBasicMaterialSettings(gui,MPM,meshMaterial);

var MPMGUI = gui.addFolder("THREE.MeshLambertMaterial")
MPMGUI.addColor(MPM,'color').onChange(function(e){
meshMaterial.color.setStyle(e);
});
MPMGUI.addColor(MPM, 'emissive').onChange(function (e) {
meshMaterial.emissive.setStyle(e);
});
MPMGUI.add(MPM,'metalness',0,1,0.01).onChange(function (e) {
meshMaterial.metalness = e;
});
MPMGUI.add(MPM,'roughness',0,1,0.01).onChange(function (e){
meshMaterial.roughness = e;
});
MPMGUI.add(MPM,'clearCoat',0,1,0.01).onChange(function (e) {
meshMaterial.clearCoat = e;
});
MPMGUI.add(MPM,'clearCoatRoughness',0,1,0.01).onChange(function (e) {
meshMaterial.clearCoatRoughness = e;
});
MPMGUI.add(MPM,'reflectivity',0,1,0.01).onChange(function (e){
meshMaterial.reflectivity = e;
});
MPMGUI.add(MPM,'wireframe').onChange(function (e) {
meshMaterial.wireframe = e;
});
MPMGUI.add(MPM,'wireframeLinewidth',0,20).onChange(function (e) {
meshMaterial.wireframeLinewidth = e;
});
loadGopher(meshMaterial).then(function(gopher){
gopher.scale.set(4,4,4);
gui.add(MPM,'selectedMesh',['cube','sphere','plane','gopher']).onChange(function (e){
scene.remove(cube);
scene.remove(sphere);
scene.remove(plane);
scene.remove(gopher);

switch (e){
case 'cube':
scene.add(cube);
selectedMesh = cube;
break;
case 'sphere':
scene.add(sphere);
selectedMesh = sphere;
break;
case 'plane':
scene.add(plane);
selectedMesh = plane;
break;
case 'gopher':
scene.add(gopher);
selectedMesh = gopher;
break;
}
});
});

gui.domElement.style.position = 'absolute';
gui.domElement.style.right = "500px";
}


function draw(){
initScene();
initCamera();
initRenderer();
initStats();
initLight();
initModels();
initControls();
initMBM();

animate();
window.onresize = windowOnresize;
}

粒子和精灵

理解粒子:

  1. 每一个粒子都是面向摄像机的二维平面。

粒子的作用:

  1. 可以非常容易的创建很多细小的物体,可以用来模拟雨滴,雪花,烟等有趣的效果

粒子的创建:

  1. 可以用THREE.Sprite(material)构造函数手工创建粒子。

var material = new THREE.Sprite(material);

material 属性可以是两种:new THREE.SpriteMaterial() 和 new THREE.SpriteCanvasMaterial()

  1. 将sprite添加到场景Scene中。如果没有给粒子指定任何属性,粒子将被渲染成二维的白色小方块。

💡对象THREE.Sprite和THREE.Mesh一样,THREE.Sprite对象也是THREE.Object3D对象的扩展。也就是说THREE.Mesh的大部分属性和函数都可以用于THREE.Sprite。THREE.Points 和 THREE.PointsMaterial

  1. THREE.Points的构造函数接受两个属性:几何体和材质

var cloud = new THREE.Points(geometry,material);

🌞 材质用来给粒子着色和添加纹理;几何体用来指定单个粒子的位置案例代码(用THREE.PointsMaterial样式化粒子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function createParticles(size,transparent,opacity,vertexColors,sizeAttenuation,colorValue,vertexColorValue){
//创建一个THREE.Geometry对象
var geom = new THREE.Geometry();
var material = new THREE.PointsMaterial({
size: size,
transparent: transparent,
opacity: opacity,
vertexColors: vertexColors,
sizeAttenuation: sizeAttenuation,
color: new THREE.Color(colorValue),
});

var range = 500;
//循环,在随机的位置中创建particle
for(var i =0; i< 15000; i++){
//创建THREE.Vector3表示的粒子
var particle = new THREE.Vector3(
Math.random() * range - range / 2,
Math.random() * range - range / 2,
Math.random() * range - range / 2,
);
//添加到几何体中
geom.vertices.push(particle);
var color = new THREE.Color(vertexColorValue);
var asHSL = {};
color.getHSL(asHSL);
color.setHSL(asHSL.h, asHSL.s, asHSL.l*Math.random());
geom.colors.push(color);
}

var cloud = new THREE.Points(geom, material);
cloud.name = "particles";
scene.add(cloud);
}

THREE.PointsMaterial对象中所有可设置的属性:

名称 描述
color 粒子系统中所有粒子的颜色。将vertexColors属性设置为true,并且通过颜色属性指定了几何体的颜色来覆盖该属性(更准确地说,顶点的颜色将乘以此值以确定最终颜色),默认值为:0xFFFFFF
map 通过这个属性可以在粒子上应用某种材质
size 指定粒子大小,默认值1
sizeAnnutation 如果该属性设置为false,那么所有粒子都将拥有相同的尺寸大小,无论距离摄像机有多远;如果设置为true,粒子的大小就取决于距离摄像机的距离远近。默认为true
vertexColors 通常,THREE.Points中所有粒子都拥有相同的颜色。如果该属性设置为THREE.VertexColors,并且几何体的颜色数组也有值,那就会使用颜色数组中的值。默认是THREE.NoColor
opacity 该属性与transparent属性一起使用,用来设置粒子的不透明度。默认值为1(完全无透明)
transparent 如果属性设置为true,那么粒子在渲染时会根据opacity属性的值来确定其透明度。默认值为false
blending 该属性指定渲染粒子时的融合模式.
fog 该属性决定粒子是否受雾化效果影响,默认为true

材质 Material 的 .blending 属性主要控制纹理融合的叠加方式,.blending 属性的默认值是 THREE.NormalBlending,其它值THREE.AdditiveBlending、THREE.SubtractiveBlending 等

  • THREE.NormalBlending:.blending 属性默认值
  • THREE.AdditiveBlending:加法融合模式
  • THREE.SubtractiveBlending:减法融合模式
  • THREE.MultiplyBlending:乘法融合模式
  • THREE.CustomBlending:自定义融合模式,与.blendSrc,.blendDst或.blendEquation属性组合使用

纹理样式化粒子

  1. 使用图像样式化粒子一种更直接的方式。使用THREE.TextureLoader().load()函数将图像加载为THREE.Texture对象,然后分配给材质的map属性

var texture = new THREE.TextureLoader().load(“../../ssf/df/raindrop-3.png”);

实现雨滴特效

  1. 加载雨滴纹理(纹理应该是正方形,尺寸最好是2的幂)
  2. 设置雨滴粒子材质属性
  3. 为每个粒子创建顶点
  4. 为粒子(THREE.Vector3)添加如何水平移动(velocityX)和下落速度(velocityY)(横向运动速度的范围-0.16+0.16,纵向速度的范围是0.10.3)
  5. 获取几何体所有粒子,对于每个粒子,用velocityX和velocityY改变它们的当前位置。
  6. 判断粒子范围,如果v.y的位置低于0,把粒子放回顶部,如果v.x的位置到达任何一条边界,就反转横向速度,让雨滴反弹。

💡depthWrite属性决定这个对象是否影响WebGL的深度缓存。设置为false,可以保证在各个位置上渲染的雨滴之间不会互相影响。如果不设置为false,那么当一个粒子在另一个THREE.Points对象的粒子前面时,会看到纹理的黑色背景。实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//场景
var scene;
function initScene(){
scene = new THREE.Scene();
}

//摄像机
var camera;
function initCamera(){
camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,1,200);
camera.position.set(0,20,100);
camera.lookAt(new THREE.Vector3(30,30,0));
scene.add(camera);
}

//渲染器
var renderer;
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
}
//光源
function initLight() {
scene.add(new THREE.AmbientLight(0x404040));

light = new THREE.DirectionalLight(0xffffff);
light.position.set(1, 1, 1);
scene.add(light);
}
var stats;
function initStats() {
stats = new Stats();
document.body.appendChild(stats.domElement);
}
var controls;
var cloud;
function initControls(){
controls = new function (){
this.size = 2;
this.transparent = true;
this.opacity = 0.6;
this.color = 0xffffff;

this.sizeAttenuation = true;

this.redraw = function () {
scene.remove(scene.getObjectByName("particle1"));

createSprites(controls.size, controls.transparent, controls.opacity, controls.color,controls.sizeAttenuation);
// controls.autoRotate = gui.rotateSystem;
};
}
var gui = new dat.GUI();
gui.add(controls,'size',0,20).onChange(controls.redraw);
gui.add(controls,'transparent').onChange(controls.redraw);
gui.add(controls,'opacity',0,1).onChange(controls.redraw);
gui.addColor(controls,'color').onChange(controls.redraw);
gui.add(controls,'sizeAttenuation').onChange(controls.redraw);
gui.domElement.style.position = 'absolute';
gui.domElement.style.top = '0px';
gui.domElement.style.right = '300px';

controls.redraw();

}
var helper;
function initModels() {
//对称轴
helper = new THREE.AxesHelper(50);
//scene.add(helper);
}
//生成粒子
function createSprites(size,transparent,opacity,color,sizeAttenuation){
//将图像加载为THREE.Texture对象,之后分配给材质的map属性,进行纹理化粒子
var texture = new THREE.TextureLoader().load("textures/particles/raindrop-3.png");
//存放粒子数据的网格
var geom = new THREE.Geometry();

geom.scale(10,10,10);
//样式化粒子的THREE.PointsMaterial材质
var material = new THREE.PointsMaterial({
size:size,
transparent:transparent,
opacity:opacity,
map:texture,
blending: THREE.AdditiveBlending,
sizeAttenuation:sizeAttenuation,
color: color,
//保证各个位置的雨滴之间不会互相影响。隐藏雨滴纹理的黑色背景
depthWrite: false,

});

var range = 150;
for(var i=0;i<15000;i++){
//添加顶点坐标
var particle = new THREE.Vector3(
Math.random()*range-range/2,
Math.random()*range*1.5,
// Math.random()*range-range/2, y轴乱序——画面中很多重叠雨滴之间有不太理想的叠加效果
1+(i/100)
);
//定义粒子如何水平移动(横向运动范围-0.16~+0.16)
particle.velocityX = (Math.random() - 0.5)/3;
//定义粒子下落速度(纵向运动速度范围0.1~0.3)
particle.velocityY = 0.1+(Math.random() /5);
//
geom.vertices.push(particle);
}
//生成模型添加到场景中
cloud = new THREE.Points(geom,material);
// cloud.sortParticles = true; 新版Three.js已经没有这个功能
cloud.name = "particle1";

scene.add(cloud);
}
//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
var controls2;
function initControls2() {

controls2 = new THREE.OrbitControls(camera, renderer);

// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls2.enableDamping = false;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls2.enableZoom = true;
//是否自动旋转
controls2.autoRotate = false;
//设置相机距离原点的最远距离
controls2.minDistance = 1;
// //设置相机距离原点的最远距离
// controls2.maxDistance = 200;
//是否开启右键拖拽
controls2.enablePan = true;
}


function render(){
renderer.render(scene,camera);
}



function animate(){


stats.update();
//实现雨滴动画效果
var vertices = cloud.geometry.vertices;
//修改顶点信息,让每一个粒子的x轴和y轴的位置信息每一帧都差生一些偏差
vertices.forEach(function (v) {
v.y = v.y - (v.velocityY);
v.x = v.x - (v.velocityX);

if (v.y <= 0) v.y = 60;
if (v.x <= -20 || v.x >= 20) v.velocityX = v.velocityX * -1;
});
//实时更新雨滴信息
cloud.geometry.verticesNeedUpdate = true;
requestAnimationFrame(animate);
render();
}
function windowOnchange(){
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
}

function draw(){
initStats();
initScene();
initModels()
initLight();
initCamera();
initControls();
initControls2();
initRenderer();
render();

animate();
window.onresize = windowOnchange;
}

加载和使用纹理

UV贴图

纹理平铺(重复)的相关属性

在之前学习纹理对象Texture已经介绍过纹理的wrapS、wrapT、repeat属性,它们都是用来设置纹理重复的相关属性,在来简单说一下这几个属性wrapS

  • 纹理在水平方向上纹理包裹方式,在UV映射中对应于U,默认THREE.ClampToEdgeWrapping,表示纹理边缘与网格的边缘贴合。中间部分等比缩放。还可以设置为:THREE.RepeatWrapping(重复平铺) 和 THREE.MirroredRepeatWrapping(先镜像再重复平铺)

wrapT

  • 纹理贴图在垂直方向上的包裹方式,在UV映射中对应于V,默认也是THREE.ClampToEdgeWrapping,与wrapS属性一样也可以设置为:THREE.RepeatWrapping(重复平铺) 和 THREE.MirroredRepeatWrapping(先镜像再重复平铺)

repeat

  • 用来设置纹理将在表面上,分别在U、V方向重复多少次。

repeat属性是Vector2类型,可以使用如下语句来为它赋值

this.cube.material.map.repeat.set(repeatX, repeatY)

repeatX 用来指定在x轴方向上多久重复一次,repeatY用来指定在y轴方向上多久重复一次这两个变量取值范围与对应效果如下

  • 如果设置等于1,则纹理不会重复
  • 如果设置为 大于0小于1 的值,纹理会被放大
  • 如果设置为 小于0 的值,那么会产生纹理的镜像
  • 如果设置为 大于1 的值,纹理会重复平铺

加载纹理并应用到网格

简介

  1. 纹理最基础的用法就是作为贴图被添加到材质上,当用这种方法创建网格时,网格的颜色就来源于纹理
  2. UV贴图实质上就是指定模型上的哪一部分需要被映射到纹理的相应位置
  3. 可以用如下方法加载纹理

var textureLoader = new THREE.TexturenLoader();var texture = textureLoader.load(“../../assets/textures/sss.jpg”);

使用THREE.TextureLoader()从指定位置加载图片,图片格式可以是png,jpg或jpeg

  1. 纹理的加载是异步的:如果纹理加载较大,而程序在文件加载完成之前开始渲染场景,那么在最开始的瞬间会看到场景中一些物体没有贴图。如果希望等待纹理加载完成。可以为THREE.TextureLoader.load()添加回调函数。

var textureLoader = new THREE.TextureLoader();var texture = textureLoder.load(“../../assets/textures/sss.jpg”,onloadFunction,onProgressFunction,onErrorFunction);

onloadFunction在纹理加载完成时被调用,onProgressFunction可以随时汇报加载进度,onError在纹理加载出故障时被调用

  1. 用图片来作为纹理使用时,最好使用长宽为2的次方的正方形图片
  2. 纹理的放大和缩小。可以设置magFilter属性来指定纹理如何放大,minFilter来指定纹理如何缩小

这两个属性的属性值可以设置如下表所示的属性值

名称 描述
THREE.NearestFilter(最邻近过滤) 这个过滤器会将纹理上最近的像素颜色应用于面上。在放大时,会导致方块化,在缩小时,会丢失很多细节
THREE.LinearFilter(线性过滤) 这个过滤器最终的颜色是由周围四个像素值决定的。在缩小时仍会丢失一些细节,但是在放大时会平滑很多,方块化也比较少一些出现

也可以使用mipmap。mipmap是把纹理按照2的倍数进行缩小。mipmap纹理过滤模式如下表所示关于mipmap,https://blog.csdn.net/qq_42428486/article/details/118856697💡 如果没有设置magFilter和minFilter属性的值。Three.js会将THREE.LinearFilter作为magFilter属性的默认值,将THREE.LinearMipMapLinearFilter作为minFilter属性的默认值。

案例

描述

结果

代码

自定义加载器

除了使用THREE.TextureLoader方法加载标准格式图片,Three.js还提供一些自定义的加载器,用来加载其他格式的 纹理文件。

加载器 描述
THREE.DDSLoader 该加载器可以加载DirectDraw Surface格式的纹理文件。首先要在HTML页面中引入DDSLoader.js文件,然后像下面这样加载纹理var textureLoader = new THREE.DDSLoader();var texture = textureLoader.load(“../../..”)
THREE.PVRLoader Power VP也是一种私有版权的压缩纹理格式。Three.js支持Power VR 3.0 版本的文件。在HTML页面导入PVRLoader文件后,像下面这样加载纹理var textureLoader = new THREE.PVRLoader();var texture = textureLoader.load(“../../..”);💡注意不是所有设备上的浏览器都支持PVR格式。如果在桌面版Chrome浏览器中加载这种格式会在控制台看到错误信息:WEBGL_compressed_texture_pvrtc extension not supported但是这种格式在iOS系统上被广泛使用
THREE.TGALoader Targa是一种在3D软件中仍被广泛使用的栅格图像格式。在HTML页面中引入TGALoader.js后,像下面这样加载纹理var textureLoader = new THREE.TGALoader();var texture = textureLoader.load(“../../..”);
THREE.KTXLoader Khronos Texture(KTX)文件格式来自于Khronos工作组。该格式的初衷是提供一个压缩纹理格式,使其可以被WebGL直接使用,以便尽量降低处理它的额外开销。KTX格式拥有多种不同的编码,不同的硬件对编码的支持有可能不同。在HTML页面中引入KTXLoader.js文件后,像下面这样加载纹理if(renderer.extension.get(‘WEBGL_compressed_texture_astc’)!==null) return “astc”;if(renderer.extension.get(‘WEBGL_compressed_texture_etc1’)!==null) return “etc1”;if(renderer.extension.get(‘WEBGL_compressed_texture_s3tc’)!==null) return “s3tc”;if(renderer.extension.get(‘WEBGL_compressed_texture_pvrtc’)!==null) return “pvrtc”;var ktxTextureLoader = new THREE.KTXLoader();var texture;switch(determineFormat()){case “astc”:texture = ktxTextureLoader.load(“../../…ktx”);break;case “etc1”:texture = ktxTextureLoader.load(“../../…ktx”);break;case “s3tc”:texture = ktxTextureLoader.load(“../../…ktx”);break;case “pvrtc”:texture = ktxTextureLoader.load(“../../…ktx”);break;}上述代码先查询WebGL当前支持的KTX编码,然后选择加载特定编码的KTX文件

上面的纹理都是直接存储或者压缩存储的普通图片。除了这些普通图片之外,Three.js还支持HDR图像(高动态范围图像)。相比普通图片,HDR图像包含了更高的亮度范围。它的亮度范围更接近人眼的光学特征。由于HDR图像所包含的亮度范围大于屏幕所能够支持的范围,可以尝试微调Three.js对HDR图像的渲染参数,并观察渲染效果的变化。这种参数调节可以通过设置THREE.WebGLRenderer类的属性来实现。下表列出了相关属性

加载器 描述
toneMapping(色调映射) 该属性控制Three.js如何将HDR色彩域映射到屏幕所支持的色彩域上,由如下选项可用:
- THREE.NoToneMapping
- THREE.LinearToneMapping
- THREE.ReinhardToneMapping
- THREE.Uncharted2ToneMapping
- THREE.CineonToneMapping
默认映射为THREE.LinearToneMapping
toneMappingExposure 该属性控制色调映射的曝光级别,可用于微调渲染场景中纹理贴图的色彩
toneMappingWhitePoint 该属性设置色调映射中的白点值

Three.js还支持EXR和RGBE格式

加载器 描述
THREE.EXRLoader EXR文件是为存储HDR图像而开发的图像文件格式。在HTML页面中引引入EXRLoader.js文件后,像下面这样加载纹理var textureLoader = new THREE.EXRloader();var texture = textureLoader.load(“../../..”);
THREE.RGBELoader RGBE文件是Radiance渲染系统的图像文件格式。在HTML页面中引入RGBELoader.js文件后,像下面这样加载纹理var hdrTextureLoader = new THREE.RGBEloader();hdrTextureLoader.load(“../../..”,function(texture,metadata){texture.encoding = THREE.RGBEEncoding;texture.flipY = true;… and use the texture})加载RGBE纹理时,必须将加载器返回的纹理对象的encoding属性设定为THREE.RGBEEncoding,否则将无法正确渲染。此外还需将flipY属性设置为true,否则图像会上下颠倒。

使用凹凸贴图创建褶皱

简介

  1. 凹凸贴图用于为材质添加厚度


与左侧墙相比右侧有更多的细节

创建

可以通过为材质设置额外的纹理(凹凸贴图)来实现

1
2
3
4
5
6
7
8
9
10
var textureLoader = new THREE.TextureLoader();
var cubeMaterial = new THREE.MeshStandardMaterial({
//设置map属性
map: textureLoader.load("../../assets/textures/stone/stone.jpg"),
//设置bumpMap属性
bumpMap: textureLoader.load("../../assets/textures/stone/stone-bump.jpg"),
metalness: 0.2,
roughness: 0.07,
});
//可以通过设置bumpScale属性,设置凹凸的高度(如果值为负数,则表示的是深度)

💡注意像素的密集程度定义的是凹凸的高度,但是凹凸图只包含像素的相对高度,没有任何倾斜的方向信息。所以使用凹凸图所能表达的深度信息是有限的,要想实现更多细节可以使用法向贴图

完整代码

使用法向贴图创建更加细致的凹凸和褶皱

简介

  1. 发现贴图保存的不是高度信息,而是法向量的方向。
  2. 使用法向贴图只需要使用很少的顶点和面就可以创建出细节很丰富的模型


右侧的图像细节更加丰富。当光源围绕方块移动时,你会看到纹理对光源做出很自然的反应,这样的模型更加真实

  1. 纹理(左图),法向贴图(右图)


  1. 使用法向贴图最大问题是它们难以创建,需要使用比如Blender和Photoshop这样的特殊工具。这些工具可以将高分辨率的效果图或者纹理作为输入来创建法向图。
  2. 使用法向贴图和凹凸贴图来增加物体表面细节时,不需要改变模型的实际形状;所有顶点都保持在原始位置不变。这些贴图利用场景中的光照来制造伪深度和细节。

创建

1
2
3
4
5
6
7
8
9
10
11
var textureLoader = new THREE.TextureLoader();
var cubeMaterial = new THREE.MeshStandardMaterial({
//设置map属性
map: textureLoader.load("../../assets/textures/general/plaster.jpg"),
//设置bumpMap属性
normalMap: textureLoader.load("../../assets/textures/general/plaster-normal.jpg"),
metalness: 0.2,
roughness: 0.07,
});
//还可以设置normalScale属性为mat.normalScale.set(1,1)来指定凹凸程度,
//通过这两个参数,可以沿着x轴和y轴进行缩放。最好的方式是设置成一样

完整代码

使用移位贴图来改变顶点位置

简介

  1. 移位贴图能够根据贴图的内容,修改模型顶点,真正改变模型的形状。
  2. 图中越亮的颜色会使顶点移位越远。
  3. 除了给displacementMap属性指定纹理对象之外,displacementScale和displacementBias两个属性也可以用来控制顶点的移位程度。
  4. 使用移位贴图的模型必须具有大量顶点。否则其顶点移位效果看起来会与移位贴图并不相像。因为顶点过少的模型没有足够的顶点可以移动。

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//加载器
var textureLoader = new THREE.TextureLoader();
//球体几何
var sphereGeometry = new THREE.SphereGeometry(4,20,20);
//球体材质
var sphereMaterial = new THREE.MeshStandardMaterial({
//纹理
map: textureLoader.load("../assets/textures/w_c.jpg"),
//移位贴图
displacementMap: texture.load("../assets/textures/w_d.jpg"),
//金属感程度
metalness: 0.2,
//粗糙程度
roughness: 0.07,
color: 0xffffff,
});

完整代码

用环境光遮挡贴图实现细节阴影

简介

  1. 场景中总是有静止不动的物体和光源,所以投射到物体上的阴影也不会变化,因此如果能够计算一此阴影数据并在渲染时重复使用,为了做到这一点,three.js提供了两种贴图:环境光遮挡贴图和光照贴图
  2. 环境光遮挡技术用于决定模型的哪一部分暴露于环境光之中
  3. 在Blender或类似的软件中,环境光通常被当作半球光源或者平行光源来考虑
  4. 虽然模型的大部分表面都能接收到环境光,但是它们接受光线的多少仍然有差别(以一个站立的人物模型为例,头顶往往接收的环境光更多,而胳膊下侧则接收更少)。
  5. 这种光照的差异可以被渲染(又称烘培)到一张纹理贴图上,然后与颜色贴图混合在一起应用到模型上。这样一来可以避免在渲染循环中重复计算光照差异。
  6. 环境光遮挡贴图

  1. 环境遮挡贴图的作用是指出模型上哪些部分处于阴影中,应该从环境光中接受较少的光照。与环境光遮挡贴图作用相反的是光照贴图。该贴图用来决定模型的哪些部分需要补充更多的光照。

创建

1
2
3
4
5
6
7
8
9
10
11
var textureLoader = new THREE.TextureLoader();
var meterial = new THREE.MeshStardardMaterial({
//设置aoMap属性,加载环境光遮挡贴图
aoMap: textureLoader.load("../assets/models/baymax/ambient.png"),
//指定环境光遮挡贴图的影响程度
aoMapIntensity: 2,
color: 0xffffff,
metalness: 0,
roughtness: 1,

});

💡请记住,在使用aoMap属性或者lightMap属性时,对于光照贴图来说,需要使用faceVertexUvs[1]。

geometry.faceVertexUvs.push(geometry.faceVertexUvs[0]);

完整代码

用光照贴图产生假阴影

简介

  1. 光照贴图里面的信息用于指出一个模型的特定部分应该从场景中接收多少光照,换句话说,模型的光照信息被预先烘培到了纹理贴图中。
  2. 有很多3D图形软件可以用于烘培光照贴图(比如Blender)
  3. 下图用于渲染地面。从图中可以看出,大部分面被白色光照亮,但有一处较暗的阴影部分。

(这是因为要在地面上放置一个物体,它会在其地面上遮挡部分光照)

  1. 烘培到纹理贴图中的阴影、光照以及环境光遮挡信息只能用于静态场景,或者场景中的静态物体。一旦光源或物体发生移动或者改变,就不得不实时计算阴影了

创建

1
2
3
4
5
6
//对于
plane.geometry.faceVertexUvs.push(plane.geometry.faceVertexUvs[0]);
plane.material = new THREE.MeshBasicMaterial({
map: textureLoader.load("../assets/textures/general/floor-wood.jpg"),
lightMap: textureLoader.load("../assets/textures/lightmap/lightmap.png");
});

完整代码

金属光泽度贴图和粗糙度贴图

简介

  1. MeshStandardMaterial材质中可以用metalness和roughness两个属性来设置大部分物体的表面质感。除了可以直接用数值来设置这两个属性值之外,还可以通过纹理贴图来设置。
  2. 如果希望在一个表面粗糙的物体上指定一些闪亮的局部,则可以为metalnessMap属性设置一张金属质感贴图。
  3. 若希望在一个光滑的物体上设置一些粗糙的局部,则可以在roughnessMap属性上使用纹理贴图来实现。
  4. 当使用纹理贴图来设置两个属性时,在模型的具体位置上,metalness和roughness两个属性的实际值等于属性值本身与响应贴图中的值的乘积。
  5. 在环境贴图的基础上,金属光泽度属性值越高的物体,表面的反射越清晰,粗糙度值越高的物体,反射越浑浊。
  6. 如下图所示,画面中的左图在roughness属性上设置了粗糙度贴图,从而使小球锈迹斑斑。右边的小球在metalness属性上设置了金属光泽度贴图,整个球面使粗糙的,只有一些区域被磨的光亮,显示出金属表面特有的镜面反射效果。


💡注意:金属光泽度属性metalness的值会先与来自metalnessMap贴图中的值相乘,粗糙度属性roughness的值则会与先来自roughnessMap贴图中的值相乘,然后再应用于物体渲染中。

创建

1
2
met1.metalnessMap = textureLoader.load("../assets/textures/engraved/roughness-map.jpg");
mat1.roughnessMap = textureLoader.load("../assets/textures/engraved/roughness-map.jpg");

完整代码

Alpha贴图

简介

  1. Alpha贴图用于控制物体表面的透明度
  2. 贴图中的纯黑部分代表该部分表面完全透明,纯白色部分则代表完全不透明

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var sphereMaterial = new THREE.MeshStandardMaterial({
alphaMap: textureLoader.load("../assets/textures/alpha/partial-transparenxy.png"),
//设置环境贴图
envMap: alternativeMap,
metalness: 0.02,
roughness: 0.07,
color: 0xffffff,
//设置alphaTest属性,避免使用半透明特性时可能出现一些小斑点问题
alphaTest: 0.5,
});
//控制纹理再模型表面的重复模式和频率
sphereMaterial.alphaMap.wrapS = THREE.RepeatWrapping;
sphereMaterial.alphaMap.wrapT = THREE.RepeatWrapping;
//当设置为(1,1)时,纹理不会重复,当设置为更高值时,纹理会被缩小,并在模型表面重复贴图
sphereMaterial.alphaMap.repeat.set(8,8);

完整代码

自发光贴图

简介

  1. 自发光贴图是一个控制模型表面实现自发光效果的纹理贴图
  2. 自发光物体本身看起来闪耀光芒,但是对周围环境毫无影响。也就是说自发光特性只能单独影响物体本身,不能使该物体变成光源
  3. 💡注意:由于来自emissiveMap的颜色由emissive属性值调制,因此需要将该属性值设置为非0才能看见自发光贴图效果。

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var cubeMaterial = new THREE.MeshStandardMaterial({
//设置emissive属性
emissive: 0xffffff,
//自发光贴图
emissiveMap: textureLoad.load("../assets/textures/emissive/lava.png"),
//法向贴图
normalMap: textureLoad.load("../assets/textures/emissive/lava-normals.png"),
//金属光泽度贴图
metalnessMap: textureLoad.load("../assets/textures/emissive/lava-smoothness.png"),
//金属感程度
metalness: 1,
//粗糙程度
roughness: 0.4,
normalScal: new THREE.Vector2(4,4),
});

完整案例

高光贴图

简介

  1. 一般情况下,在新版的three.js中THREE.MeshStandardMaterial材质是最佳选择,可以通过调节其各种属性来生成现实世界中的各种真实材质
  2. 若要使用高光贴图,需要使用MeshPhongMaterial材质。
  3. 高光贴图用于指定物体表面中哪些部分比较闪亮,或者哪部分相对暗淡。与THREE.MeshStandardMaterial材质的金属光泽度贴图和粗糙度贴图的作用有些接近

创建

1
2
3
4
5
6
7
8
9
var earthMaterial = new THREE.MeshPhongMaterial({
//加载纹理
map: textureLoader.load("../assets/textures/earth/Earth.png"),
//法向贴图
normalMap: textureLoader.load("../assets/textures/earth/EarthNormal.png"),
//高光贴图
specularMap: textureLoader.load("../assets/textures/earth/EarthSpec.png"),
normalScale: new THREE.Vertor2(6,6),
});

完整案例

渲染一个地球,其中使用高光贴图来生成相对闪亮的海面和相对暗淡的陆地表面

使用环境贴图创建伪镜面效果

简介

  1. 计算镜面反射效果对CPU的消耗是非常大的,而且通常会使用光线追踪算法
  2. 在three.js中可以通过创建一个对象所处环境的纹理来伪装镜面反射,并将它应用到指定的对象上。

创建

案例完整代码

自定义UV映射

重复纹理

简介

  1. 需要保证将纹理的包裹属性设置为THREE.RepeatWrapping

    cube.material.map.wrapS = THREE.RepeatWrapping;
    cube.material.map.wrapT = THREE.RepeatWrapping;

  2. wrapS属性定义的是纹理沿x轴方向的行为,wrapT是沿y轴方向的行为。three.js为这些属性提供了如下两个选项

  • THREE.RepeatWrapping 允许纹理重复自己
  • THREE.ClampToEdgeWrapping 是属性的默认值,纹理的整体不会重复,只会重复纹理边缘的像素来填满剩下的空间。
  1. 如果设置了THREE.RepeatWrapping,可以通过以下代码设置repeat属性

    cube.material.map.repeat.set(repeatX,repeatY);

  • 变量repeatX用来指定纹理在x轴方向多久重复一次
  • 变量repeatY用来指定纹理在y轴方向多久重复一次
  • 如果变量的值为1,那么纹理不会重复
  • 如果变量的值大于1,纹理开始重复
  • 如果变量的值小于1,纹理被放大
  • 如果变量的值为负数,会产生一个纹理的镜像
  1. 当修改repeat属性时,three.js会自动更新纹理,并使用新的值来进行渲染
  2. 如果将值从THREE.RepeatWrapping改为THREE.ClampToEdgeWrapping,需要明确的更新纹理

    cube.material.map.needsUpdate = true;

在画布上绘制图案并作为纹理

将画布作为纹理

如何在画布上创建简单纹理,并应用到网格

简介

  1. 需要使用Literally库(http://literallycanvas.com/)来创建一个交互式画布

  2. 创建一个画布元素

    1
    2
    3
    4
    5
    6
    7
    <div class="fs-container">
    <div id="canvas-output"></div>
    </div>
    ...
    var canvas = document.createElement("canvas");
    document.getElementById("canvas-output").appendChild(canvas);
    $("#canvas-output").literallycanvas({imageURLPrefix:"../libs/other/literally/img"})
  3. 将画布上绘制的结果作为输入创建一个纹理(在创建纹理时将画布元素的引用作为参数传递给纹理对象的构造函数new THREE.Texture(canvas))。

    1
    var texture = new THREE.Texture(canvas);
  4. 最后需要做的是在渲染时更新材质,这样画布上的内容就会显示在物体上。

(为了告知three.js我们需要更新材质,需要将纹理的needsUpdate属性设置为true)

1
2
3
4
5
function render(){
renderer.render(scene,camera);
// 渲染时更新材质
cube.material.map.needsUpdate = true;
}

完整案例代码

将画布用作凹凸贴图

将视频输出作为纹理

创建动画和移动摄像机

简单动画

简介

  1. 可以通过改变物体的旋转,位置,缩放,材质等属性来实现动画
  2. 以下案例实现简单动画(方块旋转动画,小球弹跳动画,圆柱缩放动画)

案例完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//场景
var scene;
function initScene() {
scene = new THREE.Scene();
}
//摄像机
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);
camera.position.set(30,40,30);
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
}
//渲染器
var renderer;
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0x000000));
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById("webgl-output").appendChild(renderer.domElement);
}
//光源
var ambientLight,spotLight;
function initLight(){
//环境光
ambientLight = new THREE.AmbientLight(0x444444);
scene.add(ambientLight);
//聚光源
spotLight = new THREE.SpotLight(0xffffff);
//光源位置
spotLight.position.set(20,30,40);
//阴影映射宽度
spotLight.shadow.mapSize.width = 2048;
//阴影映射高度
spotLight.shadow.mapSize.height = 2048;
// 投影视场
spotLight.shadow.camera.fov = 150;
//开启光源投影
spotLight.castShadow = true;
//将光源添加至场景中
scene.add(spotLight);
}
//模型
var axes,plane,cube,sphere,cylinder;
function initModels(){
//坐标轴
axes = new THREE.AxesHelper(50);
// scene.add(axes);
//平面
var planeGeometry = new THREE.PlaneGeometry(20,60);
var planeMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff,
});
var plane = new THREE.Mesh(planeGeometry,planeMaterial);
plane.rotation.x = -0.5*Math.PI;
plane.position.set(0,0,0);
plane.receiveShadow = true;
scene.add(plane);
//方块
var cubeGeometry = new THREE.CubeGeometry(4,4,4);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000,
});
cube = new THREE.Mesh(cubeGeometry,cubeMaterial);
cube.castShadow = true;
cube.position.set(0,3,20);
scene.add(cube);
//球体
var sphereGeometry = new THREE.SphereGeometry(4,20,20);
var sphereMaterial = new THREE.MeshStandardMaterial({
color: 0x0000ff,
metalness: 0.05,
roughness: 0.07,
});
sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);
sphere.position.set(0,4,-20);
sphere.castShadow = true;
scene.add(sphere);
//圆柱
var cylinderGeometry = new THREE.CylinderGeometry(2,2,15);
var cylinderMaterial = new THREE.MeshLambertMaterial({
color: 0x00ff00,
});
cylinder = new THREE.Mesh(cylinderGeometry,cylinderMaterial);
cylinder.position.set(0,0,10);
cylinder.castShadow = true;
scene.add(cylinder);
}
//GUI控制
var animation;
function initGUI(){
animation = new function (){
this.rotationSpeed = 0.02;
this.bouncingSpeed = 0.03;
this.scalingSpeed = 0.03;
}
var gui = new dat.GUI();
gui.add(animation,'rotationSpeed',0,0.5,0.001).onChange(function (e){
animation.rotationSpeed = e;
});
gui.add(animation,'bouncingSpeed',0,0.5,0.001).onChange(function (e){
animation.bouncingSpeed = e;
});
gui.add(animation,'scalingSpeed',0,0.5,0.001).onChange(function (e){
animation.scalingSpeed = e;
});
}


//场景缩放控制移动
var controls;
function initControls(){
controls = new THREE.OrbitControls(camera,renderer.domElement);
controls.enablePan = true;
controls.enableZoom = true;
}
//页面响应式布局
function windowOnresize(){
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerWidth);
}
//初始化性能插件
var stats;
function initStats(){
stats = new Stats();
document.getElementById("webgl-output").appendChild(stats.domElement);
}
//渲染
var step = 0,scalingStep = 0;
function render(){
stats.update();
//旋转方块
cube.rotation.x += animation.rotationSpeed;
cube.rotation.y += animation.rotationSpeed;
cube.rotation.z += animation.rotationSpeed;

//小球弹跳
step += animation.bouncingSpeed;
sphere.position.z = -10+10*Math.cos(step);
sphere.position.y = 4+10*Math.abs(Math.sin(step));

//圆柱缩放
scalingStep += animation.scalingSpeed;
var scaleX = Math.abs(Math.sin(scalingStep/4));
var scaleY = Math.abs(Math.cos(scalingStep/5));
var scaleZ = Math.abs(Math.sin(scalingStep/7));
cylinder.scale.set(scaleX,scaleY,scaleZ);

requestAnimationFrame(render);
renderer.render(scene,camera);
}
function draw(){
initScene();
initStats();
initCamera();
initRenderer();
initLight();
initModels();
initControls();
initGUI();

render();
window.onresize = windowOnresize;
}

如何使用鼠标选择场景中的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var projector = new THREE.Projector();
function onDocumentMouseDown(event){
// 首先基于屏幕的点击位置创建一个THREE.Vector3向量
var vector = new THREE.Vector3((event.clientX / window.innerWidth)*2-1,-(event.clientY / window.innerHeight)*2+1,0.5);
// 使用vector.unproject方法将屏幕坐标转换成三维场景中的坐标
vector = vector.unproject(camera);
// 创建THREE.Raycaster.
var raycaster = new THREE.Raycaster(camera.position,vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere,cylinder,cube]);

if(intersects.length > 0){
console.log(intersects[0]);
intersects[0].object.material.transparent = true;
intersects[0].object.material.opacity = 0.1;
}
}

window.addEventListener("mousdown",onDocumentMouseDown,false);

使用Tween.js实现动画

简介

  1. Tween.js是一个轻量级的JavaScript库,可以从http://github.com/sole/tween.js/下载
  2. 通过这个库可以很容易地实现某个属性在两个值之间进行过度,起始值和结束值的所有中间值都会自动计算出来,tweening补间。
  3. 例如,你可以使用这个值将网格的x轴坐标值在10秒内从10递减到3

    var tween = new THREE.Tween({x:10}).to({x:3},1000)
    .easing(TWEEN.Easing.Elastic.Inout)
    .onUpdate(function(){
    //update the mesh
    })

摄像机控件

名称 描述
第一视角控制器(FirstPersonControls) 该控制器的行为类似第一视角射击游戏中的摄像机,使用键盘移动,使用鼠标转动
飞行控制器(FlyControls) 飞行模拟控制器,用键盘和鼠标控制摄像机的移动
翻滚控制器(RollControls) 该控制器是飞行控制器的简化版,允许绕着z轴旋转
轨迹球控制器(TrackBallControls) 最常用的控制器,可以使用鼠标或者控制球来移动,平移和缩放场景。如果你使用的是OrtographicCamera,可以使用OrtographicTrack Ballcontrols,它是这个摄像机类型专用的
轨道控制器(OrbitControls) 该控件可以在特定的场景中模拟轨道中的卫星,可以使用鼠标和键盘在场景中游走

轨迹球控制器(TrackBallControls)

  1. 使用TrackBallControls控制器时,先在HTML页面引入对应的JavaScript文件

  2. 创建控制器,并绑定到摄像机上

    var trackballControls = new THREE.TrackballControls(camera);
    trackballControls.rotateSpeed = 1.0; //旋转速度,默认值1.0
    trackballControls.zoomSpedd = 1.0; //缩放速度,默认值1.2
    trackballControls.panSpeed = 1.0; //平移速度,默认值是0.3

  3. 摄像机的位置更新

    var clock = new THREE.Clock();
    function render(){
    var delta = clock.getDelta(); //调用clock.getDelta()方法可以精确的计算出此次调用距离上次调用的时间间隔
    trackballControls.update(delta); //更新控制器,常被用在动画循环中。
    requestAnimationFrame(render);
    webGLRender.render(scene,camera);
    }

飞行控制器(FlyControls)

  1. 使用飞行控制器可以像飞行模拟器一样在场景中飞行

  2. 首先需要在HTML页面中加载对于的JavaScript文件

  3. 创建控制器并绑定到摄像机上

    1
    2
    3
    4
    5
    6
    7
    var flyControls = new THREE.FlyControls(camera);
    flyControls.movementSpeed = 25; //移动速度,默认为1。
    flyControls.domElement = document.querySelector('#webgl-output');
    //该 HTMLDOMElement 用于监听鼠标/触摸事件
    flyControls.rollSpeed = Math.PI/24; //旋转速度。默认为0.005
    flyControls.autoForward = true; //若该值设为true,初始变换后,摄像机将自动向前移动(且不会停止)默认为false
    flyControls.dragToLook = false; //若该值设为true,你将只能通过执行拖拽交互来环视四周。默认为false
  4. 摄像机的位置更新

第一视角控制器(FirstPersonControls)

  1. 通过第一视角控制器可以像第一视角射击游戏那样控制摄像机。鼠标用于控制视角,键盘用于控制移动角色

  2. 同样先引入对应的JavaScript文件,然后创建控制器并绑定摄像机

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var fpControls = new THREE.FirstPersonControls(camera);
    fpControls.lookSpeed = 0.4;
    fpControls.movementSpeed = 20;
    fpControls.lookVertical = true;
    fpControls.constrainVertical = true;
    fpControls.verticalMin = 1.0;
    fpControls.verticalMax = 2.0;
    fpControls.lon = -150;
    fpControls.lat = 120;
  3. 摄像机的位置更新

轨道控制器(OrbitControls)

  1. 轨道控制器可以用于控制场景中的对象围绕场景中心旋转和平移。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 在HTML中先引入对应的JavaScript文件
    <script type="text/javaScript" asrc="../assets/libs/three/controls/OrbitControls">
    </script>
    ...
    // 创建控制器并绑定摄像机
    var orbitControls = new THREE.OrbitControls(camera);
    // 设置摄像机属性
    orbitControls.autoRotate = true;
    ...
    // 摄像机的位置更新
    var clock = new THREE.Clock();
    function render(){
    orbitControls.update(clock.getDelta());
    }

变形动画和骨骼动画

  • 变形动画
    • 使用变形动画,需要定义网格变形之后的状态,或者是关键位置
    • 对于变形目标,所有顶点位置都会被存储下来。
    • 所需要做的事将所有顶点从一个位置移动到另一个定义好的关键位置,并重复该过程
  • 骨骼动画
    • 使用骨骼动画时需要定义骨骼,也就是网格的骨头,并把顶点绑定到特定的骨头上。
    • 当你移动一块骨头时,任何与其相连的骨头都会做相应的移动,同时骨头上绑定的顶点也会随之移动
    • 网格的变形也是基于骨头的位置、移动和缩放实现的

用变形目标创建动画

变形目标时实现动画的最直接方式。可以为所有顶点定义一系列的关键位置(也称关键帧),然后让Three.js将这些顶点动一个位置移动到另一个位置。这种方法对大型网格和大型动画来说,模型文件会变得非常大,因为在每个关键帧的位置,所有顶点的位置都需要重复存储一遍。

使用混合器和变形目标创建动画

Three.js的三个核心动画类

  1. THREE.AnimationClip(动画片段):
    1. 当具有动画数据的模型被加载的时候,这个模型对象往往具有一个名为animations的成员对象。该对象包含了一个THREE.AnimationClip对象集合
    2. 一个模型所包含的THREE.AnimationClip对象通常保存有某种特定类型的动画数据,或者时该模型能够执行的某种动作。
    3. 比如当加载一个鸟模型时,它可能包含了两个THREE.AnimationClip对象,一个保存了拍打翅膀的作用,另一个保存了张嘴闭嘴的作用。
  2. THREE.AnimationMixer(动画混合器):
    1. THREE.AnimationMixer对象用于控制多个THREE.AnimationClip对象,确保这些动画在适当的时间发生,使动画同步或者控制从一个动画过度到另一个动画。
    2. 主要方法为:

mixer = new THREE.AnimationMixer(mesh);//通过mesh获取到AnimationMixer对象action = mixer.clipAction(clip);//用clipAction方法生成可以控制执行动画的实例

  1. THREE.AnimationAction(动画行为):
    1. 当向THREE.AnimationMixer对象添加一个THREE.AnimationClip对象时,调用者会获得一个THREE.AnimationAction。
    2. 很多动画控制功能是通过THREE.AnimationAction 调用的,而THREE.AnimationMixer并没有提供很全面的控制接口。

实例(马的奔跑运动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var loader = new THREE.JSONLoader();
var mixer = new THREE.AnimationMixer();
var mesh,clipAction,frameMesh;
function initModels(){
loader.load('../assets/models/horse/horse.js', function (geometry, mat) {
// 通过将每个顶点法线计算为共享该顶点的所有面的面法线的平均值来“平滑”顶点法线
geometry.computeVertexNormals();
// 确保在动画过程中three.js有正确的光照数据来渲染模型
geometry.computeMorphNormals();
// 定义模型材质
var mat = new THREE.MeshLambertMaterial({
// 如果为false,则没有动画效果
morphTargets:true,
// 颜色
vertexColors:THREE.FaceColors,
});
mesh = new THREE.Mesh(geometry,mat);
mesh.scale.set(0.15,0.15,0.15);
mesh.translateY(-10);
mesh.translateX(20);
mesh.rotation.y = -0.3*Math.PI;

//坐标轴
// scene.add(new THREE.AxesHelper(50));

//为加载后的模型创建一个THREE.AnimationMixer对象
mixer = new THREE.AnimationMixer(mesh);
var animationClip = geometry.animations[0];
//用mixer.clipAction方法播放模型包含的第一个动画
clipAction = mixer.clipAction(animationClip).play();
//加快所有被控制的动画速度
mixer.timeScale = 10;
//加快当前动画速度
clipAction.timeScale = 10;
clipAction.effectiveTimeScale = 10;
clipAction.setLoop(THREE.LoopRepeat);
scene.add(mesh);


});
}

最后需要更新mixer

1
2
3
4
5
6
7
8
9
10
11
12
var clock = new THREE.Clock();
function render(){
stats.update();
controls.update(clock.getDelta());

requestAnimationFrame(render);
renderer.render(scene,camera);

if(mixer){
mixer.update(clock.getDelta());
}
}

💡必须用mixer.update(delta)的形式去调用update函数,以便告诉混合器此次渲染和上次渲染的时间差

使用多个THREE.AnimationClip对象

实例(立方体的放大与缩小动画)手动创建两个动画,第一段动画:将立方体的尺寸从(2,2,2)变为(2,20,2)第二段动画:将立方体尺寸从(2,2,2)变为(40,2,2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//加载模型
var loader = new THREE.JSONLoader();
var mixer = new THREE.AnimationMixer();
var mesh,clipAction1,clipAction2,animationClip1,animationClip2;
function initModels(){
//设置立方体初始大小(原始变形目标)
var cubeGeometry = new THREE.BoxGeometry(2,2,2);
//设置立方体材质
var cubeMaterial = new THREE.MeshLambertMaterial({
morphTargets:true,
color: 0xff0000,
});
//设置立方体变形目标的大小
var cubeTarget1 = new THREE.BoxGeometry(2,20,2);
var cubeTarget2 = new THREE.BoxGeometry(40,2,2);
//将三个变形目标添加到原始立方体的morphTargets属性中
cubeGeometry.morphTargets[0] = {name:'t1',vertices:cubeGeometry.vertices};
cubeGeometry.morphTargets[1] = {name:'t2',vertices:cubeTarget1.vertices};
cubeGeometry.morphTargets[2] = {name:'t3',vertices:cubeTarget2.vertices};
//在动画过程中确保有正确的光照渲染模型
cubeGeometry.computeMorphNormals();
//创建立方体
var cube = new THREE.Mesh(cubeGeometry,cubeMaterial);
//设置立方体位置
cube.position.set(-10,0,0);
//添加立方体到场景中
scene.add(cube);


//为加载后的模型创建一个THREE.AnimationMixer对象
mixer = new THREE.AnimationMixer(cube);
//用CreateFromMorphTargetSequence函数创建AnimationClip动画的两个对象
//第一个动画从变形目标morphTargets[0]到变形目标morphTargets[1].
animationClip1 = THREE.AnimationClip.CreateFromMorphTargetSequence(
'first',
[cubeGeometry.morphTargets[0],cubeGeometry.morphTargets[1]], 1);
//第二个动画从变形目标morphTargets[0]到变形目标morphTargets[2].
animationClip2 = THREE.AnimationClip.CreateFromMorphTargetSequence(
'second',
[cubeGeometry.morphTargets[0],cubeGeometry.morphTargets[2]], 1);
//用clipAction方法播放动画
clipAction1 = mixer.clipAction(animationClip1).play();
clipAction2 = mixer.clipAction(animationClip2).play();
//加快动画播放速度
mixer.timeScale = 10;
clipAction1.timeScale = 10;

}

用骨骼和蒙皮创建动画

  1. 当使用骨骼创建动画时,移动骨骼,three.js需要决定如何相应的移动相应的皮肤(一系列顶点)

案例一个手模型,上面有几块骨头,通过移动这几块骨头,就可以让整个模型动起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var mesh;
// 模型加载
function initModels(){
// 加载器
loader = new THREE.JSONLoader();
loader.load("../assets/models/hand/hand-1.js",function (geometry,amt){
// 设置模型材质
var mat = new THREE.MeshLambertMaterial({
// 颜色
color:0xF0C8C9,
// 开启骨头运动
skinning:true,
});
mesh = new THREE.SkinnedMesh(geometry,mat);
// 模型的大小位置
mesh.scale.set(15,15,15);
mesh.position.x = -5;
mesh.rotateX(0.5*Math.PI);
mesh.rotateZ(0.3*Math.PI);
// 将模型添加至场景
scene.add(mesh);
// 开始动画
startAnimation();
});
}
// 通过设置指部骨头绕z轴旋转实现抓东西的东西的动作
var onUpdate = function(){
var pos = this.pos;

mesh.skeleton.bones[5].rotation.set(0,0,pos);
mesh.skeleton.bones[6].rotation.set(0,0,pos);
mesh.skeleton.bones[10].rotation.set(0,0,pos);
mesh.skeleton.bones[11].rotation.set(0,0,pos);
mesh.skeleton.bones[15].rotation.set(0,0,pos);
mesh.skeleton.bones[16].rotation.set(0,0,pos);
mesh.skeleton.bones[20].rotation.set(0,0,pos);
mesh.skeleton.bones[21].rotation.set(0,0,pos);

mesh.skeleton.bones[1].rotation.set(pos,0,0);

}
var tween;
function startAnimation(){
//创建tween对象,pos变量从-1.5过度到0
tween = new TWEEN.Tween({pos:-1.5})
.to({pos:0},3000)
// easing缓动函数,Cubic三次方的缓动,InOut前半段加速,后半段减速
.easing(TWEEN.Easing.Cubic.InOut)
.yoyo(true)
// 无限循环
.repeat(Infinity)
.onUpdate(onUpdate)

tween.start();
}

使用外部模型创建动画

使用Blender创建骨骼动画

为了能够让动画运行起来,还需要创建THREE.SkinnedMesh对象,共同使用THREE.AnimationMixer、THREE.AnimationClip和THREE.ClipAction告诉three.js如何运行导入的动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var mesh,animationClip,clipAction,mixer;
function initModels(){
loader = new THREE.JSONLoader();
loader.load("../assets/models/hand/hand-8.json",function (geometry,amt){
var mat = new THREE.MeshNormalMaterial({
color:0xF0C8C9,
skinning:true,
});
// 创建THREE.SkinnedMesh对象
mesh = new THREE.SkinnedMesh(geometry,mat);
mesh.scale.set(18,18,18);
scene.add(mesh);

mixer = new THREE.AnimationMixer(mesh);
animationClip = mesh.geometry.animations[0];
clipAction = mixer.clipAction(animationClip).play();
animationClip = clipAction.getClip();

});
}
var clock = new THREE.Clock();
function render(){
stats.update();

requestAnimationFrame(render);
renderer.render(scene,camera);

if(mixer){
mixer.update(clock.getDelta());
}
}

从Collada模型加载动画

  1. 第一步需要先引入对应的JavaScript文件(ColladaLoader.js)
  2. 由于普通的Collada模型是不压缩的,因此文件非常大。three.js还有一个KMZLoader加载器,用于加载KMZ(Keyhole Markup language Zipped)模型,该模型基本上就是压缩过的Collada模型。如需加载此类模型,只需要将ColladaLoader替换为KMZLoader即可
  3. 第二部创建一个加载器,加载模型文件。
  4. Collada模型不仅可以包含模型,还可以保存包含摄像机、光源和动画等的场景。使用Collada模型最好的方式是将loader.load方法的调用结果输出在控制台,然后决定使用哪些组件。
  5. 第三步set up the mixer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var mesh,animationClip,clipAction,mixer;
    function initModels(){
    loader = new THREE.ColladaLoader();
    loader.load("../assets/models/monster/monster.dae",function (result) {
    scene.add(result.scene);
    result.scene.rotateZ(-0.2*Math.PI);
    result.scene.translateX(-20);
    result.scene.translateY(-20);


    mixer = new THREE.AnimationMixer(result.scene);
    animationClip = result.animations[0];
    clipAction = mixer.clipAction(animationClip).play();
    animationClip = clipAction.getClip();

    enableControls();
    });
    }

从雷神之锤模型中加载动画

  1. 加载MD2文件的方式与前面介绍的其他文件方式非常相似,但是由于MD2文件只存储几何体,所以在加载MD2文件时,需要创建材质对象,并自行为其加载纹理资源。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function initModels(){
    var textureLoader = new THREE.TextureLoader();
    loader = new THREE.MD2Loader();
    loader.load("../assets/models/ogre/ogro.md2",function (result) {
    var mat = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    morphTargets: true,
    metalness: 0,
    map: textureLoader.load("../assets/models/ogre/skins/skin.jpg")
    })
    var mat2 = new THREE.MeshNormalMaterial();
    mesh = new THREE.Mesh(result,mat);
    scene.add(mesh);

    mixer = new THREE.AnimationMixer(mesh);
    animationClip1 = result.animations[7];
    clipAction1 = mixer.clipAction(animationClip1).play();
    animationClip2 = result.animations[9];
    clipAction2 = mixer.clipAction(animationClip2)
    animationClip3 = result.animations[10];
    clipAction3 = mixer.clipAction(animationClip3)

    // enableControls();
    });
    }

使用gltfLoader

  1. gltf格式本身侧重于优化文件尺寸以及提高资源使用效率
  2. 这种格式的新版加载器名为THREE.GLTFLoader,它支持2.0版(这是目前的gltf标准格式)以及更高版本的glTF文件。
  3. 首先在HTML页面引入对应的Javascript文件
  4. 然后按照下面方式使用glTFLoader加载器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function initModels(){
    loader = new THREE.GLTFLoader();
    loader.load("../assets/models/CesiumMan/CesiumMan.gltf",function (result) {
    scene.add(result.scene);
    result.scene.scale.set(20,20,20);
    result.scene.rotateY(-0.4*Math.PI);


    mixer = new THREE.AnimationMixer(result.scene);
    animationClip = result.animations[0];
    clipAction = mixer.clipAction(animationClip).play();
    animationClip = clipAction.getClip();

    enableControls();
    });
    }

利用fbxLoader显示动作捕捉模型动画

  1. Autodesk的FBX格式是一种非常易于使用的格式。https://www.mixamo.com/ 提供了约2500个动画模型可供下载和使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function initModels(){
    loader = new THREE.FBXLoader();
    loader.load("../assets/models/salsa/salsa.fbx",function (result) {
    scene.add(result);
    result.scale.set(0.2,0.2,0.2);
    // result.scene.rotateY(-0.4*Math.PI);


    mixer = new THREE.AnimationMixer(result);
    animationClip = result.animations[0];
    clipAction = mixer.clipAction(animationClip).play();
    animationClip = clipAction.getClip();

    enableControls();
    });
    }

通过xLoader加载古老的DirectX模型

  1. 这种文件格式将模型和动画分开存储。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function initModels(){
    var manager = new THREE.LoadingManager();
    var textureLoader = new THREE.TextureLoader();
    //创建两个THREE.XLoader加载器对象,分别用于加载模型文件和动画文件
    var loader = new THREE.XLoader(manager,textureLoader);
    var animLoader = new THREE.XLoader(manager,textureLoader);
    //加载模型
    loader.load(["../assets/models/x/SSR06_model.x"],function (result){
    var mesh = result.models[0];
    //加载动画
    animLoader.load(["../assets/models/x/stand.x",{putPos:false,putScl:false}],function (anim){
    //调用assignAnimation函数将动画对象指定给模型对象
    animLoader.assignAnimation(mesh);

    mixer = mesh.animationMixer;
    clipAction = mixer.clipAction("stand").play();
    clip = clipAction.getClip();

    mesh.translateY(-6);
    mesh.rotateY(-0.7*Math.PI);
    scene.add(mesh);
    enableControls();
    })
    })
    }

利用BVHLoader显示骨骼动画

  1. BVHLoader的特殊之处在于该加载器不返回具有动画的网格或者几何体,它只返回骨骼和动画。
  2. 为了可视化,在程序中使用了THREE.SkeletonHelper
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function initModels(){
    var loader = new THREE.BVHLoader();
    loader.load("../assets/models/amelia-dance/DanceNightClub7_t1.bvh",function (result,mat){
    skeletonHelper = new THREE.SkeletonHelper(result.skeleton.bones[0]);

    skeletonHelper.skeleton = result.skeleton;
    var boneContainer = new THREE.Object3D();
    boneContainer.translateY(-70);
    boneContainer.translateX(-100);
    boneContainer.add(result.skeleton.bones[0]);
    scene.add(skeletonHelper);
    scene.add(boneContainer);
    mixer = new THREE.AnimationMixer(skeletonHelper);
    clipAction = mixer.clipAction(result.clip).setEffectiveWeight(1.0).play();

    enableControls();
    })
    }

如何重用SEA3D模型

  1. SEA3D是一个开源软件项目,它的功能很丰富,通常可以用于制作游戏、创建模型、添加动画等
  2. 在创建THREE.SEA3D加载器对象的时候,就要同时向它提供场景容器THREE.Scene的对象。
  3. 加载状态回调函数也不是在调用load函数时提供,而是直接向onComplete属性提供回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function initModels(){
    var sceneContainer = new THREE.Scene();
    var loader = new THREE.SEA3D({
    container:sceneContainer,
    });
    loader.load("../assets/models/mascot/mascot.sea");
    loader.onComplete = function (e){
    var skinnedMesh = sceneContainer.children[0];
    skinnedMesh.scale.set(0.1,0.1,0.1);
    skinnedMesh.translateX(-40);
    skinnedMesh.translateY(-20);
    skinnedMesh.rotateY(-0.2*Math.PI);
    scene.add(skinnedMesh);

    mixer = new THREE.AnimationMixer(skinnedMesh);
    animationClip = skinnedMesh.animations[0].clip;
    clipAction = mixer.clipAction(animationClip).play();
    animationClip = clipAction.getClip();

    enableControls();
    }
    }

绘制直线和曲线

直线

方法一:
tips
var p1 = new THREE.Vector3(5,6,7);var p2 = new THREE.Vector3(50,60,4);var geometry = new THREE.Geometry();geometry.vertices.push(p1,p2); var material = new THREE.LineBasicMaterial({color:0xffffff});var line = new THREE.Line(geometry,material);scene.add(line);

方法二:
tips
var p3 = new THREE.Vector3(20,4,50);var p4 = new THREE.Vector3(1,40,8);var line2 = new THREE.LineCurve3(p3,p4);let points = line2.getPoints(100);geometry.setFromPoints(points);

曲线(自定义弧线)

通过添加点,绘制样条曲线

  1. 创建一个平面作为画板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 创建平面
    var planeGeometry = new THREE.PlaneGeomrtry(60,60);
    var planeMaterial = new THREE.MeshBasicMaterial({
    color:0xCCCCCC;
    });
    var plane = new THREE.Mesh(planeGeometry,planeMaterial);
    // 平面旋转90
    plane.rotatoX(-0.5*Math.PI);
    scene.add(plane);
  2. 创建点击事件,获取平面上的点坐标,并创建圆点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 光线投射,用于确定鼠标点击位置
    var raycaster = new THREE.Raycaster();
    // 创建二维平面
    var mouse = new THREE.Vector2();
    // 页面绑定鼠标点击事件
    window.addEventListener("mousedown",mousedown);
    // 点击函数
    function mousedown(e){
    // 判断是否点击鼠标左键
    if(e.button == 0){
    // 将html坐标系转化为webgl坐标系,并确定鼠标点击位置
    mouse.x = e.clientX/renderer.domElement.clientWidth*2-1;
    mouse.y = -(e.clientY/renderer.domElement.clientHeight*2)+1;
    // 以camera为z坐标。确定所点击物体的3D空间位置
    raycaster.setFromCamera(mouse,camera);
    // 确定所点击位置上的物体数量
    var intersects = raycaster.intersectObjects(scene.children);
    if(intersects.length){
    // 取第一个物体
    selected = intersects[0];

    }
    }
    }
  3. 绘制曲线(实时更新曲线——删除上一个曲线)

线的那些事

1. 线的材质

  • LineBasicMaterial
    tips
    const material = new THREE.LineBasicMaterial({color:0xffffff, //颜色linewidth:1, //线宽,一般线宽宽度总是1,不可设置linecap:’round’, //线端点类型,可以是:butt\round\square,默认是roundlinejoin:’round’, //线连接点类型,可以是:bevel\round\miter,默认是round})

  • LineDashedMaterial

    • 虚线材质
      tips
      const material = new THREE.LineDashedMaterial({color:0xffffff, //颜色linewidth:1, //线宽scale:1, //虚线的比例大小 ,默认1dashSize:3, //虚线的点的大小,默认3gapSize:1, //点之间的间距大小,默认1})

2. 线的几何

tips
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 geometry = new THREE.BufferGeometry().setFromPoints(points);

const line = new THREE.Line(geometry,material);

3. LineCurve

Raycaster

  1. raycaster(光线投射),用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体),物体选中
  2. Raycaster( origin, direction, near, far )
    1. origin–射线的起点向量
    2. direction–射线的方向向量,应该归一标准化
    3. near–所有返回的结果应该比near远,near不能为负,默认0
    4. far–所有返回的结果应该比far近,far不能小于near,默认值为无穷大
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      const raycaster = new THREE.Raycaster();
      const mouse = new THREE.Vector2();

      function onDocumentMouseDown(e){
      // 屏幕坐标(2维)转换为世界坐标(三维)
      mouse.x = (event.clientX / window.innerWidth)*2 - 1;
      mouse.y = -(event.clientY / window.innerHeight)*2 + 1;

      // 通过摄像机和鼠标位置更新射线
      raycaster.setFromCamera(mouse,camera);

      // 计算物体和射线交点
      var intersects = raycaster.intersectObjects(objects);

      if(intersects.length>0){
      // 选中第一个射线相交的物体
      var intersect = intersects[0].object;
      }
      }

注意点

阴影问题(无法渲染正确阴影):

  • 首先确认渲染器有无开启阴影贴图(默认是false)

    renderer.shadowMap.enabled = true;

  • 确认物体材质是否可受光照影响(MeshBasicMaterial材质不受光照影响)

  • 确认光源是否可以产生阴影(PointLight、SpotLight、DirectionalLight可以有阴影)

  • 确认光源是否开启阴影

    spotLight.castShadow = true;

  • 确认物体是否渲染阴影贴图和是否接收阴影

    //地面接收阴影
    ground.receiveShadow = true;
    //方块产生阴影
    cube.castShaow = true;

页面响应式布局

  • 给页面添加一个监听事件
  • 设置摄像机的长宽比,手动更新相机的投影矩阵
  • 设置渲染器的宽高
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //监听事件
    window.addEventListener(resize,onResize,false);
    //onResize函数
    function onResize(){
    // 摄像机长宽比
    camera.aspect = window.innerWidth/window.innerHeight;
    // 手动更新摄像机投影矩阵
    camera.updateProjectionMatrix();
    // 渲染器宽高
    renderer.setSize(window.innerWidth,window.innerHeight);
    }
  • 本文作者: SindreYang
  • 本文链接: http://blog.mviai.com/2025/8._Three.js/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道