一、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)
六、运行结果
七、代码
"""
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()