10. Vertex and Pixel Shaders - Tutorial

In this tutorial we'll implement twisting with vertex shader and simple blur with pixel shader.


It is recommend to read Shaders Concept before working with tutorial.

Prepare Vertex Shader

For implementing twisting we'll create new file twist.vs and put following HLSL code into it. Main idea is to calculate angular coordinates (angle and length) of point relative to origin point and change angle dependently on length. Far from center - bigger angle offset. Different points will be rotated with different angle with specified dependency. It creates effect of twisting.

#include <defines.vs>

//shader constants which is populated from game code
float2 TwistOriginPoint;
float TwistGradient;
float TwistStrength;

VS_OUT vs_main(VS_IN In) {	
	VS_OUT Out;
	//calculate point relative to origin point
	float2 posVector = In.Pos.xy - TwistOriginPoint;
	//find angle and length in radians of point relative to origin point
	float angle = atan2(posVector.y, posVector.x);
	float length = sqrt(posVector.x * posVector.x + posVector.y * posVector.y);
	//modify angle depending on distance to origin point
	angle += TwistStrength * (length / TwistGradient) * (length / TwistGradient);
	//calculate modified point coordinates
	float2 newPoint = TwistOriginPoint + float2 (cos(angle) * length, sin(angle) * length);
	//calculate position of point in device scale
	Out.Pos.xy = TRANSFORM_POSITION(newPoint);
	//copy rest of values
	Out.Pos.z = In.Pos.z;
	Out.Pos.w = 1.0;
	Out.Color = In.Diffuse;
	Out.Tex0 =  In.Tex0;
	Out.Tex1 =  In.Tex1;
	return Out;	

Shader is ready to use but requires some shader constants to be specified.

Prepare Pixel Shader

Pixel shader code is applying to each pixel. Idea is taking color of current pixel and calculate average with 8 pixels around. Neighbor values will affect on processed one what should cause blurring. Distance to these neighbor pixels is determined with shader constant BlurStrength.

Code is located in blur.ps.

#include <defines.ps>

sampler2D ColorMap;

//shader constants
float2 TexelOffset;
float BlurStrength;

float4 ps_main(PS_IN In) : COLOR {
	//Calculating offset depending on texel offset and blur strength
	float2 Offset = TexelOffset * BlurStrength;
	//then get color of pixel and add 8 pixels around
	float4 result = tex2D(ColorMap, In.Tex0.xy);
	result += tex2D(ColorMap, In.Tex0.xy + float2(Offset.x, 0));
	result += tex2D(ColorMap, In.Tex0.xy + float2(-Offset.x, 0));
	result += tex2D(ColorMap, In.Tex0.xy + float2(0, Offset.y));
	result += tex2D(ColorMap, In.Tex0.xy + float2(0, -Offset.y));
	result += tex2D(ColorMap, In.Tex0.xy + float2(Offset.x, Offset.y));
	result += tex2D(ColorMap, In.Tex0.xy + float2(-Offset.x, Offset.y));
	result += tex2D(ColorMap, In.Tex0.xy + float2(Offset.x, -Offset.y));
	result += tex2D(ColorMap, In.Tex0.xy + float2(-Offset.x, -Offset.y));
	//return result as average of 9 points (1 + 8 around) and vertex color specified in DrawSprite
	return result / 9 * In.Color;

Loading Shaders

Nothing to tell you here, just create VertexShader and PixelShader objects specifying path to shader files:

protected override void Load() {
    _SampleTexture = new Texture(@"graphics\chameleon.jpg");
    //loading shaders
    _TwistShader = new VertexShader(@"shaders\twist.vs");
    _BlurShader = new PixelShader(@"shaders\blur.ps");

Don't forget to unload shaders.

protected override void Unload() {
    //shaders should be disposed too

One more thing: you can specify main shader function in shader object constructor. By default it is "vs_main" and "ps_main".

Using Shaders in Code

Using of shader is similar to using other canvas states. Operator <= is better way to ask canvas drawing things with shader.

Take a look on following code:

//each of shader is set for specified scope. result of setting shader is ShaderContext which allows setup shader parameters
using (ShaderContext psContext = canvas <= _BlurShader)
using (ShaderContext vsContext = canvas <= _TwistShader) {                
    //single pixel offset in scale 0..1
    psContext.SetValue("TexelOffset", _SampleTexture.WidthRatio, _SampleTexture.HeightRatio);
    //blur amount
    psContext.SetValue("BlurStrength", _BlurStrength);

    //set up origin point for twisting
    vsContext.SetValue("TwistOriginPoint", _TwistPoint);
    vsContext.SetValue("TwistGradient", 200f);                                          // strength is 1:1 on this length
    vsContext.SetValue("TwistStrength", (float)Math.Cos(Time.TotalGameTime) * 0.75f);    // angle of bend in BendGradient

    ... draw code here

Each set shader operation returns object of ShaderContext type. This objects gives ability to specify shader constant declared in vs or ps file. There are different overloads for SetValue method, you can find best for your needs.

It is important for some vertex shaders to have well tessellated geometry. In Vortex2D you can do this with DrawTesselatedSprite/DrawTesselatedTexturedRect:

using (canvas <= (_WireFrame ? PrimitiveFillMode.Wireframe : PrimitiveFillMode.Solid)) {
    //drawing sprite broken down into _TesselationLevel x _TesselationLevel segments, it is required for twisting
    canvas.DrawTessellatedSprite(canvas.Region.Deflate(100, 60), _SampleTexture.ToSprite(), ColorU.White, _TesselationLevel, _TesselationLevel);

Tutorial screenshot with wireframe mode switched on:



Vortex2D supports vertex and pixel shaders. Vertex shader can be used to process vertices before they will be used for rasterization. Pixel shader allows specify algorithm of processing each rasterized pixel which gives ability to implement wide specter of effects. Interoperability between game code and shader program is realized with passing shader parameters.

Last edited Aug 28, 2010 at 2:17 PM by AlexKhomich, version 19


No comments yet.