-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathREADME.txt
More file actions
443 lines (425 loc) · 15.9 KB
/
Copy pathREADME.txt
File metadata and controls
443 lines (425 loc) · 15.9 KB
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
<template>
<div class="demo" ref="demo">
<!-- 注意 这里我设置了静音 muted -->
<video
ref="video"
autoplay
muted
playsinline
webkit-playsinline
style="width:0;height:0"
></video>
</div>
</template>
<script>
// import Vconsole from 'vconsole'
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import * as d3 from 'd3'
// webrtc 资料
// https://blog.csdn.net/yangzhenping/article/details/78895208?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164817406916782246448540%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164817406916782246448540&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-2-78895208.142^v3^pc_search_result_control_group,143^v4^register&utm_term=getUserMedia+%E7%94%BB%E8%B4%A8%E4%BD%8E
export default {
name: 'demo',
data() {
return {
scene: null,
camera: null,
renderer: null,
moduleObj: null,
canvasblobs: [],
mediaRecorder: null,
canvasStream: null,
// 双指
// 第一个手指数据
oneData: [],
// 第二个手指数据
twoData: [],
long: 0
}
},
async mounted() {
// return
// 从这里保存这里的this 为了接下里的函数使用
const self = this
const box = this.$refs.demo
box.style.height = window.innerHeight + 'px'
// 场景
const scene = new THREE.Scene()
// 摄像机
const camera = new THREE.PerspectiveCamera(
30,
box.clientWidth / box.clientHeight, //使摄像机和屏幕比一样
1,
1000
)
// 渲染器 这个配置 是为了开启一个什么缓冲区 不开这个 无法拍照
const renderer = new THREE.WebGLRenderer({ preserveDrawingBuffer: true })
// 设置渲染器大小
renderer.setSize(box.clientWidth, box.clientHeight)
/*
包含颜色信息(.map、.emissiveMap 和 .specularMap)的纹理在 glTF 中始终使用 sRGB 颜色空间,
而顶点颜色和材质属性(.color、.emissive、.specular)使用线性颜色空间。在典型的渲染工作流程中,
渲染器将纹理转换为线性色彩空间,进行光照计算,然后将最终输出转换回 sRGB 并显示在屏幕上。
除非您需要在线性色彩空间中进行后期处理,否则在使用 glTF 时始终按如下方式配置 WebGLRenderer :
renderer.outputEncoding = THREE.sRGBEncoding;
*/
// renderer.setClearColor('#fff', 1.0)
// 向赫兹中加入渲染器
box.appendChild(renderer.domElement)
renderer.outputEncoding = THREE.sRGBEncoding
// 摄像头录制 这个函数有很多版本 用来兼容各个浏览器的,当然当前这个就是兼容性最好的,可以去网上搜一下用来专门兼容其他浏览器的,然后进行判断和选择
// console.log(navigator.mediaDevices.getUserMedia)
// navigator.getUserMedia =
// navigator.mediaDevices.getUserMedia ||
// navigator.webkitGetUserMedia ||
// navigator.mozGetUserMedia
// const mediaStream = await navigator.mediaDevices.getUserMedia({
// // video属性设置 开摄像头的
// video: {
// width: { min: box.clientWidth },
// height: { min: box.clientHeight }
// // 下面这个是开后置摄像头的 根据需要打开
// // facingMode: { exact: "environment" },
// },
// // audio属性设置 开麦克风的
// audio: {
// // 是否尝试去除音频信号中的背景噪声
// noiseSuppression: true,
// // 回声取消
// echoCancellation: true
// }
// })
/*
权限
在一个可安装的app(如Firefox OS app)中使用 getUserMedia() ,你需要在声明文件中指定以下的权限:
"permissions": {
"audio-capture": {
"description": "Required to capture audio using getUserMedia()"
},
"video-capture": {
"description": "Required to capture video using getUserMedia()"
}
}
*/
// const video = this.$refs.video
// // 成功返回promise对象,接收一个mediaStream参数与video标签进行对接
// video.srcObject = mediaStream
// video.play()
// 加入作为背景的面 这里干的事情,就是用一个平面模型 把模型的材质变成了录制的视频
// const texture = new THREE.VideoTexture(video)
// texture.minFilter = THREE.LinearFilter
// texture.magFilter = THREE.LinearFilter
// texture.format = THREE.RGBFormat
const bgw = 30
//页面 图像出现压扁 拉长情况 是在这个地方去配置的,第一个参数是宽度的截取 第二个参数是高度的截取
// 苹果手机出现的拉长 在这里加个if判断 是苹果的话,算法就改变一下 把第一个参数 变得更多一些 就正常了
const geometry = new THREE.PlaneGeometry(
bgw,
bgw / (box.clientWidth / box.clientHeight)
)
// const material = new THREE.MeshBasicMaterial({
// map: texture,
// side: THREE.DoubleSide
// })
// const cube = new THREE.Mesh(geometry, material)
camera.position.z = 100 //参数
// 以下这行代码 代表着是否横向翻转镜像 打开,就像照镜子一样了
// cube.rotation.y += 3.1415
//增加环境光
var light = new THREE.AmbientLight( 0xffffff,0.3);
scene.add( light );
var directionalLight = new THREE.DirectionalLight( 0xffffff, 2.5 );
scene.add( directionalLight );
// scene.add(cube)
// 加载3D
this.fnLoad3D()
// cube.rotation.y = 1.6
function animate() {
// 60赫兹刷新率的执行本函数, 比定时器更流畅更适合屏幕
requestAnimationFrame(animate)
// 如何引入了模型 就把模型里的内容 进行改变
// 以下这两行 打开后 可以去看整体是咋样的 搞不明白物体位置时候 就尝试打开
if(self.moduleObj){
self.moduleObj.rotation.y += 0.01
self.moduleObj.rotation.x += 0.01
}
// /渲染
renderer.render(scene, camera)
}
animate()
this.scene = scene
this.camera = camera
this.renderer = renderer
// this.initStream = mediaStream
// // 初始位置
// self.moduleObj.scene.rotation.y = 0
// self.moduleObj.scene.rotation.x = 0
// 缩放 去设置双指时候执行 333行
this.fnDo()
},
methods: {
// 加载3D模型的函数
fnLoad3D() {
// 注意这个loader 是专门引入.glb格式的模型的 需要的话 请在three官网找需要的loader 而引入地址是一样的
const loader = new GLTFLoader()
// 这块就是引入模型地址的方式 可以使用动态引入
loader.load(
'http://qiyuan.mobi/3d/20220330/lingpai/models/lingpai.gltf',
model => {
// 把模型的东西 放入这个盒子里
this.scene.add(model.scene)
this.moduleObj = model
this.moduleObj.scene.rotation.y = 180
// !注意, 因为我不知道导入的模型都会有啥,我也没完整学过模型的东西 所以 这里导入复杂的后 怎么处理模型 均需要您在three里面找到对应的方法
// 可以通过if(...) 这样来判断某些东西存在 就引入 不存在就不引入的方式进行 关于引入模型包含的那些字段 您学模型的肯定知道都啥意思
// 还有就是 我的模型 导出时候 选择了 !! 自发光 !! 如果您那边出现了看不见模型 也没有报错信息 就可能是灯光方面的问题 也或者加个全局日光灯 或者在摄像机方向对着加个光
// http://www.yanhuangxueyuan.com/threejs/docs/#api/zh/lights/AmbientLight 全局环境光
// 当然 也可能是外包的制作模型的那些人导出没有带上所有东西的问题
// s.prototype.$vConsole =new Vconsole()
},
undefined,
function(error) {
console.log(error)
}
)
},
// 为了改变每个物体按照自己的原点旋转
changePivot(obj, scene) {
let center = new THREE.Vector3()
obj.geometry.computeBoundingBox()
obj.geometry.boundingBox.getCenter(center)
let wrapper = new THREE.Object3D()
wrapper.position.set(center.x, center.y, center.z)
obj.position.set(-center.x, -center.y, -center.z)
// 注意这个鬼东西,会删了原数组中的数据,导致循环不再正确进行
wrapper.add(obj)
scene.add(wrapper)
return wrapper
},
// 开始录制canvas
async fnPlayCanvas() {
let mimeType = ''
// 这里判断各种格式 浏览器是否支持 不同格式清晰度也不一样
// 关于各个类型 清晰度和支持情况 https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs
var types = [
'video/webm',
'video/mp4',
'video/avc',
'video/ogg',
'video/webm\;codecs=vp8',
'video/webm\;codecs=daala',
'video/webm\;codecs=h264',
'audio/webm\;codecs=opus',
'video/mpeg'
]
for (var i in types) {
if (MediaRecorder.isTypeSupported(types[i])) {
mimeType = types[i]
break
}
}
if (mimeType === '') {
alert('当前浏览器版本过低,请下载新版浏览器')
}
//防止重复点击
if (this.canvasblobs.length > 0) {
this.canvasblobs = []
}
this.$refs.video.play()
// 录制屏幕
const canvas = document.querySelector('.demo canvas')
const Stream = canvas.captureStream()
// 保存这个canvas流 用于截取拍照
this.canvasStream = Stream
// 合并音轨与视频轨道
const newStream = new MediaStream([
// 获取视频轨道
Stream.getVideoTracks()[0],
// 获取音轨 注意这个音轨,我并未开启音乐播放器 如果需要添加进去多个音轨,或者其他音轨 那后面的0可能就需要改掉,
// 如果想加入所有音轨 ...this.initStream.getAudioTracks() 用这个方法 三点运算符
this.initStream.getAudioTracks()[0]
])
let mediaRecorder = new MediaRecorder(newStream, {
// 这个地方的webm也能设置不同的清晰度
mimeType,
ignoreMutedMedia: true,
// // 音质
audioBitsPerSecond: 128000,
// 画质 可以调高
videoBitsPerSecond: 2500000
})
mediaRecorder.ondataavailable = e => {
// 保存canvas流数据
this.canvasblobs.push(e.data)
}
mediaRecorder.start(60) //60毫秒记录一次
this.mediaRecorder = mediaRecorder
},
// 保存canvas数据
fnDownloadCanvas() {
this.mediaRecorder.stop()
// webm是一种浏览器比较通用的视频格式
// https://www.npmjs.com/package/webm-to-mp4 这个是一个webm转MP4的js库 可以让后端整个连接 用nodejs加这个库来转换一下
// 如果是其他的语言 直接搜索 webm转MP4 就能找到 这个是java的 https://www.cnblogs.com/zhwl/p/3645593.html
// 注意 !!!!!!!!! 苹果的safari就支持MP4
const ua = navigator.userAgent
// console.log(navigator.userAgent)
const isSafari = /(?:Safari)/.test(ua)
const isAndroid = /(?:Android)/.test(ua)
let strEnd = 'webm'
if (isSafari && !isAndroid) {
strEnd = 'mp4'
}
let blob = new Blob(this.canvasblobs, { type: `video/${strEnd}` })
let url = URL.createObjectURL(blob)
// console.log(url)
let a = document.createElement('a')
a.href = url
a.style.display = 'none'
a.download = `record.${strEnd}`
a.click()
},
// 拍照
fnTakeImg() {
this.renderer.domElement.toBlob(
function(blob) {
let dlLink = document.createElement('a')
dlLink.download = 'newImg'
dlLink.style.display = 'none'
// 字符内容转变成blob地址
dlLink.href = URL.createObjectURL(blob)
// 触发点击
dlLink.click()
},
'image/png',
1
)
},
// 移动模式
fnMove() {
const self = this
d3.select('.demo canvas').call(
d3.drag().on('drag', function() {
// 这里其实就是每隔一段时间计算手指位移量
const d = d3.event
const cx = d.dx
const cy = d.dy
// 上面把每一段时间的位移量 进行一个缩小 改变模型内部的position属性就可以位移了
self.moduleObj.scene.position.y -= cy / 14
self.moduleObj.scene.position.x += cx / 14
// 这个z轴的 就是离摄像机的距离 也就实现了深度
// self.moduleObj.scene.position.z
})
)
},
// 旋转模式
fnRotate() {
const self = this
d3.select('.demo').call(
d3.drag().on('drag', function() {
// 这里其实就是每隔一段时间计算手指位移量
const d = d3.event
if (d.active !== 1) {
const cx = d.dx
const cy = d.dy
self.moduleObj.scene.rotation.y -= cx / 14
self.moduleObj.scene.rotation.x += cy / 14
}
// 上面把每一段时间的位移量 进行一个缩小 改变模型内部的rotation属性就可以位移了
// 这里使用时候 注意我跟上面的位移相比 cx 与 cy是反着用的 因为这样更符合操作习惯
// 这个z轴的 但是对于渲染 两个轴向也够了
// self.moduleObj.scene.rotation.z
})
)
},
// 缩放
fnDo() {
const self = this
d3.select('.demo canvas').call(
d3.drag().on('drag', function() {
const d = d3.event
// 旋转
if (d.active == 1) {
const cx = d.dx
const cy = d.dy
self.moduleObj.scene.rotation.y -= cx / 14
self.moduleObj.scene.rotation.x += cy / 14
}
// 缩放
if (d.active == 2) {
// 两个手指移动了的距离
if (d.identifier === 0) {
// 初始数据不计算
if (self.oneData.length === 0) {
self.oneData = [d.x, d.y]
self.long = Math.sqrt(
(self.oneData[0] - self.twoData[0]) ** 2 +
(self.oneData[1] - self.twoData[1]) ** 2
)
return
}
self.oneData = [d.x, d.y]
}
if (d.identifier === 1) {
if (self.twoData.length === 0) {
self.twoData = [d.x, d.y]
self.long = Math.sqrt(
(self.oneData[0] - self.twoData[0]) ** 2 +
(self.oneData[1] - self.twoData[1]) ** 2
)
return
}
self.twoData = [d.x, d.y]
}
const long = Math.sqrt(
(self.oneData[0] - self.twoData[0]) ** 2 +
(self.oneData[1] - self.twoData[1]) ** 2
)
let num = long - self.long
self.long = long
try {
self.moduleObj.scene.scale.y += num/20 //调整这里数字来调整缩小放大的速度
self.moduleObj.scene.scale.x += num/20
self.moduleObj.scene.scale.z += num/20
} catch (error) {
// alert(error)
}
}
})
)
}
}
}
</script>
<style>
* {
padding: 0;
margin: 0;
}
</style>
<style scoped>
.demo {
position: relative;
display: flex;
flex-direction: column;
width: 100vw;
overflow: hidden;
}
.btns {
width: 15vw;
height: 15vw;
font-size: 16px;
border-radius: 50%;
border: 0;
background: #ffffff00;
}
.btns:active {
background: #ffffff00;
}
.btnsBox {
position: fixed;
width: 100vw;
bottom: 0;
left: 0;
}
</style>