Scrolling Textures with Zoom and Rotation

I’ve already written twice before (here and here) about a way to make a texture seem to repeat itself infinitely in every direction as you moved your camera around, all with a single draw instruction. That can be very useful in a number of game scenarios, such as creating a spacefield background for a spaceship shooter type of game.

That technique relied on the graphic’s card wrap texture addressing mode to do the wrapping automatically for you, while the spritebatch’s source and destination rectangles were used to to move (changing the source rectangle position) or zoom (change the source rectangle size in relation to destination rectangle) the camera. But it still had the problems that it didn’t support rotation (setting the rotation on SpriteBatch.value would cause the entire quad to rotate, not just the content) or respect the camera’s origin.

In order to do that, we need a bit more control. So this time I’ll show you how to correct this behavior by taking control over the texture coordinate generation process using a simple vertex shader. Using this technique you can take any texture (of any size) and make it repeat infinitely no matter what camera orientation you’re currently in. Consider it an upgrade to the older article!

I’ll start with the video sample for this article which should show you what to expect from it:

Part 1 – Setting up XNA

Okay, so we’d like to load a texture and draw it so that it fills the entire screen and seems to repeat infinitely. Furthermore we want to do this in a single draw call, and also want to be able to transform our camera at will without the effect breaking. You should already know how to do the first two steps of the process from the other article, which are, loading the texture and starting the spritebatch in wrap mode:

1) Load the texture:

2) Activate texture wrapping – Pass SamplerState.LinearWrap to SpriteBatch.Begin:

Next what we will do is draw this texture using a quad (i.e. two triangles connected to each other that form a rectangle – SpriteBatch already does this for us) that ALWAYS covers the entire screen, and leave it to a vertex shader to displace our texture coordinates so that it looks like we’re seeing it from another angle and distance. This means that even though the quad is always standing still, because of our vertex shader, its content will appear to change position, rotation, zoom, etc.

So let’s render that quad…

3) Render the texture as a fullscreen quad – Pass GraphicsDevice.Viewport.Bound as both source and destination rectangles to SpriteBatch.Draw:

Now on to the vertex shader…

Part 2 – Vertex Shader

I’ll be using a vertex shader to take the original texture coordinates for each of the quad’s vertices, and displace them so that we see any entirely different portion of the texture depending on our camera parameters. By doing so, the texture content will appear to move, rotate or zoom, even though the quad’s geometry (the four vertices that make up the quad) will still be fixed to our screen’s corners. Also, it doesn’t matter if the resulting coordinate falls outside the 0.0-1.0 range because we are using the wrap texture addressing mode which automatically brings them back into the 0.0-1.0 range at the end!

Here’s the vetex shader we need to use:

The vertex shader is pretty minimalistic. Most of the vertex shader is boilerplate code that is needed for any vertex shader working with SpriteBatch, which I got straight of the MSDN samples. The only relevant line is where I transform the texture coordinates (last line of the vertex shader function) using a matrix that I called the “scroll matrix” (by lack of a better name – I just made that up). That’s the matrix which does all of the work, and I’ll describe it next. You’ll need to supply that matrix along with the viewport’s size from your game.

If you’ve never used a shader before, you should read some other tutorials first, but basically you need to put this code on a text file, give it an “.fx” file extension and add it to your content pipeline. You load it just like a Texture or another content pipeline object, but using the class Effect instead.

Part 3 – Back on XNA: Generating the Scroll Matrix

Creating the “scroll matrix” is not too hard. If you already have a camera class, you only need to add this method to it:

If you compare it with your camera’s usual View Matrix you’ll notice a few differences. This one is basically a World Matrix (notice the S-R-T order of multiplication instead of T-R-S) with the catch that all translations (i.e. Position and Origin) are mapped to the texture’s space (which is sometimes called uv space too – a two dimensional space where where (0,0) corresponds to the top-left corner of the texture, and and (1,1) corresponds to the bottom-right corner of the texture) before being applied, which can be done by dividing their magnitudes by the texture’s size.

This matrix basically figures out where each of the corners of our screen/quad lie in texture space based on the camera’s current transform. After figuring that out, the default pixel shader takes care of the rest!

Part 4 – Loading and using the Effect

Now the only thing we need to do is load the vertex shader effect, pass it the camera’s “scroll matrix” and viewport size, and use it on our SpriteBatch when drawing the texture:

1) Load effect:

2) Pass scroll matrix to the effect:

3) Prepare SpriteBatch to use our effect:

And that’s all there is to it. Check the source code below for the complete source code for this article:

Final Thoughts

My first version of this article used a pixel shader instead of a vertex shader to do all the work. I’ve now changed it into a vertex shader instead which gives the same result, but is much, much more efficient (e.g. on my sample, with a vertex shader I transform only four texture coordinates for the entire effect, while with a pixel shader I was transforming hundreds of thousands of texture coordinates). For reference, here is the pixel shader I was using.

Warning: Use the vertex shader version. It’s a lot more efficient.

Source Code

  1. Mister G says:

    Hey, is it possible to use this technique to scroll the background just horizontally? I’m sure rotation won’t be possible anymore, but it doesn’t matter.

    I have this ( image i want to use as background, but vertical scrolling would mess everything up because of the gradient.

    I guess I can set SamplerState.u to linearwrap and SamplerState.v to clamp, but I don’t know how to modify the .fx file to achieve this effect, since I’m not very familiar with shaders.

    Or should I “stack” different coloured backgrounds on top of each other to get the gradient? But still I would need to get rid of vertical scrolling…

    Hope my question is clear enough, thanks and great job by the way!

    • Mister G says:

      Edit: I came up with a solution while reading other questions and sites, which doesn’t need any shaders at all.

      What I needed was some type of “camera wrapping”, similar to linearwrap. Let’s say, the camera cycles through all the existing backgrounds, and when it’s out of bounds (blue screen), the first background is being rendered again right after the last one, and so on, to achieve some infinite sky effect.

      I’ll start to work on this in my free time, greetings!

  2. OK says:


    Much thanks for this tutorial.

    I am trying to use it with a different api (not XNA). So far, the z-rotation roll and the scale are working great but I have troubles with xy scrolls.

    Could you please precise what you mean by Origin and Position variables ?

    Is Origin the world center ? is Position the last camera’s local matrix row x and y values ?

    In my case, I would like to scroll xy relative with camera’s yaw and pitch as well as camera position.

    I am not sure but I think the FSQ implementation I am using is pretty similar to XNA’s spriteBatch, and I hope the problem does not come from here too :/

    Thanks for your help.

    • Hello! The Origin is the location in screen space around which the camera rotates and zooms. So if the origin is (0,0) the camera rotates around the top-left corner of the screen. If it’s (ViewportWidth/2, ViewportHeight/2) the camera rotates around the center of the screen, which is usually what you want. I also made it so that the origin only affects zoom and rotation, but not translation (by undoing the origin translation before applying the real translation to the camera). As for the camera Position it’s the position of the top-left corner of the camera in world space before applying any zoom or rotation. So if the position is (0,0) then it means that the top-left corner of the camera will start at the world’s origin. These aren’t definitions set in stone – they’re just some conventions I decided to use for this example, because it’s the same I used before on my camera tutorial. What is important is that you calculate a world matrix to transform your texture coordinates correctly, which is just like transforming vertices. So look into how to create a world matrix your platform, and adapt it. Finally, I’m not sure but I think that in OpenGL the matrix multiplication order should be the opposite of what is shown here.

    • OK says:

      Hi David,

      Much thanks for your explanations !

      Everything is limpid now.

      In the mean time, I investigated in a slightly different direction (temporary) :

      The idea behind was to manage to setup a starfield system with a single FSQ and a simple star texture(instead of your grass ;).

      As it is for a space simulation, I needed the pan to be relative to camera’s yaw and pitch angles.

      What I did was just to construct a Z rotation matrix and simply pass my xy scroll(bound to cam’s local matrix yaw and pitch) values to vs but my problem was that scrolling uvs (in the vs) before rotation would move the center of rotation away the center of the screen, whereas scrolling after rotation would also rotate my tex2D lookup and mismatch the input from controller info from the expected result.

      What I finally did was just to rotate(with the same but negated z value ;D) my scroll values(to send to vs) on the app side right after applying the transformations to the cam’s local matrix, hence adjusting the input controller info and keep my x_scroll and y_scroll sticking to respectively horizontal and vertical scroll for the tex2D.

      I still need the texture to pan upon world space cam coords so I am slowly back to what you did, somehow complete this Z rotation matrix with translation and scale ones(slightly to give an impression of travel on some of my stars passes).

      Much thanks again for your directions and your light speed assistance !

      Have fun,

      PS : The api I am using propose the use of both D3D, OpenGL and also various gaming hardware proprietary graphics engine.

  3. Tiago says:

    Hi David,

    First of all, you have written a bunch of great tutorials, awesome job!

    I’m struggling to make this work with a horizontal only background.
    I don’t want the shader to “add” the same texture as i zoom out.

    I want it to fill the screen horizontally but not vertically.

    Is this possible with this kind of shader or should i go in other method?


  4. halsafar says:

    What would be the easiest way to add parallax to this? Simply pass in a parallax vector2 into the vertex shader and use it to multiple the position by?

    Also I’d like to say I’ve found all your articles extremely useful, even though I am not using XNA.

    • halsafar says:

      In the getScrollViewMatrix() I multiple the position vector in the last matrix translation by the parallax vector. This appears to work. I have no tried rotation or zoom.

  1. By Scrolling Textures in XNA | David Gouveia on September 20, 2011 at 1:10 am

    […] Update: I wrote a more advanced version of this article now, which does the same thing but allows the camera to be scaled and rotated too. You can check it out here. […]

  2. […] Update: I wrote a new article about this topic which lets you use zoom and rotation at the same time as scrolling the background infinitely. Check it out here. […]

Leave a Reply