Skip to content

Culling

Narta Xaymar Dirks edited this page Oct 28, 2026 · 2 revisions

Culling

Every frame BlitzLLVM decides which meshes can not be seen and does not give them to the GPU. It is done before drawing because for scenes with many objects the cost of sending them to the GPU is what matters.

Frustum culling

CameraCulling bit 1 (on by default) leaves out each mesh whose box is completely outside of what the camera sees, which includes everything behind it and everything farther than CameraRange allows. The test costs a few plane checks per mesh, and for meshes that did not change it is done with a sphere that is remembered. EntityInView asks the same for one mesh. TrisRendered counts what was given to the GPU, so it shows what this culling saved.

Culling faces on the GPU

A single mesh with many faces (a terrain, a level or a scan) is either completely out of view or drawn completely, even when the camera looks at a small part of it. CameraCulling bit 2 (on by default) lets the GPU cut such meshes into pieces too. Surfaces of at least 4096 faces that are solid (no alpha, not see-through) are cut into runs of 128 faces that are close to each other, once, when they are first drawn. Before each camera draws, a compute shader tests the sphere around every run against the six planes of the view and writes a command that draws the run or skips it, and the surface is then drawn with one indirect call. Nothing is read back to the program.

The faces of a surface are drawn in a different order for this, which can not change a solid picture. Surfaces that are see-through or ordered by distance, sprites, planes and surfaces below the size limit are drawn as a whole. A surface that is changed every frame (VertexCoord in a loop) is cut again at most once every 30 frames, and is drawn as a whole in between, so keep dynamic meshes small. Meshes are only worth this when the camera sees a part of them: a mesh whose box is completely inside of the view is drawn as a whole without the test, since there is nothing to cull. GPUs that can not draw indirect calls with a first instance (multiDrawIndirect and drawIndirectFirstInstance) ignore the bit. Stats3D(1004) counts the runs the GPU decided about in the last RenderWorld. TrisRendered still counts all faces of such a surface, since the GPU decides later and nothing is read back.

Bit 4 (needs bit 2, off by default) lets the same pass also leave out runs whose faces all look away from the camera. Each run keeps a cone around the directions of its faces, and the run is skipped when the camera is on the back side of all of them. The GPU already drops such faces after the vertex shader, so this saves the vertex work of the runs that face away, and is only worth it for meshes with a lot of faces that look away (the inside of a hull, a level seen from outside). It is only used for entities that are turned and scaled the same in every direction and not mirrored, for solid faces that are not drawn two-sided, and for perspective cameras; everything else keeps all runs that are in view.

Using the culling of another camera

CameraCullingSource makes a camera use the view of another camera to decide, so that cameras that see nearly the same do the work once.

Home · Command Index



Differences
Language Features
BlitzBasic
Blitz3D
BlitzPlus

Clone this wiki locally