Better

Ethan的博客,欢迎访问交流

three.js 基础(二)

主要了解下 webgl 简单渲染流程 ,three.js 常用矩阵的推导,核心对象 BufferGeometry 进一步了解,以及纹理贴图、动画模块、加载器、后置处理器等了解。

webgl

webgl 能有效发挥 gpu 的威力,常用于如下场景

  • 图像处理
  • 深度学习
  • 3D图形渲染
  • 大数据

webgl 渲染管线

  • 显存:主要存储顶点数据和片元数据
  • 显存可以和 js 代码和着色器代码交互,比如 js 代码利用显存将相关数据传输到着色器中,就是利用 buffer 机制。数据传递就是通过显存完成的
  • 片元着色器生成的数据直接在屏幕显示,也可以通过帧缓冲区 frame buffer,对数据进行一定修改再显示,

简单实战的大致逻辑,简单了解即可

  1. 通过 webgl 上下文创建 gl.createProgram,通过 attachShader 绑定着色器代码块
  2. js 通过显存往 shader 中传递数据,通过 gl.createBuffer 创建,通过 gl.bindBuffer 使用 buffer,通过 bufferData 写入数据,通过 gl.getAttribLocation 获取 shader 中定义的,比如 a_Position 在 shader 中的地址,通过 gl.vertexAttribPointer 指定读取方式,最后通过 gl.enableVertexAttribArray 激活对应变量。如果是传递 uniform,不同于 buffer ,通过 gl.getUniformLocation 获取在 shader 中定义的变量地址,通过 gl.uniformMatrix4fv 传递矩阵数据,通过 gl.uniform3v 传递向量数据
  3. 着色器通过 gl.createShader 创建,通过 gl.sourceCode 绑定代码块,通过 gl.compileShader 编译
  4. 通过上下文 linkProgram 以及 useProgram
  5. 通过 gl.clear 清除,以及 drawArray 进行绘制

在 webgl 底层,绘制操作有无索引是不同的

  • 无索引:gl.drawArray
  • 有索引:gl.drawElements,写入缓存区的方式也不一样:通过 gl.ELEMENTS_ARRAY_BUFFER

三种数据类型

  • attribute:只能在 vertexShader 中使用的变量,一般用于传递顶点数据
  • uniform:常量,不能被 shader 修改,uniform 变量在 vertex 和 fragment 两者之间声明方式完全一样,则它可以在 vertex 和 fragment 共享使用。相当于一个被 vertex 和 fragment 共享的全局变量
  • varying:vertex 和 fragment shader 之间做数据传递用的

webgl 资料

  • 教程 github glsl-card
  • 把玩 shader-toy glslsandbox

shader 编程错误会很难定位,可以通过如下方式将错误信息打印出来

  • gl.getShaderParameter(shader, gl.COMPILE_STATUS) 的返回值如果为空,表示有错误信息
  • gl.getShaderInfoLog(shader) 返回 error

three.js 本质上是对 webgl 封装抽象

  • Geometry
  • Light
  • Material
  • Camera

BufferGeometry

BufferGeometry 只是提供顶点数据,至于怎么使用这些顶点进行绘制,和你采用的模型相关

  • 顶点位置数据(position)
  • 顶点颜色数据(color)
  • 顶点法向量数据(normal)光照计算:表示物体表面各个位置的法线方向,可以给几何体的每个顶点定义一个方向向量
  • 纹理贴图 UV 坐标(uv)
  • 光照贴图 lightMapUV2(uv2)
  • 顶点索引(index)复用顶点数据

基类 BufferGeometry 分类

  • 常见几何体:长方体、圆柱体、球体、圆锥等
  • 正多面体
  • 平面:环平面、矩形平面、圆平面、自由轮廓(ShapeGeometry)
  • 2D 转 3D:拉伸与扫描(ExtrudedGeometry)、旋转(LatheGeometry)、管道(TubeGeometry)
  • 文字:TextGeometry
  • 参数化曲面:ParametricGeometry

至于如何使用这些顶点数据,是通过模型和材质进行确定的

  • 网格(Mesh)模型、点(Point)模型、线(Line)模型,模型会有对应的材质
  • 材质分类
    • 点材质
    • 线材质
    • 网格材质
    • 精灵 Sprite 材质
    • 自定义着色器材质

图形变换

  • Geometry 对象提供 scale、rotate、translate 的 api,本质上是改变顶点位置数据
  • Mesh 模型同样提供相同的 api,可以实现同样的效果,但 Mesh 进行变换不会影响几何体的顶点位置坐标,改变的时模型的矩阵信息

纹理

纹理贴图

  • 通过 TextLoader 创建 Texture 贴图
  • 颜色贴图 map 属性:模型从纹理贴图上采色,顶点纹理坐标数据定义图片映射关系
  • 一张纹理贴图图像的坐标,比如以图片左下角为坐标原点,右上角为坐标 (1.0,1.0),图片上所有位置纵横坐标都介于 0.0~1.0 之间
  • 纹理 UV 坐标和顶点位置坐标是一一对应关系,这也就是为什么一张图片可以映射到一个模型的表面,只要把图片的每个纹理坐标和模型的顶点位置建立一对一的关系,就可以实现图像到模型的映射
  • 几何体有两组 UV 坐标,第一组组用于 map、normalMap、specularMap 等贴图的映射,第二组用于阴影贴图 lightMap 的映射
  • 数组材质和材质索引
    • 一个几何体对象不同三角形可以对应不同材质
    • Geometry 中通过 face3 的 materialIndex 进行设置,立方体、球体都有默认的材质索引。比如 BoxGeometry 默认每一个面都采集整张纹理贴图
    • BufferGeometry 通过 groups 进行设置
  • 纹理对象常用方法
    • 阵列:wrapS、wrapT、repeat 进行设置
    • 偏移:offset
    • 旋转:rotation
    • 动画:在 render 时,一直执行 offset 偏移属性的修改即可
    • 方法:transformUv.applyMatrix
  • 高级封装
    • CanvasTexture
    • VideoTexture
  • 一个复杂的曲面模型,往往模型顶点数量比较多,模型文件比较大,为了降低模型文件大小,法线贴图 normalMap 算法自然就产生了
  • 凹凸贴图 bumpMap 和法线贴图功能相似,只是没有法线贴图表达的几何体表面信息更丰富。凹凸贴图是用图片像素的灰度值表示几何表面的高低深度。如果定义了法线贴图,则将忽略该贴图
  • 阴影贴图或者说光照贴图 lightMap,比实时计算得到阴影,更解决资源,提高渲染性能
  • 高光贴图 specularMap
  • 环境贴图 envMap

three.js 纹理映射

  • three.js 对内置的 geometry 预设了纹理映射能力
  • 通过 faceVertexUvs 自定义修改,长度和 faces 是一一对应的

后处理器

学习一下 postprocessing(WIP)

精灵、粒子系统

精灵系统与粒子系统

  • 精灵模型对象和网格模型一样需要设置材质,不过精灵模型不需要设置几何体,three.js 系统渲染的时候会自动设置。可以给场景中模型对象设置标签,或构建一个粒子系统,来模拟一个下雨、森林、或下雪的场景效果
  • 精灵模型对象本质上你可以理解为已经内部封装了一个平面矩形几何体 PlaneGeometry,矩形精灵模型与矩形网格模型的区别在于精灵模型的矩形平面会始终平行于 Canvas 画布
  • 精灵模型对象显示的大小和网格模型一样受距离相机的距离影响

动画模块

动画模块

  • 帧动画模块:提供了一系列用户编辑和播放关键帧动画的 API
    • 关键帧 KeyframeTrack、剪辑 AnimationClip 编写关键帧动画
    • 操作 AnimationAction、混合器 AnimationMixer 播放编写好关键帧动画
    • Clock 对象保证均匀播放
  • 骨骼动画
    • 骨骼动画需要通过骨骼网格模型类 SkinnedMesh 来实现,一般来说骨骼动画模型都是3D美术创建,然后程序员通过 three.js 引擎加载解析
  • 变形动画
    • 多组顶点数据,从一个状态变化到另一个状态
    • 通过 geometry 的 morphTargets 设置变形目标的数据
    • 材质 morphTargets 设置为允许变形
    • 网格 mesh 的 morphTargetInfluences 属性启用变形目标并设置变形目标影响权重,范围一般 0~1
    • 配合帧动画,你只需要控制 .morphTargetInfluences 的属性值生成关键帧数据,就可以实现关键帧动画,然后播放关键帧动画即可实现变形动画。

tween.js 常见数学变换函数模型

语音模块

语音模块

  • 封装了方便使用的语音模块,一个声音和一个网格模型绑定,这样网格模型的位置就是音源位置
  • 音频 Audio、位置音频 PositionalAudio、监听者 AudioListener、音频分析器 AudioAnalyser、音频加载器 AudioLoader。
  • AudioAnalyser 用来对音频进行分析,实现类似音乐可视化的效果

加载器

加载器

  • 本质上都是解析模型文件的字符串,通过正则表达式提取相关的顶点、材质等信息转化为 three.js 自身的类表示的对象
  • .stl 格式的三维模型不包含材质 Material 信息,只包含几何体顶点数据的信息,你可以简单地把 stl 文件理解为几何体对象 Geometry
  • .obj 格式,会同时导出一个材质文件 .mtl,.obj 和 .stl 文件包含的信息一样都是几何体顶点相关数据,材质文件 .mtl 包含的是模型的材质信息,比如颜色、贴图路径等。.obj 文件不包含场景的相机 Camera、光源 Light 等信息,不能导出骨骼动画、变形动画,如果希望导出光照信息、相机信息、骨骼动画信息、变形动画信息,可以选择 .fbx、.gltf 等格式。
  • .fbx 格式:fbx 除了包含几何、材质信息,可以存储骨骼动画等数据。

Face3

这个对象在文档中已经查不到了,但其实源码中还在使用,打印一些对象时,也可以看到有些是 Face3 实例

  • Face3 对象构建三角形,通过 Face3 构建一个三角形,不要设置顶点位置坐标数据,只需要通过数组索引值从 geometry.vertices 数组中获得顶点位置坐标数据。这个在最新的 three 文档中已经找不到了
  • 设置三角形法线方向向量有两种方式,一种是直接定义三角形面的法线方向,另一个是定义三角形三个顶点的法线方向数据来表示三角形面法线方向。 face3 提供 normal 属性给三角形设置法向量,以及 vertexNormals 属性给三个点分别设置顶点法线方向数据
  • 三角形颜色设置和三角形法线方向设置类型,可以直接设置三角形颜色,也可以设置三角形三个顶点的颜色。同样也提供了 color 和 vertexColor 属性
  • 总结:Face3 同时完成了 index、color 以及 normal 的设置
  • 注意:设置三角形 Face3 的颜色对 three.js 网格模型 Mesh 有效,对于点模型 Points、线模型 Line 是无效果,如果想设置点、线模型对应的几何体 Geometry 的顶点颜色,可以通过 Geometry 的顶点颜色属性 geometry.colors 实现。

Math 常用工具

Math 下常用工具

  • Vector2/Vector3
  • Color
  • Plane
  • Sphere
  • Spherical 球坐标
  • Ray

矩阵、模型、角度

掌握 three.js 矩阵的推算很有必要(WIP)

  • 掌握如下矩阵的推算
    • 平移矩阵
    • 旋转矩阵
    • 缩放矩阵
    • 透视投影矩阵
    • 正交投影矩阵
    • 视图矩阵
  • 数学模型
    • 漫反射模型
      • 漫反射模型与视点无关,与发射光、入射点、物体材质相关
    • Phong 反射模型
      • Phong 模型认为镜面反射的光强与反射光线和视线的夹角相关
  • 角度属性:欧拉角与四元素

透视投影矩阵推导

  • 从 Frustum 内一点投影到近裁剪平面的过程,通过相似三角形进行计算
  • 由近平面到规范化设备坐标系的过程

透视除法:w 分量用来控制近大远小的逻辑

正交透影步骤是一样的,但处理比较简单,因为投影过程中,x,y是不变的。因此通常用于二维绘图。对于正交相机而言,远平面的值不是那么重要

视图矩阵推导

  • target、eye 以及 up
  • 计算镜头方向 target -> eye 得到 forward,作为 z 向量,此处归一化一下
  • 根据 up vector 和 forward 进行叉积得到 side 分量,右手法则,作为 x 向量
  • 考虑到 up 向量可能和 z 轴不垂直,计算 side 和 forward 的叉积生成 y 向量

three.js 矩阵封装

注意行优先列优先的顺序

  • set() 方法参数采用行优先 row-major
  • 内部是用列优先 column-major 顺序存储在数组当中

相关矩阵

  • 转置矩阵(transpose matrix)
  • 逆矩阵(invert matrix)
  • 正规矩阵(normal matrix):正规矩阵是矩阵 m 的逆矩阵 inverse 的转置 transpose

任何 3D 物体 Object3D 都有三个关联的矩阵

  • Object3D.matrix: 存储物体的本地变换矩阵。 这是对象相对于其父对象的变换矩阵。
  • Object3D.matrixWorld: 对象的全局或世界变换矩阵。如果对象没有父对象,那么这与存储在矩阵matrix中的本地变换矩阵相同。
  • Object3D.modelViewMatrix: 表示对象相对于摄像机坐标系的变换矩阵, 一个对象的 modelViewMatrix 是物体世界变换矩阵乘以摄像机相对于世界空间变换矩阵的逆矩阵。

摄像机 Cameras 有三个额外的四维矩阵:

  • Camera.matrixWorldInverse: 视矩阵 - 摄像机世界坐标变换的逆矩阵。
  • Camera.projectionMatrix: 投影变换矩阵,表示将场景中的信息投影到裁剪空间。
  • Camera.projectionMatrixInverse: 投影变换矩阵的逆矩阵。

有多种选项可用于从 Matrix4 中提取位置、旋转和缩放

  • Vector3.setFromMatrixPosition:可用于提取位置相关的分量。
  • Vector3.setFromMatrixScale:可用于提取缩放相关的分量。
  • Quaternion.setFromRotationMatrix, Euler.setFromRotationMatrix 或 extractRotation:可用于从纯(未缩放)矩阵中提取旋转相关分量。
  • decompose:可用于一次性提取位置、旋转和缩放

相关原理

光照原理

  • 光源类型 + 光照模型
  • 平行光、点光源、环境光、聚光灯
  • Lambert、Phong 等模型
  • 漫反射模型:光线在法线上的投影越大,表示越强
  • Phong 模型:通过 specular 定义镜面反射效果

纹理原理

  • 纹理映射坐标关系。纹理坐标系统 [0, 1],webgl 坐标系统 [-0.5, 0.5],维护好顶点坐标对应的纹理坐标,其余色值是通过光栅化自动生成的
  • webgl 提供了两个 api,纹理变量 sampler2D 存储图片和 texture2D 从图片中取值,比如 texture2D(u_Sampler, uTexCoord)
  • 其他 api:gl.createTexture(), gl.activeTexture() 等等

阴影:这一 part 在 webgl 中比较复杂

  • 生成 shadowmap,通过纹理存储
  • FragShader 的输入 gl_FragCoord 可以拿到深度信息 z,用于片元着色时定位位置
  • 使用 framebuffer 对渲染结果进行控制
  • 使用 shadowmap 生成阴影

碰撞检测

碰撞检测

  • 凹多边形,凸多边形
  • 判断点在多边形内:射线法

凸多边形:每个内角都是锐角或钝角,也就是没有大于 180° 的优角的多边形。凹多边形:至少有一个优角的多边形。

凸多边形,就是把一个多边形任意一边向两方无限延长成为一条直线,如果多边形的其他各边均在此直线的同旁,那么这个多边形就叫做凸多边形

凹多边形就是把一个各边不自交的多边形任意一边向两方无限延长成为一直线,如果多边形的所有边中只要有一条边向两方无限延长成为一直线时,其他各边不在此直线的同旁,那么这个多边形就叫做凹多边形。

小知识点

three.js 中小的知识点

  • 使用 Object3D 上的 toJSON 可以序列化 3D 对象,配合 ObjectLoader 可以进行 3D 对象还原
  • 使用 BufferGeometry merge 将多个网格合并成一个网格,对性能有奇效
  • 使用 Sprite 配合 SpriteMaterial,创建总是面朝相机的平面
  • 自行封装时,不要乱继承 Group,如果语义上的确是独立的物体,建议继承对应的 Object3D,如 Mesh、Line、Point 等
  • 使用 SceneUtils.createMultiMaterialObject 创建融合材质
  • 使用 scene.overrideMaterial 实现所有模型都使用同一种材质
  • Clock 的作用
    • 因为 requestAnimationFrame 或 interval 并不一定能看到固定频率进行调用,因此两次执行渲染函数的时间间隔也不一定相同,就有可能导致运动不均匀
    • 解决这个问题需要记录两次渲染的间隔,然后将时间应用到动画的线性变换中,这样就可以保证均匀
    • Clock 就是基于这个场景的封装
  • 对于光源颜色设置:一般渲染的时候 RGB 三个分量是相同的,也就是表示白色光源,0xffffff 表示最高强度的白色光源,0x000000 相当于没有光照
  • 相机本质上都是对视图矩阵 matrixWorldInverse、投影矩阵 projectMatrix 的封装
    • OrthographicCamera:宽度 width、高度 height 越大,三维模型顶点的位置坐标就会越大,left 与 right、参数 top 与 bottom 互为相反数,这样做的目的是 lookAt 指向的对象能够显示在 canvas 画布的中间位置
    • 影响视图矩阵的因素是:camera.position 和 camera.lootAt
    • 影响投影矩阵的因素是:构造函数相关参数
  • renderer
    • antialias:扛锯齿能力,可以由硬件做,也可以软件,现在通常显卡拥有这个能力
    • preserveDrawingBuffer 有利于提高渲染性能,貌似是降低性能


留言