Skip to content

neuroradiology/VoxelSpace

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Voxel Space

Landscape rendering in less than 20 lines of code

History

Let us go back to the year 1992. Processing power were 1000 times slower and acceleration via a GPU were unknown or unaffordable. 3D games graphics used very simple rendering algorithms and showed mostly polyons with a simple color.

Game Gunship 2000 in 1991 Game Gunship 2000 published by MicroProse in 1991

It was during that year NovaLogic published the game Comanche.

Game Comanche in 1992 Game Comanche published by NovaLogic in 1992

The graphics were awesome and in my opinion 3-5 years ahead of its time. You see a lot more details, shading and even shadows. Of course the processing power didn't change.

Render algorithm

Comanche uses a technique called Voxel Space similar to ray casting.

To display the landscape a 10241024 one byte height map and a 10241024 color map is used which you can download on this site. These maps are periodic:

periodic map

Basic algorithm

The algorithm draws just vertical lines. The following figure demonstrate this technique.

Line by line

  • Clear Screen.
  • For visible surface determination start from the back and render to the front
  • Determine the line on the map, which corresponds to the same optical distance from the observer. Consider the field of view and the perspective projection (Objects are smaller farther away)
  • Segment the line so that it matches the number of columns of the screen.
  • Load the height and color from the 2D maps corresponding of the segment of the line.
  • Perform the perspective projection for the height coordinate.
  • Draw a vertical line with the corresponding color with the height retrieved from the perspective correction.

The core algorithm contains in its simplest form only a few lines of code (python syntax):

def Render(p, height, horizon, scale_height, distance, screen_width, screen_height):
    # Draw from back to the front (high z coordinate to low z coordinate)
    for z in range(distance, 1, -1):
        # Find line on map. This calculation corresponds to a field of view of 90°
        pleft  = Point(-z + p.x, -z + p.y)
        pright = Point( z + p.x, -z + p.y)        
        # segment the line
        dx = (pright.x - pleft.x) / screen_width
        # Draw vertical line for each segment
        for i in range(0, screen_width):
            height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon
            DrawVerticalLine(i, height_on_screen, screen_height, colormap[pleft.x, pleft.y])
            p1eft.x += dx
            
# Call the render function with the camera parameters:
# position, height, horizon line position, 
# scaling factor for the height, the largest distance, the screen width and the screen height parameter
Render( Point(0, 0), 50, 120, 120, 300, 800, 600 )

Add rotation

With the algorithm above we can only view to the north. A different angle needs a few more lines of code to rotate the coordinates.

rotation

def Render(p, phi, height, horizon, scale_height, distance, screen_width, screen_height):
    # precalculate viewing angle parameters
    var sinphi = math.sin(phi);
    var cosphi = math.cos(phi);

    # Draw from back to the front (high z coordinate to low z coordinate)
    for z in range(distance, 1, -1):

        # Find line on map. This calculation corresponds to a field of view of 90°
        pleft = Point(
            (-cosphi*z - sinphi*z) + p.x,
            ( sinphi*z - cosphi*z) + p.y)
        pright = Point(
            ( cosphi*z - sinphi*z) + p.x,
            (-sinphi*z - cosphi*z) + p.y)
        
        # segment the line
        dx = (pright.x - pleft.x) / screen_width
        dy = (pright.y - pleft.y) / screen_width

        # Draw vertical line for each segment
        for i in range(0, screen_width):
            height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon
            DrawVerticalLine(i, height_on_screen, screen_height, colormap[pleft.x, pleft.y])
            p1eft.x += dx
            p1eft.y += dy

# Call the render function with the camera parameters:
# position, viewing angle, height, horizon line position, 
# scaling factor for the height, the largest distance, 
# the screen width and the screen height parameter
Render( Point(0, 0), 0, 50, 120, 120, 300, 800, 600 )

More performance

There are of course a lot of tricks to achieve higher performance.

  • Instead of drawing from back to the front we can draw from front to back. The advantage is, the we don't have to draw lines to the bootom of the screen. However, you need more logic for the visibility calcuation
  • Render more details in front but less details far away

front to back rendering

def Render(p, phi, height, horizon, scale_height, distance, screen_width, screen_height):
    # precalculate viewing angle parameters
    var sinphi = math.sin(phi);
    var cosphi = math.cos(phi);
    
    # initialize visibility array. Y position for each column on screen 
    hidden = np.zeros(screen_width)
    for i in range(0, screen_width):
        hidden[i] = screen_height

    # Draw from front to the back (low z coordinate to high z coordinate)
    dz = 1.
    z = 1.
    while z < distance
        # Find line on map. This calculation corresponds to a field of view of 90°
        pleft = Point(
            (-cosphi*z - sinphi*z) + p.x,
            ( sinphi*z - cosphi*z) + p.y)
        pright = Point(
            ( cosphi*z - sinphi*z) + p.x,
            (-sinphi*z - cosphi*z) + p.y)
        
        # segment the line
        dx = (pright.x - pleft.x) / screen_width
        dy = (pright.y - pleft.y) / screen_width

        # Draw vertical line for each segment
        for i in range(0, screen_width):
            height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon
            DrawVerticalLine(i, height_on_screen, hidden[i], colormap[pleft.x, pleft.y])
            if height_on_screen < hidden[i]:
                hidden[i] = heightonscreen            
            p1eft.x += dx
            p1eft.y += dy
            
        # Go to next line and increase step size when you are far away
        z += dz
        dz += 0.2

# Call the render function with the camera parameters:
# position, viewing angle, height, horizon line position, 
# scaling factor for the height, the largest distance, 
# the screen width and the screen height parameter
Render( Point(0, 0), 0, 50, 120, 120, 300, 800, 600 )

Maps

color, height

C1W.png D1.png

color, height

C2W.png D2.png

color, height

C3.png D3.png

color, height

C4.png D4.png

color, height

C5W.png D5.png

color, height

C6W.png D6.png

color, height

C7W.png D7.png

color, height

C8.png D6.png

color, height

C9W.png D9.png

color, height

C10W.png D10.png

color, height

C11W.png D11.png

color, height

C12W.png D11.png

color, height

C13.png D13.png

color, height

C14.png D14.png

color, height

C14W.png D14.png

color, height

C15.png D15.png

color, height

C16W.png D16.png

color, height

C17W.png D17.png

color, height

C18W.png D18.png

color, height

C19W.png D19.png

color, height

C20W.png D20.png

color, height

C21.png D21.png

color, height

C22W.png D22.png

color, height

C23W.png D21.png

color, height

C24W.png D24.png

color, height

C25W.png D25.png

color, height

C26W.png D18.png

color, height

C27W.png D15.png

color, height

C28W.png D25.png

color, height

C29W.png D16.png

About

Terrain rendering algorithm in less than 20 lines of code

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C 39.2%
  • HTML 38.1%
  • Python 21.2%
  • Shell 1.5%