一、OpenGL的目的
??在OpenGL中速缨,任何事物都在3D空間中,而屏幕和窗口卻是2D像素?cái)?shù)組,這導(dǎo)致OpenGL的大部分工作都是關(guān)于把3D坐標(biāo)轉(zhuǎn)變?yōu)檫m應(yīng)你屏幕的2D像素荤傲。3D坐標(biāo)轉(zhuǎn)為2D坐標(biāo)的處理過程是由OpenGL的圖形渲染管線(Graphics Pipeline,大多譯為管線颈渊,實(shí)際上指的是一堆原始圖形數(shù)據(jù)途經(jīng)一個(gè)輸送管道遂黍,期間經(jīng)過各種變化處理最終出現(xiàn)在屏幕的過程)管理的。圖形渲染管線可以被劃分為兩個(gè)主要部分:第一部分把你的3D坐標(biāo)轉(zhuǎn)換為2D坐標(biāo)俊嗽,第二部分是把2D坐標(biāo)轉(zhuǎn)變?yōu)閷?shí)際的有顏色的像素雾家。
??圖形渲染管線接受一組3D坐標(biāo),然后把它們轉(zhuǎn)變?yōu)槟闫聊簧系挠猩?D像素輸出绍豁。圖形渲染管線可以被劃分為幾個(gè)階段芯咧,每個(gè)階段將會(huì)把前一個(gè)階段的輸出作為輸入。所有這些階段都是高度專門化的(它們都有一個(gè)特定的函數(shù))竹揍,并且很容易并行執(zhí)行敬飒。正是由于它們具有并行執(zhí)行的特性,當(dāng)今大多數(shù)顯卡都有成千上萬的小處理核心芬位,它們?cè)贕PU上為每一個(gè)(渲染管線)階段運(yùn)行各自的小程序无拗,從而在圖形渲染管線中快速處理你的數(shù)據(jù)。這些小程序叫做著色器(Shader)昧碉。
二蓝纲、頂點(diǎn)坐標(biāo)值在-1.0到1.0之間
??在涉及到光柵器之前我們使用屏幕坐標(biāo)系繪制點(diǎn)、線晌纫、和三角形税迷,點(diǎn)的坐標(biāo)用X,Y锹漱,Z表示并且他們的值都在[-1.0, 1.0]之間箭养。光柵器將這些坐標(biāo)映射到屏幕空間(比如,如果屏幕的寬度是1024哥牍,X 的坐標(biāo)為-1.0毕泌,則 X 將映射到0喝检;若 X 坐標(biāo)為1.0,則將會(huì)被將映射到1023)撼泛。最后挠说,光柵器根據(jù)指定繪圖命令的拓?fù)浣Y(jié)構(gòu)畫出圖元。
??事實(shí)上愿题,我們選取在屏幕中的零點(diǎn)作為x损俭、y兩條軸的中心點(diǎn),換句話說潘酗,零點(diǎn)是在屏幕的正中心杆兵。
三、用OpenGL畫第一個(gè)三角形程序的處理過程
??用OpenGL繪圖的實(shí)例程序主要建立了一個(gè)FirstTriangle類,有初始化init()和渲染Render()兩個(gè)方法仔夺。
(一)init()方法負(fù)責(zé)程序中所需的數(shù)據(jù)琐脏,主要完成:
1、初始化頂點(diǎn)數(shù)組對(duì)象
2缸兔、分配頂點(diǎn)緩存對(duì)象
3日裙、將數(shù)據(jù)載入緩存對(duì)象
4、初始化頂點(diǎn)與片元著色器
5惰蜜、啟用頂點(diǎn)屬性數(shù)組阅签。
(二)Render()方法負(fù)責(zé)使用OpenGL進(jìn)行渲染
四、init()方法的解析
1蝎抽、初始化頂點(diǎn)數(shù)組對(duì)象
??VAO(vertex Array Object):頂點(diǎn)數(shù)組對(duì)象政钟,是包含一個(gè)或者多個(gè)VBOs的對(duì)象,被設(shè)計(jì)用來存儲(chǔ)被完整渲染對(duì)象的相關(guān)數(shù)據(jù)信息樟结,如渲染對(duì)象的頂點(diǎn)信息和每一個(gè)頂點(diǎn)的顏色信息养交。
??在C++中 函數(shù)形式是glGenVertexArrays(GLsizei n, GLuint *arrays);
??返回n個(gè)未使用的對(duì)象名到數(shù)組arrays中,用作頂點(diǎn)數(shù)組對(duì)象瓢宦。返回的名字可以用來分配更多的緩存對(duì)象碎连,并且它們已經(jīng)使用未初始化的頂點(diǎn)數(shù)組集合的默認(rèn)狀態(tài)進(jìn) 行了數(shù)值的初始化。
??在Python中驮履,函數(shù)形式是arrays= glGenVertexArrays((GLsizei n)
self.vao = glGenVertexArrays(1)
glBindVertexArray(self.vao)
2鱼辙、分配頂點(diǎn)緩存對(duì)象
??VBO(vertex Array Object):頂點(diǎn)緩沖對(duì)象,是被用來儲(chǔ)存頂點(diǎn)數(shù)據(jù)的玫镐。加載頂點(diǎn)進(jìn)入 GPU 最有效率的方法是 VBOs倒戏。它們是可以存儲(chǔ)在顯存中的緩沖區(qū),使得 GPU 訪問數(shù)據(jù)的速度最快恐似。VBO是頂點(diǎn)數(shù)組數(shù)據(jù)真正所在的地方杜跷。
??為了指定一個(gè)屬性數(shù)據(jù)的格式和來源,我們需要告訴OpenGL,編號(hào)為0的屬性使用哪個(gè)VBO葛闷,編號(hào)為1的屬性使用哪個(gè)VBO等等憋槐。
??在Python中我們使用numpy.array(vertices, numpy.float32)創(chuàng)建多維數(shù)組。
??在C++中 函數(shù)形式是glGenBuffers(GLsizei n, GLuint *buffers);返回n個(gè)當(dāng)前未使用的緩存對(duì)象名稱淑趾,并保存到buffers數(shù)組中阳仔。
??在Python中,函數(shù)形式是 buffers= glGenBuffers(GLsizei n);
vertexData = numpy.array(vertices, numpy.float32)
self.vertexBuffer = glGenBuffers(1) #創(chuàng)建VBOs
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
'''綁定VBOs目標(biāo) GL_ARRAY_BUFFER 表示緩沖區(qū)用于存儲(chǔ)頂點(diǎn)數(shù)組扣泊。'''
3近范、將數(shù)據(jù)載入緩存對(duì)象
glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, GL_STATIC_DRAW)
??當(dāng)我們將緩沖區(qū)綁定到目標(biāo)上之后,我們需要用數(shù)據(jù)進(jìn)行填充旷赖。上面函數(shù)的參數(shù)包括:目標(biāo)點(diǎn)名稱(和上面緩沖區(qū)綁定的目標(biāo)點(diǎn)一樣)顺又,數(shù)據(jù)的字節(jié)數(shù)更卒,頂點(diǎn)數(shù)組的地址等孵,以及一個(gè)表示數(shù)據(jù)使用方式的枚舉量。因?yàn)槲覀儾恍枰淖兙彌_區(qū)中的內(nèi)容蹂空,所以我們將數(shù)據(jù)指定為 GL_STATIC_DRAW俯萌。
4、初始化頂點(diǎn)與片元著色器(暫略)
5上枕、啟用頂點(diǎn)屬性數(shù)組咐熙。
??那就是啟用頂點(diǎn)屬性數(shù)組。我們通過調(diào)用glEnableVertexAttribArray()來完成這項(xiàng)工作辨萍。
self.vertIndex = 0
glEnableVertexAttribArray(self.vertIndex)
# 由于將要調(diào)用繪圖函數(shù)棋恼,所以這里我們?cè)僖淮谓壎司彌_區(qū)。
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
??下面這個(gè)函數(shù)調(diào)用告訴管線如何在緩沖區(qū)內(nèi)部解釋數(shù)據(jù)锈玉。第一個(gè)參數(shù)指定了屬性的索引爪飘。我們都知道0是默認(rèn)的,但是當(dāng)我們開始使用 Shaders 的時(shí)候拉背,我們?cè)?Shader 中需要明確的設(shè)置索引师崎。第二個(gè)參數(shù)是構(gòu)成屬性的分量的個(gè)數(shù)(X,Y,Z共三個(gè)分量)。第三個(gè)參數(shù)是每個(gè)分量的數(shù)據(jù)類型椅棺。第四個(gè)參數(shù)表明屬性在管線中使用之前是否需要被規(guī)范化犁罩。第五個(gè)參數(shù)是在緩沖區(qū)中兩個(gè)相同屬性值之間的間隔的字節(jié)數(shù),當(dāng)只有一個(gè)屬性時(shí)(比如緩沖區(qū)只包含頂點(diǎn)位置)并且數(shù)據(jù)是緊挨著的两疚,那么我們?cè)O(shè)置這個(gè)值為0床估。如果我們有一個(gè)包含位置屬性和法線屬性的數(shù)組(每個(gè)屬性都是一個(gè)float類型的三維向量),我們將這個(gè)值設(shè)置為6*4=24诱渤。最后一個(gè)參數(shù)在多個(gè)屬性時(shí)是有用的顷窒。我們需要指定在緩沖區(qū)中存儲(chǔ)數(shù)據(jù)的偏移值,這樣管線才會(huì)找到數(shù)據(jù)。當(dāng)頂點(diǎn)的位置和法線相鄰存儲(chǔ)時(shí)時(shí)鞋吉,我們?cè)O(shè)置頂點(diǎn)位置的偏移值為0而頂點(diǎn)法線的偏移值為12鸦做。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
# unbind VAO
glBindVertexArray(0)
五、Render()方法的解析
??渲染工作是選擇我們準(zhǔn)備繪制的頂點(diǎn)數(shù)據(jù)谓着,然后請(qǐng)求進(jìn)行繪制泼诱。首先調(diào)用glBindVertexArray()來選擇作為頂點(diǎn)數(shù)據(jù)使用的頂點(diǎn)數(shù)組。正如前文中提到的赊锚,我們可以用這個(gè)函數(shù)來切換程序中保存的多個(gè)頂點(diǎn)數(shù)據(jù)對(duì)象集合治筒。
其次調(diào)用glDrawArrays()來實(shí)現(xiàn)頂點(diǎn)數(shù)據(jù)向OpenGL管線的傳輸。
glBindVertexArray(self.vao)
# 最后舷蒲,調(diào)用glDrawArrays函數(shù)來畫幾何體耸袜。這里就是 GPU 真正開始工作的地方。它現(xiàn)在將結(jié)合繪圖命令的參數(shù)牲平,然后創(chuàng)建一個(gè)三角形并將結(jié)果渲染到屏幕堤框。
glDrawArrays(GL_TRIANGLES, 0, 3)
# unbind VAO
glBindVertexArray(0)
六、運(yùn)行結(jié)果
七纵柿、代碼
"""
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()