python绘制三维地形_Voxel Space:少于20行代码的地形渲染

Voxel Space

webdemo.gif

History

Let us go back to the year 1992. The CPUs were 1000 times slower than today and the acceleration via a GPU was unknown or unaffordable. 3D games were calculated exclusively on the CPU and the rendering engine rendered filled polygons with a single color.

gunship2000-1991.gif Game Gunship 2000 published by MicroProse in 1991

It was during that year NovaLogic published the game Comanche.

comanche-1992.gif Game Comanche published by NovaLogic in 1992

The graphics were breathtaking for the time being and in my opinion 3 years ahead of its time. You see many more details such as textures on mountains and valleys, and for the first time a neat shading and even shadows. Sure, it's pixelated, but all games in those years were pixelated.

Render algorithm

Comanche uses a technique called Voxel Space, which is based on the same ideas like ray casting. Hence the Voxel Space engine is a 2.5D engine, it doesn't have all the levels of freedom that a regular 3D engine offers.

Height map and color map

The easiest way to represent a terrain is through a height map and color map. For the game Comanche a 1024 * 1024 one byte height map and a 1024 * 1024 one byte color map is used which you can download on this site. These maps are periodic:

periodicmap.gif

Such maps limit the terrain to "one height per position on the map" - Complex geometries such as buildings or trees are not possible to represent. However, a great advantage of the colormap is, that it already contains the shading and shadows. The Voxel Space engine just takes the color and doesn't have to compute illumination during the render process.

Basic algorithm

For a 3D engine the rendering algorithm is amazingly simple. The Voxel Space engine rasters the height and color map and draws vertical lines. The following figure demonstrate this technique.

linebyline.gif

Clear Screen.

To guarantee occlusion start from the back and render to the front. This is called painter algorithm.

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)

Raster the line so that it matches the number of columns of the screen.

Retrieve 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 projection.

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

# Raster line and draw a 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])

pleft.x += dx

# Call the render function with the camera parameters:

# position, height, horizon line position,

# scaling factor for the height, the largest distance,

# 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.

rotate.gif

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

# Raster line and draw a 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])

pleft.x += dx

pleft.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,

# 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 bottom of the screen every time because of occlusion. However, to guarantee occlusion we need an additional y-buffer. For every column, the highest y position is stored. Because we are drawing from the front to back, the visible part of the next line can only be larger then the highest line previously drawn.

Level of Detail. Render more details in front but less details far away.

fronttoback.gif

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

ybuffer = np.zeros(screen_width)

for i in range(0, screen_width):

ybuffer[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

# Raster line and draw a 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, ybuffer[i], colormap[pleft.x, pleft.y])

if height_on_screen < ybuffer[i]:

ybuffer[i] = height_on_screen

pleft.x += dx

pleft.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,

# screen width and the screen height parameter

Render( Point(0, 0), 0, 50, 120, 120, 300, 800, 600 )

Links

Maps

C1W.pngD1.png

C2W.pngD2.png

C3.pngD3.png

C4.pngD4.png

C5W.pngD5.png

C6W.pngD6.png

C7W.pngD7.png

C8.pngD6.png

C9W.pngD9.png

C10W.pngD10.png

C11W.pngD11.png

C12W.pngD11.png

C13.pngD13.png

C14.pngD14.png

C14W.pngD14.png

C15.pngD15.png

C16W.pngD16.png

C17W.pngD17.png

C18W.pngD18.png

C19W.pngD19.png

C20W.pngD20.png

C21.pngD21.png

C22W.pngD22.png

C23W.pngD21.png

C24W.pngD24.png

C25W.pngD25.png

C26W.pngD18.png

C27W.pngD15.png

C28W.pngD25.png

C29W.pngD16.png

License

The software part of the repository is under the MIT license. Please read the license file for more information. Please keep in mind, that the Voxel Space technology might be still patented in some countries. The color and height maps are reverse engineered from the game Comanche and are therefore excluded from the license.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值