1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
**Step 1 - Writing Vertex Shader**

You can use a vertex shader to rotate the sprite around different axes than the default one. This is the vertex shader I used:

    sampler TextureSampler : register(s0);
    float2 ViewportSize;
    float4x4 Matrix;
     
    void SpriteVertexShader(inout float4 color : COLOR0, inout float2 texCoord : TEXCOORD0, inout float4 position : POSITION0)
    {
       // This is the important step
       position.xy = mul(position, Matrix).xy;
       
       // Half pixel offset for correct texel centering.
       position.xy -= 0.5;
     
       // Viewport adjustment
       position.xy = position.xy / ViewportSize;
       position.xy *= float2(2, -2);
       position.xy -= float2(1, -1);
    }
     
    technique SpriteBatch
    {
        pass
        {
            VertexShader = compile vs_2_0 SpriteVertexShader();
        }
    }

It's actually 99% boilerplate code required for any vertex shader to work with SpriteBatch, and the only line of code that I added was:

    position.xy = mul(position, Matrix).xy;

I'm only touching the X and Y coordinates otherwise the polygons would end up being clipped by the near and far planes when it rotates.

**Step 2 - Using the Vertex Shader**

Load the effect file and set the ViewportSize parameter right away since it's required by the shader:

    effect = Content.Load<Effect>("Effect1");
    effect.Parameters["ViewportSize"].SetValue(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height));

Then you have to update the Matrix attribute inside the shader with whatever rotation matrix you want to apply. This is what I did:

    Matrix matrix = Matrix.CreateRotationX(MathHelper.ToRadians(60f)) *
                    Matrix.CreateRotationY(MathHelper.ToRadians(30f));

    effect.Parameters["Matrix"].SetValue(matrix);

But the vertices inside the shader are already defined in world space, not in model space, so you might need to compensate for that in the matrix otherwise the position will appear to change too.

Finally, use the effect object when calling SpriteBatch.Begin:

    spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, effect);

**EDIT - Alternative using BasicEffect**

Managed to get the same results with the built-in BasicEffect class.

    BasicEffect basicEffect = new BasicEffect(GraphicsDevice);
    Matrix projection = Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, -1000, 1000);
    Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);
    basicEffect.World = Matrix.Identity;
    basicEffect.View = Matrix.Identity;
    basicEffect.Projection = halfPixelOffset * projection;
    basicEffect.TextureEnabled = true;
    basicEffect.VertexColorEnabled = true;

Was not sure how to prevent the clipping from the near and far planes in this case, so I enlarged the range.

Next update the matrix with:

    basicEffect.World = matrix;

And draw as before but using this instance instead.