Reputation: 998
I want to morph the vertices from one 3D model to the next with three.js, using different durations and delays for individual vertices.
First of all, I load both of my models, take the model with bigger vert count and copy it's vertices positions. Then, I take the second model and assign it's vertices positions as a targetPosition
attribute:
class CustomGeometry extends THREE.BufferGeometry {
constructor (refGeo1, refGeo2) {
super()
let count = refGeo1.attributes.position.count
let targets = new Float32Array(count * 3)
for (let i = 0; i < count; i += 3) {
// if there is no corresponding vertex, simply assign 0
targets[i + 0] = refGeo2.attributes.position.array[i + 0] || 0
targets[i + 1] = refGeo2.attributes.position.array[i + 1] || 0
targets[i + 2] = refGeo2.attributes.position.array[i + 2] || 0
}
this.addAttribute('position', refGeo1.attributes.position)
this.addAttribute('targetPosition', new THREE.BufferAttribute(targets, 3))
}
}
Then I can pass an uniform variable mixFactor
and do this in my vertex shaders:
vec3 newPosition = mix(position, targetPosition, mixFactor)
And here is an live example
This works fine, except the vertices move uniformly through space (with same timing durations) when the mixFactor variable is changed. How does one add delay and different timings to the mix?
I understand I can do attribute float mixFactor
insted of uniform and then tween all of them on the CPU, but this will kill my performance... I suspect I have to add transitionTiming
and transitionDelay
float attributes to my vertices, but am lost how to do the math in my shaders...
Upvotes: 0
Views: 227
Reputation:
You add yet another attribute for each vertex, maybe call it mixAmountOffset
. Use that in your mixAmount calculation in the shader.
attribute float mixAmountOffset;
uniform float mixAmount;
....
float realMixAmount = clamp(mixAmount + mixAmountOffset, 0., 1.);
Then use realMixmount
in you mix
vec4 position = mix(position1, position2, realMixAmount);
You only have to update mixAmount
in JavaScript
to try to be clearer you need realMixAmount
to pass between 0 and 1 for every vertex. So let's say you set mixAmountOffset
to negative values from 0 to -1 for vertex
for (let i = 0; i < vertexCount ++i) {
mixAmountOffset[i] = -i / vertexCount;
}
now the math for mixAmount + mixAmountOffset
needs to pass all points from 0 to 1. The range for mixAmountOffset
is now 0 to -1 which means when mixAmount is 0 mixAmount + mixAmountOffset
will go from 0 to -1 across all vertices. Because of the clamp
that will be 0 to 0 across all vertices.
When mixAmount
is 1
then mixAmount + mixAmountOffset
will go from 1 to 0. Meaning only the first vertex is using the second model's positions, the last vertex is still using the first model's position.
You need to lerp mixAmount
from 0 to 2. That way when mixAmount
is 2 then mixAmount + mixAmountOffset
will go from 2 to 1 across all vertices and the clamp will make that 1 to 1 across all vertices. In other words all vertices will be using model2
What mixAmountOffsets
you use and how to do the math to compute a realMixeAmount
is really up to you. The point is you need to make the math so before things start all vertices get a realMixAmount
of 0 and after things end all vertices get a realMixAmount
of 1. There are an infinite number of ways to do that.
Looking at your code you were passing in the vertex index for mixAmountOffset
so for example you could pass yet another uniform vertexCount
and do scaledMixAmountOffset = mixAmountOffset / vertexCount
.
Or, you could put positive values from 0 to 1 in the mixAmountOffset
buffer and lerp mixAmount
from -1 to 1.
Or ... etc ...
Other things you might want to do:
Of course you can use some kind of smoothing function on mixAmount
when you lerp but in this case since every vertex is effectively lerping separately you probably want to put the smoothing function inside the shader
Example:
...
float smooth(float pos) {
pos *= 2.;
if (pos < 1.) {
return 0.5 * pow(2., 10. * (pos - 1.));
}
return 0.5 * (-pow(2., -10. * --pos) + 2.);
}
void main () {
float realFactorOffset = clamp(mixFactor + mixFactorOffset, 0.0, 1.0);
realFactorOffset = smooth(realFactorOffset);
vec3 newPosition = mix(position, targetPosition, realFactorOffset);
...
see: https://codepen.io/greggman/pen/aymqdV
Upvotes: 1