前端工程化六-WebGL

前端架构

1.分解需求

2.样式

3.组件库

4.静态资源与图标

5.异步请求

6.路由

7.构建优化

8.测试框架

9.组件库

10.vuex

11.过滤器

12.指令

13.开发规范

14.详细的使用文档

15.Git多人协作流程

https://juejin.cn/post/6901466994478940168

前端安全

xss攻击

XSS(cross-site scripting跨域脚本攻击)攻击是最常见的Web攻击,其重点是“跨域”和“客户端执行”。有人将XSS攻击分为三种,分别是:

  1. Reflected XSS(基于反射的XSS攻击)
  2. Stored XSS(基于存储的XSS攻击)
  3. DOM-based or local XSS(基于DOM或本地的XSS攻击)

基于存储的XSS攻击,是通过发表带有恶意跨域脚本的帖子/文章,从而把恶意脚本存储在服务器,每个访问该帖子/文章的人就会触发执行。

基于DOM或本地的XSS攻击。一般是提供一个免费的wifi,但是提供免费wifi的网关会往你访问的任何页面插入一段脚本或者是直接返回一个钓鱼页面,从而植入恶意脚本。这种直接存在于页面,无须经过服务器返回就是基于本地的XSS攻击。

防御方法:使用https验证

Service Worker

平常浏览器窗口中跑的页面运行的是主JavaScript线程,DOM和window全局变量都是可以访问的。

Service Worker是走的另外的线程,可以理解为在浏览器背后默默运行的一个线程,或者说是独立于当前页面的一段运行在浏览器后台进程里的脚本。

它脱离浏览器窗体,异步地运行在一个完全独立的上下文环境,不会对主线程造成阻塞。在service worker中,window以及DOM都是不能访问的,但可以使用self访问全局上下文。

WebGL现状

https://zhuanlan.zhihu.com/p/162878354

WebGL 框架和引擎按照定位可以分成这三种类型:

  • WebGL 封装,定位是简化 WebGL 开发,最大的特点是必须自己写 GLSL 才能用。
  • 渲染引擎,定位是三维物体及场景展示,一般会抽象出场景、相机、灯光等概念,上手门槛低,不需要自己写 GLSL。
  • 游戏引擎,定位是游戏开发,在前面的渲染引擎基础上,还提供了骨骼动画、物理引擎、AI、GUI 等功能,以及可视化编辑器来设计关卡,支撑大型游戏的开发。

WebGL 源自 OpenGL,它最早可以追溯到 1992 年,那个时候还是以 C 这种面向过程式的语言为主,所以 OpenGL 的 API 也是过程式的,对于熟悉面向对象的开发者来说,它的代码看起来冗长且可读性差,因此有必要对其进行封装和简化。

所谓webGL库,主要解决的问题是 WebGL 的 API 过于繁琐。

webGL不火的原因:

  1. WebGL 和前端开发差异性太大,前端工程师学 WebGL 相当于删号重练,除了繁琐的 API,图形学的数学知识也导致了门槛高,最基础的 MVP 就能劝退不少初学者,更别说后面的辐射度量及采样学知识了。
  2. 网速发展太慢,Web 不能像桌面游戏那样预先下载几十 G 的模型和材质,在 Draco 压缩技术出现前,随便一个模型文件就要十几 M,极大限制了 WebGL 能做的事情,没人愿意打开个页面还要先等几分钟加载。
  3. JavaScript 本身的性能限制,除了 JavaScript 虚拟机的消耗,在调用 WebGL API 的时候还需要经过一层转换,而很多浏览器是基于 ANGLE 支持 WebGL 的,在 ANGLE 中又会做一层检查,然后在 Windows 下还得将 GLSL 转成 HLSL,在 CPU 上要做的额外工作大大高于桌面程序。
  4. WebGL 2.0 相当缓慢的发展,使得 WebGL 引擎的技术也停滞不前,前面提到过 WebGL 1.0 缺少很多重要功能,比如 Draw Buffers、Occlusion queries 等现代渲染引擎优化性能所依赖的技术,而 WebGL 2.0 的发展又极其缓慢,尽管启动于 2013 年,但直到 2017 年才正式推出,要知道 OpenGL 的替代者 Vulkan 是在 2016 年推出的,所以 WebGL 2.0 还没发布就面临被淘汰,加上 Apple 的刻意打压,至今 Safari 在桌面和 iOS 上都不默认开启 WebGL 2.0,所以 WebGL 2.0 几乎没人使用,用也只能用那些支持面比较广的 WebGL 1.0 扩展功能。
  5. 经济方面的原因,前面提到的很多原因导致了 WebGL 只能开发小游戏,收益不高,收益不高投入就少,导致相关工具的缺乏,尤其是优秀的场景编辑器,UE4 之所以能实时渲染逼真画面,就是靠编辑器的烘焙和计算 Lightmass 来解决间接光照问题,编辑器开发成本极高,不如就只能等光线追踪普及并标准化,预计至少五年。

Twgl.js

twgl.js 就是最典型的做法,比如创建一个最常见物体在 WebGL 中需要这样写,其中反复调用 bindBuffer 和 bufferData,很容易写错

const arrays = {
  position: [1,1,-1,1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1],
  normal:   [1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1],
  texcoord: [1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1],
  indices:  [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

twgl 的定位只是减少重复代码,并没有进一步抽象,所以使用它和直接用 WebGL 在学习成本上没太大区别,因此非常适合初学者,但也意味着它没什么独特的功能。

Regl.js

和 twgl 单纯简化代码相比,regl 提供了跟高层的抽象,将原本的过程式转成了函数式,使得看起来更符合直觉,比如下面这个入门三角形比原生 WebGL 要少很多代码。

const drawTriangle = regl({
  frag: `
  void main() {
    gl_FragColor = vec4(1, 0, 0, 1);
  }`,

  vert: `
  attribute vec2 position;
  void main() {
    gl_Position = vec4(position, 0, 1);
  }`,

  attributes: {
    position: [[0, -1], [-1, 0], [1, 1]]
  },

  count: 3
})

Cobe

canvas 3d库

<canvas
  id="cobe"
  style="width: 500px; height: 500px;"
  width="1000"
  height="1000"
></canvas>

然后使用cobe

import createGlobe from 'cobe'

let phi = 0
let canvas = document.getElementById("cobe")

const globe = createGlobe(canvas, {
  devicePixelRatio: 2,
  width: 1000,
  height: 1000,
  phi: 0,
  theta: 0,
  dark: 0,
  diffuse: 1.2,
  scale: 1,
  mapSamples: 16000,
  mapBrightness: 6,
  baseColor: [0.3, 0.3, 0.3],
  markerColor: [1, 0.5, 1],
  glowColor: [1, 1, 1],
  offset: [0, 0],
  markers: [
    { location: [37.7595, -122.4367], size: 0.03 },
    { location: [40.7128, -74.006], size: 0.1 },
  ],
  onRender: (state) => {
    // Called on every animation frame.
    // `state` will be an empty object, return updated params.
    state.phi = phi
    phi += 0.01
  },
})

// `globe` will be a Phenomenon (https://github.com/vaneenige/phenomenon) instance.
// To pause requestAnimationFrame:
// `globe.toggle()`
// To remove the instance:
// `globe.destroy()`
// ...

https://github.com/shuding/cobe

OGL

OGL 的定位比较特别,它有点介于 WebGL 封装和渲染引擎之间

可以看到它一方面提供了渲染引擎才有的场景树、相机等概念,另一方面又没有材质和光源,所以需要自己写 Shader 来完成基本渲染。

因此我觉得它比较适合 TA(Technical Artist) 使用,基于它开发可以不用了解太多 WebGL 的细节,专注于图形学算法,而且它的 Uniform 变量名和 Three.js 是一样的,网上找基于 Three.js 写的例子都能直接用。

不过如果当成 WebGL 封装,它成熟度不如 regl,因为缺乏单元测试使得预计会有不少 bug,而如果当成渲染库在功能上又没法和 Three.js 比,因此主要优势似乎只有体积小了。

luma.gl

luma.gl 是 Uber 开发的,主要用它开发地理可视化框架,比如 Desk.glKepler.gl,还有无人车数据可视化 AVS

使用前面几个库要同时支持 WebGL 2.0 和 1.0 需要自己做兼容,而 luma.gl 可以自动解决这个问题,方便在支持 WebGL 2.0 的设备上优先使用 WebGL 2.0,比如直接调用 createVertexArray,不过这种 api 没几个,所以这个亮点倒是不显著。

它的独特功能其实是 Shader 模块化拆分,这对于写复杂的 Shader 很有帮助。

渲染器

由于 WebGL 本身只是光栅引擎,基于它开发需要了解矩阵变换并编写着色器,所以 WebGL 学习门槛很高,光入门就要看特别长的文档,比如 WebGL Fundamentals 这个教程系列就有 60 多篇文章。

相比之下使用渲染引擎就容易得多,它将其中的矩阵变化封装成了相机、场景树,并提供了材质和光源,运行时自动生成对应的 GLSL,使得即使完全不懂 WebGL 也能用,大大降低了门槛。

对于大部分应用而言,比起前面的 WebGL 封装,最好还是选择渲染引擎,因为大部分渲染引擎也提供了自定义 Shader 功能,也提供了 GPU 实例等功能,只是一般不能改渲染管线。

Filament

Filament 是 Google 基于 C++ 开发的跨平台物理渲染引擎,支持 Android、iOS、Windows、Mac 等系统,还提供了基于 WebAssembly 的 Web 版本,它用在了 Google 地图和搜索这两个核心 APP 中,因此大概率会长期维护,不用担心弃坑。

尽管只是顺带支持 Web,但完善的实现使得它的渲染效果很突出,在渲染管线上使用了较为新颖的 Clustered forward renderer,因此能支持大量光源。

它生成的 wasm 文件有 2M,不算太大,我原本打算将它引入到 Sugar 中,但这个渲染库在 Web 上使用有两个缺点:

  1. 核心是 C++ 写的,如果功能不满足只能去改 C++,而这几乎是必须的,因为暴露给 JavaScript 的 API 很少,最简单的点击交互都不支持,各个组件的设置也很少,比如相机要实现无交互时自动旋转就得改 C++ 代码,不熟悉 C++ 将寸步难行。
  2. 只支持 WebGL 2.0,这点比较致命,导致无法在 iOS 的浏览器上使用,看了一下它有调用 glDrawBuffers,但不清楚用于做什么。

CLayGL

Claygl 是 ECharts 核心开发者 pissang 大神的开发的 WebGL 游戏引擎,它还用在了 ECharts-gl 项目中,使得 ECharts 在三维图表方面远超所有竞品,

在我看来它最大的亮点是支持延迟着色,目前除了前面提到的 Filament 和后面的 LayaAir,其它 WebGL 引擎都是传统的前向着色,这种管线在渲染时,会对每个物体计算所有光照的贡献,类似如下的写法:

要解决这个问题只能使用延迟着色或者分簇(Clustered)前向着色,目前主流的桌面游戏引擎都是使用延迟着色,并配合前向着色来支持透明物体,相关细节推荐看看 lygyue:延迟渲染,ClayGL 也是目前唯一实现这种管线的开源渲染引擎。

不过在 WebGL 中实现延迟着色最大的问题是兼容性,因为它依赖 MTR(Multiple Render Targets) 技术,只有在 WebGL 2.0 下原生支持,在 WebGL 1.0 中必须依赖「WEBGLdrawbuffers」扩展,但它的兼容性较差,目前桌面浏览器的支持率也只有 71%,在手机上更是只有 2%,想在手机上使用只能等 WebGL 2.0 普及,但 iOS 一直默认不开启,不知道要再等几年了。

Litescene.js

Litescene.js 主要用于开发 WebGL 场景编辑器 WebGLStudio,它其中有些 API 就是专门给编辑器用的,WebGLStudio 是少有的开源 WebGL 编辑器,功能很丰富,但使用体验不好,给我的感受是必须用过 Unity 等编辑器才会用,上手门槛有点高。

游戏引擎在渲染器的基础上增加了面向游戏开发的各种功能,包括 AI、物理、编辑器等,工作量巨大,比起图形学算法,更重要还有工程能力,

Unreal Engine

目前最火的游戏引擎是 Unreal Engine 和 Unity,它们都可以使用 Emscripten 编译出 WebAssembly 版本的项目,直接运行在浏览器中。

Unreal Engine 从 4.24 版本开始不默认提供这个功能,只作为扩展存在,交给社区,要用得自己编译一个,所以 Unreal Engine 目前已经基本放弃了 HTML5 版本。

Unity

相比之下 Unity 编译出来的体积小得多,自带的简单 3D 项目编译出 wasm 只有 4M,所以虽然也很少人用,但至少在线上有真实见到过几个。

值得一提的是 Unity 还在开发专门针对小游戏的 Project Tiny 版本,相当于一个精简版的 Unity,它输出的体积更小,比如这个官方的 Tiny Racing 项目 wasm 只有 607k,即便是所有模型和图片加起来的体积也只有 4.4M,虽然这个项目还在预览阶段,很多重要功能缺失,但 Unity 目前普及度高,所以它未来有不小潜力,但它在国内的发展取决于官方的是否重视,比如会不会支持微信等。

Godot

Godot 是目前最火的开源游戏引擎,它有 1182 个贡献者,提交很频繁,最近在开发的 4.0 版本,将支持 Vulkan API,并在渲染方面做了加强,比如支持 SDFGI

它也能导出 WebGL 版本,但只是「能导出」,并没有专门优化过,拿几个材质测试了一下生成的 wasm 有 20M,但性能太差,在我的 i9 + RX 5700 XT 上都卡成 PPT,而且卡顿这个问题官方也不打算修了,预计 Godot 短期不会在 Web 领域有所发展,只能等它未来或许会支持 WebGPU 了。

three.js

Three.js 是最知名的 WebGL 项目,Contributions 人数高达 1313,和 React 是一个量级的,尽管它自身的定位只是渲染引擎,但社区硬是把不少游戏引擎的功能都加上了,比如物理引擎、贴花、动画等,在源码中有大量例子,很适合学习,但不少重要功能,比如 gltf 加载器,都是放在 examples 目录里,让人感觉很不正式。

Three.js 的历史几乎和 WebGL 一样长,它早在 2010 年 7 月 7 日就支持 WebGL 渲染了,那个时候 WebGL 规范还在草案中,要等到 2011 年 3 月才正式发布,恐怕这就是为什么提到 WebGL 大家都会想到 Three.js,它大概是第一个支持 WebGL 的引擎。

由于知名度最高,Three.js 最大的优势就是社区强大,搜索问题能找到很多答案,也有非常多开源和商业项目使用,比如 Google 的 WebGL Globemodel-viewer、NASA 的 WorldWind、Autodesk 的 Forge Viewer 等。

但 Three.js 在版本管理方面很不专业,到现在都还没采用 semver 版本命名规范,每次发布都是一个叫 rXXX 的版本,我见过不少基于 Three.js 的项目都是固定在某个版本不敢升级了,比如 Autodesk 就提到过。

虽然 Three.js 有很多人使用,但因为整体代码质量一般,我只推荐用来学习,而不是用在正式项目中。

Egret

Egret 和后面介绍的 LayaAir 和 cocos 都是国内创业公司开发的游戏引擎,Egret 最早是通过一款《围住神经猫》的 HTML5 游戏莫名其妙火的,它最早只支持 2D,但也在 2018 年 5 月推出了开源的 Egret 3D

Egret 3D 引擎使用了 ECS 架构,所以它的编辑器提供了类似 Unity 那样添加组件的能力。

但 Egret 3D 开源后没多久就陷入停滞状态了,最新发布的版本是 2018 年 9 月,据说是在重构新版,然而已经过去一年了,可能 3D 并不是公司的重点,开源的版本甚至连 license 都没说明,加上文档比较简陋,所以不推荐使用。

LayaAir

LayaBox 公司最早推出的是 LayaFlash 工具来将 Flash 页游装成 HTML5 版本,随着 Flash 的没落,他们又开发了基于 Web 技术的 LayaAir 引擎。

LayaAir 的三维编辑器主要依赖 Unity,它甚至不能直接使用 WebGL 中最常用的 glTF 格式,要使用三维模型必须先导入到 Unity 中,然后再通过插件转成 LayaAir 所使用的格式。

比起 Three.js/Babylon,LayaAir 有两个比较大的优势,一个是对小程序支持友好,这个算是国内特色,Three.js/Babylon j的核心开发者也没条件测试,所以实际用起来容易遇到 bug,玩玩需要改引擎本身代码才能解决;另一个是近期实现了 Clustered Forward 渲染,可以支持大量光源。

LayaAir 还提供了生成原生 Android/iOS 程序的 LayaNative,这里并非使用 WebView,所以更容易过审。

但需要注意 LayaAir 只是源码开放,并不是真正的开源项目,使用前需要仔细阅读它的协议,比如未经授权是不允许对引擎代码进行修改的,免费使用需要加 LayaBox 的 logo。

从提交历史看,LayaAir 提交量最多的开发者在今年 4 月份忽然停止了,似乎是被阿里挖走了,不知道对引擎本身的发展会有多大影响。

Cocos 3D

cocos 曾经是最流行的 2D 手游引擎,但随着游戏逐渐转向 3D,它在 3D 方面和 Unity 差距太大,就渐渐淡出大家的视野了。

cocos 所属的触控科技本来打算 2014 年在美国上市,但由于对估值不满意,尤其是 cocos2d-x 的 MIT 协议被认为价值几乎为零,所以最后放弃了上市,具体细节可以看看创始人的回答cocos2dx 还有未来么? - 陈昊芝的回答,其中还提到了和 Unity 的故事,比如本来还想收购 Unity 但被拒了,在放弃上市后,触控经历了很多危机,人数也收缩为之前的 1/5,从那时起 cocos2d-x 其实就在走下坡路了,逐渐被 Unity 超越。

尽管很艰难,触控一直没放弃引擎的开发,在 2019 年 10 月发布了 Cocos Creator 3D,和 cocos2d-x 基于 C++ 不同,Cocos Creator 3D 是基于 TypeScript 开发的 WebGL 引擎。

在协议方面,Cocos Creator 3D 吸取了 cocos2d-x 的教训,和 LayaAir 一样只是源码开放,它也有一份定制的协议,有很多限制,需要仔细阅读

尽管 Cocos Creator 3D 很想成为 Unity,编辑器在很多细节点上都参考了 Unity,比如资源管理的 .meta 文件,基于 ECS 的组件机制等,但 WebGL 的限制使得它只能用做小游戏的引擎,因为 OpenGL ES 2.0 功能的缺失,虽然可以发布到微信、百度、支付宝等平台上,但在重度游戏领域没法和 Unity 竞争。

Babylon

最后压轴的是 Babylon,它也是 Sugar 最终采用的 WebGL 引擎,不仅功能强大,代码质量也很高,TypeScript 类型完善,几乎每个函数都有注释。

我个人的使用体会是 Babylon 虽然入门要复杂点,但功能成熟度要比 Three.js 高不少,Three.js 至今在 gLTF 的支持上还有 bug,而 Babylon 是唯一通过所有测试的框架,如果要深入使用 gLTF,Babylon 是最好选择,因为它还支持大量扩展,比如 KHRmeshquantization、KHRdracomeshcompression、KHRtexturebasisu、MSFTlod 等,这些扩展能显著减小体积和提升性能。

Babylon 在材质方面功能丰富,除了基础的 PBR,还提供了用于皮肤的次表面渲染 SubSurface、用于车漆的 ClearCoat、用于布料的 Sheen,以及用于光盘之类的各向异性材质 Anisotropy 等等。

在后期特效方面有 Lut 颜色校正、Tonemap 映射、SSAO、镜面反射、Bloom 等常见特效,还有基于屏幕的反射 SSR(Screen Space Reflections)。

目前 Babylon 在渲染方面也是使用最传统的前向着色,如果要改造成类似 ClayGL 那样的延迟着色成本太高,兼容性也不好,所以最好的选择是用分簇来减少光源,这个想法在 2017 年就有提出,但然后就没有然后了。

除了在渲染方面的功能很多,Babylon 的周边工具也很丰富,最近还推出了类似 UE4 蓝图的材质编辑器

总结

  • 对于一般 WebGL 开发,推荐使用 Babylon.js。
  • 如果要支持微信小程序,最好用国内的 LayaAir 和 Cocos,但需要注意它们只是源码开放,并不是无条件免费使用,需要仔细阅读使用协议。
  • 如果只想写原生 WebGL 特效,建议用 regl。
  • 如想支持大量光源和后期特效,又不需要支持 iOS,用 Claygl。
  • 如果熟悉 Unity,直接用它导出 WebGL 也是可行的。

WebGL原理

WebGL是一种3D图形绘图标准,把JavaScript与openGL ES2.0结合在一起,通过增加OpenGL ES 2.0的绑定,WebGL可以为HTML5 canvas提供硬件3d渲染,从而可以借助系统显卡在浏览器中更流畅地展示3D场景和模型,还能创建复杂的导航和数据视觉化。

OpenGL ES(embeded system)是为嵌入式设备或手机提供高级3D图形应用编程接口。Open GL ES目前支持ios、android、linux、windows。

OpenGL渲染流程

Shader

shader的开发语言

HLSL: 主要用于Direct3D。平台:windows。

GLSL: 主要用于OpenGL。 平台:移动平台(iOS,安卓),mac(only use when you target Mac OS X or OpenGL ES 2.0)

CG:与DirectX 9.0以上以及OpenGL 完全兼容。运行时或事先编译成GPU汇编代码。CG比HLSL、GLSL支持更多的平台,Unity Shader采用CG/HLSL作为开发语言。

开发步骤

https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-fundamentals.html

gltf、glb文件相关

gltf-pipeline

转换gltf文件为glb或者draco gltf,或者相反

安装

npm install -g gltf-pipeline

使用

gltf-pipeline -i model.gltf -o model.glb

gltf-pipeline -i model.gltf -b

gltf-pipeline -i model.glb -o model.gltf

gltf-pipeline -i model.glb -j

gltf-pipeline -i model.gltf -o modelDraco.gltf -d

在js中使用

const gltfPipeline = require("gltf-pipeline");
const fsExtra = require("fs-extra");
const gltfToGlb = gltfPipeline.gltfToGlb;
const gltf = fsExtra.readJsonSync("model.gltf");
gltfToGlb(gltf).then(function (results) {
  fsExtra.writeFileSync("model.glb", results.glb);
});

webgl教程

https://www.ctolib.com/docs-webgl-c-using-a-texture.html

threeJs

threejs是基于webGL的纯js的3D渲染库,可以快速搭建web3D界面

安装包

npm i three

全局导入或者部分导入

//全部导入
import * as THREE from 'three';
//部分按需导入
import {Scene} from 'three'

基本步骤

在THREEjs中,渲染一个3d世界的必要因素是场景(scene)、相机(camera)、渲染器(renderer)。渲染出一个3d世界后,可以往里面增加各种各样的物体、光源等,形成一个3d世界:

创建场景

var scene = new THREE.Scene();

创建相机

相机是3d场景中的角度,在三维空间中代表观察者所在的位置。在Threejs中有多种相机 呈现的不同效果,主要有透视相机(THREE.PerspectiveCamera)和 正相交相机 (THREE.OrthographicCamera)

var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
camera.position.z = 5; // 设置相机相对物体的坐标

创建渲染器

var renderer = new THREE.WebGLRenderer(); 
renderer.setSize( window.innerWidth, window.innerHeight );  // 设置渲染范围
document.body.appendChild( renderer.domElement );

添加物体到场景

//创建立方体
var geometry = new THREE.BoxGeometry( 1, 1, 1 ); // 物体形状
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); // 材质
var cube = new THREE.Mesh( geometry, material ); 
scene.add( cube );

执行渲染

function animate() { 
    cube.rotation.x += 0.1;
    cube.rotation.y += 0.1;  // 物体旋转
    requestAnimationFrame( animate ); 
    renderer.render( scene, camera ); 
} 
animate(); 

详细名词解释

相机即视角

正交摄像机(orthographic)是一个矩形可视区域,物体只有在这个区域内才是可见的物体无论距离摄像机是远或事近,物体都会被渲染成一个大小。一般应用场景是2.5d游戏如跳一跳、机械模型

透视摄像机(perspective)是最常用的摄像机类型,模拟人眼的视觉,近大远小(透视)。Fov表示的是视角,Fov越大,表示眼睛睁得越大,离得越远,看得更多。如果是需要模拟现实,基本都是用这个相机

透视摄像机视角参数

THREE.PerspectiveCamera(fov,aspect,near,far)

fov:视野角度,从镜头可以看到的场景的部分。通常3D游戏的FOV取值在60-90度之间较好的默认值为60

aspect:渲染区域的纵横比。较好的默认值为window.innerWidth/window.innerHeight

near:最近离镜头的距离

far:远离镜头的距离

物体

物体是由几何体组成的。threejs中定义了常见的3D几何体。

BoxGeometry--长方体 CylinderGeometry--圆柱体 CircleGeometry--圆形平面 PlaneGeometry--方形平面

SphereGeometry--球体 TextGeometry--文字

2D模型

THREE.PlaneGeometry 矩形 THREE.CircleGeometry 圆形或者扇形

THREE.RingGeometry 圆环或者扇环 THREE.ShapeGeometry 自定义形状

材料

一个物体很多的物理性质,取决于其材料,材料也决定了几何体的外表。材料的创建方法也是new。

MeshBasicMaterial -- 自发光材质 MeshLambertMaterial--漫反射材质 MeshPhongMaterial--镜面反射材质

LineBasicMaterial-用于THREE.Line对象,创建彩色线条 LineDashMaterial-用于THREE.Line对象,创建虚线条

SpriteCanvasMaterial、SpriteMaterial、PointCloudMaterial-在针对单独的点进行渲染时用到

透明度:可以设置材料的透明程度,先设置transparent属性为true开启透明,然后设置opacity的值设置透明程度。opacity为9-1,0表示完全透明,1表示完全不透明。默认值是1。

纹理

还可以设置纹理使物体更加真实,纹理通过TextureLoader加载纹理图片:

var texture = new THREE.TextureLoader().load( 'textures/crate.gif' );

光源

一个3d世界,如果需要更加逼真,那就需要光源了。光也有很多种,常见的有平行光、点光源、环境光(环境光充满所有的几何体表面)、聚光灯等。

创建光源

const light = new THREE.DirectionalLight(0xffffff, 0.9)//平行光源

其他光源

PointLight:3D空间中的一个点光源,向所有方向发出光线 AmbientLight 环境光,其颜色均匀的应用到场景及其所有对象上,这种光源为场景添加全局的环境光。

AmbientLight。环境光,其颜色均匀的应用到场景及其所有对象上,这种光源为场景添加全局的环境光。这种光没有特定的方向,不会产生阴影。通常不会把AmbientLight作为唯一的光源,而是和SpotLight、DirectionalLight等光源结合使用,从而达到柔化阴影、添加全局色调的效果。指定颜色时要相对保守,例如#0c0c0c。设置太亮的颜色会导致整个画面过度饱和,什么都看不清:

HemisphereLight 特殊光源,用于创建户外自然的光线效果,此光源模拟物体表面反光效果、微弱发光的天空,模拟穹顶(半球)的微弱发光效果,SpotLight 产生圆锥形光柱的聚光灯,台灯、天花板射灯通常都属于这类光源,这种光源的使用场景最多

SpotLight:产生圆锥形光柱的聚光灯,台灯、天花板射灯通常都属于这类光源,这种光源的使用场景最多,特别是在你需要阴影效果的时候。

AreaLight 面光源 指定一个发光区域 LensFlare 不是光源,用于给光源添加镜头光晕效果

LensFlare:不是光源,用于给光源添加镜头光晕效果

加载外部模型

加载器用于加载外部模型。通过Three.js加载器(Loader)实现的。加载器把文本/二进制的模型文件转化为Three.js对象结构。 每个加载器理解某种特定的文件格式。

jsonloader 导入 json 文件

格式 说明
Babylon 游戏引擎Babylon的私有格式
STL STereoLithography的简写,在快速原型领域被广泛使用。3D打印模型通常使用该格式定义Three.js提供了STLExporter.js,使用它可以把Three.js模型导出为STL格式
CTM openCTM定义的格式,以紧凑的格式存储基于三角形的Mesh
VTK Visualization Toolkit定义的格式,用于声明顶点和面。此格式有二进制/ASCII两种变体,Three.js仅支持ASCII变体
AWD 3D场景的二进制格式,主要被away3d引擎使用,Three.js不支持AWD压缩格式
Assimp 开放资产导入库(Open asset import library)是导入多种3D模型的标准方式。使用该Loader你可以导入多种多样的3D模型格式
VRML 虚拟现实建模语言(Virtual Reality Modeling Language)是一种基于文本的格式,现已经被X3D格式取代尽管Three.js不直接支持X3D,但是后者很容易被转换为其它格式
Collada(dae) 基于XML的格式,被大量3D应用程序、渲染引擎支持
JSON Three.js自定义的、基于JSON的格式。可以声明式的定义一个Geometry或者Scene.利用该格式,你可以方便的重用复杂的Geometry或Scene
OBJ / MTL OBJ是Wavefront开发的一种简单3D格式,此格式被广泛的支持,用于定义Geometry,MTL用于配合OBJ,它指定OBJ使用的材质

loader

dracoloader

// Instantiate a loader
const loader = new DRACOLoader();

// Specify path to a folder containing WASM/JS decoding libraries.
loader.setDecoderPath( '/examples/js/libs/draco/' );

// Optional: Pre-fetch Draco WASM/JS module.
loader.preload();

// Load a Draco geometry
loader.load(
	// resource URL
	'model.drc',
	// called when the resource is loaded
	function ( geometry ) {

		const material = new THREE.MeshStandardMaterial( { color: 0x606060 } );
		const mesh = new THREE.Mesh( geometry, material );
		scene.add( mesh );

	},
	// called as loading progresses
	function ( xhr ) {

		console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );

	},
	// called when loading has errors
	function ( error ) {

		console.log( 'An error happened' );

	}
);

粒子

在WebGlRenderer渲染器中使用THREE.Sprite创建的粒子可以直接添加到scene中。创建出来的精灵总是面向镜头的。即不会有倾斜变形之类透视变化,只有近大远小的变化。

交互

Three.js中并没有直接提供“点击”功能,我们可以基于THREE.Raycaster来判断鼠标当前对应到哪个物体,用来进行碰撞检测.

动画

实时渲染requestAnimationFrame

场景中如果我们添加了各种 mesh 和模型并给他加入了一些 tweend动画会发现他并不会运动,因为你的场景并没有实时渲染,所以要让场景真的动起来,我们需要用到requestAnimationFrame;

调试工具

轨道控制器

加上此控制器,就可以通过鼠标拖拽、滚动对整个画面进行拖拽放缩

使用方法就是new一个控制器,然后监听变化,触发render

        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.addEventListener("change", () => {
          renderer.render(scene, camera);
        });
        controls.minDistance = 1;
        controls.maxDistance = 2000;
        controls.enablePan = false;

性能监控

可以拷贝下来,挂在window上。官方大部分例子都使用了一个stat的插件,在左上角会出现性能变化的曲线,供我们调试使用。使用方法:

    const stat = new Stats();
    document.body.appendChild(stat.dom);
    
    // 改造render函数
    function render() {
      renderer.render(scene, camera);
      stat.update();
    }

实例1:跳一跳游戏

var Game = function () {
  ...
}
Game.prototype = {
  init:  // 初始化
  restart: // 重新开始
  addSuccessFn:  // 成功进入下一步,执行外部函数, 用于更新分数
  addFailedFn: // 游戏失败, 执行外部函数, 用于显示失败弹窗
  _createJumper: // 创建 会跳的那个
  _createCube: // 创建方块
  _setLight: // Three.js设置光照
  _setCamera: // Three.js设置相机
  _setRenderer: // Three.js设置渲染器
  _render: // Three.js 执行渲染
  _createHelpers: // Three.js场景辅助工具
  _checkUserAgent: // 检测是否是移动端
  _handleWindowResize: // 窗口缩放绑定函数
  _handleMousedown: // 鼠标按下绑定函数
  _handleMouseup: // 鼠标松开绑定函数
  _fallingRotate: // 会跳的那个 摔落动画
  _falling: // 会跳的那个 摔落
  _checkInCube: // 判断落点位置
  _updateCameraPos: // 更新相机坐标参数
  _updateCamera: // 更新相机
  _setSize:   // 设置画布尺寸
}
  
var game = new Game()
game.init()
game.addSuccessFn(success)
game.addFailedFn(failed)

...

// 游戏重新开始,执行函数
function restart () {
    ...
}
// 游戏失败执行函数
function failed(){
    ...
}
// 游戏成功,更新分数
function success (score) {
    ...
}

实例2:雪花粒子

//创建场景、相机、渲染器
    let width = window.innerWidth; // 画布的宽度
    let height = window.innerHeight; // 画布的高度

    // 渲染器
    let renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    // 设置背景颜色
    renderer.setClearColor('rgb(22,33,82)', 1.0);
    let element = document.getElementById('snow');
    element.appendChild(renderer.domElement);

    // 场景
    let scene = new THREE.Scene();

    // 透视投影摄像机
    let camera = new THREE.PerspectiveCamera(75, width / height, 1, 500);
    camera.position.set(0, 0, 50);
    camera.lookAt(scene.position);
    scene.add(camera);
//加载雪花图片
    let loader = new THREE.TextureLoader()
    loader.load('../assets/snow.png', (texture) => {
        let material = new THREE.PointsMaterial({
            map: texture, // 纹理
            transparent: true, // 透明
            size: 5,
        });
        
        // 创建 THREE.Points
        ...
    });
//随机生成雪花
    // 雪花出现范围
    let range = 400; 
    // 通过自定义几何体设置粒子位置
    let geom = new THREE.Geometry();
    for (let i = 0; i < 800; i++) {
        // 随机生成雪花的位置
        let v = new THREE.Vector3(
            Math.random() * range - range / 2,
            Math.random() * range - range / 2,
            Math.random() * range - range / 2
        );
        // 随机生成雪花分别沿x、y、z轴方向移动速度
        v.velocity = createVelocity();
        // 添加顶点
        geom.vertices.push(v);
    }
    points = new THREE.Points(geom, material);
    scene.add(points);
    // 渲染
    renderer.render(scene, camera);
    
    // 创建指定范围内的随机数
    function randomRange(t, i) {
        return Math.random() * (i - t) + t
    }

    // 创建运动方向
    function createVelocity() {
        // 默认向下
        let velocity = new THREE.Vector3(0, -0.4, 0);
        velocity.rotateX(randomRange(-45, 45));
        velocity.rotateY(randomRange(0, 360));
        return velocity;
    }
//让雪花动起来
    setInterval(animate, 1000 / 40);
    
    // 动画
    function animate() {
        let vertices = points.geometry.vertices;
        vertices.forEach(function (v, idx) {
            // 计算位置
            v.y = v.y + (v.velocity.y);
            v.x = v.x + (v.velocity.x);
            v.z = v.z + (v.velocity.z);

            // 边界检查
            if (v.y <= -range / 2) v.y = range / 2;
            if (v.x <= -range / 2 || v.x >= range / 2) v.x = v.x * -1;
            if (v.z <= -range / 2 || v.z >= range / 2) v.velocity.z = v.velocity.z * -1;
        });

        //重要:渲染时需要更新位置(如果没有设为true,则无法显示动画)
        points.geometry.verticesNeedUpdate = true;
        renderer.render(scene, camera);
    };

实例3:变换粒子效果

//创建相机、场景即渲染器与雪花粒子相同,唯一的不同是使用正交相机
let width = window.innerWidth; // 画布的宽度
    let height = window.innerHeight; // 画布的高度

    // 渲染器
    let renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    // 设置背景颜色
    renderer.setClearColor('rgb(22,33,82)', 1.0);
    let element = document.getElementById('snow');
    element.appendChild(renderer.domElement);

    // 场景
    let scene = new THREE.Scene();
    //正交相机
    let camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 1000);
    camera.position.set(0, 0, 10);
    scene.add(camera);

//定义两种形状
// 绿色的圆环
const rng = d3.randomNormal(0, 0.01);
for (let i = 0; i < pointsCount; i++) {
  let v = new THREE.Vector3(
      (rng() + Math.cos(i)) * (width / 2.5),
      (rng() + Math.sin(i)) * (height / 2.5),
      0
  );

  let c = new THREE.Color(0, 1, 0); // 绿色
}
// 蓝色的星云
const rng = d3.randomNormal(0, 0.05);
for (let i = 0; i < pointsCount; i++) {
    let v = new THREE.Vector3(
        rng() * width,
        rng() * height,
        0
    );

    let c = new THREE.Color(0, 0.5, 1); // 蓝色
}
//定义模型材料
let material = new THREE.PointsMaterial({
    size: 1.0,
    vertexColors: THREE.VertexColors, // 按顶点颜色渲染
})

//切换动画
// 切换为绿色圆环
function greenCircleLayout(geometry) {
    const rng = d3.randomNormal(0, 0.01);
    geometry.vertices.forEach((d, i) => {
        new TWEEN.Tween(d).to({
                x: (rng() + Math.cos(i)) * (width / 2.5),
                y: (rng() + Math.sin(i)) * (height / 2.5),
            }, 800)
            // .delay(500 * Math.random())
            .start()
    });
    geometry.colors.forEach((d, i) => {
        new TWEEN.Tween(d).to({
                g: 1,
                b: 0,
            }, 800)
            .start()
    });
}

// 切换为蓝色点云
function blueNormalLayout(geometry) {
  // 和切换为绿色圆环代码相似,此处省略
  ...
}
  
//切换动画
let layouts = [greenCircleLayout, blueNormalLayout];
let startTime = new Date().getTime();
let currentLayout = 0;

requestAnimationFrame(animate);

function animate() {
    let now = new Date().getTime();
    if (now - startTime > 1500) {
        currentLayout = (currentLayout + 1) % layouts.length;
        layouts[currentLayout](pointCloud.geometry);
        startTime = now;
    }
    TWEEN.update();
    pointCloud.geometry.verticesNeedUpdate = true;
    pointCloud.geometry.colorsNeedUpdate = true;
    renderer.render(scene, camera);
    requestAnimationFrame(animate)
}

https://juejin.im/post/6866335813790072845#heading-7

RegL

regl是

官网:http://regl.party/

GitHub:https://github.com/regl-project/regl

例程地址:https://regl-project.github.io/regl/www/gallery.html

三角形

const drawTriangle = regl({
  frag: `
  void main() {
    gl_FragColor = vec4(1, 0, 0, 1);
  }`,

  vert: `
  attribute vec2 position;
  void main() {
    gl_Position = vec4(position, 0, 1);
  }`,

  attributes: {
    position: [[0, -1], [-1, 0], [1, 1]]
  },

  count: 3
})

css3d

css3d是一个小的js库,能够实现简易的3d动画,复杂动画还是推荐threejs去做

如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github