Welcome back to the XNA Shader Programming series. Since the start of tutorial 1, we have been looking at different lighting algorithms. Todays tutorial will be quite short and different, compared to those others, a pure vertex shader effect for deforming objects.
Before we start
In this tutorial, you will need some basic knowledge of shader programming, a understanding of geometry, vector math and matrix math.
Deforming objects
Since vertex shaders can be used to process and transform vertices on a per vertex basis, it’s quite ideal to use them to deform objects/meshes. Vertex shaders make it really easy to deform objects.
Let’s look at an example. Say you have a game that will make it possible to create your own character. This includes changing skin color, eye color, hair, clothes and so on. We can in this example create a vertex shader to create a weight property for our character, where say 0 means that our character will be very slim, and 1 that says that our character will be fat.
Fat/Slim
To do this, we need a vertex shader that simply moves a vertex along it’s normal:
If we move all the vertices along their normals, the object will be bigger, or smaller.
Ocean waves
Instead of making a big bone animated mesh to create a realistic looking ocean in your game, you could use a vertex shader to produce waves.
To do this, you will need a big flat mesh that will represent your ocean without any waves. You could either do this in 3Ds, or produce it with code. It will need many vertexes, as the shader will move them up and down according to a sine/cos function.
As we can see in this picture, we got a plane defined by a lot of vertices. We can use a Vertex Shader to move all vertexes by its Y-axis with a sine function, say
f(y)=sin(y).
Say vertex Vx ‘s Y-axis is defined by sin(X.pos+time);
This will produce waves on the ocean. Of course, this is really simple and pretty ugly. There is a lot of different Ocean algorithms out there, so if you want to look more closely on this subject, just do a bing the topic.
To make the ocean look better, you could apply a normal map to create small bumps on the surface, in addition to huge waves. You can also mix sine and cos functions to make more realistic waves.
Fake Spherical harmonics
This is what I’m going to implement today. Its pretty much a combination of the slim/fat algorithm and the ocean algorithm. The example will use a sphere object, and apply a pretty advanced sin/cos function to move vertexes along their normal in order to deform it, based on time.
Implementing the shader
The shader is just a Vertex Shader. The pixel shader will only do some basic lighting to make it look more real. You can add normal-mapping here and so on to create really cool looking effects( refer to my previous tutorial on normal mapping ).
In this shader, we will need a time variable, so we can move our vertexes along with a timer to make it look animated, and then we only make a huge mess of sine and cos functions to make it look cool. Feel free to play with these values.
We will use the same shader as in Tutorial #3.
First of all, we must add a timer to the shader so we can create an animation.
float TotalTime;
Then the rest of the effect is happening in the Vertex Shader only, and only the position variable of the vertex is changed.
Here is the Vertex Shader for this effect:
// The VertexShader. VertexShaderOutput VertexShaderFunction(VertexShaderInput input,float3 Normal : NORMAL) { VertexShaderOutput output; // Just some random sin/cos equation to make things look random float angle=(TotalTime%360)*2; float freqx = 0.4f+sin(TotalTime)*1.0f; float freqy = 1.0f+sin(TotalTime*1.3f)*2.0f; float freqz = 1.1f+sin(TotalTime*1.1f)*3.0f; float amp = 1.0f+sin(TotalTime*1.4)*10.0f; float f = sin(Normal.x*freqx + TotalTime) * sin(Normal.y*freqy + TotalTime) * sin(Normal.z*freqz + TotalTime); input.Position.z += Normal.z * freqz * amp * f; input.Position.x += Normal.x * freqx* amp * f; input.Position.y += Normal.y * freqy* amp * f; float4 worldPosition = mul(input.Position, World); float4 viewPosition = mul(worldPosition, View); output.Position = mul(viewPosition, Projection); float3 normal = normalize(mul(Normal, World)); output.Normal = normal; output.View = normalize(float4(EyePosition,1.0) - worldPosition); return output; }
It’s all about modifying the position of the vertex, moving them along their normal to shrink/expand the model. Doing this with a sin/cos function, we can create some pretty cool objects. If you for example modify the timer-value with the input from the keyboard or gamepad – you can search for some pretty cool objects, save the parameter and use them as enemies or anything you’d like.
This works great on the sphere in your download, but when I tried with another model (a simple block – wall section – subdivided tom give a few thousand vertices) it seems to just rip the model apart at the seams instead of rippling the vertices within (flat sides remain flat, but tear away from the model at the “borders”; 90 degree edges). The only difference I can find is that you are using the old X model and I’m using FBX 2013.
Cheers for any import, Richard
Now I’m really confused. Using your shader (rather than one I wrote from your tutorial) and your sphere.x, and copy & pasted your Draw and Update functions into C#, run it alongside your code (2 VS2010 instances) – in your it works fine, in mine it tears the sphere to pieces. Every thing is the same (as far as I can see) – any ideas?
OK – sorry ignore last post – camera was too close (wasn’t actually tearing the sphere) – so back to the first post, sphere.x works fine, fbx2013 subdivided box tears itself apart instead.
At the risk of spamming – exported the wall section as .X (to see if this was an importer issue) – but same problem – edited model to make it convex (just in case it was optimising out the flat plane mid-vertices) – but all to no avail, it just doesn’t seem to like corners. Not overly worries as I am unlikely to do this sort of thing in a shader anyway in practise.
Hi,
I’m working on this tutorial trying to figure out how i can modify your code in order to get the deformation along only one axis (for example Z axis) when I hit a button on my keybord, but I’ve several problem. Could you please help me
Pingback: XNA Shader deformation event driven | Question and Answer
Wonderful, what a webpage it is! This webpage presents helpful facts to us,
keep it up.