webGL相关问题
# webGL相关问题
# 1.参数vertexShader和fragmentShader分别对应顶点着色器和片段着色器的内容(由GLSL写成)区别?
- 使用HLSL语言编写
- 顶点着色器的功能是把原始顶点数据变换到裁减空间坐标。每个顶点都会调用该着色器函数
- 顶点着色器的输入数据有如下2种方式: Attributes (从缓存中获取的数据),Uniforms (单次绘制中对所有顶点保持不变的值)
- 顶点着色器的输出数据经过光栅化处理后,输入给片段着色器,而片段着色器的功能就是为正在光栅化的当前像素提供颜色
- 每个像素都会调用片段着色器。片段着色器的输入数据有如下3种方式: Uniforms (对于单个绘图调用的每个像素保持相同的值,同上),Textures (从像素pixels和纹元texels中读取的数据),Varyings (从顶点着色器传递并插值的数据)
- 变量(Varyings)是一种将值从顶点着色器传递到片段着色器的方法
# 2.fbx模型里面有啥?模型是如何加载到网页并显示出来的?
- fbx模型返回数据
- 加载模型:使用load加载模型,add将模型添加到场景
() => {
new FBXLoader().load(
"./resources/models/building.FBX",
(obj) => {
_Global.scene.add(obj);
// 显示模型
makeShadow(obj);
},
null,
(e) => {
console.error(e);
}
);
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 显示模型:使用traverse方法,让castShadow和receiveShadow变为true
- traverse方法,通过在递归遍历中访问每个节点来遍历和变换对象
- 因为阴影要投射到“地面”上,所以“地面”这个物体的receiveShadow属性为true;
- 因为想要物体要产生阴影,那么该物体的castShadow属性为true
function makeShadow(obj) {
return 0;
obj.traverse((c) => {
if (c.isMesh) {
c.castShadow = true;
c.receiveShadow = true;
}
});
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 3.如何让3d模型只能在上一半旋转?
- 设置最大仰角
- _Global.orbit.maxPolarAngle = Math.PI * 0.45; // 最大仰角
# 4.加载3d的音频文件时,内容不显示?
- audio加个controls
# 5.3d的video没法播发http和m3u8,常见video解决方案
- JSMpeg,EasyPlayer.js,ckplayer
# 6.3d的audio加载格式及问题?
- 加载常见格式:ogg,mp3,wav
- 可视化音效,AudioAnalyser,傅里叶变换
- 注意:getFrequencyData不能在load中,也不能在当前点击播放函数中,必须在render中通过requestAnimationFrame循环调用,analyser必须为全局变量
# 7.audio让模型发声,远声音小,近声音大如何实现?
- 使用PositionalAudio—位置音频
- setDirectionalCone—设置向某一个方向发声—设置方向线—这个方法用来把环绕声音转换为定向声音
# 8.3d视频如何加载的?
- 创建video元素,传入mp4视频
- 使用VideoTexture--视频纹理
- 在视频纹理内传入dom元素
- 创建Mesh对象,将网格和贴图传入,视频纹理为贴图的map属性
- 将Mesh对象加入场景中
# 9.常用2d和3d技术
- 2d:leaflet--https://leafletjs.com/
- 3d:three和cesium
# 10.常见坐标系
- 火星—gcj-02—高德
- 百度—bd-09
- 地理gps—wgs84
- 国标2000—cgcs2000
# 11.常见点位操作
- 地图点拾取:https://lbs.amap.com/tools/picker—高德
- 坐标系转换:http://www.giscalculator.com/enter_coordpicker/ (opens new window)
# 12.常用地图数据和3d模型处理工具
- arcgis,qgis,cad,3dmax
# 13.模型来源
- 无人机
- 自己建立
- 网上寻找
- 200多页免费3d模型 www.turbosquid.com (opens new window)
- 免费3D雕像: threedscans.com (opens new window)
- 免费3D模型:free3d.com (opens new window)
# 14.3dtiles模型文件是怎么样的?模型是如何加载加载出来的?
- .b3dm文件记录了模型信息,tileset.json则记录了模型的地理位置、.b3dm文件的路径、与其他子瓦片的关系
- 依靠tileset.json文件,粗糙的模型和细节模型建立了联结
- 加载粗糙模型之后cesium会自动遍历tileset.json文件中的children项,加载children中声明的细节模型
- 并通过refine(值为ADD或REPLACE)参数决定自身替换原模型或叠加在原模型上
// tileset.js文件结构
"asset": {
"version": "0.0",
"tilesetVersion": "1.0.0-obj23dtiles"
},
"geometricError": 500,
"root": {
"boundingVolume": {
"region": [
1.9812667196223426,
0.3977279931116791,
1.98161901607411,
0.39801077589267914,
-11.305000305175781,
134.83670043945312
]
},
"refine": "ADD",
"geometricError": 500,
"children": [
{
"boundingVolume": {
"region": [
1.9814442514794284,
0.39777419936273345,
1.9815212909274045,
0.3978240929187335,
-3.1728999614715576,
98.01309967041016
]
},
"geometricError": 200,
"refine": "ADD",
"content": {
"url": "Batchedbhyc/tileset.json"
}
}
]
}}
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
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
# 15.导出瓦片文件指令是什么?常见配置项有那些?
- obj23dtiles -i model.obj --tileset -p customTilesetOptions.json
- customTilesetOptions.json为配置文件,可以指定导出文件的坐标、离地高度、外包体信息
{
"longitude": -1.31968, // 瓦片原点(模型原点 (0,0,0)) 经度的弧度值。
"latitude": 0.698874, // 瓦片原点维度的弧度值。
"transHeight": 0.0, // 瓦片原点所在高度,单位为米。
"region": true, // 使用 region 作为外包体。
"box": false, // 使用 box 作为外包体。
"sphere": false // 使用 sphere 作为外包体。
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 16.第一人称和第三人称视角是什么?three中如何实现的?
- 第一人称—自己/个人/主角—最大限度沉浸感—个人开枪
- 第三人称—旁观者/别人/观众—视野开阔—视野+个人
- 所有的运动都是矩阵变化(物体移动的原理)
- 前—负z,左—负x
# 17.人物移动如何实现?
- 使用new THREEx.KeyboardState()—获取键盘点击事件
- 前:keyboard.pressed("down")—前—负z—box.translateZ(5 * new THREE.Clock().getDelta())
- 向上旋转:keyboard.pressed("w")—box.rotateOnAxis( new THREE.Vector3(1,0,0), Math.PI / 2 * new THREE.Clock().getDelta()
import * as THREE from '../../source/three.module.js';
import { OrbitControls } from '../../source/OrbitControls.js';
import THREEx from "./keyboardState.js";
// 小滑块
const boxgeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterials = [];
for (let i = 0; i < 6; i++) {
const boxMaterial = new THREE.MeshBasicMaterial({
color: Math.random() * 0xffffff,
});
boxMaterials.push(boxMaterial);
}
// 小块
const box = new THREE.Mesh(boxgeometry, boxMaterials);
box.position.y = 1;
box.position.z = 8;
// 控制代码
const keyboard = new THREEx.KeyboardState();
const clock = new THREE.Clock();
const tick = () => {
const delta = clock.getDelta();
const moveDistance = 5 * delta;
const rotateAngle = Math.PI / 2 * delta;
if (keyboard.pressed("down"))
box.translateZ(moveDistance);
if (keyboard.pressed("up"))
box.translateZ(-moveDistance);
if (keyboard.pressed("left"))
box.translateX(-moveDistance);
if (keyboard.pressed("right"))
box.translateX(moveDistance);
if (keyboard.pressed("w"))
box.rotateOnAxis( new THREE.Vector3(1,0,0), rotateAngle);
if (keyboard.pressed("s"))
box.rotateOnAxis( new THREE.Vector3(1,0,0), -rotateAngle);
if (keyboard.pressed("a"))
box.rotateOnAxis( new THREE.Vector3(0,1,0), rotateAngle);
if (keyboard.pressed("d"))
box.rotateOnAxis( new THREE.Vector3(0,1,0), -rotateAngle);
const relativeCameraOffset = new THREE.Vector3(0, 5, 10);
const cameraOffset = relativeCameraOffset.applyMatrix4( box.matrixWorld );
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
controls.target = box.position;
controls.update();
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick();
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
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
扩展:人物如何和相机一起移动的?
- camera.position.x = new THREE.Vector3(0, 5, 10).applyMatrix4( box.matrixWorld ).x;
- controls.target = box.position; -始终让控制器定位到物体
# 18.如何让带有动画的模型导入的动画运动起来?
- 加载模型—new THREE.FBXLoader().load
- 找到可以动画的模型—const animationClip = obj.animations.find(animationClip => animationClip.name === "mixamo.com");
- 创建动画
// 动画
var animationMixer = new THREE.AnimationMixer(scene);
1
2
2
- 找到可以动画的模型名称设置加载速度播放
// 找到可以动画的模型名称
const animationClip = obj.animations.find(animationClip => animationClip.name === "mixamo.com");
const action2 = animationMixer.clipAction(animationClip);
// 7.5倍速度播放
action2.timeScale = 7.5//24帧每秒变为30帧每秒,要慢放为0.8倍,做动画要做30帧/s的
//调用播放器的播放事件
action2.play();
1
2
3
4
5
6
7
2
3
4
5
6
7
- 循环播放调用更新
// 人物动画播放变化
const clock = new THREE.Clock();
const delta = clock.getDelta();
const renderAnimate = () => {
const delta = clock.getDelta();
animationMixer.update(delta);
renderer.render(scene, camera);
window.requestAnimationFrame(renderAnimate)
}
renderAnimate();
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 19.人物是如何转向的,在前后左右掉头的?
- 各个方向为不同的模型动画,当运动时,播放不同的模型动画
# 20.汽车开门,关门动画实现思路
- 开门:将状态置为开门—改变摄像机位置—让车门沿着y轴旋转90度—item.rotation.y - 0.5 * Math.PI
- 关门:相机和地板位置置为起始位置—小车位置和旋转,视角变化—将状态置为关闭门—门的位置关闭恢复—item.rotation.y + 0.5 * Math.PI
# 21.模型加载慢,如何优化?
- 让模型师优化
- 开发优化:无损压缩模型--压缩前131M,压缩后31M
- 模型压缩一般有两种路线,一个是减小网格体的顶点和面数,一个是减小纹理材质的贴图
- gltf/glb 文件转换压缩上百M到十几M—https://zhuanlan.zhihu.com/p/48735094
步骤
npm i gltf-pipeline
gltf-pipeline -i main1.glb -o main2.glb -d
-i:输入
-o:输出
-d:draco压缩
1
2
3
4
5
6
2
3
4
5
6