Voxelscape A GPU-based ray caster for semi-voxel terrains like in the old Novalogic titles (eg Delta Force or Comance).

Development Iteration

Algorithm

  • Render to a framebuffer of the desired resolution
  • A fullscreen-quad is rendered, the corners are the normalised vectors into world space. The following fragment shader is used:
  • Each pixel is a ray into world space and starts at the camera position (store this in a uniform variable)
  • Right now, there is a fixed step size (It might be better to have a long step size if the camera looks down and a shorter at grazing angles)
  • Iterate until you hit the maximum iteration count:
  • Add the view direction to your current position with (multiplied by the step size)
  • If the current position if above the maximum terrain height (or 1.0) and the view direction is facing upwards, break and set the sky color
  • Use the position’s xz to access the current heightmap position
  • If the y coordinate is below the heightmap height, calculate the colour and break from the iterations:
  • Use the normalised height to access a 1D colour map (in this example from brown - low to green - medium to white - high)
  • Look up detail in a noise texture.
  • If the y coordinate is below the water line, set the water colour and break
  • Mix the fragment colour with the fog colour, use iteration count divided by max iteration count as the delta for the interpolation. This will shade both water and terrain.
  • Draw another fullscreen quad and use the framebuffer as input texture, this should scale it to the desired resolution.
  • Notes on gl_FragDepth in a ray caster

    To combine multiple buffers with their depth textures, the depth values have to be in the same range and with the same scaling. Multiple buffers are used and most have the standard OpenGL depth range/test. Calculating the ‘correct’ and OpenGL compatible z-buffer value in the raycaster follows this formula (which can be extracted when multiplying the z coordinate with the modelview projection transform):

    float r;  // distance along ray -- the same as the vertex - eye distance
    
    float zf; // farplane distance
    float zn; // nearplane distance
    
    gl_FragCoord = zf / (zf - zn) * (1.0 - zn / r);
          

    Depth Textures in FBOs

    FBOs can bind render buffers or bindable textures at the depth buffer attachment point. Depth textures provide readback facilities for example for shadow mapping or general depth compares.

    To speed up many FBO rendering operations with fullscreen quads, I usually don’t clear the buffer specifically, but draw a screen-space fullscreen quad, with glDepthTest disabled. This work almost always, because the content of the screen gets overwritten by the new texture anyway -- in this case neither color nor depth buffer need to be cleared. However, this fails if depth textures are needed. It seems that if glDepthTest en/disables the complete depth compare and writing stage. So with a disabled depth test, no values are written to the texture! Clearing the color and depth buffer and enabling depth test fixed this problem.