實作流程主要分為幾個步驟,匯入相關模組(PySide和PyOpenGL)、設置繪製對象(Widget or Canvas)、定義GL Shaders、定義VBO和VAO、繪製物件。如下圖所示:
這個環節需要匯入 Qt 官網推出的 Python 介面模組 - PySide,然後我們可以直接用 OpenGL 模組直接對 QtOpenGLWidgets 做繪圖的動作。
import sys
from PySide6.QtWidgets import *
from PySide6.QtOpenGLWidgets import *
from PySide6.QtGui import QSurfaceFormat
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
以繪製 2D 三角形為例,撰寫 GLSL 常見的方式有兩種,一種是將兩個 Shaders 的執行內文各自寫在另一個文字檔中,而另一種是將執行內文各自寫成一個字串。
選擇哪一種都不是重點,因為內文都是一樣的,Vertex Shader 和 Fragment Shader 內文如下:
/* Vertex Shader */
#version 330 core
layout (location=0) in vec3 vertexPos;
layout (location=1) in vec3 vertexColor;
out vec3 fragmentColor;
void main()
{
gl_Position = vec4(vertexPos, 1.0);
fragmentColor = vertexColor;
}
/* Fragment Shader */
#version 330 core
in vec3 fragmentColor;
out vec4 color;
void main()
{
color = vec4(fragmentColor, 1.0);
}
若將 GLSL 執行內文寫在文字檔,讀取文字檔的方法如下:
with open("shaders/vertex.txt", 'r') as f:
vertex_src = f.readlines() # src type is list
with open("shaders/fragment.txt", 'r') as f:
fragment_src = f.readlines()
若將 GLSL 執行內文分別定義成字串,可直接調用以下方法:
VERTEX_SHADER = """
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 frag_color;
void main()
{
gl_Position = vec4(position, 1.0);
frag_color = color;
}
"""
FRAGMENT_SHADER = """
#version 330 core
in vec3 frag_color;
out vec4 color;
void main()
{
color = vec4(frag_color, 1.0);
}
"""
self.shader_program = compileProgram(
compileShader(VERTEX_SHADER, GL_VERTEX_SHADER),
compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
)
注意 compileProgram 函式已經包含了幾個繪製管線化的步驟,你可以看到參數中有 GLSL 原始碼變數和 GL 巨集定義的 Shader 類型,函式執行包含了 glShaderSource 綁定 Shader 原始碼、glCompileShader 編譯 GLSL 程式碼、glCreateProgram 新增一個 Program、glAttchShader 將Vertex 和 Fragment Shader 附加到新增的 Program 以及 glLinkProgram 鏈結新增的 Program。
完整攤開來看的話,步驟流程如下:
def createShaders(self):
self.vertexShader = glCreateShader(GL_VERTEX_SHADER)
self.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
with open("shaders/vertex.txt", 'r') as f:
vertex_src = f.readlines() # src type is list
with open("shaders/fragment.txt", 'r') as f:
fragment_src = f.readlines()
glShaderSource(self.vertexShader, vertex_src)
glShaderSource(self.fragmentShader, fragment_src)
glCompileShader(self.vertexShader)
print("Vertex Shader compile log: "+str(glGetShaderInfoLog(self.vertexShader)))
glCompileShader(self.fragmentShader)
print("Fragment Shader compile log: "+str(glGetShaderInfoLog(self.fragmentShader)))
def createProgram(self):
self.program = glCreateProgram()
glAttachShader(self.program, self.vertexShader)
glAttachShader(self.program, self.fragmentShader)
glLinkProgram(self.program)
glUseProgram(self.program)
VAO & VBO 是決定繪製什麼的主要環節,VAO 全名為 Vertex Array Object,VBO 則是 Vertex Buffer Object。
# 三角形頂點數據
vertices = np.array([
-0.5, -0.5, 0.0, 1.0, 0.0, 0.0, # 左下
0.5, -0.5, 0.0, 0.0, 1.0, 0.0, # 右下
0.0, 0.5, 0.0, 0.0, 0.0, 1.0 # 上
], dtype=np.float32)
# 創建 VAO 和 VBO
self.VAO = glGenVertexArrays(1)
VBO = glGenBuffers(1)
glBindVertexArray(self.VAO)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * 4, None)
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * 4, ctypes.c_void_p(12))
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
glBindBuffer 和 glBindVertexArray 具有向 gl 表示目前要進行操作的對象是哪一個 Buffer 和 Array。
glVertexAttribPointer 的參數分別是 layout location (對應到 GLSL 和 glEnableVertexAttribArray,這是在定義資料分配的位址)、資料數、資料型態、是否正規化、單一頂點所占用的記憶體大小、offset(資料分配的起始點)。
這裡雖然是繪製三角型為範例,但在繪製 3D 物體時,繪製三角面組成 3D 物體也是常見的方式。
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(self.shader_program)
glBindVertexArray(self.VAO)
glDrawArrays(GL_TRIANGLES, 0, 3)
glBindVertexArray(0)
為了確保正確性,再調用 glDrawArrays 之前,最好再調用一次 glUseProgram 和 glBindVertexArray。
Last updated: