Georgi B. Nikolov
Georgi B. Nikolov

Reputation: 998

Moving vertices with different transition delays using an uniform variable

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

Answers (1)

user128511
user128511

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

Related Questions