用PyOpenGL叩开3D的心扉——OpenGL全解析(5)

By | 2013/01/15

我不得不演示几个例子来加深一下之前学习的东西(时隔这么久了,有点难以为继的感觉啊)~

我恨数学

据说这个世界上最深沉的感情不是爱而是恨,或许一开始就亮出一个数学函数能让你有动力进行下去?

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
#from numpy import *
import sys

def init():
    glClearColor(1.0, 1.0, 1.0, 1.0)
    gluOrtho2D(-5.0, 5.0, -5.0, 5.0)

def plotfunc():
    glClear(GL_COLOR_BUFFER_BIT)
    glPointSize(3.0)

    glColor3f(1.0, 1.0, 0.0)
    glBegin(GL_LINES)
    glVertex2f(-5.0, 0.0)
    glVertex2f(5.0, 0.0)
    glVertex2f(0.0, 5.0)
    glVertex2f(0.0, -5.0)
    glEnd()

    glColor3f(0.0, 0.0, 0.0)
    glBegin(GL_LINES)
    #for x in arange(-5.0, 5.0, 0.1):
    for x in (i * 0.1 for i in range(-50, 50)):
        y = x*x
        glVertex2f(x, y)
    glEnd()

    glFlush()

def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB)
    glutInitWindowPosition(50,50)
    glutInitWindowSize(400,400)
    glutCreateWindow("Function Plotter")
    glutDisplayFunc(plotfunc)
    init()
    glutMainLoop()

main()

这个程序也是很简单的,绘制y=x2的抛物线图像,应该是初中的知识,怎么样恨意上来了没?

这里有几个小地方要说明一下,两个注释,是使用numpy这个强大的数学库进行便捷的运算,为防止有人没装或者不知道怎么装,我注释了numpy而使用Python原生的方法做了。这里需要生产从-5.0到5.0的序列,间隔为0.1,然而python的range函数只接受整数,所以用表达式来代替了。但是记住numpy的速度是很快的,3D图像处理和展示需要大量的运算,Python孱弱的原生运算能力很快就会捉襟见肘,如果可能,请使用numpy库,以后的例子,看运算量我可能会混合使用两种方法,不一定给出替代方法,这点请谅解。

再看一下glPointSize(3.0)这个语句,它是在整个绘制函数最前面调用的,但是实际的结果,仅有点变粗了,虽然坐标系也是通过点画出来的,但是没有影响;如果你想把线也画粗一点,请使用glLineWidth

但我热爱艺术

再来一个抽象艺术,第一次看到结果,我自己都被震惊了:)
OpenGL 抽象艺术画

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from numpy import *
import sys

global W, H, R
(W, H, R) = (500, 500, 10.0)

def init():
    glClearColor(1.0, 1.0, 1.0, 1.0)

def drawfunc():
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(0.0, 0.0, 0.0)
    glBegin(GL_POINTS)
    for x in arange(-R, R, 0.04):
        print '%.1f%%r' % ((R + x) / (R + R) * 100),
        for y in arange(-R, R, 0.04):
            r = cos(x) + sin(y)
            glColor3f(cos(y*r), cos(x*y*r), sin(x*r))
            glVertex2f(x, y)
    print '100%!!'
    glEnd()
    glFlush()

def reshape(w, h):
    if h <= 0: h = 1;
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    if w <= h:
        gluOrtho2D(-R, R, -R * h / w, R * h / w)
    else:
        gluOrtho2D(-R * w / h, R * w / h, -R, R)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

def keyboard(key, x, y):
    if key == chr(27) or key == "q": # Esc is 27
        sys.exit()

def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB)
    glutInitWindowPosition(20, 20)
    glutInitWindowSize(W, H)
    glutCreateWindow("Artist Drawer")
    glutReshapeFunc(reshape)
    glutDisplayFunc(drawfunc)
    glutKeyboardFunc(keyboard)
    init()
    glutMainLoop()

main()

这个结果很漂亮,但你问怎么出来的,这个还是要归结于万恶的数学啊,而且运算速度很慢,要慢慢等才能看到结果,美丽是有代价的啊:)

这个程序里面,就有比较多的东西了,drawfunc还是一如既往,画点而已,具体为什么要这么画,我也不知道……
还注册了一个glutReshapFunc的函数,这个是什么意思呢,就是当窗口大小改变的时候做的事情,如果你不注册这个函数,那么当改变窗口大小时,可能有一部分的图像就无法显示了。而reshape里做的事情,是我们这次学习的重点。

glViewport:指定了视口程序显示的范围,也就是OpenGL绘制的范围,这里使用(0, 0, w, h)便是说明整个窗口,一般情况下总是如此,但是我们也是可以指定小于这个范围的ViewPort的。事实上我隐瞒了很多细节,这个函数必须和下面要讲的gluOrtho2D函数一起用才能出现正确的结果。
gluOrtho2D:这个函数派生于OpenGL的glOrtho,它创建了一个正交的视景体(View Volume),我们所看到的物体,都处在这个体中,四个参数分别代表了(left, right, bottom, top),也就是竖直的左右边界和水平的下上边界;而近远则是默认的(-1, 1),glOrtho有六个参数可以设定。这个体越大,我们看到的东西就越小;反之看到的东西就越大。我知道这样很难理解,打个比方就是一个六边形的鱼缸,这个函数定出了一个鱼缸的大小,我们所看的东西呢,都在这个鱼缸里面。
上面说glViewport要和gluOrtho2D一起用才能正确显示是个什么意思呢?gluOrtho2D只管创建一个视体,而glViewport只管绘图的范围,如果视体是个正方体,而窗口是个长方体,直接绘制的结果会是什么呢?很明显,整个视体里的东西都被拉长了,而一般我们viewport都是指明了窗口大小,自然只能修改视体来适应各种不同的比例了。
修改代码,拉伸窗口,查看最终的结果会是怎样的。

glMatrixMode:这个函数非常难以理解,但是又极其重要!这关系到了OpenGL中的“矩阵”的概念。矩阵……你是说黑客帝国么?好像很有趣诶~~ 嗯嗯没错,矩阵是个伟大的东西,通过它,3D世界的所有维度都蜷曲到内存中的一维数据里去了。这是一个有点儿抽象的概念但其实也没什么特别的,OpenGL里有如下几种矩阵:

  • GL_MODELVIEW:模型观察矩阵,表示物体的位置变化和观察点的改变;
  • GL_PROJECTION:投影矩阵,描述如何将一个物体投影到平面上;
  • GL_TEXTURE:纹理矩阵,描述纹理坐标的动态变化
  • ...

我不想搬出一堆数字和大括号来说明矩阵的基本运算和应用(好吧其实真实原因是我也不会:),也不会告诉你最后的ModelView矩阵是View矩阵与Model矩阵的乘积,更不会告诉你有glRotate和glTranslate之流的函数来改变矩阵!暂时这么理解就好了,矩阵就是我们走路的方向,我们现在朝南走,看到的南边的风景,然后说“向右拐”,现在看到西边的风景了,再说“向后转”,现在看到东边的风景了。就是通过这样可以累积的变换,我们把我们最初的一些数据变成了更复杂的东西表达了出来,转了几圈后也许有点糊涂了,用glLoadIdentity将当前指定的矩阵还原为最初的状态。

19 thoughts on “用PyOpenGL叩开3D的心扉——OpenGL全解析(5)

  1. langyxxl

    我在查阅opengl的资料时有个疑问,就是pyglet和pyopengl应该选择哪个使用?

    Reply
  2. ZHG

    终于等到了新的讲解,第二个demo果然不同凡响,希望再接再厉能够多站十几个demo和讲解 ^_^

    Reply
  3. aaa

    一口气看完了,博主快出新教程吧。纹理,光照,and so on。

    Reply
  4. whh163

    博主大大啊,继续更新呐,等你好久罗,http://pyopengl.sourceforge.net/documentation/manual-3.0/index.xhtml
    这个链接被墙了,郁闷

    Reply
  5. Xtricman

    你写的都是 glBegin(GL_LINES),根本没有点,在我的ArchLinux上运行的结果也是线

    Reply
  6. ddgysx

    pyopengl好慢啊,显示一个海量的点集,c语言版的可以实时,python的就不行

    Reply
  7. 小张

    你的教程写得很好啊。通俗易懂很适合入门啊。继续撒。嘿嘿

    Reply
  8. 小张

    比如贴纹理啊,粒子效果之类的啊。期待继续啊。

    Reply
  9. chendanlan

    请问每次运行完程序,那个图片怎么关闭,因为都没有关闭的叉

    Reply
  10. ZHG

    很长时间没有更新的教程了,这是工作比较忙还是完全放弃了啊? 一直很期待后续的。例如什么normal mapping 和height map之类, 最好还能简单讲解一些GLSL的渲染例子。。

    Reply
  11. Justice

    Just want to say your article is as amazing. The clearness in your post is just cool and i could assume you87&21#;re an expert on this subject. Well with your permission let me to grab your feed to keep updated with forthcoming post. Thanks a million and please continue the gratifying work.

    Reply
  12. = =

    博主,你撬开了我的心扉
    然后告诉我坑了= =
    然而还是感谢博主 教程很好 对新手帮助很大 感谢

    Reply
  13. wdc

    楼主您好,有这么一个问题希望得到您的指点,Pyopengl怎么加载进来obj三维模型文件,并且从中提取出三维模型的正面、侧面、上面的三视二维图像?谢谢。

    Reply
  14. windchout

    博主,您好,如有有可能请您继续更新吧,博主的讲解思路清晰,易懂,对新人(我)入门学习是一套非常棒的教程。
    文笔不错,很有才华。
    博主加油,继续更新。
    再次十分感谢1-5的教程~~~

    Reply
  15. xl.cn

    ‘%.1f%%r’ 后面的单引号 ’ 提示 “invalid syntax”,是什么原因呢?

    Reply
  16. chason.Xu

    @xl.cn
    我也碰到同样的问题,将源代码中的这两行
    print ‘%.1f%%r’ % ((R + x) / (R + R) * 100),
    print ‘100%!!’
    改为下面这两行,就行了
    print (“%.1f%%r” , ((R + x) / (R + R) * 100))
    print (“100%!!”)

    Reply
  17. 是我

    七年了,博主还在写博客。。。只是七年里写的PyOpenGL变少了,五篇偏偏精华,NB

    Reply

发表评论

您的电子邮箱地址不会被公开。