Python之OpenGL笔记(3):用OpenGL画第一个三角形程序解析

一、OpenGL的目的

  在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
  图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。

二、顶点坐标值在-1.0到1.0之间

  在涉及到光栅器之前我们使用屏幕坐标系绘制点、线、和三角形,点的坐标用X,Y,Z表示并且他们的值都在[-1.0, 1.0]之间。光栅器将这些坐标映射到屏幕空间(比如,如果屏幕的宽度是1024,X 的坐标为-1.0,则 X 将映射到0;若 X 坐标为1.0,则将会被将映射到1023)。最后,光栅器根据指定绘图命令的拓扑结构画出图元。
  事实上,我们选取在屏幕中的零点作为x、y两条轴的中心点,换句话说,零点是在屏幕的正中心。

三、用OpenGL画第一个三角形程序的处理过程

  用OpenGL绘图的实例程序主要建立了一个FirstTriangle类,有初始化init()和渲染Render()两个方法。

(一)init()方法负责程序中所需的数据,主要完成:

1、初始化顶点数组对象
2、分配顶点缓存对象
3、将数据载入缓存对象
4、初始化顶点与片元着色器
5、启用顶点属性数组。

(二)Render()方法负责使用OpenGL进行渲染

四、init()方法的解析

1、初始化顶点数组对象
  VAO(vertex Array Object):顶点数组对象,是包含一个或者多个VBOs的对象,被设计用来存储被完整渲染对象的相关数据信息,如渲染对象的顶点信息和每一个顶点的颜色信息。
  在C++中 函数形式是glGenVertexArrays(GLsizei n, GLuint *arrays);
  返回n个未使用的对象名到数组arrays中,用作顶点数组对象。返回的名字可以用来分配更多的缓存对象,并且它们已经使用未初始化的顶点数组集合的默认状态进 行了数值的初始化。
  在Python中,函数形式是arrays= glGenVertexArrays((GLsizei n)

self.vao = glGenVertexArrays(1)
glBindVertexArray(self.vao)

2、分配顶点缓存对象
  VBO(vertex Array Object):顶点缓冲对象,是被用来储存顶点数据的。加载顶点进入 GPU 最有效率的方法是 VBOs。它们是可以存储在显存中的缓冲区,使得 GPU 访问数据的速度最快。VBO是顶点数组数据真正所在的地方。
  为了指定一个属性数据的格式和来源,我们需要告诉OpenGL,编号为0的属性使用哪个VBO,编号为1的属性使用哪个VBO等等。
  在Python中我们使用numpy.array(vertices, numpy.float32)创建多维数组。
  在C++中 函数形式是glGenBuffers(GLsizei n, GLuint *buffers);返回n个当前未使用的缓存对象名称,并保存到buffers数组中。
  在Python中,函数形式是 buffers= glGenBuffers(GLsizei n);

vertexData = numpy.array(vertices, numpy.float32)
self.vertexBuffer = glGenBuffers(1) #创建VBOs
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
'''绑定VBOs目标 GL_ARRAY_BUFFER 表示缓冲区用于存储顶点数组。'''

3、将数据载入缓存对象

glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, GL_STATIC_DRAW)

  当我们将缓冲区绑定到目标上之后,我们需要用数据进行填充。上面函数的参数包括:目标点名称(和上面缓冲区绑定的目标点一样),数据的字节数,顶点数组的地址,以及一个表示数据使用方式的枚举量。因为我们不需要改变缓冲区中的内容,所以我们将数据指定为 GL_STATIC_DRAW。
4、初始化顶点与片元着色器(暂略)

5、启用顶点属性数组。
  那就是启用顶点属性数组。我们通过调用glEnableVertexAttribArray()来完成这项工作。

self.vertIndex = 0
glEnableVertexAttribArray(self.vertIndex)
# 由于将要调用绘图函数,所以这里我们再一次绑定了缓冲区。
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)

  下面这个函数调用告诉管线如何在缓冲区内部解释数据。第一个参数指定了属性的索引。我们都知道0是默认的,但是当我们开始使用 Shaders 的时候,我们在 Shader 中需要明确的设置索引。第二个参数是构成属性的分量的个数(X,Y,Z共三个分量)。第三个参数是每个分量的数据类型。第四个参数表明属性在管线中使用之前是否需要被规范化。第五个参数是在缓冲区中两个相同属性值之间的间隔的字节数,当只有一个属性时(比如缓冲区只包含顶点位置)并且数据是紧挨着的,那么我们设置这个值为0。如果我们有一个包含位置属性和法线属性的数组(每个属性都是一个float类型的三维向量),我们将这个值设置为6*4=24。最后一个参数在多个属性时是有用的。我们需要指定在缓冲区中存储数据的偏移值,这样管线才会找到数据。当顶点的位置和法线相邻存储时时,我们设置顶点位置的偏移值为0而顶点法线的偏移值为12。

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
# unbind VAO
glBindVertexArray(0)

五、Render()方法的解析

  渲染工作是选择我们准备绘制的顶点数据,然后请求进行绘制。首先调用glBindVertexArray()来选择作为顶点数据使用的顶点数组。正如前文中提到的,我们可以用这个函数来切换程序中保存的多个顶点数据对象集合。
其次调用glDrawArrays()来实现顶点数据向OpenGL管线的传输。

glBindVertexArray(self.vao)
# 最后,调用glDrawArrays函数来画几何体。这里就是 GPU 真正开始工作的地方。它现在将结合绘图命令的参数,然后创建一个三角形并将结果渲染到屏幕。
glDrawArrays(GL_TRIANGLES, 0, 3)
# unbind VAO
glBindVertexArray(0)

六、运行结果

11.png

七、代码

"""
glfw_FirstTriangle.py
Author: dalong10
Description: Draw a Triagle, learning OPENGL 
"""
import glutils    #Common OpenGL utilities,see glutils.py
import sys, random, math
import OpenGL
from OpenGL.GL import *
from OpenGL.GL.shaders import *
import numpy 
import numpy as np
import glfw

strVS = """
#version 330 core
layout(location = 0) in vec3 vertexPosition_modelspace;
void main(){
    gl_Position.xyz = vertexPosition_modelspace;
    gl_Position.w = 1.0;
    }
"""

strFS = """
#version 330 core
out vec3 color;
void main(){
    color = vec3(1,0,0);
    }
"""

class FirstTriangle:
    def __init__(self, side):
        self.side = side

        # load shaders
        self.program = glutils.loadShaders(strVS, strFS)
        glUseProgram(self.program)
        
        s = side/1.0
        vertices = [
            -s, -s, 0, 
             s, -s, 0,
             0, s, 0
             ]
                
        # set up vertex array object (VAO)
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)
        # set up VBOs
        vertexData = numpy.array(vertices, numpy.float32)
        self.vertexBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, 
                     GL_STATIC_DRAW)
        #enable arrays
        self.vertIndex = 0
        glEnableVertexAttribArray(self.vertIndex)
        # set buffers 
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
        # unbind VAO
        glBindVertexArray(0)

    def render(self):
        # use shader
        glUseProgram(self.program)
        # bind VAO
        glBindVertexArray(self.vao)
        # draw
        glDrawArrays(GL_TRIANGLES, 0, 3)
        # unbind VAO
        glBindVertexArray(0)

if __name__ == '__main__':
    import sys
    import glfw
    import OpenGL.GL as gl
    def on_key(window, key, scancode, action, mods):
        if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
            glfw.set_window_should_close(window,1)

    # Initialize the library
    if not glfw.init():
        sys.exit()

    # Create a windowed mode window and its OpenGL context
    window = glfw.create_window(640, 480, "Hello World", None, None)
    if not window:
        glfw.terminate()
        sys.exit()

    # Make the window's context current
    glfw.make_context_current(window)
    # Install a key handler
    glfw.set_key_callback(window, on_key)

    # Loop until the user closes the window
    while not glfw.window_should_close(window):
        # Render here
        width, height = glfw.get_framebuffer_size(window)
        ratio = width / float(height)
        gl.glViewport(0, 0, width, height)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)

        gl.glClearColor(0.0,0.0,4.0,0.0)
        firstTriangle0 = FirstTriangle(1.0)
        # render
        firstTriangle0.render()
        # Swap front and back buffers
        glfw.swap_buffers(window)
        # Poll for and process events
        glfw.poll_events()

    glfw.terminate()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容