0
votes

I've been working on a 3D renderer for a game, and until now it rendered all the textureless meshes first and all the textured meshes afterwards, using DrawIndexed. In an effort to improve performance, I've switched to DrawIndexedInstanced and made it so that textured meshes are rendered first, and this has revealed an issue with how my alpha blending and/or depth checking is set up. The following images should illustrate what the problem is:

View through the top of the front-most textures (textured meshes rendered first)

The same view, slightly different angle (textureless meshes rendered first)

In the foreground and the background are rows of textured rectangle meshes, and the ones in the foreground have partly transparent meshes. In the middle row are untextured meshes with their transparency set to 0.3f. When textured meshes are rendered first, the untextured ones are obscured by the transparent meshes in the foreground. However, when it's the untextured meshes that are rendered first, they obscure the textured meshes behind them completely, even when their transparency is at 0.3f. This does not happen when untextured meshes obscure other untextured meshes, alpha blending works correctly in that scenario.

This is where I set up the rasterizer state, depth stencil state and depth stencil view:

ID3D11Texture2D *pBackBuffer;
D3D11_TEXTURE2D_DESC backBufferDesc;
m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
pBackBuffer->GetDesc(&backBufferDesc);
RELEASE_RESOURCE(pBackBuffer);

// creating a buffer for the depth stencil
D3D11_TEXTURE2D_DESC depthStencilBufferDesc;
ZeroMemory(&depthStencilBufferDesc, sizeof(D3D11_TEXTURE2D_DESC));

depthStencilBufferDesc.ArraySize = 1;
depthStencilBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilBufferDesc.CPUAccessFlags = 0; // No CPU access required.
depthStencilBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilBufferDesc.Width = backBufferDesc.Width;
depthStencilBufferDesc.Height = backBufferDesc.Height;
depthStencilBufferDesc.MipLevels = 1;
depthStencilBufferDesc.SampleDesc.Count = 4;
depthStencilBufferDesc.SampleDesc.Quality = 0;
depthStencilBufferDesc.Usage = D3D11_USAGE_DEFAULT;
m_device->CreateTexture2D(&depthStencilBufferDesc, NULL, &m_depthStencilBuffer);

// creating a depth stencil view
HRESULT hr = m_device->CreateDepthStencilView(  m_depthStencilBuffer,
                                                NULL,   
                                                &m_depthStencilView);


// setup depth stencil state.
D3D11_DEPTH_STENCIL_DESC depthStencilStateDesc;
ZeroMemory(&depthStencilStateDesc, sizeof(D3D11_DEPTH_STENCIL_DESC));

depthStencilStateDesc.DepthEnable = TRUE;
depthStencilStateDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilStateDesc.DepthFunc = D3D11_COMPARISON_LESS;
depthStencilStateDesc.StencilEnable = FALSE;

hr = m_device->CreateDepthStencilState(&depthStencilStateDesc, &m_depthStencilState);

// setup rasterizer state.
D3D11_RASTERIZER_DESC rasterizerDesc;
ZeroMemory(&rasterizerDesc, sizeof(D3D11_RASTERIZER_DESC));

rasterizerDesc.AntialiasedLineEnable = FALSE;
rasterizerDesc.CullMode = D3D11_CULL_BACK;
rasterizerDesc.DepthBias = 0;
rasterizerDesc.DepthBiasClamp = 0.0f;
rasterizerDesc.DepthClipEnable = TRUE;
rasterizerDesc.FillMode = D3D11_FILL_SOLID;
rasterizerDesc.FrontCounterClockwise = FALSE;
rasterizerDesc.MultisampleEnable = FALSE;
rasterizerDesc.ScissorEnable = FALSE;
rasterizerDesc.SlopeScaledDepthBias = 0.0f;

// create the rasterizer state
hr = m_device->CreateRasterizerState(&rasterizerDesc, &m_RasterizerState);

m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);
m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);
m_deviceContext->RSSetState(m_RasterizerState);

And this is where I enable alpha blending:

D3D11_BLEND_DESC blendDescription;
ZeroMemory(&blendDescription, sizeof(D3D11_BLEND_DESC));
blendDescription.RenderTarget[0].BlendEnable = TRUE;
blendDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
blendDescription.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
blendDescription.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
blendDescription.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
blendDescription.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
blendDescription.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
blendDescription.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
m_device->CreateBlendState(&blendDescription, &m_blendState);
m_deviceContext->OMSetBlendState(m_blendState, 0, 0xffffffff);

I know that giving the textureless mesh a plain white fully opaque texture would fix the problem in a way, but I suspect the depth testing is at fault.

When I create the device with the D3D11_CREATE_DEVICE_DEBUG flag, it doesn't give me any errors or warnings.

All the HRESULTs returned by the Create functions are S_OK.

Thanks in advance.

1

1 Answers

2
votes

For blending to work, you must render all fully opaque objects first and then all objects with transparency in a back to front order. This means that your transparent objects are sorted based on the distance from the camera with farther objects first.

Ideally your opaque objects are sorted in the opposite direction (front to back) so that pixels that are obscured are discarded by the depth test.

This is typically done by placing all draw requests into a queue. Once everything in the scene is in the queue, you can sort it based on various factors including transparency, distance, material, etc. Then you can loop through the queue and make all of your draw requests in the proper order.

For simple cases though, just make sure your opaque objects are drawn first and your transparent objects are drawn next in a general back to front order.