本文介绍下 THREE.js 外面和 geometry 相干的 morphTargets。
THREE.js 有两种根本的 geometry:Geometry 和 BufferGeometry。这两种类型创立 morphTargets 的形式不一样,所以会别离进行讲述。因而,本文包含以下三个局部:
- morphTargets 是啥;
- 给 Geometry 增加 morphTargets;
- 给 BufferGeomtry 增加 morphAttributes;
本文示例基于 THREE.js 的 124 版本,能够通过 THREE.REVISION
属性获取 THREE.js 的版本。
morphTargets 是啥
morph是 图像变换 、 变形 的意思。那么,一个物体的几何状态是如何示意的呢?
顶点地位。
THREE.js 中用于示意顶点地位的数据包含 Geometry 的 vertices 属性,以及,BufferGeometry 的 attributes 属性的 position 属性。那么,如何示意变形后的物体呢?
THREE.js 采纳的是通过变形的顶点来定义变形物体。这里,原物体的顶点和变形后物体的顶点是一一对应关系,包含以下特点:
- 数量雷同;
- 程序统一;
后面提到 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 的配合:
- 初始化 Material 的时候,设置 morphTargets 属性为 true;
- 给 Mesh 增加 morphTargetInfluences 属性,属性值能够是 0 - 1 之间,示意利用变形的水平;
const material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true, morphTargets: true})
mesh.morphTargetInfluences = [1]
截图如下,蓝色线框是原始长宽高为 2 的立方体,此处用来比照:
morphTargetInfluence: 1
morphTargetInfluence: 0.5
此时,遇到了一些疑难:
- 如果 influence 的值介于 0 到 1 之间,计算理论顶点地位的算法是啥;
- 查看 Geometry 的 morphTargets 属性会发现该属性是一个数组,同样 Mesh 的 morphTargetInfluences 属性也是一个数组,所以这是不是示意咱们能够增加多个 morphTargets;
- 如果第 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];
// ...
也就是
- 当 BufferGeometry.morphTargetsRelative 是 false 的时候,计算形式为:
base + sum((target - base) * influence)
,或者依照上述局部的代码逻辑:base * (1 - sum(influences)) + sum(target * influence)
,这两个计算形式是等价的。 - 当 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 同时存在的时候,最终顶点信息的计算方法,心愿大家有所播种。
如有谬误,欢送留言评论。