帧率显示代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//这段代码可以在右上角显示帧率(复制自官方代码 修改了引用js的路径)
//官方地址:https://github.com/mrdoob/stats.js/
(function(){
var script=document.createElement('script');//添加script标签
script.onload=function(){//当脚本加载时运行
var stats=new Stats();
document.body.appendChild(stats.dom);//添加到body标签中
requestAnimationFrame(function loop(){//添加刷新函数
stats.update();//调用实时刷新
requestAnimationFrame(loop)//递归调用实现刷新
});
};
script.src='https://cdnjs.cloudflare.com/ajax/libs/stats.js/16/Stats.js';//script标签加入src
document.head.appendChild(script);//script标签添加到头部
})()//闭包编写,这样对其他代码的影响是最少的

//闭包编写,相当于 C++ 类中的private变量和函数,无法在外部调用
//console.log(script)
//执行后会报错 Uncaught ReferenceError: script is not defined

Three.js 三件套

用init()函数将下面的代码包含起来
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
//创建threejs的场景
var scene = new THREE.Scene();

// 创建透视摄像机 并且初始化位置
var camera = new THREE.PerspectiveCamera(
45, //视场角
window.innerWidth / window.innerHeight, // 长宽比
1, // 近景裁剪
1000 // 远景裁剪
);
camera.position.z = 20;
camera.position.x = 0;
camera.position.y = 5;
camera.lookAt(new THREE.Vector3(0, 10, 0));//确保摄像机望向坐标原点

//创建 webGL的渲染器 这里的设置和上一节一致
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

//为渲染器开启 阴影贴图
renderer.shadowMap.enabled = true;

//开启这个 阴影效果会抗锯齿 但是也会卡许多
//renderer.shadowMapType = THREE.PCFSoftShadowMap;

//官方说明:Sets device pixel ratio. This is usually used for HiDPI device to prevent bluring output canvas.
//大意就是针对HiDPI设备(比如使用苹果Retina的)进行的设置,防止输出画面被施加模糊
renderer.setPixelRatio( window.devicePixelRatio );

//启用orbit摄像机 需要OrbitControls.js文件支持 可以实现环绕摄像机 支持缩放、平移功能
var controls = new THREE.OrbitControls( camera, renderer.domElement );

//将渲染器绑定到html的特定div上 方便以后的DOM操作
document.getElementById('webgl').appendChild(renderer.domElement);

//调用update函数
update(renderer, scene, camera, controls);

资料补充
  • PCF解释
  • 阴影贴图
    与阴影贴图相对的技术是光线追踪阴影(Ray Tracing Shadow),效果更真实,也更加消耗资源。

Update函数编写

补充update函数的调用
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 update(renderer, scene, camera, controls) {

//OrbitControl 如果要开启damping效果才需要这一行
//controls.update();

//这里的写法与上一节一致 controls也可以去掉
renderer.render(scene, camera);
requestAnimationFrame(function() {
update(renderer, scene, camera, controls);
});

//创建resize函数 在调节窗口大小时三维界面会响应缩放
window.addEventListener('resize',function(){//窗口大小改变时响应
var width = window.innerWidth;//计算当前窗口的宽度
var height = window.innerHeight;//计算当前窗口的高度
renderer.setSize(width,height);//重新设置渲染的区域大小
camera.aspect = width/height;//重新设置摄像机的长宽比

//官方说明:Updates the camera projection matrix. Must be called after any change of parameters.
//任何参数的变化都需要调用这个函数进行计算
camera.updateProjectionMatrix();

})
}

添加灯光以及控制灯光的GUI

这里我们又回到了init()函数内部
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 lightLeft = getSpotLight(0.4, 'rgb(255, 220, 180)');
var lightRight = getSpotLight(1.25, 'rgb(255, 220, 180)');

//调整聚光灯的位置
lightLeft.position.x = 6;
lightLeft.position.y = 8;
lightLeft.position.z = 12;

lightRight.position.x = 50;
lightRight.position.y = 14;
lightRight.position.z = -6;

// 将灯光添加到场景中
scene.add(lightLeft);
scene.add(lightRight);

//导入gui界面
var gui = new dat.GUI();

// 添加图形操作界面 滑竿 参数分别代表 控制源 具体属性(控制源的子对象) 最小值 最大值
gui.add(lightLeft, 'intensity', 0, 10);
gui.add(lightLeft.position, 'x', -50, 50);
gui.add(lightLeft.position, 'y', -50, 50);
gui.add(lightLeft.position, 'z', -50, 50);

gui.add(lightRight, 'intensity', 0, 10);
gui.add(lightRight.position, 'x', -50, 50);
gui.add(lightRight.position, 'y', -50, 50);
gui.add(lightRight.position, 'z', -50, 50);

编写灯光调用函数

我们又跳出init()函数看看灯光调用的编写
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
//聚光灯构造函数
function getSpotLight(intensity, color) {
/*
a = a == b ? b++ : 0
这个就是如果a等于b成立,就返回b++,如果不成立,就返回0

等价于
if(a==b)
a = b++
else
a = 0
*/
//设置颜色,如果color没有定义直接定义为纯白,否则就是color传入的值
color = color === undefined ? 'rgb(255, 255, 255)' : color;
var light = new THREE.SpotLight(color, intensity);//创建聚光灯
light.castShadow = true;//开启阴影

//使聚光灯边缘柔化
light.penumbra = 0.5;//penumbra半影 在三维中专门指聚光灯轮廓的模糊程度

//设置深度贴图的大小以及偏差值
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
light.shadow.bias = 0.001;

return light;
}

加载OBJ模型和贴图

这里我们又双叒叕回到了init()函数内部
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
//加载外部模型
//需要OBJLoader.js的支持
var loader = new THREE.OBJLoader();
var textureLoader = new THREE.TextureLoader();

//加载模型,以及模型预加载的函数
loader.load('model/fellow.obj', function (object) {
//获取颜色贴图和凹凸贴图的路径
var colorMap = textureLoader.load('model/texture.jpg');
var bumpMap = textureLoader.load('model/texture.jpg');
//获取材质,材质函数在下面定义,传入参数 材质类型 颜色
var faceMaterial = getMaterial('lambert', 'rgb(255, 255, 255)');

//traverse 对模型的各个部分进行逐一操作,可以用for loop递归来实现
//traverse 是threejs自带的功能
object.traverse(function(child) {
//设置材质以及材质的属性
child.material = faceMaterial;
faceMaterial.roughness = 0.375;//粗糙度
faceMaterial.map = colorMap;//颜色贴图
faceMaterial.bumpMap = bumpMap;//凹凸贴图
faceMaterial.roughnessMap = bumpMap;//粗糙度贴图
faceMaterial.metalness = 0;//金属度
faceMaterial.bumpScale = 0.175;//凹凸程度
child.material.side = THREE.DoubleSide;//双面材质
} );

//放大和移动模型
object.scale.x = 0.01;
object.scale.y = 0.01;
object.scale.z = 0.01;

object.position.z = 0;
object.position.y = -2;

//添加模型
scene.add(object);
});

//init()函数返回值 返回场景
return scene;

编写getMaterial函数

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
function getMaterial(type, color) {
var selectedMaterial;
var materialOptions = {
color: color === undefined ? 'rgb(255, 255, 255)' : color
};//这里如果color没有定义直接定义为纯白,否则就color的值

//下面展示了threejs内置的材质(可以加载第三方js来扩展材质,或者通过GLSL语言自己写材质)
switch (type) {
case 'basic':
selectedMaterial = new THREE.MeshBasicMaterial(materialOptions);
break;//这个材质等同于Maya中的surface表面材质,不受灯光影响
case 'lambert':
selectedMaterial = new THREE.MeshLambertMaterial(materialOptions);
break;//这个材质等同于Maya中的lambert材质
case 'phong':
selectedMaterial = new THREE.MeshPhongMaterial(materialOptions);
break;//这个材质等同于Maya中的phong材质
case 'standard':
selectedMaterial = new THREE.MeshStandardMaterial(materialOptions);
break;//这个材质等同于unity的standrad材质
case 'PBR':
selectedMaterial = new THREE.MeshPhysicalMaterial(materialOptions);
break;//PBR材质
default:
selectedMaterial = new THREE.MeshBasicMaterial(materialOptions);
break;
}

return selectedMaterial;
}

添加全屏按钮

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
$(function(){   

//添加按钮的CSS样式
var full_screen_css = $("<style></style>").text("#full_screen {position: fixed;bottom: 20px;right: 20px;padding: 8px;color: #fff;background-color: #555;opacity: 0.7;}#full_screen:hover {cursor: pointer;opacity: 1;}");
$("head").append(full_screen_css);

//添加按钮
$("#webgl").after("<button id=\"full_screen\" onclick=\"full_screen_fun()\">全屏</button>");
});

var full_screen_check = false;//判断全屏状态

function full_screen_fun(){
if(full_screen_check){//全屏状态
exitFull();//退出全屏
}else{
requestFullScreen(document.documentElement);// 全屏整个网页
}

};

function requestFullScreen(element) {
// 判断各种浏览器,找到正确的方法
full_screen_check = true;
var requestMethod = element.requestFullScreen || //W3C
element.webkitRequestFullScreen || //Chrome等
element.mozRequestFullScreen || //FireFox
element.msRequestFullScreen; //IE11
if (requestMethod) {
requestMethod.call(element);
}
else if (typeof window.ActiveXObject !== "undefined") {//for Internet Explorer
var wscript = new ActiveXObject("WScript.Shell");
if (wscript !== null) {
wscript.SendKeys("{F11}");
}
}
}

//退出全屏 判断浏览器种类
function exitFull() {
full_screen_check = false;
// 判断各种浏览器,找到正确的方法
var exitMethod = document.exitFullscreen || //W3C
document.mozCancelFullScreen || //Chrome等
document.webkitExitFullscreen || //FireFox
document.webkitExitFullscreen; //IE11
if (exitMethod) {
exitMethod.call(document);
}
else if (typeof window.ActiveXObject !== "undefined") {//for Internet Explorer
var wscript = new ActiveXObject("WScript.Shell");
if (wscript !== null) {
wscript.SendKeys("{F11}");
}
}
}

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
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
<!doctype html>
<html>
<head>

<meta charset="utf-8">
<title>实物演示</title>
</head>
<style>
/*overflow:hidden取消滑动栏*/
body { margin: 0;overflow: hidden }
</style>
<body>
<!--这个div用来加载js-->
<div id="webgl"></div>
<!--链接threejs的库-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
<script src="../js/controls/OrbitControls.js"></script>
<script src="../js/loaders/OBJLoader.js"></script>

<script type="application/javascript">

//这段代码可以在右上角显示帧率(复制自官方代码 修改了引用js的路径)
//官方地址:https://github.com/mrdoob/stats.js/
(function(){
var script=document.createElement('script');//添加script标签
script.onload=function(){//当脚本加载时运行
var stats=new Stats();
document.body.appendChild(stats.dom);//添加到body标签中
requestAnimationFrame(function loop(){//添加刷新函数
stats.update();//调用实时刷新
requestAnimationFrame(loop)//递归调用实现刷新
});
};
script.src='https://cdnjs.cloudflare.com/ajax/libs/stats.js/16/Stats.js';//script标签加入src
document.head.appendChild(script);//script标签添加到头部
})()//闭包编写,减少对其他代码的影响

//闭包编写,相当于 C++ 类中的private变量和函数,无法在外部调用
//console.log(script)
//执行后会报错 Uncaught ReferenceError: script is not defined


function init() {

//创建threejs的场景
var scene = new THREE.Scene();

// 创建透视摄像机 并且初始化位置
var camera = new THREE.PerspectiveCamera(
45, //视场角
window.innerWidth / window.innerHeight, // 长宽比
1, // 近景裁剪
1000 // 远景裁剪
);
camera.position.z = 20;
camera.position.x = 0;
camera.position.y = 5;
camera.lookAt(new THREE.Vector3(0, 10, 0));//确保摄像机望向坐标原点

//创建 webGL的渲染器 这里的设置和第一节一致
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
//为渲染器开启 深度贴图阴影
renderer.shadowMap.enabled = true;
renderer.setPixelRatio( window.devicePixelRatio );
//启用orbit摄像机 需要OrbitControls.js文件支持 可以实现环绕360度摄像机 支持缩放功能
var controls = new THREE.OrbitControls( camera, renderer.domElement );

//将渲染器绑定到html的div上
document.getElementById('webgl').appendChild(renderer.domElement);

//调用update函数
update(renderer, scene, camera, controls);

/************************/
/************************/
/************************/

//添加两个聚光灯 参数分别是 强度 和 颜色
var lightLeft = getSpotLight(1.6, 'rgb(255, 220, 180)');
var lightRight = getSpotLight(0.4, 'rgb(255, 220, 180)');

//调整聚光灯的位置
lightLeft.position.x = 6;
lightLeft.position.y = 8;
lightLeft.position.z = 12;

lightRight.position.x = 50;
lightRight.position.y = 14;
lightRight.position.z = -6;

// 加载灯光到场景中
scene.add(lightLeft);
scene.add(lightRight);

//导入gui界面
var gui = new dat.GUI();

// 添加图形界面 滑竿 参数分别代表 控制的原件 具体属性 最小值 最大值
gui.add(lightLeft, 'intensity', 0, 10);
gui.add(lightLeft.position, 'x', -50, 50);
gui.add(lightLeft.position, 'y', -50, 50);
gui.add(lightLeft.position, 'z', -50, 50);

gui.add(lightRight, 'intensity', 0, 10);
gui.add(lightRight.position, 'x', -50, 50);
gui.add(lightRight.position, 'y', -50, 50);
gui.add(lightRight.position, 'z', -50, 50);

//加载外部模型
//需要OBJLoader.js的支持
var loader = new THREE.OBJLoader();
var textureLoader = new THREE.TextureLoader();

//加载模型,以及模型预加载的函数
loader.load('model/fellow.obj', function (object) {
//获取颜色贴图和凹凸贴图的路径
var colorMap = textureLoader.load('model/texture.jpg');
var bumpMap = textureLoader.load('model/texture.jpg');
//获取材质,材质函数在下面定义,传入参数 材质类型 颜色
var faceMaterial = getMaterial('lambert', 'rgb(255, 255, 255)');

//traverse 对模型的各个部分进行逐一操作,可以用for loop递归来实现
//traverse 是threejs自带的功能
object.traverse(function(child) {
//设置材质以及材质的属性
child.material = faceMaterial;
faceMaterial.roughness = 0.375;//粗糙度
faceMaterial.map = colorMap;//颜色贴图
faceMaterial.bumpMap = bumpMap;//凹凸贴图
faceMaterial.roughnessMap = bumpMap;//粗糙度贴图
faceMaterial.metalness = 0;//金属度
faceMaterial.bumpScale = 0.175;//凹凸程度
child.material.side = THREE.DoubleSide;//双面材质
} );
//放大和移动模型
object.scale.x = 0.01;
object.scale.y = 0.01;
object.scale.z = 0.01;

object.position.z = 0;
object.position.y = -2;

//添加模型
scene.add(object);
});
//返回场景
return scene;
}

//聚光灯构造函数
function getSpotLight(intensity, color) {
/*
a==b?b++:0
这个就是如果a等于b成立,就返回b++,如果不成立,就返回0
*/
color = color === undefined ? 'rgb(255, 255, 255)' : color;//设置颜色,如果color没有定义直接定义为纯白,否则就color的值
var light = new THREE.SpotLight(color, intensity);//创建聚光灯
light.castShadow = true;//开启阴影
light.penumbra = 0.5;//使灯光边缘柔化

//设置深度贴图的大小以及偏差值
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
light.shadow.bias = 0.001;

return light;
}

//材质构造函数
function getMaterial(type, color) {
var selectedMaterial;
var materialOptions = {
color: color === undefined ? 'rgb(255, 255, 255)' : color
};//这里如果color没有定义直接定义为纯白,否则就color的值

//下面展示了threejs内置的材质(可以加载第三方js来扩展材质,或者通过GLSL语言自己写材质)
switch (type) {
case 'basic':
selectedMaterial = new THREE.MeshBasicMaterial(materialOptions);
break;//这个材质等同于Maya中的surface表面材质,不受灯光影响
case 'lambert':
selectedMaterial = new THREE.MeshLambertMaterial(materialOptions);
break;//这个材质等同于Maya中的lambert材质
case 'phong':
selectedMaterial = new THREE.MeshPhongMaterial(materialOptions);
break;//这个材质等同于Maya中的phong材质
case 'standard':
selectedMaterial = new THREE.MeshStandardMaterial(materialOptions);
break;//这个材质等同于unity的standrad材质
case 'PBR':
selectedMaterial = new THREE.MeshPhysicalMaterial(materialOptions);
break;//PBR材质
default:
selectedMaterial = new THREE.MeshBasicMaterial(materialOptions);
break;
}

return selectedMaterial;
}

function update(renderer, scene, camera, controls) {
controls.update();//controls是orbit摄像机的传参用来实现摄像机的更新,不过没有它目前也没问题
//这里的写法与前文一致 controls也可以去掉
renderer.render(scene, camera);
requestAnimationFrame(function() {
update(renderer, scene, camera, controls);
});

//创建resize函数 在调节窗口大小时三维界面会响应缩放
window.addEventListener('resize',function(){
var width = window.innerWidth;
var height = window.innerHeight;
renderer.setSize(width,height);
camera.aspect = width/height;
camera.updateProjectionMatrix();

})
}


//调用初始化函数
var scene = init();

/************************/
/*******添加全屏按钮********/
/*****并且添加全屏功能*******/
/************************/

$(function(){

//添加按钮的CSS样式
var full_screen_css = $("<style></style>").text("#full_screen {position: fixed;bottom: 20px;right: 20px;padding: 8px;color: #fff;background-color: #555;opacity: 0.7;}#full_screen:hover {cursor: pointer;opacity: 1;}");
$("head").append(full_screen_css);

//添加按钮
$("#webgl").after("<button id=\"full_screen\" onclick=\"full_screen_fun()\">全屏</button>");
});

var full_screen_check = false;//判断全屏状态

function full_screen_fun(){
if(full_screen_check){//全屏状态
exitFull();//退出全屏
}else{
requestFullScreen(document.documentElement);// 全屏整个网页
}

};

function requestFullScreen(element) {
// 判断各种浏览器,找到正确的方法
full_screen_check = true;
var requestMethod = element.requestFullScreen || //W3C
element.webkitRequestFullScreen || //Chrome等
element.mozRequestFullScreen || //FireFox
element.msRequestFullScreen; //IE11
if (requestMethod) {
requestMethod.call(element);
}
else if (typeof window.ActiveXObject !== "undefined") {//for Internet Explorer
var wscript = new ActiveXObject("WScript.Shell");
if (wscript !== null) {
wscript.SendKeys("{F11}");
}
}
}

//退出全屏 判断浏览器种类
function exitFull() {
full_screen_check = false;
// 判断各种浏览器,找到正确的方法
var exitMethod = document.exitFullscreen || //W3C
document.mozCancelFullScreen || //Chrome等
document.webkitExitFullscreen || //FireFox
document.webkitExitFullscreen; //IE11
if (exitMethod) {
exitMethod.call(document);
}
else if (typeof window.ActiveXObject !== "undefined") {//for Internet Explorer
var wscript = new ActiveXObject("WScript.Shell");
if (wscript !== null) {
wscript.SendKeys("{F11}");
}
}
}


/************************/
/************************/
/************************/


</script>

</body>
</html>

实现效果

效果链接
备注:全屏按钮在<iframe>中不起作用,可以点击链接感受