webGL相关问题

# webGL相关问题

# 1.参数vertexShader和fragmentShader分别对应顶点着色器和片段着色器的内容(由GLSL写成)区别?

  • 使用HLSL语言编写
  • 顶点着色器的功能是把原始顶点数据变换到裁减空间坐标。每个顶点都会调用该着色器函数
  • 顶点着色器的输入数据有如下2种方式: Attributes (从缓存中获取的数据),Uniforms (单次绘制中对所有顶点保持不变的值)
  • 顶点着色器的输出数据经过光栅化处理后,输入给片段着色器,而片段着色器的功能就是为正在光栅化的当前像素提供颜色
  • 每个像素都会调用片段着色器。片段着色器的输入数据有如下3种方式: Uniforms (对于单个绘图调用的每个像素保持相同的值,同上),Textures (从像素pixels和纹元texels中读取的数据),Varyings (从顶点着色器传递并插值的数据)
  • 变量(Varyings)是一种将值从顶点着色器传递到片段着色器的方法

# 2.fbx模型里面有啥?模型是如何加载到网页并显示出来的?

  • fbx模型返回数据

Image Text

  • 加载模型:使用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
  • 显示模型:使用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

# 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.常见点位操作

# 12.常用地图数据和3d模型处理工具

  • arcgis,qgis,cad,3dmax

# 13.模型来源

# 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

# 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

# 16.第一人称和第三人称视角是什么?three中如何实现的?

  • 第一人称—自己/个人/主角—最大限度沉浸感—个人开枪
  • 第三人称—旁观者/别人/观众—视野开阔—视野+个人
  • 所有的运动都是矩阵变化(物体移动的原理)
  • 前—负z,左—负x

Image Text

# 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

扩展:人物如何和相机一起移动的?

  • 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
  • 找到可以动画的模型名称设置加载速度播放
// 找到可以动画的模型名称
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
  • 循环播放调用更新
// 人物动画播放变化
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

# 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