2D pixelated grass waving shader

How it is done?

https://giphy.com/gifs/tree-xTiTnjaheUQF1qQL8Q

And this?

Ok, the simple answer is indeed: shaders. But how? What should I learn and search for to create such effects in Defold?

4 Likes

For grass I think basic idea is to move sprite top corners in vertex shader and for interaction probably need to pass uniform to alter movement. My knowledge of shaders are super small.
For wind it seems common shader. I’ll ask my friend since I know he wrote pretty much the same thing in Godot shader.

1 Like

You can watch my talk for an insight. https://www.youtube.com/watch?v=Qkhv2UWAeA8
But yes, move the top vertices of grass quads - cos(t) for wind, on collision additionally supply velocity to the shader for kinematic response. So basically v.x = cos(t) - velocity;
For the tree - grid mesh with cos(t) modification and additional dependence on position in the grid (outermost vertices bend most).
All doable in Defold, but not as elegant or efficient as in Unity or Godot.

4 Likes

I got reply from my friend.
Keep in mind it’s for Godot, so maybe something needs to be changed and take out the uniforms.

shader_type canvas_item;
uniform float hair_vertical_offs;
uniform float hair_horizontal_offs = - 60.0;

void fragment() {
    float t = TIME;
    vec2 uv = UV;
    float uv_x_deform = cos(uv.x);
    vec2 offs_uv = vec2(cos(t * 2.0 + uv.y * 10.0) + hair_horizontal_offs * ( uv.x) * ( uv.x) * 0.3, cos(t * 2.0 + uv.x * 20.0) + hair_vertical_offs * (1.2 - uv.x)) * 0.03 * (1.0 - uv.x);
    
    vec4 img = texture(TEXTURE, uv + vec2(offs_uv.x, offs_uv.y));
    COLOR = img;
    
}
5 Likes

That would work for the tree and easy to port to Defold.

1 Like

Hmm… https://www.gamasutra.com/blogs/SvyatoslavCherkasov/20181023/329151/Graveyard_Keeper_How_the_graphics_effects_are_made.php

There are also an animation of a wheat field shake and things are pretty simple here too. The vertex shader changes the shape of the x-coordinates taking y-coordinate into account. The highest point will be shaken the most intensely. The intention here is that the top should move while the root shouldn’t. Plus the phase of shaking varies according to the x/y coordinates to make different sprites move separately.

I saw this one, but it needs another image of leaves, and the one above seems to work with only one static image that is transformed by the shader. Though it is something I want to create someday too :smiley:

1 Like

From one of my demo projects.

grass.vp

attribute mediump vec4 position;
attribute mediump vec2 texcoord0;
attribute mediump vec3 normal;

uniform mediump mat4 mtx_view;
uniform mediump mat4 mtx_proj;
uniform mediump vec4 time;

varying mediump vec4 var_position;
varying mediump vec3 var_normal;
varying mediump vec2 var_texcoord0;

void main() {
	var_position = position;
	var_texcoord0 = texcoord0;
	var_normal = normalize(normal);
	vec4 offset = vec4(0.0);
	if (position.y > 5.0) {
		offset = vec4(0.0, 0.0, 5.0 * sin(5.0 * time.x), 0.0);
	}
	gl_Position = mtx_proj * mtx_view * (position + offset);
}
8 Likes

I think your example has more than 2 layers (tree, front and back layers).
BTW where those are from?

1 Like

@sergey.lerg @NeZvers thanks for the tips and solutions! :smiley: I will try to adapt some vertex program :wink: can I ask for some .dae files you were working on, with more vertices?

Sure.
quad_4x4.dae.zip (1.5 KB)

mesh.vp

attribute mediump vec4 position;
attribute mediump vec2 texcoord0;

uniform mediump mat4 mtx_view;
uniform mediump mat4 mtx_proj;
uniform mediump mat4 mtx_worldview;

uniform mediump vec4 vertex1;
uniform mediump vec4 vertex2;
uniform mediump vec4 vertex3;
uniform mediump vec4 vertex4;
uniform mediump vec4 vertex5;
uniform mediump vec4 vertex6;
uniform mediump vec4 vertex7;
uniform mediump vec4 vertex8;
uniform mediump vec4 vertex9;
uniform mediump vec4 vertex10;
uniform mediump vec4 vertex11;
uniform mediump vec4 vertex12;
uniform mediump vec4 vertex13;
uniform mediump vec4 vertex14;
uniform mediump vec4 vertex15;
uniform mediump vec4 vertex16;

varying mediump vec2 var_texcoord0;

#define o33 0.3333333

void main() {
	var_texcoord0 = texcoord0;
	vec4 p = position;
	if (position.x == -1.0 && position.y == -1.0) {
		p.xy = vertex1.xy;
	} else if (position.x == -o33 && position.y == -1.0) {
		p.xy = vertex2.xy;
	} else if (position.x == o33 && position.y == -1.0) {
		p.xy = vertex3.xy;
	} else if (position.x == 1.0 && position.y == -1.0) {
		p.xy = vertex4.xy;
		
	} else if (position.x == -1.0 && position.y == -o33) {
		p.xy = vertex5.xy;
	} else if (position.x == -o33 && position.y == -o33) {
		p.xy = vertex6.xy;
	} else if (position.x == o33 && position.y == -o33) {
		p.xy = vertex7.xy;
	} else if (position.x == 1.0 && position.y == -o33) {
		p.xy = vertex8.xy;

	} else if (position.x == -1.0 && position.y == o33) {
		p.xy = vertex9.xy;
	} else if (position.x == -o33 && position.y == o33) {
		p.xy = vertex10.xy;
	} else if (position.x == o33 && position.y == o33) {
		p.xy = vertex11.xy;
	} else if (position.x == 1.0 && position.y == o33) {
		p.xy = vertex12.xy;

	} else if (position.x == -1.0 && position.y == 1.0) {
		p.xy = vertex13.xy;
	} else if (position.x == -o33 && position.y == 1.0) {
		p.xy = vertex14.xy;
	} else if (position.x == o33 && position.y == 1.0) {
		p.xy = vertex15.xy;
	} else if (position.x == 1.0 && position.y == 1.0) {
		p.xy = vertex16.xy;
	}
	gl_Position = mtx_proj * mtx_worldview * p;
}
2 Likes

Thaaanks! :smiley: If I understand it correctly it won’t affect “logic” performance and the program with static trees and grass should be as smooth as with modified in each frame in that way by a shader, right? Of course I mean, if I don’t overuse graphic card resources, correct? It’s just better comparing to the solution where all of the grass and trees are animated, right?

Performance comes down to batching and number of objects. I guess these shaders break batching for every tree, but the overall number of them shouldn’t affect much the performance.

1 Like

Yeah! I’ve made it up to this point when I have a sine waving “flag” and it’s drawn above the tiles and sprites. The material has nearest filters (for pixels). Next question - how should I integrate it to the rest of the stage? I already have one model - a plane on which I’m drawing anything else like tiles, sprites, particles and lights. How do we draw “models on models”? Or maybe how to get rid of the black pixels on the model (in the sprite those are transparent")?

1 Like

Check blending setting in the render script. Do you use a separate predicate? You can use the same as for tiles, just keep the z value appropriate.

I moved it to the tile predicate. In render script I have blending:

-- BASE RENDER
--		Draw all normal stuff to base render target
render.enable_render_target(self.base_target)
render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})

render.draw(self.tile_pred)
render.draw(self.particle_behind_pred)
render.draw(self.tile_foreground)
render.draw(self.particle_pred)
render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
render.disable_render_target(self.base_target)

Though I can now move it towards z axis willingly, the black pixels are still below the texture, on the model, like above :confused:

Perhaps you disregard the alpha value in the fragment shader of the model somehow?

1 Like

And you probably should set the blend function before the first render.draw() call.

Yes, there is another at the beginning. But I’ve moved it anyway at the beginning of the above fragment.

Indeed, the fragment program (copied from model) was causing it, I’ve changed it to the one provided for sprites and there are no more black pixels, but everything is in my opinion a little bit transparent yet :confused:

grass2

P.S. I’m excited I’m learning such things :smiley:

3 Likes

Maybe I spoiled something in the pipeline?

render.zip (3.5 KB)