XNA UK User Group

A helping hand for bedroom coders throughout the land.
in

This Blog

Syndication

Twitters

RandomChaos

January 2009 - Posts

  • Basic HLSL Lighting Techniques - Episode 7: Simple Animation

    http://www.youtube.com/v/Igt5R9Eigvw <p><a href="http://www.youtube.com/v/Igt5R9Eigvw">http://www.youtube.com/v/Igt5R9Eigvw</a></p>

    Now this is very simple animation, no bones are used at all here. The point of this post is to show you how you can manipulate vertices in the shader. What I intend to do with this tutorial is go a little further than just lighting and finish it of with some posts around doing a multi pass shader and reusing geometry in those subsequent passes as well as some other HLSL bits and bobs I have found useful. This post may seem a little abstract because of this, but please bare with me...

    In the DirectX SDK there is a simple HLSL tutorial and in there there is a technique that covers what I am going to do here, there is also a shader on the NVIDIA site that does a similar thing, I think it's called "Mr Wiggle", also the same idea is applied to the Ocean shaders I have posted before. So what we are going to look at, in it's simplest form, is this kind of animation. The great thing with this IMhO is that it's all done on the GPU, you don't have to re position the vertices on the CPU and pass them over, so making it efficient.

    I first started to play about with this sort of thing when I had the idea to do a 3D type of JellyCar (I love that game!) so I started playing with this kind of animation, I started off with a cube mesh I created in Blender 3D and applied the same principles I am going to show here.

    From a still you can't get a very good idea of the motion of the cube, but what you can see is how I have altered it's shape, this is done inside the shader, as I said the model is a cube. There is a small amount of motion in this shader, I have added a clip of it to the back of the tutorial clip at the top so you can see it in motion,it's not finished, still a WIP.

    Simple Animation for a Models

    What we are going to do is alter the existing BlinnPhong shader we did in an earlier tutorial. We are first going to add a new parameter of Time to the shader

    float Time : Time;

    And a function called CalcVertexAnimation (pretty sure this is the name of the method in the SDK tut too:P)

    float4 CalcVertexAnimation(float4 Offset)
    {
    return float4((cos(Time+Offset.y)*.5)+Offset.x ,Offset.y,(sin(Time-Offset.y) * .5)+Offset.z,Offset.w);
    }

    Now  add a call to this function in the vertex shader so that we alter the position of the incoming vertex

    input.Position += CalcVertexAnimation(input.Position);

    And then alter the call to the shader to pass the current time in (I multiply by 3 to speed it up a bit)

    if(effect.Parameters["Time"] != null)
    effect.Parameters["Time"].SetValue((float)gameTime.TotalGameTime.TotalSeconds * 3);

    And TA DA!! Simple Animation... I know, it's not that awesome, but when  I move onto multi pass shader and show how we can reuse the geometry, this sort of thing could be used to animate a force field around a model, well that was the implementation that sprang to my mind. But I am sure you will come up with far better applications for this sort of thing.

    http://www.youtube.com/v/x3kkoNGbA_s <p><a href="http://www.youtube.com/v/x3kkoNGbA_s">http://www.youtube.com/v/x3kkoNGbA_s</a></p>

    At the time of me playing about with this, I was on the XNA IRC and someone on there was asking for some help regarding how to go about doing a flag animation (sorry forget your name now..) So I suggested this method of doing it.

    http://www.youtube.com/v/ZRvTJxd-Ed4 <p><a href="http://www.youtube.com/v/ZRvTJxd-Ed4">http://www.youtube.com/v/ZRvTJxd-Ed4</a></p>

    Animated Flags

    What I did the first time around did the job, but, I was not happy with the way it was being lit. What is happening when we animate the vertex is that the relating normal for that vertex is not altering. This makes the lighting look unnatural, so in the shader we also have to re calculate the normal based on the animation function we have, the current vertex and it's surrounding vertices.

    I am not going to go into the detail of how I created the flag it's self, it's just a plane of vertices created on the XY axis, and it's normals all set to Vecto3.Backward so 0,0,1 facing.

    So the flag shader it's self is really just an Ambient light shader, that is passed a texture. As before we add a Time parameter and a CalcVertexAnimation function

    float4 CalcVertexAnimation(float4 Offset)
    {
    return float4(Offset.x,Offset.y,sin(Time+Offset.z+(Offset.y-Offset.x)) * 1.5,Offset.w);
    }

    As you can see the animation is a little different, we are just moving the vertex along the Z axis by the time + the X and Y axis I am multiplying by 1.5 to give it a bit more depth to each of the waves.

    The vertex shader has altered a fair bit, first thing I do is store the original position for the vertex, then set up an array of 3 positions, these three positions will give us our new normal value, but first we have to work out where they are. This will only work with this flag class as we know that our vertices are evenly spaced, this may not be the case in a model. So to get the correct normal we need to get the cross product from two sides of this vertex.

    The first side is created from the current vertex position - the vertices position below this current vertex.

    The second side is created from the current vertex position - the vertices position to the right of it.

    We can now use these two vales to give us the current vertices normal by using the intrinsic HLSL method cross and giving it our two sides.

    VS_OUT VS_ColorMap(VS_IN input)
    {
    VS_OUT output = (VS_OUT)0;

    float4 orgPos = input.Position;

    // Calc other vert positions to get aniamted normal.
    float4 poss[3];
    float4 nextPos = orgPos.xyzw;
    for(int p=1;p<3;p++)
    {
    if(p == 2)
    {
    nextPos.x = orgPos.x;
    nextPos.y -= .5;
    }
    else
    nextPos.x += .5;

    poss[p] = nextPos;
    poss[p] += CalcVertexAnimation(nextPos);
    }

    input.Position += CalcVertexAnimation(input.Position);
    poss[0] = input.Position;

    output.Position = mul(input.Position ,wvp);
    output.TexCoord = input.TexCoord;

    // Alter for animation;
    float3 side1 = poss[0] - poss[2];
    float3 side2 = poss[0] - poss[1];
    output.Normal = cross(side1,side2);
    //output.Normal = input.Normal;

    return output;
    }

    The Vertex shader is unchanged to, but here it is anyway

    PS_OUT PS_ColorMap(VS_OUT input)
    {
    PS_OUT output = (PS_OUT)0;

    float3 LightDir = normalize(LightDirection);
    float Diffuse = saturate(dot(LightDir,normalize(input.Normal)));

    float4 texCol = tex2D(ColorMapSampler,input.TexCoord);
    float4 Ambient = AmbientIntensity * AmbientColor;

    texCol *= Diffuse;

    output.Color = Ambient + texCol;

    return output;
    }

    And we then get this

    http://www.youtube.com/v/EDTu2eOZX8M <p><a href="http://www.youtube.com/v/EDTu2eOZX8M">http://www.youtube.com/v/EDTu2eOZX8M</a></p>

    I would like to thank my friend Michael Quandt for helping me sanity check the flag shader too, thanks mate :)

    So, the solution to this episode can be found here.

  • Dark Omen Games - Restart

    Well I am sure I have mentioned I am part of a small group of XNA developers and collectively we call our selves Dark Omen Games. Well, we had all sorts of issues with our domain and blog site last year, we still don't have the domain sorted out yet, but the blog is up and running.

    We have a few projects in the pipeline (as ever) and hope to actually finish some of them this year, so for all the news and development updates from the DOG team check out our blog here.

    Or if you want you can subscribe direct to the RSS feed here.

    With any luck we will get the domain sorted out and actually have a site too, until then please visit and comment on the blog. I have just put up a post on my first real attempt at an intro for DOG games that have been written with the RC3D engine, take a look and let us know what you think.

    Posted Jan 16 2009, 12:25 PM by Nemo Krad with no comments
    Filed under:
  • Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction

    http://www.youtube.com/v/_gSZ8CA2t70 <p><a href="http://www.youtube.com/v/_gSZ8CA2t70">http://www.youtube.com/v/_gSZ8CA2t70</a></p>

    Having played about with the NVIDIA Glass shader and my own (old) attempts at glass I have come up with this post

    Refraction

    Refraction is the light passing through an object but as they do so they bend. This is the effect we will get with this effect. Luckily HLSL has an intrinsic refraction function (refract) that we can use to get this effect. To get the refracted vector we need to calculate the direction of the viewer in relation to the surface and pass that with the surfaces normal to the refract function along with the degree of refraction we want.

    So we need to pass in a world view projection and a world matrix as well as the camera position, oh and for the hell of it a tint for colour.

    float4x4 wvp : WorldViewProjection;
    float4x4 world : World;
    
    float3 EyePosition : CAMERAPOSITION;
    float4 tint = float4(1,1,1,1);

    And the structures look like this

    struct VS_IN
    {
        float4 Position : POSITION;
        float3 Normal : NORMAL;    
    };
    struct VS_OUT
    {
        float4 Position : POSITION;
        float3 Refract: TEXCOORD1;
    };
    struct PS_OUT
    {
        float4 Color : COLOR;
    };

    We also need to pass in the Environment Map we are going to use to make the refraction and include a function to make reading it a little simpler

    texture cubeMap;
    samplerCUBE CubeMap = sampler_state 
    { 
        texture = <cubeMap> ;     
    };
    
    float4 CubeMapLookup(float3 CubeTexcoord)
    {    
        return texCUBE(CubeMap, CubeTexcoord);
    }

    So now we have the bits we need to do the job we need a vertex shader to calculate our position and refraction value. In the NVIDIA shader this was done in the pixel shader, but I figured this may give some performance boost, but doing this means that I have had to make sacrifices (not literal ones) later on.

    VS_OUT VS_Refraction(VS_IN input)
    {
        VS_OUT output = (VS_OUT)0;
        
        output.Position = mul(input.Position,wvp);    
        
        float3 Normal = mul(normalize(input.Normal), world);   
        float3 PosWorldr  = (mul(input.Position, world));
        float3 ViewDir = normalize(PosWorldr - EyePosition);
        
        output.Refract = refract(ViewDir, Normal, .99);
        
        return output;
    }

    Now all we need to do is get the correct colour from the environment map

    PS_OUT PS_Refraction(VS_OUT input)
    {
        PS_OUT output = (PS_OUT)0;
        
        output.Color = CubeMapLookup(input.Refract) * tint;
        
        return output;
    }

    And we get an effect like this

    A bit hard to see, click the picture for a bigger version.

    Reflection

    And so onto reflection, again HLSL has another function we can use, reflect, and again we need to pass a view direction (incident vector) and the surface normal.

    The shaders parameters are the same as before, the only change is to the structures, and all that is changing is the name of the parameters

    struct VS_OUT
    {
        float4 Position : POSITION;
        float3 Reflect: TEXCOORD0;    
    };

    Again a small change in the vertex shader to populate the Reflect parameter with the result of the intrinsic reflect method

    output.Reflect = reflect(ViewDirection, Normal);

    Then as before, we get the colour from the environment map

    output.Color = CubeMapLookup(input.Reflect) * tint;

    And we then get this effect

    So if we now combine the two effects we get a glass effect, not as nice as the NVIDIA one I grant you, but an effect none the less. Again we have to alter the the output structure

    struct VS_OUT
    {
        float4 Position : POSITION;
        float3 Reflect: TEXCOORD0;    
        float3 Refract: TEXCOORD1;
    };

     

    We populate both the parameters as before in the vertex shader

    output.Reflect = reflect(ViewDirection, Normal);    
    output.Refract = refract(ViewDirection, Normal, 0.99);

     

    And then in the pixel shader we get the colours from the environment map, I have taken the liberty of using the same method the NVIDIA shader did and used a lerp.

    output.Color = lerp(CubeMapLookup(input.Refract),CubeMapLookup(input.Reflect),.5f) * tint;

    And we get this

    Last Minute Addition

    Now I kind of liked the prism effect you get with the NVIDIA Glass shader and thought I would add it here, so I have created a forth shader RC3DGlass shader.

    So I added the RGB array and the refraction index for the RGB array (etas)

    half3 etas = { 0.80, 0.82, 0.84 };
    
    // wavelength colors
    const half4 colors[3] = {
        { 1, 0, 0, 0 },
        { 0, 1, 0, 0 },
        { 0, 0, 1, 0 },
    };

    As I mentioned before the view direction and surface normals are calculated in the vertex shader in my version and so I need the vertex shader to output an array of refraction values.

    struct VS_OUT
    {
        half4 Position : POSITION;
        half3 Reflect: TEXCOORD0;    
        half3 RefractRGB[3]: TEXCOORD1;    
    };

    Also I have replaced the in intrinsic refract method with the one in the NVIDIA shader so I can get the nice prism effect. So the vertex shader now looks like this

    VS_OUT VS_Glass(VS_IN input)
    {
        VS_OUT output = (VS_OUT)0;
        
        output.Position = mul(input.Position,wvp);    
        
        half3 Normal = mul(normalize(input.Normal), world);   
        half3 PosWorldr  = (mul(input.Position, world));
        half3 ViewDirection = normalize(PosWorldr - EyePosition);
        
        output.Reflect = reflect(ViewDirection, Normal);    
        
        // transmission
          bool fail = false;
        for(int i=0; i<3; i++) 
            output.RefractRGB[i] = refract2(ViewDirection, Normal, etas[i]);
        
        return output;
    }

    And finally the pixel shader

    PS_OUT PS_Glass(VS_OUT input)
    {
        PS_OUT output = (PS_OUT)0;
        
        half4 refract = half4(0,0,0,0);
        for(int c=0;c<3;c++)
            refract += CubeMapLookup(input.RefractRGB[c]) * colors[c];
        
        output.Color = lerp(refract,CubeMapLookup(input.Reflect),.5) * tint;
        
        return output;
    }

    As you can see I have had to put a loop in the vertex and the pixel shader which may well take away any perf boot I get from doing the calcs in the vertex shader this also means that the Fresnel term could not be applied, well I tried and could not get a decent effect when calculating in the VS, guess that's why NVIDIA did it in the PS lol

    And we finally get this effect

    Solution for this post can be down loaded here.

  • Basic HLSL Lighting Techniques - Episode 5: Environment Maps - NVIDIA Glass

    http://www.youtube.com/v/WK6pDZz1xBk <p><a href="http://www.youtube.com/v/WK6pDZz1xBk">http://www.youtube.com/v/WK6pDZz1xBk</a></p>

    OK, as you can see this may well not be all that basic, but if you have already used my sky box sample then you have already used an Environment map (as a result of this have found that my sky box shader is still in correct..sorry, fixed in this post though). In this post I hope to show the NVIDIA Glass shader.

    The NVIDIA Glass Shader

    This shader comes with a nice Fresnel function, now I had never hear of Fresnel (to my shame) until I started to play with shaders, but the Fresnel Term is useful for reflective surfaces as it calculates the ratio between reflected and refracted rays and so the surface becomes more reflective the angle of view changes. I have given 3 examples of the glass shader, the first calculates it's own Fresnel  term, the second is passed a texture to emulate the Fresnel term and the last uses no Fresnel term at all.

    As you will see the shaders are drawn in the following order from top to bottom, self calculating Fresnel Term, Fresnel texture passed and then no Fresnel used. I have also added a tint parameter so the class can be coloured.

    I am not going to give an explanation of the NVIDIA glass shader as I don't feel qualified to having not written it myself, but I will show what we need to pass to the shader to get this effect working.

    We need to pass a World, World Inverse Transpose, World View Projection and an Inverse View matrix to the shader like this:

    Matrix world = Matrix.CreateScale(Scale) * Matrix.CreateFromQuaternion(Rotation) * Matrix.CreateTranslation(Position);
    Matrix wvp = world * Camera.View * Camera.Projection;
    
    if (effect.Parameters["world"] != null)
        effect.Parameters["world"].SetValue(world);
    if (effect.Parameters["wvp"] != null)
        effect.Parameters["wvp"].SetValue(wvp);
    if (effect.Parameters["worldIT"] != null)
        effect.Parameters["worldIT"].SetValue(Matrix.Invert(Matrix.Transpose(world)));
    
    if(effect.Parameters["viewI"] != null)
        effect.Parameters["viewI"].SetValue(Matrix.Invert(Camera.View));

     

    We also need to pass the environment map to the shader to as well as the Fresnel texture to use (if the shader needs it) I have also added a "tint" variable so the glass can be coloured. The environment map is the same one that has been passed to the sky box

    if (effect.Parameters["cubeMap"] != null)
        effect.Parameters["cubeMap"].SetValue(env);
    
    if (effect.Parameters["tint"] != null)
        effect.Parameters["tint"].SetValue(Tint.ToVector4());
    
    if (effect.Parameters["fresnelTex"] != null)
        effect.Parameters["fresnelTex"].SetValue(fresnel);

    There are other parameters in the shader but I have not modified them as they look pretty good now, but by all means give them a go too.

    My next post will again be on environment maps using a reflection and refraction method.

    Thanks NVIDIA!

    You can download the solution here.