Occlusion culling
Direct3D 11 occlusion culling
Of course I don't know how to implement it like a PRO.
But here what I did.
As I understood, for occlusion culling, need to draw simple low poly mesh - occluder.
For each draw need to create special D3D11 object : ID3D11Predicate
With this object it is possible to get information about occluder (it visible or not).
Occlusion culling will be done automatically, but perhaps user want to know if occluder
is visible or not.
New class
class slGPUOcclusionObject
{
public:
slGPUOcclusionObject() {}
virtual ~slGPUOcclusionObject() {}
bool m_visible = true;
};
And D3D11 implementation
class slGSD3D11OcclusionObject : public slGPUOcclusionObject
{
public:
slGSD3D11OcclusionObject();
virtual ~slGSD3D11OcclusionObject();
ID3D11Predicate* m_predicate = 0;
};
...
slGSD3D11OcclusionObject::slGSD3D11OcclusionObject() {}
slGSD3D11OcclusionObject::~slGSD3D11OcclusionObject()
{
SAFE_RELEASE(m_predicate);
}
Create this object
slGPUOcclusionObject* slGSD3D11::SummonOcclusionObject()
{
slGSD3D11OcclusionObject* newO = slCreate<slGSD3D11OcclusionObject>();
D3D11_QUERY_DESC qdesc;
qdesc.MiscFlags = 0;// do not use D3D11_QUERY_MISC_PREDICATEHINT;
qdesc.Query = D3D11_QUERY_OCCLUSION_PREDICATE;
if (m_d3d11Device->CreatePredicate(&qdesc, &newO->m_predicate) != S_OK)
{
slLog::PrintError("Can't create Direct3D 11 Predicate\n");
SLSAFE_DESTROY2(newO);
}
return newO;
}
Now I need to add this. Two methods, preparation for drawing an occluder
and return default values.
void slGSD3D11::OcclusionBegin()
{
SetShader(slShaderType::Occlusion,0);
m_d3d11DevCon->RSSetState(m_RasterizerSolidNoBackFaceCulling);
m_d3d11DevCon->OMSetDepthStencilState(this->m_depthStencilStateForOcclusion, 0);
float blendFactor[4];
blendFactor[0] = 0.0f;
blendFactor[1] = 0.0f;
blendFactor[2] = 0.0f;
blendFactor[3] = 0.0f;
m_d3d11DevCon->OMSetBlendState(m_blendStateForOcclusion, blendFactor, 0xffffffff);
}
void slGSD3D11::OcclusionEnd()
{
UseDepth(true);
UseBlend(true);
}
Shader is most simpliest shader. Next, I need to disable backface culling,
and set special depth stencil state and blend state
...
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
depthStencilDesc.StencilEnable = true;
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
if (FAILED(m_d3d11Device->CreateDepthStencilState(&depthStencilDesc, &this->m_depthStencilStateForOcclusion)))
{
slLog::PrintError("Can't create Direct3D 11 depth stencil state\n");
return false;
}
....
bd.RenderTarget[0].BlendEnable = FALSE;
bd.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
bd.RenderTarget[0].DestBlend = D3D11_BLEND_ONE;
bd.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
bd.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO;
bd.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
bd.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
bd.RenderTarget[0].RenderTargetWriteMask = 0;
if (FAILED(m_d3d11Device->CreateBlendState(&bd, &m_blendStateForOcclusion)))
{
slLog::PrintError("Can't create Direct3D 11 blend state\n");
return false;
}
Draw occluder. I need to set mesh `m_currMesh` using SetMesh before calling this method.
Here, I need to set all things for drawing, and draw model between `m_d3d11DevCon->Begin` and
`m_d3d11DevCon->End`
void slGSD3D11::DrawOccluder(slGPUOcclusionObject* o)
{
SL_ASSERT_ST(m_currMesh);
SL_ASSERT_ST(o);
SL_ASSERT_ST(slFramework::GetMatrix(slMatrixType::WorldViewProjection));
slGSD3D11OcclusionObject* ocl = dynamic_cast<slGSD3D11OcclusionObject*>(o);
m_shaderOcclusion->SetData( *slFramework::GetMatrix(slMatrixType::WorldViewProjection));
m_shaderOcclusion->SetConstants(0);
uint32_t offset = 0u;
m_d3d11DevCon->IASetVertexBuffers(0, 1, &m_currMesh->m_vBuffer, &m_currMesh->m_meshInfo.m_stride, &offset);
//m_currMesh->m_predicate->Begin();
m_d3d11DevCon->Begin(ocl->m_predicate);
switch (m_currMesh->m_meshInfo.m_vertexType)
{
default:
case slMeshVertexType::Triangle:
m_d3d11DevCon->IASetIndexBuffer(m_currMesh->m_iBuffer, m_currMesh->m_indexType, 0);
m_d3d11DevCon->DrawIndexed(m_currMesh->m_meshInfo.m_iCount, 0, 0);
break;
}
m_d3d11DevCon->End(ocl->m_predicate);
}
Final step, draw object. I modified Draw method (added parameter slGPUOcclusionObject* oo)
void slGSD3D11::Draw(slGPUOcclusionObject* oo)
{
...
In this method, I need to put drawing between m_d3d11DevCon->SetPredication
...
uint32_t offset = 0u;
m_d3d11DevCon->IASetVertexBuffers(0, 1, &m_currMesh->m_vBuffer, &m_currMesh->m_meshInfo.m_stride, &offset);
if (ocl)
{
m_d3d11DevCon->SetPredication(ocl->m_predicate, FALSE);
BOOL pixelsVisible = 0;
while (m_d3d11DevCon->GetData(ocl->m_predicate, (void*)&pixelsVisible, sizeof(BOOL), 0) == S_FALSE);
ocl->m_visible = (bool)pixelsVisible;
}
switch (m_currMesh->m_meshInfo.m_vertexType)
{
default:
case slMeshVertexType::Triangle:
m_d3d11DevCon->IASetIndexBuffer(m_currMesh->m_iBuffer, m_currMesh->m_indexType, 0);
m_d3d11DevCon->DrawIndexed(m_currMesh->m_meshInfo.m_iCount, 0, 0);
break;
}
if (ocl)
m_d3d11DevCon->SetPredication(NULL, FALSE);
With this imlementation I can't use `m_visible`, I can only read it.
I think, best way to implement occlusion culling is to draw only occluders
into render target texture. With this, it possible to use `m_visible` for drawing
heavy mesh.
In future I will add demo example using RTT.
Demo app
Initialize objects
m_GPUMesh = m_app->CreateMeshSphere(30, 0.5, true);
m_GPUMeshOccluder = m_app->CreateMeshBox(m_GPUMesh->m_meshInfo.m_aabb);
slAabb wallAabb;
wallAabb.m_min.set(-5.f, -5.f, 10.f, 0.f);
wallAabb.m_max.set(0.f, 5.f, 10.2f, 0.f);
m_GPUMeshWall = m_app->CreateMeshBox(wallAabb);
real_t x = -5.0, z = -5.0, y = -5.0;
for (int i = 0; i < 1000; ++i)
{
m_objects[i] = slCreate<SceneObject_ExampleSceneOcclusionCulling>();
auto & P = m_objects[i]->GetPosition();
P.x = x;
P.y = y;
P.z = z;
m_objects[i]->m_occlusionObject = m_gs->SummonOcclusionObject();
m_objects[i]->RecalculateWorldMatrix();
m_objects[i]->GetAabb() = m_GPUMesh->m_meshInfo.m_aabb;
m_objects[i]->UpdateBV();
x += 1.1f;
if (x > 6.f)
{
x = -5.f;
z += 1.1f;
if (z > 6.f)
{
y += 1.1f;
z = -5.0;
}
}
}
Drawing
m_gs->BeginDraw();
m_gs->OcclusionBegin();
m_gs->SetMesh(m_GPUMeshOccluder);
m_objectsVisible.clear();
for (size_t i = 0; i < m_objectsInFrustum.m_size; ++i)
{
slFramework::SetMatrix(slMatrixType::WorldViewProjection, &m_objectsInFrustum.m_data[i]->WVP);
m_gs->DrawOccluder(m_objectsInFrustum.m_data[i]->m_occlusionObject);
if (m_objectsInFrustum.m_data[i]->m_occlusionObject->m_visible)
{
m_objectsVisible.push_back(m_objectsInFrustum.m_data[i]);
}
}
m_gs->OcclusionEnd();
m_gs->ClearAll();
slMaterial material;
material.m_sunPosition.set(1.f, 1.f, 0.5f);
material.m_sunPosition.normalize();
material.m_cullBackFace = true;
material.m_colorAmbient = ColorWhite;
material.m_maps[0].m_texture = m_texture;
m_gs->SetMaterial(&material);
m_gs->SetShader(slShaderType::Solid, 0);
slMat4 WVP = m_camera->m_projectionMatrix * m_camera->m_viewMatrix * slMat4();
slFramework::SetMatrix(slMatrixType::WorldViewProjection, &WVP);
slMat4 w;
slFramework::SetMatrix(slMatrixType::World, &w);
m_gs->SetMesh(m_GPUMeshWall);
m_gs->Draw(0);
for(size_t i = 0; i < m_objectsInFrustum.m_size; ++i)
{
auto& W = m_objectsInFrustum.m_data[i]->GetMatrixWorld();
slFramework::SetMatrix(slMatrixType::World, &W);
slFramework::SetMatrix(slMatrixType::WorldViewProjection, &m_objectsInFrustum.m_data[i]->WVP);
m_gs->SetMesh(m_GPUMesh);
m_gs->Draw(m_objectsInFrustum.m_data[i]->m_occlusionObject);
}
Download
Please enable JavaScript to view the comments powered by Disqus.