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