Better

Ethan的博客,欢迎访问交流

Three.js 基础及二维绘图

three.js 核心基础以及点线面的二维绘制基础

WebGL & Three.js 基础

对于图形绘制而言,只有点、线和面片,对应 WebGL 中就是

  • gl.POINTS 画点
  • gl.LINES 画线
  • gl.TRIANGLES 画三角形

WebGL 绘制流程

  1. 获取顶点坐标
    1. 定义的几何体或者三维软件导出,生成顶点坐标
    2. 顶点坐标写入缓存区,写入显存,方便 GPU 更快读取
  2. 图元装配(三角化)
    1. 由顶点坐标生成一个个图元,将顶点坐标传入顶点着色器 gl_Position
    2. 顶点着色器先通过 apply 一个投影矩阵,将世界坐标转换成屏幕坐标,有多少个顶点,顶点着色器程序就会运行多少次
    3. 图元装配
  3. 光栅化(生成片元,即一个个像素点)
    1. 完成模型上色工作,通过 GPU 的片元着色器完成 gl_FragColor
    2. 公式为 gl_FragColor = color * light,生成多少片元(像素),运行多少次

WebGL 完整工作流程

  1. 准备数据阶段
    • 顶点坐标:顶点数据存储在缓存区 Vertex Buffer Objects(因为数量巨大),以修饰符 attribute 传递给顶点着色器
    • 索引:三角形绘制顺序,存储在索引缓存区 Index Buffer Objects
    • uv:贴图坐标
    • 法向量:决定光照效果,法线是垂直于我们想要照亮的物体表面的向量。法线代表表面的的方向,因此它们为光源和物体的交互建模中具有决定性作用
    • 各种矩阵:以修饰符 uniform 传递给顶点着色器
  2. 生成顶点着色器
  3. 生成片元着色器:质地、光照、阴影
  4. 光栅化:能过片元着色器,确定好了每个片元的颜色,以及根据深度缓存区判断哪些片元被挡住了,不需要渲染,最终将片元信息存储到颜色缓存区,最终完成整个渲染

Three.js 是基于 WebGL 的封装,帮我们做了很多事

  • 各种内置矩阵
  • 生成顶点着色器,不需要自己写 opengl es 代码,毕竟比较难懂
  • 提供材质、灯光对象,自动生成片元着色器

聊聊各种矩阵在顶点处理过程中发挥的作用

  1. 准备好顶点数据
  2. 应用模型矩阵(modelMatrix):物体自身的变换以及父元素的变换,在 Three.js 中的定义
    • object.matrix:局部变换矩阵
    • object.matrixWorld:物体的世界变换,如果没有父级,则和 matrix 等同
  3. 视图矩阵(viewMatrix):相机的位置及方向,在 Three.js 中的定义
    • 相机的位置矩阵是:camera.matrixWorld,通过 camera 的 position、up 以及 lookAt 会改变位置视图矩阵
    • 视图矩阵是:相机矩阵的逆矩阵,camera.matrixWorldInverse
  4. 投影矩阵(projectionMatrix):根据相机的投影类型,会有不同的效果。Three.js 中通过 camera.projectMatrix 直接拿到
  5. 传入顶点着色器:gl_Position = position * modelMatrix * viewMatrix * projectionMatrix
  6. 图元装配

注意点1:Three.js 使用的列向量,同时矩阵存储列式存储

注意点2:最终的投影是在 GLSL 顶点着色器中计算的。在一次绘制中,ProjectionMatrix 和 CameraMatrixWorldInverse 一般不会发生变化,而ObjectMatrixWorld 每个物体都可能不同, 所以为了减少顶点着色器中的计算量,有些三维引擎会在 javascript 程序中提前计算出 ProjectionMatrix * CameraMatrixWorldInverse 的值传递给顶点着色器,这个矩阵一般称为 ViewProjectionMatrix

我们需要特别注意的是,顶点着色器和片元着色器是通过 GPU 进行计算的,比如旋转一个模型,我们可以手动计算每个顶点坐标进行变换后的新坐标,然后重新进行渲染,但针对复杂图形,通常顶点是非常多的,这样通过 CPU 计算,难免性能会下降,更合适的方式是通过记录一个 modelMatrix,然后传入顶点着色器中进行计算。

片元着色器处理流程

  1. 材质颜色
  2. 灯光颜色
  3. 雾化颜色
  4. 传入片元着色器:gl_FragColor = vec4(color.rgb * vLighting, color.a)

BufferGeometry

BufferGeometry 是所有 Geometry 的核心基类。你可以使用它创建任何形状。内置的其他 Geometry 仅仅是特定场景的 High Level Api 封装而已。

BufferGeometry 是点、线或面片的有效表述,包括

  • 顶点位置 position
  • 面片索引 index
  • 法向量 normal
  • 颜色值 color
  • UV 坐标 uv
  • 自定义缓存属性值

常用 Api

  • 变换相关:translate、rotate、scale、center
  • 工具相关:merge、dispose、computeBoundingBox、computeBoundingSphere
  • 初始化相关:setFromPoints
  • 属性值
    • index:允许顶点在多个三角面间可以重用,这样的顶点被称为已索引的三角面片,每个三角面片和三个顶点的索引相关。如果该 attribute 没有设置过,则认定没三个连续的位置代表一个三角面片,默认值为 null
    • setIndex/getIndex
    • setAttribute/getAttribute/deleteAttribute

Material

材质的抽象类,不能被直接实例化。

包含太多高级参数了,列举几个常见的

  • fog 是否受雾影响
  • opacity 透明度,如果材质的 transparent 属性未设置为 true,则材质将保持完全不透明,此值仅影响其颜色
  • polygonOffset、polygonOffsetFactor、polygonOffsetUnits
  • side:FrontSide、BackSide、DoubleSide
  • transparent
  • vertexColors:是否使用顶点着色,如果该选项设置为 true,则 color 属性失效
  • visible:材质是否可见

Texture

创建一个纹理贴图,将其应用到一个表面。

常见属性

  • image:通常由 TextureLoader.load 方法创建
  • mapping:图像如何应用到物体上,默认方式 uvmapping 表示应用纹理坐标
  • wrapS:纹理贴图在水平方向上将如何包裹,在 UV 映射中对应于 U
  • wrapT:纹理贴图在垂直方向上将如何包裹,在 UV 映射中对应于 V
  • repeat:设置重复次数
  • rotation
  • center

关于点

相关类: Points、BufferGeometry、PointsMaterial

其中 PointsMaterial 常用属性有

  • color
  • size 点像素尺寸
  • map 贴图

关于线

相关类

  • Geometry:BufferGeometry
  • Material:LineBasicMaterial、LineDashedMaterial
  • Object:Line、LineSegments

linecap 和 linejoin 会被 webgl 忽略

以上的实现方案,会存在一些限制,最典型的就是 linewidth 不会生效。因此 examples 中有提供了其他封装

  • LineMaterial
    • worldUnits 用于控制否是响应缩放
    • resolution 用于使得 pixel 生效所必须
  • LineGeometry
  • Line2

进一步使用可以参考 EdgesGeometry 源码

关于平面

相关类

  • Geometry:ShapeGeometry、PlaneGeometry、RingGeometry
  • Material:MeshBasicMaterial、MeshLambertMaterial、MeshPhongMaterial、……
  • Object:Mesh

关于 Geometry 这一块,最灵活的当属于 ShapeGeometry 了。它依赖 Shape 类,你可以创建任意形状,以及 holes 的支持。更重要的 Shape 继承 Path 类,不仅可以创建多段线,甚至各种曲线也是 OK 的。具体见最后 Path 的介绍

实用工具

BufferGeometryUtils

  • mergeBufferGeometries
  • mergeBufferAttributes
  • mergeVertices

ShapeUtils

  • area 计算形状面积
  • isClockWise 判断是不是顺时针
  • triangulateShape 三角化

SceneUtils

  • createMultiMaterialObject

Path 提供很多实用 api 用于绘制多段线,甚至曲线

  • moveTo 移动起点
  • absarc/arc 绘制圆弧
  • absellipse/ellipse 绘制椭圆弧
  • bezierCurveTo 三次贝塞尔曲线
  • lineTo 连接直线
  • quadraticCurveTo 二次贝塞尔曲线
  • splineThru 样条曲线

规则的曲线比如圆、椭圆、抛物线都可以用一个函数去描述,对于不规则的曲线无法使用一个特定的函数去描述,这也就是样条曲线和贝塞尔曲线出现的原因。

组合曲线:CurvePath,把多个圆弧线、样条曲线、直线等多个曲线合并成一个曲线

// 创建组合曲线对象 CurvePath
const curvePath = new THREE.CurvePath();
// 把多个线条插入到 curvePath中
curvePath.curves.push(line1, arc, line2);

得到 path 后,通过 getPoints(),传入采样点数量得到所有的点坐标,通过 geometry. setFromPoints 即可完成 geometry 的创建

你可能观察到 BufferGeometry 的 boundingBox 和 boundingSphere 的属性,Three.js 中也提供了很多数学工具供我们使用,不在需要我们手动去计算。

首先我们先了解一下包围盒,主要有轴对齐包围盒(AABB)和有向包围盒(OBB)

  • 包围盒广泛地应用于碰撞检测,比如射击、点击、相撞等,每一个物体都有自己的包围盒。因为包围盒一般为规则物体,因此用它来代替物体本身进行计算,会比直接用物体本身更加高效和简单。
  • AABB 包围盒更常见,因为生成方法简单,它是与坐标轴对齐的。但也有不足,就是它不随物体旋转,比如一只笔水平或垂直放,则包围盒会比较小,斜着放则包围盒就会比较大
  • OBB 有向包围盒,它始终沿着物体的主成分方向生成最小的一个矩形包围盒,可用于较精确的碰撞检测。three.js 有对于 OBB 的封装,具体见OBB

Box2 包围矩形

  • containsBox
  • containsPoint
  • distanceToPoint
  • expandByPoint
  • expandByScalar
  • expandByVector
  • getCenter
  • getSize
  • intersect
  • intersectsBox
  • isEmpty
  • makeEmpty
  • setFromCenterAndSize
  • setFromPoints
  • translate
  • union

Box3 包围盒,三维长方体包围区域

  • 调用模型的 geometry.boundingBox,然后乘以模型的世界变换矩阵,就可以实现包围盒的获取(AABB 盒)。注意:几何体包围盒属性 boundingBox 默认值为空 null,执行 computeBoundingBox() 方法才会计算该几何体的包围盒 Box3,然后赋值给 boundingBox 属性
  • 调用模型的 geometry.center 方法,也就是把几何体对应的包围盒中心平移到坐标原点
  • applyMatrix4
  • containsBox
  • containsPoint
  • distanceToPoint
  • expandByObject:获取到此模型的包围盒(AABB 盒),而且还能包含它的子对象
  • expandByPoint
  • expandByScalar
  • getBoundingSphere
  • getSize 返回长宽高尺寸
  • getCenter 返回几何体中心
  • intersect
  • intersectsBox
  • intersectsPlane
  • intersectsTriangle
  • isEmpty
  • makeEmpty
  • setFromArray
  • setFromBufferAttribute
  • setFromCenterAndSize
  • setFromPoints
  • setFromObject
  • translate
  • union

Sphere 包围球

  • 通过 center 和 radius 描述
  • 几何体包围球属性 boundingSphere 使用方式和包围盒属性 boundingBox 一样
  • containsPoint
  • distanceToPoint
  • expandByPoint
  • isEmpty
  • makeEmpty
  • getBoundingBox
  • intersectsBox
  • intersectsPlane
  • set
  • setFromPoints
  • translate
  • union

hhhh,看到这些 api 命名,真的是体会到命名的快乐。同时感觉 Three.js 内置提供的数学库,已经能应对很多常用的场景了,不需要在自行编写或使用第三方库,如 jsts、turfjs 等

fill/stroke

你如果想图形支持类似 Canvas2D 对于 fill 和 stoke 样式的配置,这在 three.js 中会比较复杂一点,因为 three.js 中线是线、面是面,不存在 fill 这种概念。因此我们需要分两步走,第一拆成线框的方式,使用线的方式绘制,第二创建 Shape,使用 mesh 的方式进行绘制。

在 Three 中实现类似 canvas2D 的 stroke 和 fill 效果,需要通过平面和线来实现

  • createPolygon:Shape、ShapeGeometry、MeshBasicMaterial
  • createLine:BufferGeometry、LineBasicMaterial

three-dxf

意外发现一个three-dxf库,还是有不少借鉴点的

  • 如何实现 fitExtents
  • 如何利用 THREE 内置的 Curve 对象进行曲线绘制的
  • 如何解析和渲染文本的
  • 如何解析元素颜色的,entity 有设置颜色,则使用 entity.color,否则获取 layer 颜色,再没有则使用默认颜色

ShapeBufferGeometry 源码分析

分析 ShapeGeometry 需要先了解 Path 和 Shape 类

其实源码比较简单,流程图如下

three.js ShapeBufferGeometry.png

不错的资料



留言