NEWS: This project is now hosted on GitHub https://github.com/Microsoft/DirectXTK. This site is being maintained for now, but please move to using GitHub https://github.com/Microsoft/DirectXTK/wiki/Sprites-and-textures particularly as the CodePlex copy of the tutorials are a little dated.

In this lesson, we will cover the basics of creating a texture from a bitmap file, and then rendering it using a 2D sprite with various drawing options.

Setup

First create a new project using the instructions from the first two lessons: The basic game loop and
Adding the DirectX Tool Kit which we will use for this lesson.

Loading a texture

Start by saving cat.png into your new project's directory, and then from the top menu select Project / Add Existing Item.... Select "cat.png" and click "OK".

In the Game.h file, add the following variable to the bottom of the Game class's private declarations:

Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_texture;

In Game.cpp, add to the TODO of CreateDevice:

DX::ThrowIfFailed(
    CreateWICTextureFromFile(m_d3dDevice.Get(), L"cat.png", nullptr,
    m_texture.ReleaseAndGetAddressOf()));

In Game.cpp, add to the TODO of OnDeviceLost:

m_texture.Reset();

Build and run the application which will still not be displaying anything but the cornflower blue window, but will have a texture loaded.

Troubleshooting: If you get a runtime exception, then you may have the "cat.png" in the wrong folder, have modified the "Working Directory" in the "Debugging" configuration settings, or otherwise changed the expected paths at runtime of the application. You should set a break-point on CreateWICTextureFromFile and step into the code to find the exact problem.

Drawing a sprite

In the Game.h file, add the following variables to the bottom of the Game class's private declarations:

std::unique_ptr<DirectX::SpriteBatch> m_spriteBatch;
DirectX::SimpleMath::Vector2 m_screenPos;
DirectX::SimpleMath::Vector2 m_origin;

In Game.cpp, modify TODO of CreateDevice to be:

m_spriteBatch.reset(new SpriteBatch(m_d3dContext.Get()));

ComPtr<ID3D11Resource> resource;
DX::ThrowIfFailed(
    CreateWICTextureFromFile(m_d3dDevice.Get(), L"cat.png", resource.GetAddressOf(),
    m_texture.ReleaseAndGetAddressOf()));

ComPtr<ID3D11Texture2D> cat;
DX::ThrowIfFailed(resource.As(&cat));

CD3D11_TEXTURE2D_DESC catDesc;
cat->GetDesc(&catDesc);

m_origin.x = float(catDesc.Width / 2);
m_origin.y = float(catDesc.Height / 2);

In Game.cpp, add to the TODO of CreateResources:

m_screenPos.x = backBufferWidth / 2.f;
m_screenPos.y = backBufferHeight / 2.f;

In Game.cpp, add to the TODO of OnDeviceLost:

m_spriteBatch.reset();

In Game.cpp, add to the TODO of Render:

m_spriteBatch->Begin();

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, nullptr, Colors::White, 0.f, m_origin);

m_spriteBatch->End();

Build and run, and you should get the following screen:

screenshotSpriteCat.png

Alpha mode

One thing you should notice is that the edges of the cat look strange with a bit of white outline. The problem here is that the cat.png file's alpha channel is straight alpha (i.e. the pixels are of the form (R,G,B,A)). The default behavior of SpriteBatch, however, is to assume you are using premultiplied alpha (i.e. the pixels are of the form (R*A, G*A, B*A, A)). There are many reasons why using premultiplied alpha is superior, but for now we can fix this mismatch by changing our use of SpriteBatch to use straight alpha blending instead by supplying our own ID3D11BlendState object. We'll make use of the CommonStates factory to provide one of the built-in blend state objects.

In the Game.h file, add the following variable to the bottom of the Game class's private declarations:

std::unique_ptr<DirectX::CommonStates> m_states;

In Game.cpp, add to the TODO of CreateDevice:

m_states.reset(new CommonStates(m_d3dDevice.Get()));

In Game.cpp, add to the TODO of OnDeviceLost:

m_states.reset();

In Game.cpp, modify the TODO of Render:

m_spriteBatch->Begin( SpriteSortMode_Deferred, m_states->NonPremultiplied() );

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, nullptr, Colors::White, 0.f, m_origin);

m_spriteBatch->End();

Build and run again, and you'll get a nice clean cat:

screenshotSpriteCat2.png

Using DDS files for textures

Rather than use a PNG and the Windows Imaging Component (WIC) to load the texture, a more efficient thing for us to do is to make use of a DDS file instead. A DDS file is a container for all kinds of Direct3D resources including 1D and 2D textures, cubemaps, volume maps, arrays of 1D or 2D textures or cubemaps each optionally with mipmaps. It can contain a wide-array of pixel formats and hardware-supported 'block-compression' schemes to save on video memory usage at runtime.

Visual Studio has a built-in system for converting images to DDS as part of the build process, which you can read about here - http://msdn.microsoft.com/en-us/library/hh972446.aspx.

For this tutorial, we will instead make of use of the DirectXTex (http://go.microsoft.com/fwlink/?LinkId=248926) texconv command-line tool.
  1. Download the "Texconv.zip" from the DirectXTex CodePlex site and extract the EXE into your project's folder.
  2. Open a command-prompt and then change to your project's folder. http://windows.microsoft.com/en-us/windows/command-prompt-faq
  3. Run the following command-line
texconv cat.png -pmalpha -m 1 -f BC3_UNORM

Then from the top menu in Visual Studio select Project / Add Existing Item.... Select cat.dds and click "OK".

Now will return to Game.cpp in the CreateDevice and change our use of CreateWICTextureFromFile to CreateDDSTextureFromFile:

DX::ThrowIfFailed(
    CreateDDSTextureFromFile(m_d3dDevice.Get(), L"cat.dds", resource.GetAddressOf(),
    m_texture.ReleaseAndGetAddressOf()));

Note that since we used the option "-pmalpha", we should also make sure we change back to the default Begin in our Render because our "cat.dds" has premultiplied alpha in it.

In Game.cpp, modify the TODO of Render:

m_spriteBatch->Begin();

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, nullptr, Colors::White, 0.f, m_origin);

m_spriteBatch->End();

Build and run we are rendering our 'clean' cat with premultiplied alpha:

screenshotSpriteCat2.png

Technical notes

  • The switch "-pmalpha" causes the texconv command-line tool to convert the image to premultiplied alpha before saving the .dds file. This assumes the source image is in straight-alpha.
  • The switch "-m 1" disables the generation of mipmaps for the image. By default, the tool generates a full set of mipmaps when converting to a .dds, but since our source image is not a power of two in width & height, it also generates a warning message about use with feature level 9.x devices. For standard sprites, we typically do not make use of mipmaps.
  • The switch "-f BC3_UNORM" selects the DXGI_FORMAT_BC3_UNORM format for the resulting .dds file. In combination with the "-pmalpha" switch, this results in the "DXT4" block-compression format being used. http://blogs.msdn.com/b/chuckw/archive/2012/05/04/direct3d-11-textures-and-block-compression.aspx
Windows phone: Note that on Windows phone 8.0, you can't use CreateWICTextureFromFile as WIC is not available on that platform and you have to use CreateDDSTextureFromFile to load a .dds instead. Both are available for Windows phone 8.1.

Rotating a sprite

Now that we have our cat rendering, we can start to animate it. Here's a simple rotation where we are using the cosf function to give us a time-varying value from -1 to 1.

In Game.cpp, modify the TODO of Render:

float time = float(m_timer.GetTotalSeconds());

m_spriteBatch->Begin();

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, nullptr, Colors::White,
    cosf(time) * 4.f, m_origin);

m_spriteBatch->End();

Build and run to see the cat spinning.

Scaling a sprite

We can scale a sprite's size as well. Again, we are using cosf to give us a time-varying value between -1 and 1.

In Game.cpp, modify the TODO of Render:

float time = float(m_timer.GetTotalSeconds());

m_spriteBatch->Begin();

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, nullptr, Colors::White, 0.f, m_origin,
    cosf(time) + 2.f);

m_spriteBatch->End();

Build and run to see the cat growing and shrinking.

Tinting a sprite

We can modify the color of the sprite with a tint as well:

In Game.cpp, modify the TODO of Render:

m_spriteBatch->Begin();

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, nullptr, Colors::Green, 0.f, m_origin);

m_spriteBatch->End();

Build and run to see a green-tinged cat.

Tiling a sprite

With the optional source-rectangle parameter, we can tile a sprite.

In the Game.h file, add the following variable to the bottom of the Game class's private declarations:

RECT m_tileRect;

In the Game.cpp file, modify in the TODO section of CreateDevice:

change

m_origin.x = float(catDesc.Width / 2);
m_origin.y = float(catDesc.Height / 2);

to

m_origin.x = float(catDesc.Width * 2);
m_origin.y = float(catDesc.Height * 2);

m_tileRect.left = catDesc.Width * 2;
m_tileRect.right = catDesc.Width * 6;
m_tileRect.top = catDesc.Height * 2;
m_tileRect.bottom = catDesc.Height * 6;

In the Game.cpp file, modify in the TODO section of Render:

m_spriteBatch->Begin(SpriteSortMode_Deferred, nullptr, m_states->LinearWrap());

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, &m_tileRect, Colors::White,
    0.f, m_origin);

m_spriteBatch->End();

Build and run to see the sprite as an array of 4x4 cats.

screenshotSpriteCat3.png

Drawing a background image

Our last exercise for this lesson is rendering a sprite as a full background image. Start by saving sunset.jpg to your project directory, and then from the top menu select Project / Add Existing Item.... Select "sunset.jpg" and click "OK".

In the Game.h file, add the following variables to the bottom of the Game class's private declarations:

RECT m_fullscreenRect;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_background;

In Game.cpp, add to the TODO of CreateDevice:

DX::ThrowIfFailed(
    CreateWICTextureFromFile(m_d3dDevice.Get(), L"sunset.jpg", nullptr,
    m_background.ReleaseAndGetAddressOf()));

In Game.cpp, add to the TODO of CreateResources:

m_fullscreenRect.left = 0;
m_fullscreenRect.top = 0;
m_fullscreenRect.right = backBufferWidth;
m_fullscreenRect.bottom = backBufferHeight;

and then modify the m_origin initialization back to:

m_origin.x = float(catDesc.Width / 2);
m_origin.y = float(catDesc.Height / 2);

In Game.cpp, add to the TODO of OnDeviceLost:

m_background.Reset();

In Game.cpp, modify the TODO section of Render to be:

m_spriteBatch->Begin();

m_spriteBatch->Draw(m_background.Get(), m_fullscreenRect);

m_spriteBatch->Draw(m_texture.Get(), m_screenPos, nullptr, Colors::White, 0.f, m_origin);

m_spriteBatch->End();

Build and run to see our cat drawing over a sunset background.

screenshotSpriteCat4.png

Next lesson: More tricks with sprites

Further reading

DirectX Tool Kit docs CommonStates, DDSTextureLoader, SpriteBatch, WICTextureLoader

http://blogs.msdn.com/b/chuckw/archive/2012/05/04/direct3d-11-textures-and-block-compression.aspx

http://blogs.msdn.com/b/shawnhar/archive/2009/11/06/premultiplied-alpha.aspx
http://home.comcast.net/~tom_forsyth/blog.wiki.html#%5B%5BPremultiplied%20alpha%5D%5D

Last edited Jul 28, 2015 at 6:52 PM by walbourn, version 43