# How can I use shaders to make a square have a waving effect?

I'm new to using shaders to do some fancy effects and I'm struggling with them. I'm using DirectX 11 and HLSL. I have this square in the middle of the screen:

It's just a square that I've created using 4 vertices. I want to give it this waving effect on the sides, but I don't know how to do it.

This is for a little 2D game I'm making, so I have an orthogonal projection matrix which is the one I use. View matrix is set to identity.

DirectX::XMMATRIX view = DirectX::XMMatrixIdentity();
DirectX::XMMATRIX projection = DirectX::XMMatrixOrthographicOffCenterLH(0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f);


This is the constant buffer I set each frame, so I can pass a float time variable to the shader:

struct constant_buffer {
DirectX::XMMATRIX  WVP;
// this value starts in 0 and increases by 1 each frame until 359 degrees
float time;
};

constant_buffer cbuffer;


The shader that I'm currently using is very simple, it just applies the matrix to each vertex to transform it.

struct in_pshader
{
float4 position : SV_POSITION;
float4 color : COLOR;
};

cbuffer constant_buffer
{
float4x4 WVP;
float time;
};

{

output.position = mul(position, WVP);
output.color = color;

return output;
}

float4 pshader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}


I'm guessing there's some sine function involved around, so that's why I decided to pass angles per frame as input. I tried many combinations, but I'm such a newbie and can't get it right. :( Any help on this would be very appreciated.

• The older games used bitmaps. Then to wave, they wrote each horizontal line separatly. The x position was varied over time. – Lvl15TechNinja Apr 30 '13 at 19:49
• @Lvl15TechNinja can also do it with sine and a time variable. – UnderscoreZero Apr 30 '13 at 20:35

First of all, to create this kind of effect you're going to need more than 4 vertices. You can move the vertex positions around all you like, but a shape made with 4 vertices will only ever be a quadrilateral, not a shape with wavy sides like you've shown. You'll have to subdivide the shape vertically, at least, so that it has enough vertices along each side to make the smooth sine curve. It's not necessary to subdivide it horizontally for the effect you've shown.

Once that's done, it's pretty easy to do this. You'd add a line to your vertex shader like

position.x += a * sin(k * position.y + f * time);


where a, k, f are some constants you can tweak (they can be hard-coded, or you can put them in your constant buffer). This will move each vertex horizontally (adjusting position.x) depending on both its vertical position (position.y) and the current time.

This should be done before the multiplication by the WVP matrix, assuming you want the distortion to apply in world space as opposed to screen space.

• Wow, I never thought a effect that seemed so simple to do would require tessellation. How would you do this with cards that don't have this feature? I have seen waving flags in older games so there's got to be some other trick to do it. :/ Nathan I'm gonna have to pay you for your 3D classes ^^, you are being very helpful to me, thanks, really. – Kaboose Apr 30 '13 at 19:44
• No, it doesn't need tesselation. The classic way to do this is to use render to texture, then draw the rendertarget as a full-screen quad with the sine distortion in the pixel shader. – Maximus Minimus Apr 30 '13 at 19:47
• @Kaboose You don't need to do hardware tessellation. You just need to author/generate the mesh so that it's vertically subdivided into enough pieces to make the curves appear smooth. – Nathan Reed Apr 30 '13 at 20:10
• I'd imagine that this effect could be done quite easily in the pixel shader as well. – user9790 Apr 30 '13 at 20:21
• For those of you arguing that there's another way of solving the problem, perhaps you could write up an answer that describes what you're suggesting. – Tetrad Apr 30 '13 at 21:55

As an alternate solution from vertex tessellation/subdivision, you can also do this in the fragment shader by simply drawing a smaller square within your quad and offsetting the pixels. You will need to resize your geometry to a larger size to make it match your desired result.

For example, in your fragment shader, you have local X,Y coordinates passed in from the vertex shader which range from 0-1. You check to see if they are in range 0.25 to 0.75, and if they aren't, then discard the pixel. To apply the waving effect, you add the sine function to the X coordinate. Depending on the strength of your sine wave, you will need to shrink the square to fit the distorted edges within your drawing area (the quad).

Keep in mind this method may not be as efficient as a subdivided quad, due to more pixels being processed by the GPU to draw the square, ie "overdraw".