Proto Chunks and Visibility Rays
Home -->
Programming Projects -->
Fractal Block Engine -->
Coordinate Systems
The Problem
We do not want to render a chunk that is not visible.
Even though we are using view frustum culling,
we do not want to render chunks that are
completely occluded
by blocks in front of them.
Moreover, we do not want to expand a block into a chunk
in the first place if that block is not visible.
The Solution: Proto Chunks and Visibility Rays
To solve these problems, every second we shoot thousands of rays
from the camera into the world.
Call these visibility rays.
What these rays hit determine when we should render
a chunk and when we should expand a block into a chunk.
We shoot more rays closer to the direction of where
the player is looking.
What is a Proto Chunk?
For a given level of detail, a block is expanded into a chunk
when it is a certain distance from the player.
However when it is first expanded, it is a
proto chunk.
This is an empty shell.
It has not yet been loaded (procedurally generated)
and behaves as if it is still just a block.
Once a proto chunk is hit by a visibility ray,
a request is made to load the chunk (procedurally generate it
and update it with any changes that have previously been saved).
When do we render chunks?
When do we render a chunk (including proto chunks)?
First, to render a chunk, it must intersect the view frustum.
We do a little math to perform this test every frame.
We even do this for proto chunks,
although perhaps that is not worth it.
Assume that a chunk has passed the view frustum test.
- If it is a proto chunk, we now render it
(as a single cube).
- If it is not a proto chunk (if it is a fully loaded chunk),
we only render it if it has been hit by a
visibility ray within the last 10 seconds.
Rendering all 6 sides of a Proto Chunk?
When we do render a proto chunk P, we probably do not need to render
all 6 sides of the cube.
This is because some of the sides of P may be adjacent to either
- solid blocks or
- proto chunks that were previously solid blocks.
Not rendering certain sides of a proto chunk might seem like
only a small optimization.
However keep in mind that each of these
proto chunk cubes takes up a large amount of the screen,
so drawing these would draw many pixels.
Code for shooting visibility rays
Here is code for shooting a visibility ray into the world.
We start at a point, and then repeatedly move the point
by 0.9 (a block is 1.0 wide).
So if a chunk is visible, it is possible to hit it with
a visibility ray.
If a chunk is NOT visible, then it is very unlikely
to be hit by a visibility ray (but it is theoretically possible).
We are trading speed for accuracy.
Once the point is no longer inside a chunk on the current
level, we consider the point on the next coarsest level.
That is, we have to change coordinates systems from a level L
to a level L-1.
The FastBlockHolder is a class that holds all the blocks of a chunk.
void ShootVisRay(
ChunkGetter * chunk_getter,
CoordConverter * coord_converter,
int start_level,
const Vector & start_pos,
const Vector & dir)
{
int cur_level = start_level;
Vector cur_pos = start_pos;
Vector add = dir;
Normalize(add);
add *= 0.9f;
//We cache the last chunk that we considered.
//If our vcp has not changed, we reuse that chunk's
//fast block holder.
bool have_cached = false;
ViewerCentricPos cached_chunk_vcp;
FastBlockHolder * cached_chunk_fbh = 0;
while(true) {
//Only shooting a ray through the 5 finest levels.
if( start_level - cur_level >= 5 ) return;
//The reader can figure out how to
//write fast versions of these next two functions.
//We might want to inline them.
ViewerCentricPos cur_vcp = VectorToVCP(cur_pos);
LocalBlockPos cur_lbp = VectorToLBP(cur_pos);
//Fast track if we are in same chunk as last time.
if( have_cached &&
cached_chunk_vcp == cur_vcp )
{
bool solid = cached_chunk_fbh->IsSolid(cur_lbp);
if( solid ) return;
cur_pos += add;
continue;
}
//At this point, we do not have any cached information
//about the current chunk.
Chunk * chunk = chunk_getter->GetChunk(cur_level, cur_vcp);
if( !chunk ) {
//The chunk does not exist.
//Going one level closer to the root
//(going to a coarser level).
have_cached = false;
if( cur_level-1 < 0 ) return;
//Converting the vector cur_pos
//for level cur_level to a vector
//for level cur_level-1.
Vector new_pos;
coord_converter->ConvertPos(
cur_pos,
cur_level,
cur_level-1,
new_pos);
cur_pos = new_pos;
cur_level = cur_level-1;
} else {
//The first time within a particular chunk.
have_cached = true;
cached_chunk_vcp = cur_vcp;
//Telling the chunk that is has been hit
//by a visibility ray.
chunk->MarkVisRay();
//If the chunk is a proto chunk, the
//solid function will return true.
//That is important.
if( chunk->IsSolid(cur_lbp) ) return;
//At this point, the chunk is not a proto chunk,
//and so the fbh will be non-null.
cached_chunk_fbh = chunk->GetFastBlockHolder();
cur_pos += add;
}
}
}
Different types of solid
There are actually several different notions of what it means
for a block to be solid:
- Physically solid
- Visibly solid
- VisRay solid
A block is physically solid if the player cannot move through it.
A block is visibly solid if it appears solid for rendering.
A block is visray solid if and only if it is both
physically and visibly solid.
The code above should really be asking if the blocks
are visray solid.