本文介绍下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 weightingtransformed *= 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.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同时存在的时候,最终顶点信息的计算方法,心愿大家有所播种。
如有谬误,欢送留言评论。