Posted on

Batched rendering and sort keys

When rendering objects I want to:

  1. Minimise state switches (i.e. shader activation – glUseProgram())
  2. Render opaque objects front to back to reduce overdraw
  3. Render transparent objects back to front to get transparency

For this I use a 64 bit sort key that is composed of:

  • A transparency bit in a high order bit
  • A 16 bit material (i.e. shader) id
  • A distance (nb. this value is always +ve as it’s the squared distance of an object from the viewpoint)

For the distance (i.e. the distance from the viewpoint of an item to be drawn) I want to:

  • Preserve some of the fractional resolution (nb. this is application dependent. For the example below a single byte of fractional resolution is used)
  • Transform the distance value so that the relative ordering of objects to be drawn i.e. items (1) and (2) from list1 is correct i.e. the key’s value is set according to the order in which the objects should be drawn
  • Utilise as many bits for the distance so that scenes with a large draw distance don’t overflow resulting in incorrect draw ordering (and overdraw)

Finally for opaque / transparent objects I want to:

  • Order opaque objects by Material first, then by distance
  • Order transparent objects by (reverse) distance first then by material

Here it is:

// -------
// Format:
// -------
// T = Transparent bit (0 = opaque, 1 = transparent)
// M = Material id
// D = Distance (near to far)
// E = Distance (far to near)
// -------
// opaque objects:
//      0000000T MMMMMMMM MMMMMMMM DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD FFFFFFFF
// transparent objects:
//      0000000T 00000000 EEEEEEEE EEEEEEEE EEEEEEEE FFFFFFFF MMMMMMMM MMMMMMMM
// -------
float distance = ... // omitted (squared distance of object from viewpoint)
static const int FRACTIONAL_BITS = 8;        
uint64_t tran = 0;
uint64_t dist = 0;
uint64_t matd = (uint16_t)material->GetId();
uint64_t skey = 0;
if(material->IsOpaque())
{
    dist = (uint64_t)(distance * (1 << FRACTIONAL_BITS));
    dist = dist & 0xFFFFFFFFFF; // 5 bytes worth of distance for opaque objects
    matd = matd << 40;          // LSH MaterialID up 5 bytes
    skey = matd | dist;
}
else
{
    tran = 0x100000000000000ull; // 1 << 56;
    // NB. transparent object distance occupies 4 bytes (3 bytes of value + 1 byte of fractional)
    // The max value that can be held in 3 bytes = 16,777,216
    // As the distance is in fact the squared distance, the sqrt(16,777,216) = 4096
    // This means transparent object rendering breaks down when the object being rendered is > 4096 units from the viewpoint
    dist = (std::numeric_limits<uint32_t>::max() - (uint32_t)(distance * (1 << FRACTIONAL_BITS)));
    dist = dist << 16;
    skey = tran | dist | matd;
}

For each frame:

  1. Perform frustum culling
  2. Perform a single sort of the items (in ascending sort key order)
  3. Draw them

It works well but if you notice any issues with it post a comment below.

Leave a Reply

Your email address will not be published. Required fields are marked *