Occlusion query

De Codepixel
Revisión a fecha de 12:03 20 may 2009; 127.0.0.1 (Discusión)
(dif) ← Revisión anterior | Revisión actual (dif) | Revisión siguiente → (dif)

Contenido

[editar] 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.

[editar] 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.

  • Limpiamos los buffers iniciales, tanto el backbuffer como el z-buffer.
  • Renderizamos todas las mallas de la escena para que estas actualicen el z-buffer
  • Activamos la Occlusion Query en la tarjeta grafica
  • Renderizamos otra vez las mallas de la escena y obtenemos el numero de pixeles que se han escrito en el backbuffer.
  • 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.

[editar] 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();

[editar] 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.

[editar] Links