Pulpcode

捕获,搅碎,拼接,吞咽

0%

用2D画个3D图形

嗯,我需要我的程序能够在屏幕上显示一个长方体,实际上,它还能随机旋转,像是一个动画演示程序

画3D图形,我需要一个3D图形库吗?像是opengl什么的…

不,我们只需要一些简单的2d图形api,能画线就行,当然还要些高中的数学知识。

这,可以吗?

你有没有想过这个问题,你的显示器是二维的,它为什么能玩3D游戏?(比如wow)

答案是投影,程序构建了一个虚拟的3D世界,然后把它投影到你的二维显示器上,绘制出来。想像这个虚拟的3d世界就像一个鱼缸,你的显示器是一个平面,鱼缸的正面与你显示器重合,鱼缸中会发生许多事情,而你能看到的仅仅是被投影到显示器的部分 。

3dscreen

当然这是个很简化的模型,如果你用过opengl,它就很强大,它所描述的3D世界就是个虚拟世界,你能看见的是被一个老式照相机照到底片的部分,(所以你可以调整照相机的位置,调焦距。。。来看这个虚拟世界)

平行投影和正交投影

 平行投影就是简单的把3D投影到一个面,而正交投影因为有视点距离,所以投影出来的图像有近大远小的特征,平行投影物体在哪都那么大,(所以传说平行投影可以看到无限远),画家坐在街角,画的写生就是正交投影,我们高中坐的立体几何题就是平行投影 ,有些蛋疼的人可能会用 正交投影........

下面就是我用python实现的一组关于3D绘图的api,我把一个长方体用八个顶点来表示,每个顶点是一个三维坐标,还能通过 这 八个顶点坐标来取得一个长方体的十二条边(好吧,我承认这两个函数写的是不好,我决定等我要的程序实现后再来修改这两个函数,让他们更 “优美” 一点),我还实现了基本的像平移旋转(绕x, y, z轴的旋转),和投影到一个平面的算法,(因为是平行投影,所以就是简单的把3D转成2D),这些都是高中的数学知识,下次有人再说高中学的东西只能考试的时候,你就可以反驳说,谁说的,你知道3D图形是怎么画出来的吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import math
#
# this tools contain the tools for graph
# Author: aiqier
#
def cubovers(center,xlength,ywidth,zheight,times = 1):
"""
get cuboid vertexs
"""
vertexs = []
ox,oy,oz = center
length = times * xlength
width = times * ywidth
height = times * zheight
# vertexs for a, b, c, d
vertexs.append((ox-length/2,oy-width/2,oz-height/2))
vertexs.append((ox+length/2,oy-width/2,oz-height/2))
vertexs.append((ox+length/2,oy-width/2,oz+height/2))
vertexs.append((ox-length/2,oy-width/2,oz+height/2))
# vertexs for e, f, g, h
vertexs.append((ox-length/2,oy+width/2,oz-height/2))
vertexs.append((ox+length/2,oy+width/2,oz-height/2))
vertexs.append((ox+length/2,oy+width/2,oz+height/2))
vertexs.append((ox-length/2,oy+width/2,oz+height/2))
return vertexs

def rotatex(point, angle):
"""
rotate with x axis
"""
x,y,z = point
radians = math.radians(angle)
cos = math.cos(radians)
sin = math.sin(radians)
p = x
q = y*cos-z*sin
r = y*sin+z*cos
return (p, q, r)

def rotatey(point, angle):
"""
rotate with y axis
"""
x,y,z = point
radians = math.radians(angle)
cos = math.cos(radians)
sin = math.sin(radians)
p = x*cos+z*sin
q = y
r = z*cos-x*sin
return (p, q, r)

def rotatez(point, angle):
"""
rotate with z axis
"""
x,y,z = point
radians = math.radians(angle)
cos = math.cos(radians)
sin = math.sin(radians)
p = x*cos-y*sin
q = y*cos+x*sin
r = z
return (p, q, r)

def pproxy(point):
"""
project a point to x,y
"""
return point[:2]

def psproxy(points):
"""
project points to x,y
"""
tdims = []
for i in points:
tdims.append(pproxy(i))
return tdims

def translate(point,vec):
"""
translate to vec
"""
x,y,z = point
v1, v2, v3 = vec
return (x+v1,y+v2,z+v3)

def cubolines(points):
"""
get cuboid line
"""
lines = [(points[0],points[1]),(points[1],points[2]),
(points[2],points[3]),(points[3],points[0]),
(points[4],points[5]),(points[5],points[6]),
(points[6],points[7]),(points[7],points[4]),
(points[0],points[4]),(points[1],points[5]),
(points[2],points[6]),(points[3],points[7])]
return lines

现在基本的逻辑算法有了,我们用什么画呢?其实随便一个GUI都可以,只要它能画线就行了,比如wxPython,比如Tkinter,不过,我喜欢用pygame。

没错,我就喜欢用pygame做演示程序,用pygame真正写个游戏是比较麻烦,但是用它来做演示程序多好呀?绘图又漂亮,还有双缓存,FPS之类的。。。。GUI画出来的难看死了。

下面是游戏的逻辑代码,就算你没用过pygame,也能看懂,因为python就是这么清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import sys, pygame
import math
from pygame.locals import*
from random import*
from graph_tools import*


# initalize var
dmin = 10
dmax = 20
bg = (0, 0, 0) # Black
linecolor = (255,255,0) # Yellow
center = (400,300,300) # cuboid center
cencolor = (0,255,0) # green

# pygame initalize
pygame.init()
screen_size = 800, 600
pygame.display.set_mode(screen_size, 0, 32)
pygame.mouse.set_visible(0)

screen = pygame.display.get_surface()
clock = pygame.time.Clock()
# create a cuboid

cubovers = cubovers(center, 200, 200, 100)

# our cubover will rotate with x, y, z axis
# the dgree wil random in dmin~dmax
dgreex = 0.0
dgreey = 0.0
dgreez = 0.0

while True:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
if event.type == K_ESCAPE:
sys.exit()

time_passed = clock.tick(30)
time_passed_seconds = time_passed / 1000.0

dgreex = uniform(dmin, dmax)*time_passed_seconds
dgreey = uniform(dmin, dmax)*time_passed_seconds
dgreez = uniform(dmin, dmax)*time_passed_seconds

#translate to (0, 0, 0)
for i in range(len(cubovers)):
cubovers[i] = translate(cubovers[i],tuple([-x for x in center]))

for i in range(len(cubovers)):
cubovers[i] = rotatex(cubovers[i],dgreex)
cubovers[i] = rotatey(cubovers[i],dgreey)
cubovers[i] = rotatez(cubovers[i],dgreez)

# translate to center
for i in range(len(cubovers)):
cubovers[i] = translate(cubovers[i],center)

xypoints = psproxy(cubovers)

# get cute_lines
clines = cubolines(xypoints)
screen.fill(bg)
for i in clines:
pygame.draw.line(screen,linecolor,i[0],i[1])
# draw center point
pygame.draw.circle(screen,cencolor,(center[0],center[1]),3)

pygame.display.update()

2d-3dscreen

说明

  1. 我的程序在一个空间绘制一个随机旋转的长方体。我计算出它在空间中的顶点坐标,然后把它们投影到xy平面(屏幕),在把他们连成应该有的线。
  2. 我总是把它移动到原点在旋转,之后在移回去,因为要自 转 就要这样,否则就是绕轴旋转了。
  3. 注意屏幕的Y轴正方向是向下的,绘图程序要时刻记住这点。
  4. 我当然没有一次就写出这个程序,实际上,我先试着画一个不动的,然后旋转它,看我的程序是否有错,实际上,我还试着用不同的颜色表示不同的线,来进行调试,等确认我的算法没有问题在开始下一步,(没有别的程序参考,我只能这样,不过这有点原型开发的意思,是不是,会XP的朋友们?)。
  5. 我没有用oop,我为什么要用oop?我的目的是为了演示3D图形是如何画出来的,只要能做好就行了,再说了,oop哪有那么强大?人云亦云而已,所以我没有写个主类,然后许多N面体去继承它 。
  6. 慢,效率低,恩,这点我承认,图形库一般都要很高的效率,像是opengl,都是C语言实现的,不过我说了,我是想展示3D图形是如何绘制的 。
  7. 我决定要修改那两个函数,得到线和得到顶点的,看上去太难看了。
  8. 如果你喜欢3D图形编程,请学习Opengl,反正我学的不好,我只是因为受不了老师给我们上计算机图形学的时候,教我们在DOS下画图。。。就是去年的事,我可不会买他的帐,所以就去学Opengl,opengl是3D图形的王者(没有之一,DirectX的同学不要喷我),建模(世界和照相机),变换,光照,纹理,高效…