Fast Culling

From X-Plane SDK
Jump to: navigation, search

This code snippet can be used to determine if a sphere at a location in the current OpenGL coordinates is visible.

  • Call setup_cull_info once per drawing callback...this queries OpenGL to get the visibility info from OpenGL's state. Between callbacks the camera may have moved.
  • Call sphere_is_visible once for each visibility check you want to make - r = radius of the sphere in meters; if the sphere whose center is XYZ and radius is r is completely off screen, the function returns false, otherwise is returns true.
  • Be careful about tuning cull checks vs. drawing; you only want to cull large chunks of geometry, so that the fps boost from not drawing justifies the CPU time spent culling.
  • You don't need to cull individual OBJs drawn with the XPLMDrawObject API - this API culls for you. But you might want to cull many objects at once, by culling a sphere that contains a whole set of objects.
  • This code works in all projection matrices, ortho and frustum, including oblique frustums.
struct cull_info_t {					// This struct has everything we need to cull fast!
	float	model_view[16];				// The model view matrix, to get from local OpenGL to eye coordinates.
	float	nea_clip[4];				// Four clip planes in the form of Ax + By + Cz + D = 0 (ABCD are in the array.)
	float	far_clip[4];				// They are oriented so the positive side of the clip plane is INSIDE the view volume.
	float	lft_clip[4];
	float	rgt_clip[4];
	float	bot_clip[4];
	float	top_clip[4];

static void setup_cull_info(cull_info_t * i)
	float m[16];
	// First, just read out the current OpenGL this once at setup because it's not the fastest thing to do.
	glGetFloatv(GL_MODELVIEW_MATRIX ,i->model_view);
	// Now...what the heck is this?  Here's the deal: the clip planes have values in "clip" coordinates of: Left = (1,0,0,1)
	// Right = (-1,0,0,1), Bottom = (0,1,0,1), etc.  (Clip coordinates are coordinates from -1 to 1 in XYZ that the driver
	// uses.  The projection matrix converts from eye to clip coordinates.)
	// How do we convert a plane backward from clip to eye coordinates?  Well, we need the transpose of the inverse of the
	// inverse of the projection matrix.  (Transpose of the inverse is needed to transform a plane, and the inverse of the
	// projection is the matrix that goes clip -> eye.)  Well, that cancels out to the transpose of the projection matrix,
	// which is nice because it means we don't need a matrix inversion in this bit of sample code.
	// So this nightmare down here is simply:
	// clip plane * transpose (proj_matrix)
	// worked out for all six clip planes.  If you squint you can see the patterns:
	// L:  1  0 0 1
	// R: -1  0 0 1
	// B:  0  1 0 1
	// T:  0 -1 0 1
	// etc.
	i->lft_clip[0] = m[0]+m[3];	i->lft_clip[1] = m[4]+m[7];	i->lft_clip[2] = m[8]+m[11];	i->lft_clip[3] = m[12]+m[15];
	i->rgt_clip[0] =-m[0]+m[3];	i->rgt_clip[1] =-m[4]+m[7];	i->rgt_clip[2] =-m[8]+m[11];	i->rgt_clip[3] =-m[12]+m[15];
	i->bot_clip[0] = m[1]+m[3];	i->bot_clip[1] = m[5]+m[7];	i->bot_clip[2] = m[9]+m[11];	i->bot_clip[3] = m[13]+m[15];
	i->top_clip[0] =-m[1]+m[3];	i->top_clip[1] =-m[5]+m[7];	i->top_clip[2] =-m[9]+m[11];	i->top_clip[3] =-m[13]+m[15];

	i->nea_clip[0] = m[2]+m[3];	i->nea_clip[1] = m[6]+m[7];	i->nea_clip[2] = m[10]+m[11];	i->nea_clip[3] = m[14]+m[15];
	i->far_clip[0] =-m[2]+m[3];	i->far_clip[1] =-m[6]+m[7];	i->far_clip[2] =-m[10]+m[11];	i->far_clip[3] =-m[14]+m[15];

static int sphere_is_visible(const cull_info_t * i, float x, float y, float z, float r)
	// First: we transform our coordinate into eye coordinates from model-view.
	float xp = x * i->model_view[0] + y * i->model_view[4] + z * i->model_view[ 8] + i->model_view[12];
	float yp = x * i->model_view[1] + y * i->model_view[5] + z * i->model_view[ 9] + i->model_view[13];
	float zp = x * i->model_view[2] + y * i->model_view[6] + z * i->model_view[10] + i->model_view[14];

	// Now - we apply the "plane equation" of each clip plane to see how far from the clip plane our point is.  
	// The clip planes are directed: positive number distances mean we are INSIDE our viewing area by some distance;
	// negative means outside.  So ... if we are outside by less than -r, the ENTIRE sphere is out of bounds.
	// We are not visible!  We do the near clip plane, then sides, then far, in an attempt to try the planes
	// that will eliminate the most geometry first...half the world is behind the near clip plane, but not much is
	// behind the far clip plane on sunny day.
	if ((xp * i->nea_clip[0] + yp * i->nea_clip[1] + zp * i->nea_clip[2] + i->nea_clip[3] + r) < 0)	return false;
	if ((xp * i->bot_clip[0] + yp * i->bot_clip[1] + zp * i->bot_clip[2] + i->bot_clip[3] + r) < 0)	return false;
	if ((xp * i->top_clip[0] + yp * i->top_clip[1] + zp * i->top_clip[2] + i->top_clip[3] + r) < 0)	return false;
	if ((xp * i->lft_clip[0] + yp * i->lft_clip[1] + zp * i->lft_clip[2] + i->lft_clip[3] + r) < 0)	return false;
	if ((xp * i->rgt_clip[0] + yp * i->rgt_clip[1] + zp * i->rgt_clip[2] + i->rgt_clip[3] + r) < 0)	return false;
	if ((xp * i->far_clip[0] + yp * i->far_clip[1] + zp * i->far_clip[2] + i->far_clip[3] + r) < 0)	return false;
	return true;