c++directxspritedirect3ddirectx-10

Nearest Neighbour interpolation for Direct3D sprites?


I'm working on a 2D game using Direct3D 10's ID3DX10Sprite interface. It works pretty well except for that the textures are filtered using a linear algorithm (I think?), which makes them look pretty ugly when you scale them.

Original texture (32 x 32):

Original 32 x 32 texture

What it looks like scaled up in-game:

What it currenty looks like

What I want it to look like:

What I want it to look like


So my question is: Is there a way to use Nearest Neighbour filtering (aka Point filtering) for the sprites, and how do you do that?

This is my code:

Initialization:

float width = 818.0F;
float height = 646.0F;

IDXGISwapChain*             swapChain;
ID3D10Device*               device      = Direct3D_CreateDevice(hWnd, swapChain, (int)width, (int)height);
ID3D10RenderTargetView*     rtv         = Direct3D_CreateRenderTargetView(device, swapChain);
ID3DX10Sprite*              mainSprite  = Direct3D_CreateMainSpriteObject(device);
ID3D10ShaderResourceView*   texture     = Direct3D_CreateTexture(device, "C:\\Users\\Vincent\\Documents\\visual studio 2010\\Projects\\DirectX Test C++\\Debug\\base_grass.png", 32, 32);
D3DX10_SPRITE*              sprite      = Direct3D_CreateSprite(texture, 0.0F, 0.0F, 1.0F, 1.0F); //800.0F / 64.0F, 600.0F / 64.0F);

Direct3D_CreateViewport(device, 0, 0, (UINT)width, (UINT)height);

Rendering:

FLOAT* backColor = new FLOAT[4];
backColor[0] = 0.0F;
backColor[1] = 0.5F;
backColor[2] = 0.0F;
backColor[3] = 1.0F;

device->ClearRenderTargetView(rtv, backColor);
device->Draw(3, 0);

Direct3D_DrawSpritesBuffered(mainSprite, sprite, 1);
swapChain->Present(0, 0);

Direct3D functions:

/////////////////////////////////////////////////
//            Direct3D_CreateDevice            //
/////////////////////////////////////////////////
ID3D10Device * __stdcall Direct3D_CreateDevice(HWND hWnd, IDXGISwapChain* &swapChain, int width, int height)
{
    //Variables.
    ID3D10Device* D3DDevice;
    DXGI_SWAP_CHAIN_DESC swapChainDescription;

    ZeroMemory(&swapChainDescription, sizeof(DXGI_SWAP_CHAIN_DESC));

    //Buffer settings.
    swapChainDescription.BufferCount                            = 1;
    swapChainDescription.BufferDesc.Format                      = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapChainDescription.BufferDesc.Width                       = width;
    swapChainDescription.BufferDesc.Height                      = height;
    swapChainDescription.BufferDesc.RefreshRate.Numerator       = 60;
    swapChainDescription.BufferDesc.RefreshRate.Denominator     = 1;
    swapChainDescription.BufferUsage                            = DXGI_USAGE_RENDER_TARGET_OUTPUT;

    //Misc.
    swapChainDescription.Flags                                  = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    swapChainDescription.OutputWindow                           = hWnd;
    swapChainDescription.SampleDesc.Count                       = 1;
    swapChainDescription.SampleDesc.Quality                     = 0;
    swapChainDescription.SwapEffect                             = DXGI_SWAP_EFFECT_DISCARD;
    swapChainDescription.Windowed                               = TRUE;

    //Try to create the device and SwapChain.
    if (FAILED(D3D10CreateDeviceAndSwapChain(NULL,
                                             D3D10_DRIVER_TYPE_HARDWARE,
                                             NULL,
                                             D3D10_CREATE_DEVICE_DEBUG,
                                             D3D10_SDK_VERSION,
                                             &swapChainDescription,
                                             &swapChain,
                                             &D3DDevice))) return NULL;
    return D3DDevice;
}

/////////////////////////////////////////////////
//       Direct3D_CreateRenderTargetView       //
/////////////////////////////////////////////////
ID3D10RenderTargetView * __stdcall Direct3D_CreateRenderTargetView(ID3D10Device* device, IDXGISwapChain* swapChain)
{
    //Variables.
    HRESULT hRes = 0;
    ID3D10Texture2D* backBuffer;
    ID3D10RenderTargetView* renderTargetView;

    //Get the back buffer.
    hRes = swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&backBuffer);
    if(FAILED(hRes)) { return NULL; }

    //Try to create the RenderTargetView.
    hRes = device->CreateRenderTargetView(backBuffer, NULL, &renderTargetView);
    if(FAILED(hRes)) { return NULL; }

    //Release the back buffer
    backBuffer->Release();

    //Set the render target
    device->OMSetRenderTargets(1, &renderTargetView, NULL);

    return renderTargetView;
}

/////////////////////////////////////////////////
//           Direct3D_CreateViewport           //
/////////////////////////////////////////////////
void __stdcall Direct3D_CreateViewport(ID3D10Device* device, int x, int y, UINT width, UINT height)
{
    D3D10_VIEWPORT* viewport = new D3D10_VIEWPORT();

    viewport->TopLeftX = x;
    viewport->TopLeftY = y;
    viewport->Width = width;
    viewport->Height = height;
    viewport->MinDepth = 0.0F;
    viewport->MaxDepth = 1.0F;

    device->RSSetViewports(1, viewport);
}

/////////////////////////////////////////////////
//       Direct3D_CreateMainSpriteObject       //
/////////////////////////////////////////////////
ID3DX10Sprite * __stdcall Direct3D_CreateMainSpriteObject(ID3D10Device* device)
{
    //Create the sprite object.
    ID3DX10Sprite* s;
    HRESULT hRes = D3DX10CreateSprite(device, 4096, &s);

    if(FAILED(hRes)) { return NULL; }

    //Construct the Projection- and ViewTransform matrix.
    D3DXMATRIX matview;
    matview._12 = 0.0F;
    matview._13 = 0.0F;
    matview._14 = 0.0F;
    matview._21 = 0.0F;
    matview._23 = 0.0F;
    matview._24 = 0.0F;
    matview._31 = 0.0F;
    matview._32 = 0.0F;
    matview._34 = 0.0F;
    matview._41 = 0.0F;
    matview._42 = 0.0F;
    matview._43 = 0.0F;

    matview._11 = 1.0F;
    matview._22 = 1.0F;
    matview._33 = 1.0F;
    matview._44 = 1.0F;

    //Set the Projection- and ViewTransforms.
    s->SetProjectionTransform(&matview);
    s->SetViewTransform(&matview);

    return s;
}

/////////////////////////////////////////////////
//        Direct3D_DrawSpritesBuffered         //
/////////////////////////////////////////////////
void __stdcall Direct3D_DrawSpritesBuffered(ID3DX10Sprite* spriteObject, D3DX10_SPRITE* sprites, int count)
{
    spriteObject->Begin(0);
    spriteObject->DrawSpritesBuffered(sprites, count);
    spriteObject->Flush();
    spriteObject->End();
}

/////////////////////////////////////////////////
//           Direct3D_CreateTexture            //
/////////////////////////////////////////////////
ID3D10ShaderResourceView * __stdcall Direct3D_CreateTexture(ID3D10Device* device, LPCSTR file, int width, int height)
{
    //Variables.
    D3DX10_IMAGE_LOAD_INFO imgLoadInfo;
    ID3D10ShaderResourceView * shaderResourceView;

    ZeroMemory(&imgLoadInfo, sizeof(imgLoadInfo));

    //Image load settings.
    imgLoadInfo.BindFlags = D3D10_BIND_SHADER_RESOURCE;
    imgLoadInfo.CpuAccessFlags = 0;
    imgLoadInfo.Filter = D3DX10_FILTER_NONE;
    imgLoadInfo.FirstMipLevel = 0;
    imgLoadInfo.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    imgLoadInfo.MipFilter = D3DX10_FILTER_NONE;
    imgLoadInfo.MipLevels = 1;
    imgLoadInfo.MiscFlags = 0;
    imgLoadInfo.Usage = D3D10_USAGE_DEFAULT;

    //Get the source image's info.
    imgLoadInfo.pSrcInfo = new D3DX10_IMAGE_INFO();
    D3DX10GetImageInfoFromFileA(file, NULL, imgLoadInfo.pSrcInfo, NULL);

    //Set the texture dimensions.
    imgLoadInfo.Width = width;
    imgLoadInfo.Height = height;

    HRESULT hRes;

    //Attempt to create the ShaderResourceView.
    if(FAILED(D3DX10CreateShaderResourceViewFromFile(device, file, &imgLoadInfo, NULL, &shaderResourceView, &hRes)))
    {
        return NULL;
    }

    return shaderResourceView;
}

/////////////////////////////////////////////////
//            Direct3D_CreateSprite            //
/////////////////////////////////////////////////
D3DX10_SPRITE * __stdcall Direct3D_CreateSprite(ID3D10ShaderResourceView* texture, float textureX, float textureY, float textureWidth, float textureHeight)
{
    //Variables.
    D3DX10_SPRITE* sprite = new D3DX10_SPRITE();

    //Color settings.
    sprite->ColorModulate.r = 1.0f;
    sprite->ColorModulate.g = 1.0f;
    sprite->ColorModulate.b = 1.0f;
    sprite->ColorModulate.a = 1.0f;

    //Texture settings.
    sprite->pTexture = texture;
    sprite->TextureIndex = 0;

    sprite->TexCoord.x = textureX;
    sprite->TexCoord.y = textureY;
    sprite->TexSize.x = textureWidth;
    sprite->TexSize.y = textureHeight;

    //Dimension and location matrix.
    sprite->matWorld._12 = 0.0F;
    sprite->matWorld._13 = 0.0F;
    sprite->matWorld._14 = 0.0F;
    sprite->matWorld._21 = 0.0F;
    sprite->matWorld._23 = 0.0F;
    sprite->matWorld._24 = 0.0F;
    sprite->matWorld._31 = 0.0F;
    sprite->matWorld._32 = 0.0F;
    sprite->matWorld._34 = 0.0F;
    sprite->matWorld._41 = 0.0F;
    sprite->matWorld._42 = 0.0F;
    sprite->matWorld._43 = 0.0F;

    sprite->matWorld._11 = 1.0F;
    sprite->matWorld._22 = 1.0F;
    sprite->matWorld._33 = 1.0F;
    sprite->matWorld._44 = 1.0F;

    return sprite;
}

Solution

  • Legacy D3DX10_SPRITE only supports using a single sampler:

    D3D10_SAMPLER_DESC splDesc;
    ZeroMemory(&splDesc, sizeof(D3D10_SAMPLER_DESC));
    splDesc.Filter = D3D10_FILTER_MIN_MAG_MIP_LINEAR;
    splDesc.AddressU = D3D10_TEXTURE_ADDRESS_CLAMP;
    splDesc.AddressV = D3D10_TEXTURE_ADDRESS_CLAMP;
    splDesc.AddressW = D3D10_TEXTURE_ADDRESS_CLAMP;
    splDesc.ComparisonFunc = D3D10_COMPARISON_NEVER;
    splDesc.MaxLOD = FLT_MAX;
    VH( m_pDevice->CreateSamplerState(&splDesc, &m_pSampler) );
    

    It also does not provide any overloading/custom state mechanism.

    SpriteBatch in the DirectX Tool Kit for DirectX 11 does provide the ability to set which sampler state to use and provides hooks for custom state call-backs:

    void Begin(SpriteSortMode sortMode = SpriteSortMode_Deferred,
        ID3D11BlendState* blendState = nullptr,
        ID3D11SamplerState* samplerState = nullptr,
        ID3D11DepthStencilState* depthStencilState = nullptr,
        ID3D11RasterizerState* rasterizerState = nullptr,
        std::function<void __cdecl()> setCustomShaders = nullptr,
        XMMATRIX transformMatrix = MatrixIdentity);
    

    The most sensible solution is to port from Direct3D 10 to Direct3D 11 and stop using legacy Direct3D 10.

    If there's some particularly compelling reason why you have to stay on Direct3D 10, then you can take a look at SpriteBatch.h / SpriteBatch.cpp which you could copy out and back-port to Direct3D 10.

    See MSDN, Where is the DirectX SDK (2015 Edition?), and Living without D3DX