乐趣区

关于前端:THREEjs-morphTargets介绍

本文介绍下 THREE.js 外面和 geometry 相干的 morphTargets。

THREE.js 有两种根本的 geometry:Geometry 和 BufferGeometry。这两种类型创立 morphTargets 的形式不一样,所以会别离进行讲述。因而,本文包含以下三个局部:

  1. morphTargets 是啥;
  2. 给 Geometry 增加 morphTargets;
  3. 给 BufferGeomtry 增加 morphAttributes;

本文示例基于 THREE.js 的 124 版本,能够通过 THREE.REVISION 属性获取 THREE.js 的版本。

morphTargets 是啥

morph 图像变换 变形 的意思。那么,一个物体的几何状态是如何示意的呢?
顶点地位。

THREE.js 中用于示意顶点地位的数据包含 Geometry 的 vertices 属性,以及,BufferGeometry 的 attributes 属性的 position 属性。那么,如何示意变形后的物体呢?

THREE.js 采纳的是通过变形的顶点来定义变形物体。这里,原物体的顶点和变形后物体的顶点是一一对应关系,包含以下特点:

  1. 数量雷同;
  2. 程序统一;

后面提到 Geometry 和 BufferGeometry 是通过不同的属性来存储顶点信息的,所以导致它们存储变形顶点的属性也是不一样的。所以,下文会针对这两种类型别离进行介绍。

给 Geometry 增加 morphTargets

首先,创立一个长宽高都是 2,材质是线框的红色立方体:

const boxGeometry = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true})
const mesh = new THREE.Mesh(boxGeometry, material)

设置 Geometry 变形后的状态。因为变形后的顶点信息是和 Geometry 的 vertices 属性绝对应的,所以咱们先看下原 vertices 属性是咋样的:

console.log(boxGeometry.vertices)


因为是要把图形放大一半,所以顶点信息的单位长度变为一半就行,咱们通过一个循环实现:

const morphVertices = boxGeometry.vertices.map(vector => {return vector.clone().multiplyScalar(0.5)
})
console.log(morphVertices)


给 Geometry 增加 morphTargets 属性:

boxGeometry.morphTargets.push({
    target: 'halfBox', // 名字轻易设置,目前还没有发现有啥用
    vertices: morphVertices
})

增加之后,发现并没有失效,通过查找材料,发现除了设置 Geometry 之外,还须要 Material 和 Mesh 的配合:

  1. 初始化 Material 的时候,设置 morphTargets 属性为 true;
  2. 给 Mesh 增加 morphTargetInfluences 属性,属性值能够是 0 - 1 之间,示意利用变形的水平;
const material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true, morphTargets: true})
mesh.morphTargetInfluences = [1]

截图如下,蓝色线框是原始长宽高为 2 的立方体,此处用来比照:
morphTargetInfluence: 1

morphTargetInfluence: 0.5

此时,遇到了一些疑难:

  1. 如果 influence 的值介于 0 到 1 之间,计算理论顶点地位的算法是啥;
  2. 查看 Geometry 的 morphTargets 属性会发现该属性是一个数组,同样 Mesh 的 morphTargetInfluences 属性也是一个数组,所以这是不是示意咱们能够增加多个 morphTargets;
  3. 如果第 2 项的答案是能够增加多个,那么,多个 morphTargets 如何同时作用呢;

咱们能够在下面的根底上再增加一个 morphTargets,这个 morphTargets 把原立方体放大 2 倍:

const morphVertices2 = boxGeometry.vertices.map(vector => {return vector.clone().multiplyScalar(2)
})
boxGeometry.morphTargets.push({
    target: 'doubleBox',
    vertices: morphVertices2
})

而后从新设置 morphTargetInfluences:

mesh.morphTargetInfluences = [1, 0.8]

那么,最初的效果图立方体的尺寸是多少呢?

通过查看源码,发现文件 src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js 中有这样的正文:

// morphTargetBaseInfluence is set based on BufferGeometry.morphTargetsRelative value:
// When morphTargetsRelative is false, this is set to 1 - sum(influences); this results in position = sum((target - base) * influence)
// When morphTargetsRelative is true, this is set to 1; as a result, all morph targets are simply added to the base after weighting
transformed *= morphTargetBaseInfluence;
transformed += morphTarget0 * morphTargetInfluences[0];
transformed += morphTarget1 * morphTargetInfluences[1];
transformed += morphTarget2 * morphTargetInfluences[2];
transformed += morphTarget3 * morphTargetInfluences[3];
// ...

也就是

  1. 当 BufferGeometry.morphTargetsRelative 是 false 的时候,计算形式为:base + sum((target - base) * influence),或者依照上述局部的代码逻辑:base * (1 - sum(influences)) + sum(target * influence),这两个计算形式是等价的。
  2. 当 BufferGeometry.morphTargetsRelative 是 true 的时候,计算形式是:base + sum(target * influence)

上述中,base 指原始顶点的值,target 指每个变形定义的顶点的值,influence 是每个 target 对应的影响值。

还存在一个问题,计算形式是和 BufferGeometry.morphTargetsRelative 的值相干的,然而咱们用的是 Geometry,并没有 morphTargetsRelative 属性。又通过一番查找,发现 Geometry 是有一个对应的 BufferGeometry 的,挂在 _bufferGeometry 属性上面。

须要留神的是,咱们创立完 Geometry,在首次渲染之前,THREE.js 并不会给 Geometry 创立 _bufferGeometry,那么如何捕获这个设置 morphTargetsRelative 属性的机会呢?我应用的是Mesh.onBeforeRender 回调:

mesh.onBeforeRender = function () {boxGeometry._bufferGeometry.morphTargetsRelative = true // 默认是 false}

当 morphTargetsRelative 是 false 的时候,立方体的长宽高是2 + (2 * 0.5 - 2) * 1 + (2 * 2 - 2) * 0.8 = 2.6,我是通过设置下面那个蓝色的线框立方体为 2.6,进行比对来验证的。

当 morphTargetsRelative 是 true 的时候,立方体的长宽高是2 + 2 * 0.5 * 1 + 2 * 2 * 0.8 = 6.2

更多 morphTargets 同时作用的细节能够参见 src/renderers/webgl/WebGLMorphtargets.js 文件,比方当 morphTargets 的个数超过 8 个的时候。

给 BufferGeomtry 增加 morphAttributes

与 Geometry 的 morphTargets 对应的是 BufferGeometry 的 morphAttributes。

同样,首先创立一个长宽高都是 2,材质是线框的红色立方体:

const boxGeometry = new THREE.BoxBufferGeometry(2, 2, 2)
const material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true, morphTargets: true})
const mesh = new THREE.Mesh(boxGeometry, material)
scene.add(mesh)

此时,顶点数据存储在 BufferGeometry.attributes.position 外面,所以,遍历这个数据,生成一个新的 morphAttribute,而后增加在 morphAttributes 属性上:

const morphPositions = []
const positions = boxGeometry.attributes.position.array
for (let i = 0; i < positions.length; i++) {morphPositions.push(positions[i] * 0.5)
}
const morphAttribute = new THREE.BufferAttribute(Float32Array.from(morphPositions), 3)
morphAttribute.name = 'halfBox'
boxGeometry.morphAttributes.position = [ // 留神,咱们这里批改的是 position 属性,对应 attributes.position
  morphAttribute
]

Material 和 Mesh 的批改与后面一样。
同样,咱们能够增加多个 morphAttribute,对应下面的例子:

const morphPositions2 = []
const positions2 = boxGeometry.attributes.position.array
for (let i = 0; i < positions.length; i++) {morphPositions2.push(positions2[i] * 2)
}
const morphAttribute2 = new THREE.BufferAttribute(Float32Array.from(morphPositions2), 3)
morphAttribute2.name = 'doubleBox'
boxGeometry.morphAttributes.position.push(morphAttribute2)

boxGeometry.morphTargetsRelative = true // 比 geometry 设置 morphTargetsRelative 的形式要简略

总结

本文通过 Geometry 和 BufferGeometry 介绍了 morphTargets 是啥,以及多个 morphTargets 同时存在的时候,最终顶点信息的计算方法,心愿大家有所播种。

如有谬误,欢送留言评论。

退出移动版