本文介绍下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 weightingtransformed *= 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.arrayfor (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.arrayfor (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同时存在的时候,最终顶点信息的计算方法,心愿大家有所播种。

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