Vertex Shader for swaying branches and leaves

I am trying to animated tree branches and leaves to simulate wind.
I searched here and found two threads but neither provide a solution and are quite old.

The solution seems to be Vertex Shaders. Has anyone got this working?
@Leonidas can you share any projects that have this?

I don’t have a project like this.
There is a similar engine example, but that uses completely custom vertex shader:
https://playcanvas.vercel.app/#/graphics/shader-wobble

in your case you most likely want to use StandardMaterial and override a chunk on it, to move vertices, while still using the rest of StandardMaterial functionality, like lighting and shadows and all that.

This is the chunk to override:

and most likely you’d want to add some uniform to this chunk, see uniforms on top, something like time and intensity, and then modify getLocalPosition function to modify vertex position based on time and some ā€˜wind’ function, typically just a sin wave or similar in a simple case.

Hi @Dylan,

I have a project that uses the vertex shader to move leaves on the tree.

I haven’t touched it in along time, so it looks like tweens and the other fragment shaders are broken, but the vertex shader seems to work:

https://playcanvas.com/editor/scene/1389980

It’s set up with this vert shader (wind.vert):

uniform float time;
uniform float amplitude;
uniform float wavelength;


void main(void) {

    vec3 pos = vertex_position;

    pos.y +=  cos(pos.z * wavelength + time) * amplitude * pow(sin(pos.x * wavelength + time), 3.0);
    
    gl_Position = matrix_viewProjection * matrix_model * vec4(pos, 1.0);    

and this script wind.js:

var Wind = pc.createScript('wind');

Wind.attributes.add('vShader', {
    type: 'asset',
    title: 'Vertex Shader',
    assetType: 'shader'
});

Wind.attributes.add('amplitude', {
    type: 'number',
    title: 'Amplitude',
    default: 1
});

Wind.attributes.add('wavelength', {
    type: 'number',
    title: 'Frequency',
    default: 1
});

// initialize code called once per entity
Wind.prototype.initialize = function() {
    this.timer = 0;
    
    this.material = this.entity.render.meshInstances[0].material;
    this.material.chunks.startVS = this.vShader.resource;
    // console.log(this.material.chunks);
};

// update code called every frame
Wind.prototype.update = function(dt) {
    this.timer += dt;

    this.material.setParameter('time', this.timer);
    this.material.setParameter('amplitude', this.amplitude);
    this.material.setParameter('wavelength', this.wavelength);
    this.material.update();
    
};

// swap method called for script hot-reloading
// inherit your script state here
// Wind.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/

Apply the script to the leaves of the tree and then use the amplitude and frequency attributes to get a good wave size and speed.

I hope this is helpful.

Oh wow, @eproasim thank you so much! This sounds perfect!
I tried your link but I get ā€œError while saving changes. Please refresh the editorā€. When I refresh I get the same error.

We will try the scripts out.

No problem! You can see it in action with the old version here:

https://adinteractive.net/a-quiet-vacation

@eproasim That looks awesome and exactly what we are trying to do!

I tried the scripts. Added Wind.js to the leaves object, added the wind.vert script to the vertex shader input of wind.js then launched a preview, but unfortunately I didn’t see any motion. No errors. I also tried increasing frequency and amplitude from 1 to 10,000 in increments of x10.

Is there anything additional I need to enable for vertex shaders to work?

If possible it’d be great to see the scene you have with the tree. It’s only the leaf vertex shader that I’d need to see working.

That’s interesting. Let me see if I can get a quick minute to get you a working copy. Real quick. Is your project on engine v1 or v2? This is an old project, so it’s on v1, and I haven’t had a chance to really dig into the differences between v1 and v2 for shaders.

Thank you! I’m running 2.7.1
I just tried in version 1, there were a lot of errors and it failed to compile the Vertex Shader.

@Dylan

Here you go!

https://playcanvas.com/editor/scene/2226527

Here’s quick and dirty with just the tree on a new project using Engine v1.

1 Like

Thank you so much!
I tried to open but got the same "Error Saving Changes, Please Refresh Editorā€
Though weirdly your other link now works!

@eproasim I looked at your scene and got your scripts working in Engine v1
The only thing I was missing was adding .glsl at the end of the wind.vert file

Unfortunately it doesn’t working in Engine v2 as you have mentioned, though there are no errors.

Since my main scene is using Engine v2. Do you have any idea how to update wind.js or wint.vert.glsl to work in Engine v2?

Thank you so much for your help!

I see here that startVS is no longer supported. They suggest using litMainVS instead.

@Wagner @mvaligursky

@Wagner @mvaligursky This is the code that @eproasim kindly shared for leaf swaying. It works in engine v1 but not v2. From the thread above it looks like startVS is not supported anymore. I will try litMainVS. Any suggestions much appreciated

var Wind = pc.createScript('wind');

Wind.attributes.add('vShader', {
    type: 'asset',
    title: 'Vertex Shader',
    assetType: 'shader'
});

Wind.attributes.add('amplitude', {
    type: 'number',
    title: 'Amplitude',
    default: 1
});

Wind.attributes.add('wavelength', {
    type: 'number',
    title: 'Frequency',
    default: 1
});

// initialize code called once per entity
Wind.prototype.initialize = function() {
    this.timer = 0;
    
    this.material = this.entity.render.meshInstances[0].material;
    this.material.chunks.startVS = this.vShader.resource;
    // console.log(this.material.chunks);
};

// update code called every frame
Wind.prototype.update = function(dt) {
    this.timer += dt;

    this.material.setParameter('time', this.timer);
    this.material.setParameter('amplitude', this.amplitude);
    this.material.setParameter('wavelength', this.wavelength);
    this.material.update();
    
};

Replacing startVS with LitMainVS threw a lot of errors.
I see LitMainVS has been updated here:

@Wagner @mvaligursky Any help much appreciated

There’s an example coming out most likely next week with the new release of the engine v2, not sure how applicable this is for you: PlayCanvas Examples

1 Like

Omg this is perfect! Does this work with the latest Engine (2.7.4) ?
I’m hoping to use this for the Viverse Hackathon that submits on Thursday.

Looking at the example, I would likely only need the shader chunks part of this right?
Though it’s cool you have object instancing too, I may try to leverage that, but for now I’m just trying to get it to work on 1 tree leaves objecy.
@eproasim This example might have the code necessary to update your example

If I can extract the shader chunks part, I should be able to apply this to my geometry directly right? Or should this run as a script under root and reference the object from within the script?

Hi @Dylan !

I think I might have some spare time to take a look at this and the docs for V2 to update the example tomorrow. I don’t know off the top of my head, but I assume the process for the conversion of the minimal example should ve straightforward enough. I’ll post my aolution here

1 Like

That would be amazing! Thank you!

The example @mvaligursky shared might be useful for how the chunks are implemented now

Just letting you know that I am working on this, but I have to step away for a little while, right now. I don’t think I have more than an hour or so to sort it out.

Here’s my basline, from looking at the example:

var Windv2 = pc.createScript('windv2');

Windv2.attributes.add('vShader', {
    type: 'asset',
    title: 'Vertex Shader',
    assetType: 'shader'
});

Windv2.attributes.add('amplitude', {
    type: 'number',
    title: 'Amplitude',
    default: 1
});

Windv2.attributes.add('wavelength', {
    type: 'number',
    title: 'Frequency',
    default: 1
});

// initialize code called once per entity
Windv2.prototype.initialize = function() {
    this.timer = 0;

    this.material = this.entity.render.meshInstances[0].material;
    // this.material.chunks.startVS = this.vShader.resource;
    // this.material.chunks.transformCoreVS = this.vShader.resource;
    console.log(this.material);
    this.material.shaderChunks.glsl.set('transformCoreVS', this.vShader.resource);
    // console.log(this.material.chunks);
};

// update code called every frame
Windv2.prototype.update = function(dt) {
    this.timer += dt;

    // this.material.setParameter('time', this.timer);
    app.graphicsDevice.scope.resolve('time').setValue(this.timer);
    // this.material.setParameter('amplitude', this.amplitude);
    app.graphicsDevice.scope.resolve('amplitude').setValue(this.amplitude);
    // this.material.setParameter('wavelength', this.wavelength);
    app.graphicsDevice.scope.resolve('wavelength').setValue(this.wavelength);
    // this.material.update();
    
};

// swap method called for script hot-reloading
// inherit your script state here
// Windv2.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/

But material.shaderChunks is returning undefined. I’ll keep working in a bit, but if it’s because it was forked form a v1 project and then switched to v2 I’ll try it again without the fork. Otherwise, I’ll just keep trying and digging =]

@eproasim Thank you for looking into this. Yes the core of this issue seems to be around this function:
this.material.shaderChunks.glsl.set(ā€˜transformCoreVS’, this.vShader.resource);
@mvaligursky Is this the correct usage for shaderChunks now?

[edit] looking at the tree demo, it does seem like transformCoreVS is the correct term