手册:www.wenjiangs.com/docs/three-…
what
threejs 是一个易用,轻量级的开源3d库,底层基于 webgl 来绘制 3d 内容。他封装好一些常用抽象,如场景、相机、几何体、材质(外观)、光源、纹理(细节)、动画等,采用面向对象开发的形式,可以让我们更方便创建和控制3d场景。可能会存在性能的问题,以及对一些高级或特殊的3D效果的支持不足
结合开发套件
- gsap 动画库
- tweenJs 动画库
- poliigon 3d 素材库
- 虚幻引擎 quivel Bridge 资源
八股文
有哪些常用的几何体和材质?如何创建和使用它们
- 立方体box,球体sphere,圆柱体,平面。
- 点材质、线材质、网格材质(基础材质MeshBase,光滑材质MeshPhone,金属材质MeshStandard)、精灵材质。
有哪些常用的光源和相机?如何创建和使用它们
- 环境光、平面光、平行光、点光源。
- 透视相机、正交相机。
如何进行渲染和动画?如何控制渲染循环和帧率
- requestAnimationFrame 函数不断重绘,在回调中更新场景和相机。
- Three.js提供的Clock(时钟)类和 AnimationMixer
如何加载外部的模型,音频,视频等资源?如何处理加载过程中的进度和错误
- 各种封装好的文件加载器。或者基于 three 的加载器自封装。
- 在回调接口中处理
如何编写和使用着色器(shader)?如何实现一些常见的着色器效果,如阴影,反射,折射等
如何进行后期处理(post-processing)?如何实现一些常见的后期处理效果,如景深,运动模糊,泛光等
- EffectComposer(效果合成器)和各种 Pass 通道。通过组合多个渲染效果按一定顺序进行渲染。
如何优化性能和内存?有哪些工具或方法可以帮助分析和改进性能和内存问题
- 优化场景对象复杂度及其数量。
何使用物理引擎(physics engine)?如何实现一些常见的物理效果,如碰撞,重力,弹力等
3D 离线仿真
- 3d离线仿真是指在建立了三维模拟场景后,经由软件仿真计算,生成控制机器人或数控机床的运动轨迹,进而生成控制指令的过程。
上传大模型溢出
- 优化模型,减少面片数和顶点数,使用压缩格式,如 glTF 或 drc ,使用 DracoLoader 或 GLTFLoader 分块加载
- 优化内存,及时释放不需要的资源
- 优化渲染,主要渲染那些核心的模块
threejs 移出一个模型时如何防止内存泄漏的方法
- 调用模型的 geometry 和 material 的 dispose () 方法,释放它们占用的内存
- 调用场景的 remove () 方法,移除模型对象
- 调用 renderer 的 clear () 方法,清除渲染缓存
问题日志
Uncaught TypeError: Failed to execute ‘uniform3fv’ on ‘WebGL2RenderingContext’: Overload resolution failed.
原因:修改 Material 的颜色要通过 set 方法。
解决:将 material.color = 0xffffff 改为 material.color.set(0xffffff)
- 逼真水流效果
- 模型 children 的添加和删除只能通过 add 和 remove 来操作。
- 内存遗留问题
- 通过代码编辑模型时注意世界坐标和本地坐标问题。
- 模型与 threeJs 中的坐标轴兼容是个棘手的问题。
- Sprite 精灵在计算机图形学中指包含在场景中的二维动画和图像,即 sprite 是一个永远朝向相机的平面,类似广告牌,没有 z 轴的概念,并且 sprite 不接受阴影。
✳如果精灵被多次添加,那么最后一个会覆盖前面的。
实践方案
- 牢记模型数据类型是一个对象。
- 创建 cubeBox (小地图)控制器采用正交摄像机。
- 使用 boxHelper、box3 包围盒模型计算动态模型的全景捕获问题。
- 模型边缘高亮需要是以 scene 为第一父元素才有效。
- 通过 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;
}
- 修改模型材质时需要注意该材质是否为建模时已设定为共享材质。否则可以通过往 userData 上打标记,用于手动赋予新的材质。
- 模型视觉追踪(以模型每个面五个方向创建射线,拿取最少拾取量)
- 动画。实时获取模型运行参数,通过 tweenjs 函数输出前后两个运动状态动画。
- 物体只有和摄像机同一个层级才可见。利用这个特点可以控制物体的显隐藏。
- 模型线框发光
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);
}
})
常规布置
- 创建场景
- 创建模型
- 添加材料
- 设置灯光
- 设置摄像机
- 渲染
相机
- 正交相机
OrthographicCamera(left, right, top, bottom, near, far)
- 透视相机
PerspectiveCamera(fov, aspect, near, far)
理解视锥 docs.unity3d.com/Manual/Unde…
着色器
- 声明
attribute 关键字用于声明在顶点着色器中每个顶点都会有的输入变量。它通常用于表示顶点的属性,如位置、颜色、法线等
uniform 关键字用于声明在顶点着色器和片元着色器之间共享的全局变量。它通常用于表示对所有顶点或片元都相同的数据,如光照参数、纹理对象等
场景贴图
- 官方 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;
- 使用 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;
});
通道
知乎全篇 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效果。 |
灯光
反射
- 直接漫反射(材质越粗糙,漫反射越厉害)
- 直接高光
- 间接漫反射
- 镜面反射(材质越光滑,镜面反射越厉害)
渲染阴影
满足渲染阴影的条件:
- 材质要满足能光照有反应
- 场景渲染器开启渲染阴影
renderer.shadowMap.enabled = true
- 灯光开启渲染阴影
light.castShadow = true
- 物体开启渲染阴影和接收阴影
mesh.castShadow = true;
mesh.receiveShadow = true;
- 创建一个接收阴影的平面
const groud = new THREE.Mesh(
new THREE.PlaneGeometry(2000, 2000),
new THREE.MeshLambertMaterial({color: 0x999999})
);
groud.castShadow = true;
- 导入的模型需要遍历其所有子元素,开启并接收阴影
- 需要生成阴影的物体过大,则放大阴影渲染相机的视锥体
材质
纹理
材质的皮肤。某个材质可以由多个不同类型的纹理拼装。
- 偏移
texture.offset.x = 0.5
texture.offset.y = 0.5
// 或
texture.offset.set(0.5, 0.5)
- 旋转
texture.center.set(0.5, 0.5) // 设置旋转中心
texture.rotation = Math.PI
- 重复
texture.repeat.set(2)
texture.wrapS = THREE.RepeatWrapping // 水平重复模式,无限重复
texture.wrapT = THREE.RepeatWrapping // 垂直重复模式,无限重复
- 显示算法
magFilter和 minFilter
- 透明材质
只需要显示材质的某部分,其余部分都透明。
new THREE.MeshBasicMaterial({
map: texture,
alphaMap: texture2,
transparent: true,
opacity: 0.5 // 显示部分透明度
})
- 环境遮挡贴图
显示阴影效果。需要设置第二组 uv(why?)。
new THREE.MeshBasicMaterial({
map: texture,
aoMap: texture2,
aoMapIntensity: 0.5 // 阴影深度
})
- 置换贴图
显示凹凸效果。
new THREE.MeshBasicMaterial({
map: texture,
displacementMap: texture2, // 置换贴图,没有作用则在 map
displacementScale: .1 // [0, 1], 凸出效果,默认为 1
})
- 粗糙度
new THREE.MeshBasicMaterial({
map: texture,
roughnessMap: texture2, // 粗糙度贴图,没有作用则在 map
roughness: .5 // [0, 1]
})
- 金属度
new THREE.MeshBasicMaterial({
map: texture,
metalnessMap: texture2, // 金属贴图,没有作用则在 map
metalness: .5 // [0, 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
待记录