Occlusion Query's

Introducción

El Occlusion Culling se basa principalmente en determinar si una malla se ve o no en pantalla mediante la cantidad de pixeles que se han renderizado de ésta. Por lo tanto, será necesario indicarle a la tarjeta grafica que queremos que cuente los pixeles que esta renderizando en ese momento.

Para eso necesitaremos dos mallas como mínimo para un mismo objeto, una malla muy simple y representativa de la forma del objeto y la malla original que nosotros renderizaremos en nuestra aplicación.

Proceso

Una vez tengamos las dos mallas, será necesario hacer primero dos pasadas iniciales para determinar que objetos se están viendo, posteriormente y una vez sepamos qué objetos se ven, entonces renderizaremos esos objetos con nuestro render normalmente.

A continuación podemos observar paso a paso que debemos hacer.

  1. Limpiamos los buffers iniciales, tanto el backbuffer como el z-buffer.
  2. Renderizamos todas las mallas de la escena para que estas actualicen el z-buffer
  3. Activamos la Occlusion Query en la tarjeta grafica
  4. Renderizamos otra vez las mallas de la escena y obtenemos el numero de pixeles que se han escrito en el backbuffer.
  5. Por cada objeto que se hayan renderizado por lo menos 1 pixel, lo metemos en una lista de objetos a renderizar y se la pasamos al render principal.

Como podemos observar, es necesario hacer dos pasadas, con una no funciona ya que no tenemos información del z-buffer y por lo tanto, aproximadamente todos los objetos escribirían en el backbuffer.

Código

A continuación muestro el código empleado por el iL-engine para realizar las Occlusion Query’s.

//Hacemos la Occlusion Query para ver que objetos se ven desde la camara o no
    m_RTOcclusion.BeginScene();
        // Limpiamos el RenderTarget
        V( m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 0, 0, 0), 1.0f, 0) );
        V( m_EffectMgr.GetEffect(IL_EFFECT_OCCLUSION)->SetMatrix("world_view_proj", &inMatrix) );
 
        unsigned int cPasses;
        m_EffectMgr.GetEffect(IL_EFFECT_OCCLUSION)->Begin( &cPasses, 0 );
        for( unsigned int p = 0; p < cPasses; ++p )
        {
            m_EffectMgr.GetEffect(IL_EFFECT_OCCLUSION)->BeginPass( p );
 
            m_pD3DDevice->SetFVF( D3DFVF_SIMPLEVERTEX );
 
            m_EffectMgr.GetEffect(IL_EFFECT_OCCLUSION)->CommitChanges();
 
            // Renderizamos todos los objetos de pantalla
            for(unsigned int j = 0; j < (unsigned int)m_vMeshToOclQuery.size(); j++)
            {
                m_pD3DDevice->DrawIndexedPrimitiveUP(    D3DPT_TRIANGLELIST, 
                                                        0, 
                                                        m_PVertexArrayMgr.GetVertexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicVBId()).GetNumVertex(),
                                                        m_IndexArrayMgr.GetIndexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicIBId()).GetNumIndex(),
                                                        m_IndexArrayMgr.GetIndexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicIBId()).GetIndexPtr(), 
                                                        D3DFMT_INDEX16, 
                                                        m_PVertexArrayMgr.GetVertexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicVBId()).GetVertexPtr(), 
                                                        sizeof(SIMPLEVERTEX)
                                                    );
            }
 
            // Activamos la query para que pueda contar los pixeles
            m_pD3DDevice->CreateQuery( D3DQUERYTYPE_OCCLUSION, &m_D3DQuery );
            for(unsigned int j = 0; j < (unsigned int)m_vMeshToOclQuery.size(); j++)
            {
                m_D3DQuery->Issue(D3DISSUE_BEGIN);
 
                m_pD3DDevice->DrawIndexedPrimitiveUP(    D3DPT_TRIANGLELIST, 
                                                        0, 
                                                        m_PVertexArrayMgr.GetVertexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicVBId()).GetNumVertex(),
                                                        m_IndexArrayMgr.GetIndexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicIBId()).GetNumIndex(),
                                                        m_IndexArrayMgr.GetIndexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicIBId()).GetIndexPtr(), 
                                                        D3DFMT_INDEX16, 
                                                        m_PVertexArrayMgr.GetVertexArray(m_SMeshMgr.GetSMesh(m_vMeshToOclQuery[j]).GetPhysicVBId()).GetVertexPtr(), 
                                                        sizeof(SIMPLEVERTEX)
                                                    );
                m_D3DQuery->Issue(D3DISSUE_END);
 
                // Espera activa para poder recuperar el numero de pixeles renderizados visibles.
                unsigned int iVisiblePixels = 0;
                while (m_D3DQuery->GetData((void *) &iVisiblePixels, sizeof(unsigned int), D3DGETDATA_FLUSH) == S_FALSE);
                if( iVisiblePixels != 0 )
                {
                    // Si hay pixeles, ponemos el objeto en una lista para su posterior renderizado normal
                    m_vMeshNotOccluded.push_back(m_vMeshToOclQuery[j]);
                }
 
            }
            m_D3DQuery->Release();
 
            m_EffectMgr.GetEffect(IL_EFFECT_OCCLUSION)->EndPass();
        }
        m_EffectMgr.GetEffect(IL_EFFECT_OCCLUSION)->End();
 
    m_RTOcclusion.EndScene();

Conclusiones

Si nos fijamos, es necesario hacer una espera activa para obtener la cantidad de pixeles renderizados en pantalla y como tal, mucha gente recela de este proceso por ese motivo. Existe otro método que es generar una query por cada uno de los objetos y no una para todos. De esa forma podemos generar mediante un bucle una query para cada una de los objetos, cuando salgamos del bucle podemos empezar a comprobar los resultados de dichas querys pues el tiempo que hemos utilizado en ese bucle ya nos permite preguntar la respuesta a la primera query.

Otro aspecto a tener en cuenta es que el render target donde debemos renderizar no necesariamente debe ser del mismo tamaño que nuestro backbuffer, puede ser de 1/4 tranquilamente y por supuesto, no es necesario que renderizemos con ninguna textura aplicada, con un color plano nos sirve.

En mi opinión personal, las Occlusion Query’s funcionan muy bien y nos permiten hacer una optimización tanto en el numero de mallas renderizadas como en la disminución de redibujado de los pixeles del backbuffer.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License