0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでOpenGLをはじめる

Last updated at Posted at 2025-08-03

1. はじめに

本記事ではPythonでOpenGLの基本をまとめる。なぜPythonですか?
Cmake含めて、Cの環境設定はしんどいので(長年間経験したので)、Pythonを選択した。
また、PythonでやるとOS環境など無視して、どのOSでもすぐできるようにと考える。
目的はあくまでC言語の勉強ではなく、CGプログラミングを勉強すること。

もし、Cでやりたいであれば、 和歌山大学の床井浩平先生による GLUTによる『手抜き』OpenGL入門をが参考にしてください。

また、以下のgithubですべてのコードを公開する予定。
https://github.com/fermanda/Python_CG_Programming

1.1 環境

  • Windows 11
  • VS Code
  • Python 3.8 (venv仮想環境)

1.2 セットアップ

まず、必要なパッケージをインストールする。

pip install PyOpenGL PyOpenGL_accelerate glfw

以上。
Pythonはめちゃくちゃ便利。
では、ささっと本番のCGプログラミングを開始。
その前、PyOpenGLのsyntaxは、なんとC言語とまったく一緒!
なので、床井先生の「GLUTによる『手抜き』OpenGL入門」は参考にする。

2. ウィンドウ

2.1 ウィンドウ生成

ウィンドウ作成方法はいくらでもある。しかし今回はGLUTよりGLFWを利用する。
一つの理由はGLFWの開発は進めている。

create_window.py
import glfw
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *


def main():
    # glfwを初期化する
    if not glfw.init():
        return

    # "Hello World"のタイトルで640 × 480のウィンドウ作成する
    window = glfw.create_window(640, 480, "Hello World", None, None)
    if not window:
        glfw.terminate()
        return
    
    # 作成したウィンドウをメインにする(レンダリングのためとか)
    glfw.make_context_current(window)
    
    # ウィンドウ開ける状態に以下のプロセスをループする。
    while not glfw.window_should_close(window):
        # ここからレンダリングプロセスをバッファーで行う

        # レンダリングされているバッファーを当たれしいバッファーと交換する。
        glfw.swap_buffers(window)

        # イベント(キーなどの入力)を認識と集まる。
        glfw.poll_events()

    glfw.terminate()

if __name__ == "__main__":
    main()

上のコードで新しい640 × 480のウィンドウを作成する。実行したら以下の結果になる。

image.png

2.2 ウィンドウの調整する

ウィンドウの背景の色も変われる。
glClearColor(R, G, B, Alpha)を0.0から1.0の範囲で以下のように設定する。

create_window.py
...
    while not glfw.window_should_close(window):
        # ここからレンダリングプロセスをバッファーで行う
        
        # ウインドウの色変更する
        glClearColor(0.0, 0.0, 1.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT)
...

実行したら、以下の結果となる。

image.png

3. 2D図形

次は2D図形を描くプログラム作りましょう。

3.1 2D図形を描く

以下のコードで四角の2D図形を描けた。

draw_2d.py
import glfw
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

def main():
    if not glfw.init():
        return

    window = glfw.create_window(640, 480, "Hello World", None, None)
    if not window:
        glfw.terminate()
        return

    glfw.make_context_current(window)
    
    while not glfw.window_should_close(window):
        # ここからレンダリングプロセスをバッファーで行う
        glClear(GL_COLOR_BUFFER_BIT)

        glBegin(GL_LINE_LOOP)
        glVertex2d(-0.9, -0.9)
        glVertex2d(0.9, -0.9)
        glVertex2d(0.9, 0.9)
        glVertex2d(-0.9, 0.9)
        glEnd()

        glFlush()

        glfw.swap_buffers(window)
        glfw.poll_events()

    glfw.terminate()

if __name__ == "__main__":
    main()

上のコードはほどんど前と一緒ですが、注目すべき部分はレンダリングのところです。

...
        glBegin(GL_LINE_LOOP)
        glVertex2d(-0.9, -0.9)
        glVertex2d(0.9, -0.9)
        glVertex2d(0.9, 0.9)
        glVertex2d(-0.9, 0.9)
        glEnd()

        glFlush()
...

glBeginのパラメタ以下の画像で説明する。

image.png
図参考:https://tokoik.github.io/opengl/libglut.html

上のプログラムはGL_LINE_LOOPを使って、四角を描けた。描くためにはglBegin()で開始、glEnd()で終了する。glFlush()を実行したら、以前のすべてのglコマンドを実行する。

コードを実行したら、以下の結果となる。
image.png

3.2 カラーを入れよう

白い薄い四角がレンダーされたが、あまり魅力じゃないので、色を付けてみよう。
vertexを描く前に色を指定する。

...
        glBegin(GL_LINE_LOOP)
        glColor3d(1.0, 1.0, 0.0) # ここで色を指定する
        glVertex2d(-0.9, -0.9)
        glVertex2d(0.9, -0.9)
        glVertex2d(0.9, 0.9)
        glVertex2d(-0.9, 0.9)
        glEnd()

        glFlush()
...

上のコードを実行したら以下の結果となる。

image.png

それはglBegin()がまだGL_LINE_LOOPのてめ、色変われる部分がエッジしかない。
四角全体を色を付けたりするためにはGL_POLYGONに変更する。
GL_POLYGONに変更と実行すると以下の結果となる。

image.png

色も、各頂点に指定できる。例えば以下のように指定しよう。

...
        glBegin(GL_POLYGON)
        glColor3d(1.0, 1.0, 0.0)
        glVertex2d(-0.9, -0.9)
        glColor3d(1.0, 0.0, 1.0)
        glVertex2d(0.9, -0.9)
        glColor3d(0.0, 1.0, 1.0)
        glVertex2d(0.9, 0.9)
        glColor3d(1.0, 1.0, 1.0)
        glVertex2d(-0.9, 0.9)
        glEnd()
...

上のコードを実行したら以下の結果となる。

image.png

4. プロジェクション

3D図形を2D画面に表示するためには、いくつかプロジェクション方法はあります。
しかしメインとしたはOrtographic ProjectionとPerspective Projection。
Ortographic Projectionまたはパラレルプロジェクションには2つに似た図形を遠いさを関係なく、同じサイズに見える。
Perspective Projectionには2つに似た図形を遠い方の図形は小さいに見える。

image.png
図参考:https://www.labri.fr/perso/nrougier/python-opengl/#id14

4.1 Orthographic Projection

以前に設定した四角は、各エッジに同じサイズね設定しますが、以下のような640×480の画面に表示されたのは上下と左右のエッジのサイズが違うように見える。

image.png

修正するためには今回Projectionを設定しましょう。

import glfw
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480

def projection_matrix(w, h):
    glViewport(0, 0, w, h)
    glLoadIdentity()
    glOrtho(-w/200, w/200, -h/200, h/200, -1.0, 1.0)
...
    glfw.make_context_current(window)
    projection_matrix(WINDOW_WIDTH, WINDOW_HEIGHT)
...

image.png

4.2 Perspective Projection

...
def projection_matrix(w, h):
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(35.0, w/h, 1.0, 200.0)
    glMatrixMode(GL_MODELVIEW)
...
...
    while not glfw.window_should_close(window):
        glClearColor(0.8, 0.8, 0.8, 1.0)
        glClear(GL_COLOR_BUFFER_BIT)

        glLoadIdentity()
        gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
...

image1-99.png

5. 3D図形

5.1 3D図形を描く

次は3D図形を描きましょう。以前はglVertex2d()のコマンドで、2Dポイントあるいはvertexを描けるようにしますが、3Dポイントを描くためには glVertex3d()のコマンドで描けます。
早速にコードを書くのも難しいので、とりあずポイントの座標を想像しましょう。
以下のようにキューブの中心が(0,0,0)の座標に設定しましょう。

rect21.png

では、各ポイントの座標を以下のように措定できます。

VERTEXES = [
    [-1, 1, -1],   # A
    [1, 1, -1 ],   # B
    [1, -1, -1],   # C
    [-1, -1, -1],  # D
    [-1, 1, 1],  # E
    [1, 1, 1],   # F
    [1, -1, 1],  # G
    [-1, -1, 1], # H
]

キューブでは1つのループで描けないので、今回はGL_LINESで各ペアのポイントを線を描く。
上のキューブの画像を参考にして、以下のようにペアを措定する。

EDGES = [
    [0, 1], # AB
    [1, 2], # BC
    [2, 3], # CD
    [3, 0], # DA
    [4, 5], # EF
    [5, 6], # FG
    [6, 7], # GH
    [7, 4], # HE
    [0, 4], # AE
    [1, 5], # BF
    [2, 6], # CG
    [3, 7]  # DH
]

これで3Dキューブを以下のコードで描ける。

import glfw
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

# 1.0とは大きいので、0.2に変更しましょう。
VERTEXES = [
    [-0.2, 0.2, -0.2],   # A
    [0.2, 0.2, -0.2],    # B
    [0.2, -0.2, -0.2],   # C
    [-0.2, -0.2, -0.2],  # D
    [-0.2, 0.2, 0.2],  # E
    [0.2, 0.2, 0.2],   # F
    [0.2, -0.2, 0.2],  # G
    [-0.2, -0.2, 0.2], # H
]

EDGES = [
    [0, 1], # AB
    [1, 2], # BC
    [2, 3], # CD
    [3, 0], # DA
    [4, 5], # EF
    [5, 6], # FG
    [6, 7], # GH
    [7, 4], # HE
    [0, 4], # AE
    [1, 5], # BF
    [2, 6], # CG
    [3, 7]  # DH
]

def main():
    if not glfw.init():
        return
    
    window = glfw.create_window(640, 480, "Hello World", None, None)
    if not window:
        glfw.terminate()
        return
    
    glfw.make_context_current(window)
    
    while not glfw.window_should_close(window):
        glClear(GL_COLOR_BUFFER_BIT)
        glClearColor(0.8, 0.8, 0.8, 1.0)

        glBegin(GL_LINE)
        glColor3d(0.0, 0.0, 0.0)
        for edge in EDGES:
            for vx in edge:
                glVertex3dv(VERTEXES[vx])
                # glVertex3d()を利用する場合はいかとなります
                # glVertex3d(VERTEXES[vx][0], VERTEXES[vx][1], VERTEXES[vx][2])
        glEnd()
        glFlush()

        glfw.swap_buffers(window)
        glfw.poll_events()

    glfw.terminate()

if __name__ == "__main__":
    main()

上のコード実行すると、以下のように普通の四角の図形しか見えてないですね。
それは正面の視点から見えるです。今後はちゃんとまともなキューブを見えるようにするため、表示方法を学ぶ。

image.png

6. トランスフォーメーション (図形変換)

import glfw
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480

lx = 0.4

VERTEXES = [
    [-lx, lx, lx],   # A
    [lx, lx, lx],   # B
    [lx, -lx, lx],   # C
    [-lx, -lx, lx],   # D
    [-lx, lx, -lx],   # E
    [lx, lx, -lx],   # F
    [lx, -lx, -lx],   # G
    [-lx, -lx, -lx],   # H
]

EDGES = [
    [0, 1],
    [1, 2],
    [2, 3],
    [3, 0],
    [4, 5],
    [5, 6],
    [6, 7],
    [7, 4],
    [0, 4],
    [1, 5],
    [2, 6],
    [3, 7]
]

def set_view(w, h):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(35.0, w/h, 1.0, 200.0)
    glMatrixMode(GL_MODELVIEW)

def main():
    if not glfw.init():
        return
    
    window = glfw.create_window(WINDOW_WIDTH, WINDOW_HEIGHT, "Hello World", None, None)
    if not window:
        glfw.terminate()
        return
    
    glfw.make_context_current(window)
    set_view(WINDOW_WIDTH, WINDOW_HEIGHT)

    r = 1

    while not glfw.window_should_close(window):
        glClear(GL_COLOR_BUFFER_BIT)
        glClearColor(0.8, 0.8, 0.8, 1.0)
        
        glLoadIdentity()
        gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
        glRotated(r, 0.0, 1.0, 0.0)
        r += 1
        if r > 360: r=0

        glBegin(GL_LINES)
        glColor3d(0.0, 0.0, 0.0)
        for edge in EDGES:
            for vx in edge:
                glVertex3dv(VERTEXES[vx])
        glEnd()
        glFlush()

        glfw.swap_buffers(window)
        glfw.poll_events()

    glfw.terminate()

if __name__ == "__main__":
    main()

cube2.gif

6. 3D空間変換

7. 3Dファイルフォーマット

7.1 OBJファイルをレンダリングする

...
def load_obj(file_path):
    vertices = []
    faces = []
    with open(file_path, 'r') as file:
        for line in file:
            if line.startswith('v '):  # Vertex
                vertices.append(list(map(float, line.strip().split()[1:])))
            elif line.startswith('f '):  # Face
                face = [int(idx.split('/')[0]) - 1 for idx in line.strip().split()[1:]]
                faces.append(face)
    return np.array(vertices, dtype=np.float32), np.array(faces, dtype=np.int32)



vertices, faces = load_obj("Star/Star_01.obj")
...
...
        glBegin(GL_TRIANGLES)
        glColor3d(1.0, 1.0, 0.0)
        for face in faces:
            for vertex_idx in face:
                glVertex3fv(vertices[vertex_idx])
        glEnd()
...

star2.gif

8. 操作と入力

9. アニメーション

つづき準備中

参考資料

  1. Learn OpenGL - Graphics Programming
  2. GLUTによる「手抜き」OpenGL入門
  3. OpenGL Documentation
  4. Python & OpenGL for Scientific Visualization
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?