程序开发手册-ThreeJs

316 阅读9分钟

官网:www.webgl3d.cn/Three.js/

官网论坛:discourse.threejs.org/

手册:www.wenjiangs.com/docs/three-…

what

threejs 是一个易用,轻量级的开源3d库,底层基于 webgl 来绘制 3d 内容。他封装好一些常用抽象,如场景、相机、几何体、材质(外观)、光源、纹理(细节)、动画等,采用面向对象开发的形式,可以让我们更方便创建和控制3d场景。可能会存在性能的问题,以及对一些高级或特殊的3D效果的支持不足

结合开发套件

  1. gsap 动画库
  2. tweenJs 动画库
  3. poliigon 3d 素材库
  4. 虚幻引擎 quivel Bridge 资源

八股文

  1. 有哪些常用的几何体和材质?如何创建和使用它们
  • 立方体box,球体sphere,圆柱体,平面。
  • 点材质、线材质、网格材质(基础材质MeshBase,光滑材质MeshPhone,金属材质MeshStandard)、精灵材质。
  1. 有哪些常用的光源和相机?如何创建和使用它们
  • 环境光、平面光、平行光、点光源。
  • 透视相机、正交相机。
  1. 如何进行渲染和动画?如何控制渲染循环和帧率
  • requestAnimationFrame 函数不断重绘,在回调中更新场景和相机。
  • Three.js提供的Clock(时钟)类和 AnimationMixer
  1. 如何加载外部的模型,音频,视频等资源?如何处理加载过程中的进度和错误
  • 各种封装好的文件加载器。或者基于 three 的加载器自封装。
  • 在回调接口中处理
  1. 如何编写和使用着色器(shader)?如何实现一些常见的着色器效果,如阴影,反射,折射等
  1. 如何进行后期处理(post-processing)?如何实现一些常见的后期处理效果,如景深,运动模糊,泛光等
  • EffectComposer(效果合成器)和各种 Pass 通道。通过组合多个渲染效果按一定顺序进行渲染。
  1. 如何优化性能和内存?有哪些工具或方法可以帮助分析和改进性能和内存问题
  • 优化场景对象复杂度及其数量。
  1. 何使用物理引擎(physics engine)?如何实现一些常见的物理效果,如碰撞,重力,弹力等
  1. 3D 离线仿真
  • 3d离线仿真是指在建立了三维模拟场景后,经由软件仿真计算,生成控制机器人或数控机床的运动轨迹,进而生成控制指令的过程。
  1. 上传大模型溢出
  • 优化模型,减少面片数和顶点数,使用压缩格式,如 glTF 或 drc ,使用 DracoLoader 或 GLTFLoader 分块加载
  • 优化内存,及时释放不需要的资源
  • 优化渲染,主要渲染那些核心的模块
  1. threejs 移出一个模型时如何防止内存泄漏的方法
  • 调用模型的 geometry 和 material 的 dispose () 方法,释放它们占用的内存
  • 调用场景的 remove () 方法,移除模型对象
  • 调用 renderer 的 clear () 方法,清除渲染缓存

问题日志

  1. Uncaught TypeError: Failed to execute ‘uniform3fv’ on ‘WebGL2RenderingContext’: Overload resolution failed.

原因:修改 Material 的颜色要通过 set 方法。

解决:将 material.color = 0xffffff 改为 material.color.set(0xffffff)

  1. 逼真水流效果

www.shadertoy.com/view/lsXGzH

  1. 模型 children 的添加和删除只能通过 add 和 remove 来操作。
  2. 内存遗留问题
  3. 通过代码编辑模型时注意世界坐标本地坐标问题。
  4. 模型与 threeJs 中的坐标轴兼容是个棘手的问题。
  5. Sprite 精灵在计算机图形学中指包含在场景中的二维动画和图像,即 sprite 是一个永远朝向相机的平面,类似广告牌,没有 z 轴的概念,并且 sprite 不接受阴影。

✳如果精灵被多次添加,那么最后一个会覆盖前面的。

实践方案

  1. 牢记模型数据类型是一个对象。
  2. 创建 cubeBox (小地图)控制器采用正交摄像机。
  3. 使用 boxHelper、box3 包围盒模型计算动态模型的全景捕获问题。
  4. 模型边缘高亮需要是以 scene 为第一父元素才有效。
  5. 通过 raycaster 射线拾取场景中的模型中鼠标坐标转为设备坐标的计算需要注意。
// offset 中分别为目标 canvas 容器的距离屏幕左边和顶部的距离。
if (event.touches) {
  // 是否是移动端触摸事件
  mouse.x = ((event.touches[0].clientX  - offset[0]) / threeDom.current!.offsetWidth) * 2 - 1;
  mouse.y = -((event.touches[0].clientY - offset[1]) / threeDom.current!.offsetHeight) * 2 + 1;
} else {
  mouse.x = ((event.clientX - offset[0]) / threeDom.current!.offsetWidth) * 2 - 1;
  mouse.y = -((event.clientY - offset[1]) / threeDom.current!.offsetHeight) * 2 + 1;
}
  1. 修改模型材质时需要注意该材质是否为建模时已设定为共享材质。否则可以通过往 userData 上打标记,用于手动赋予新的材质。
  2. 模型视觉追踪(以模型每个面五个方向创建射线,拿取最少拾取量)
  3. 动画。实时获取模型运行参数,通过 tweenjs 函数输出前后两个运动状态动画。
  4. 物体只有和摄像机同一个层级才可见。利用这个特点可以控制物体的显隐藏。
  5. 模型线框发光
model.traverse((child: THREE.Mesh) => {
  if (child.isMesh) {
    child.material = new THREE.MeshLambertMaterial({
      color: 0x004444,
      transparent: true,
      opacity: 0.5,
    })
      ;
    //模型边线设器
    const edges = new THREE.EdgesGeometry(child.geometry);
    const edgesMaterial = new THREE.LineBasicMaterial({
      color: 0x00ffff,
    })
    const line = new THREE.LineSegments(edges, edgesMaterial);
    child.add(line);
  }
})

常规布置

  1. 创建场景
  2. 创建模型
  3. 添加材料
  4. 设置灯光
  5. 设置摄像机
  6. 渲染

相机

  1. 正交相机

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

  1. 透视相机

PerspectiveCamera(fov, aspect, near, far)

理解视锥 docs.unity3d.com/Manual/Unde…

着色器

  1. 声明

attribute 关键字用于声明在顶点着色器中每个顶点都会有的输入变量。它通常用于表示顶点的属性,如位置、颜色、法线等

uniform 关键字用于声明在顶点着色器和片元着色器之间共享的全局变量。它通常用于表示对所有顶点或片元都相同的数据,如光照参数、纹理对象等

场景贴图

  1. 官方 demo,用 CubeTextureLoader 加载盒子的六个面环境贴图
const reflectionCube = new THREE.CubeTextureLoader()
  .setPath('image')
  .load(['top.jpg', 'bottom.jpg', 'left.jpg', 'right.jpg', 'front.jpg', 'back.jpg']);

reflectionCube.encoding = THREE.sRGBEncoding;

scene.background = reflectionCube;
  1. 使用 RGBELoader 加载 hdr 环境贴图。
new RGBELoader()
  .setDataType( THREE.UnsignedByteType )
  .load( './images/982960.hdr', function ( texture ) {
    var envMap = pmremGenerator.fromEquirectangular( texture ).texture;
    scene.background = envMap;
    scene.environment = envMap;
});

通道

blog.csdn.net/qw8704149/a…

知乎全篇 zhuanlan.zhihu.com/p/476644719

✳ 如果在某个通道上设置 renderToScreen = true,则渲染结果会直接输出到屏幕上而不会有进一步的后期处理。

官方所有通道

名称描述
AdaptiveToneMappingPass该通道可以根据场景的光照度自动调节场景的亮度。
BloomPass该通道通过增强场景中明亮的区域来模拟真实世界中的摄像机。
BokehPass该通道可以实现类似大光圈镜头的景深效果。
ClearPass该通道清空当前纹理缓存。
CubeTexturePass用于渲染天空盒。
DotScreenPass将黑点图层应用于屏幕的原始图片上。
FilmPass通过扫描线和失真来模拟电视屏幕效果。
GlitchPass随机在屏幕上显示电脉冲。
HalftonePass用于模拟传统印刷术的半色调效果。
MaskPass在图片上显示掩码,后续通道只会影响到掩码区域。
OutlinePass勾勒出场景中的物体轮廓。
RenderPass在当前场景和摄像机的基础上渲染出一个新场景。
SAOPass实现实时环境光遮挡效果。
SMAAPass全屏反锯齿效果。
SSAARenderPass使用另一种算法实现全屏反锯齿效果。
SSAOPass使用另一种算法实现实时环境光遮挡效果
SavePass该通道执行时会赋值当前渲染结果,在后续步骤中可以使用。实际应用中用处不大。
ShaderPass自定义着色器通道,可以传入自定义着色器作为参数,以生成一个高级、自定义的后期处理通道。
TAARenderPass也是一个全屏反锯齿效果。
TexturePass将合成器的当前状态保存为纹理,然后将其作为参数传入到其他的EffectComposer组合器中。
UnrealBloomPass该通道与 Bloom 类似,但是它实现的效果更接近于Unreal3D引擎的Bloom效果。

灯光

反射

  1. 直接漫反射(材质越粗糙,漫反射越厉害)
  2. 直接高光
  3. 间接漫反射
  4. 镜面反射(材质越光滑,镜面反射越厉害)

渲染阴影

满足渲染阴影的条件:

  1. 材质要满足能光照有反应
  2. 场景渲染器开启渲染阴影

renderer.shadowMap.enabled = true

  1. 灯光开启渲染阴影

light.castShadow = true

  1. 物体开启渲染阴影和接收阴影

mesh.castShadow = true;

mesh.receiveShadow = true;

  1. 创建一个接收阴影的平面
const groud = new THREE.Mesh(
  new THREE.PlaneGeometry(2000, 2000),
  new THREE.MeshLambertMaterial({color: 0x999999})
);
groud.castShadow = true;
  1. 导入的模型需要遍历其所有子元素,开启并接收阴影
  2. 需要生成阴影的物体过大,则放大阴影渲染相机的视锥体

材质

纹理

材质的皮肤。某个材质可以由多个不同类型的纹理拼装。

  1. 偏移
texture.offset.x = 0.5
texture.offset.y = 0.5
// 或
texture.offset.set(0.5, 0.5)
  1. 旋转
texture.center.set(0.5, 0.5) // 设置旋转中心
texture.rotation = Math.PI 
  1. 重复
texture.repeat.set(2)
texture.wrapS = THREE.RepeatWrapping // 水平重复模式,无限重复
texture.wrapT = THREE.RepeatWrapping // 垂直重复模式,无限重复
  1. 显示算法

magFilterminFilter

  1. 透明材质

只需要显示材质的某部分,其余部分都透明。

new THREE.MeshBasicMaterial({
  map: texture,
  alphaMap: texture2,
  transparent: true,
  opacity: 0.5 // 显示部分透明度
})
  1. 环境遮挡贴图

显示阴影效果。需要设置第二组 uv(why?)。

new THREE.MeshBasicMaterial({
  map: texture,
  aoMap: texture2,
  aoMapIntensity: 0.5 // 阴影深度
})
  1. 置换贴图

显示凹凸效果。

new THREE.MeshBasicMaterial({
  map: texture,
  displacementMap: texture2, // 置换贴图,没有作用则在 map
  displacementScale: .1 // [0, 1], 凸出效果,默认为 1
})
  1. 粗糙度
new THREE.MeshBasicMaterial({
  map: texture,
  roughnessMap: texture2, // 粗糙度贴图,没有作用则在 map
  roughness: .5 // [0, 1]
})
  1. 金属度
new THREE.MeshBasicMaterial({
  map: texture,
  metalnessMap: texture2, // 金属贴图,没有作用则在 map
  metalness: .5 // [0, 1]
})
  1. 法线贴图

控制光照时的反射。

new THREE.MeshBasicMaterial({
  map: texture,
  normalMap: texture2, // 法向贴图,没有作用则在 map
})

纹理加载进度

// 单加载器
textureLoader.load(url, onLoad, onProcess, onError)

// 通过纹理加载器
const loadingManager = new THREE.LoadingManager(
  onLoad, onProcess: (url, num, total) => {}, onError
  )
new THREE.TextureLoader(loadingManager);

环境

贴图

// 场景贴图
scene.background = new THREE.CubeTextureLoader()
// 给所有物体设置默认环境贴图
scene.environment = new THREE.CubeTextureLoader()

// 物体反射环境贴图(单体)
new THREE.MeshStandardMaterial({
  envMap: texture
})

// hdr 环境贴图使用 RGBELoader

渲染

PBR

待记录