HOME ABOUT CONTACT

技術(Python) - PySide實作PyOpenGL繪製的基本架構

Rain February 22, 2025
Outline

1. 基本架構圖

2. 匯入模組

3. GL Shaders

4. VAO & VBO

5. 繪製物件

基本架構圖 top

實作流程主要分為幾個步驟,匯入相關模組(PySide和PyOpenGL)、設置繪製對象(Widget or Canvas)、定義GL Shaders、定義VBO和VAO、繪製物件。如下圖所示:

匯入模組 top

這個環節需要匯入 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
                    

GL Shaders top

以繪製 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 top

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(資料分配的起始點)。

繪製物件 top

這裡雖然是繪製三角型為範例,但在繪製 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:

Related Article List

  1. CAD工作對應徵EDA職缺有哪些幫助
  2. CAD工程師在做什麼
  3. 什麼是GDSII Stream Format
  4. 技術(Python) - 3D Brain Viewer
  5. 技術 - CUDA 重點整理和範例