mieki256's diary



2022/09/11() [n年前の日記]

#1 [python] PyOpenGLでテクスチャを表示

PyOpenGL を使ってテクスチャ画像を描画してみたい。

環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + PyOpenGL 3.1.6 + freeglut 3.0.0 64bit。

実行結果は以下。ポリゴン1枚(GL_QUADS)にテクスチャを貼り付けることができている。

ソース。 :

こんな感じのソースになった。

_01_draw_quads_tex.py
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import sys
from PIL import Image


IMGS = [
    "img_rgb.png",
    "img_rgba.png",
    "img_grayscale.png",
    "img_palette.png",
]

IMG_IDX = 0

SCRW, SCRH = 512, 512
FPS = 60
PANGLE = 60.0

window = 0

# Rotation angle for the Quads
rotx = 0.0
roty = 0.0

texture = 0


def load_texture():
    global texture

    # load image by using PIL
    im = Image.open(IMGS[IMG_IDX])
    w, h = im.size
    print("Image: %d x %d, %s" % (w, h, im.mode))

    if im.mode == "RGB":
        # RGB convert to RGBA
        im.putalpha(alpha=255)
    elif im.mode == "L" or im.mode == "P":
        # Grayscale, Index Color convert to RGBA
        im = im.convert("RGBA")

    raw_image = im.tobytes()

    ttype = GL_RGBA
    if im.mode == "RGB":
        ttype = GL_RGB
    elif im.mode == "RGBA":
        ttype = GL_RGBA

    glBindTexture(GL_TEXTURE_2D, glGenTextures(1))

    # glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4)

    # set texture
    glTexImage2D(
        GL_TEXTURE_2D,      # target
        0,                  # MIPMAP level
        ttype,              # texture type (RGB, RGBA)
        w,                  # texture image width
        h,                  # texture image height
        0,                  # border width
        ttype,              # texture type (RGB, RGBA)
        GL_UNSIGNED_BYTE,   # data is unsigne char
        raw_image,          # texture data pointer
    )

    glClearColor(0, 0, 0, 0)
    glShadeModel(GL_SMOOTH)

    # set texture repeat
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)

    # set texture filter
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
    # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
    # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
    # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND)


def InitGL(width, height):
    glClearColor(0.125, 0.25, 0.5, 0.0)  # background color
    glClearDepth(1.0)  # Enables Clearing Of The Depth Buffer
    glDepthFunc(GL_LESS)  # The Type Of Depth Test To Do
    glEnable(GL_DEPTH_TEST)  # Enables Depth Testing
    glShadeModel(GL_SMOOTH)  # Enables Smooth Color Shading

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()  # Reset The Projection Matrix

    # Calculate The Aspect Ratio Of The Window
    gluPerspective(PANGLE, float(width) / float(height), 0.1, 100.0)

    glMatrixMode(GL_MODELVIEW)


def ReSizeGLScene(width, height):
    # Prevent A Divide By Zero If The Window Is Too Small
    if height == 0:
        height = 1

    # Reset The Current Viewport And Perspective Transformation
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(PANGLE, float(width) / float(height), 0.1, 100.0)
    glMatrixMode(GL_MODELVIEW)


def DrawGLScene():
    global rotx, roty

    glClearColor(0.125, 0.25, 0.5, 0.0)  # background color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()  # Reset The View

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glEnable(GL_BLEND)
    glEnable(GL_TEXTURE_2D)

    glPushMatrix()
    glTranslatef(0.0, 0.0, -2.0)  # translate

    # Rotate
    glRotatef(roty, 0.0, 1.0, 0.0)
    glRotatef(rotx, 1.0, 0.0, 0.0)

    glColor4f(1.0, 1.0, 1.0, 1.0)  # color

    # draw quads
    x, y, z = 1.0, 1.0, 0.0
    glBegin(GL_QUADS)

    glTexCoord2f(0.0, 0.0)  # set u, v
    glVertex3f(-x, y, z)  # Top Left

    glTexCoord2f(1.0, 0.0)
    glVertex3f(x, y, z)  # Top Right

    glTexCoord2f(1.0, 1.0)
    glVertex3f(x, -y, z)  # Bottom Right

    glTexCoord2f(0.0, 1.0)
    glVertex3f(-x, -y, z)  # Bottom Left

    glEnd()

    glPopMatrix()
    glDisable(GL_TEXTURE_2D)
    glDisable(GL_BLEND)

    glutSwapBuffers()


def on_timer(value):
    global rotx, roty
    rotx = -10.0
    roty = roty + 1.0
    glutPostRedisplay()
    glutTimerFunc(int(1000 / FPS), on_timer, 0)


def keyPressed(key, x, y):
    # If escape is pressed, kill everything.
    ESCAPE = b"\x1b"
    if key == ESCAPE or key == b'q':
        if glutLeaveMainLoop:
            glutLeaveMainLoop()
        else:
            sys.exit()


def main():
    global window

    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)

    glutInitWindowSize(SCRW, SCRH)
    # glutInitWindowPosition(0, 0)

    window = glutCreateWindow(b"Draw texture")

    glutDisplayFunc(DrawGLScene)
    glutReshapeFunc(ReSizeGLScene)
    # glutFullScreen()

    # glutIdleFunc(DrawGLScene)
    glutTimerFunc(int(1000 / FPS), on_timer, 0)

    glutKeyboardFunc(keyPressed)
    InitGL(SCRW, SCRH)

    load_texture()

    glutMainLoop()


if __name__ == "__main__":
    print("Hit ESC key to quit.")
    main()

使用画像は以下。

_img_rgb.png
_img_rgba.png
_img_grayscale.png
_img_palette.png

少し解説。 :

テクスチャ画像の読み込みと、OpenGL にテクスチャ画像を登録(?)するあたりは、load_texture() で処理している。

テクスチャ画像の読み込みには、PIL の Image を使った。更に、PIL (の Image) を使って、画像を必ず RGBA に変換してから画像を使う。

OpenGL に渡すためのテクスチャ画像はベタ画像データ(1ピクセル4バイトがピクセル数分ずらずらと並んでる感じのデータ)になってないといけないけれど、それについては PIL の .tobytes() で取得できる。

OpenGLにテクスチャ画像を登録するには、glTexImage2D() を利用する。

描画時にテクスチャを利用したい場合は、glEnable(GL_TEXTURE_2D) を呼んでから描画指定をする。逆に、テクスチャを利用しない場合は、glDisable(GL_TEXTURE_2D) を呼ぶ。

複数のテクスチャを描画。 :

前述のサンプルはテクスチャ画像を1つしか描画してないけれど、複数のテクスチャ画像を読み込んで描画できないか試してみた。

実行結果は以下。



複数のテクスチャを描画することができている。

ソースは以下。

_02_draw_quads_tex_m.py
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import sys
from PIL import Image


IMGS = [
    [GL_TEXTURE0, "img_rgb.png"],
    [GL_TEXTURE1, "img_rgba.png"],
    [GL_TEXTURE2, "img_grayscale.png"],
    [GL_TEXTURE3, "img_palette.png"],
]

SCRW, SCRH = 512, 512
FPS = 60
PANGLE = 45.0

window = 0

# Rotation angle for the Quads
rotx = 0.0
roty = 0.0

textures = []


def load_texture(filename):
    # load image by using PIL
    im = Image.open(filename)
    w, h = im.size
    print("Image: %d x %d, %s" % (w, h, im.mode))

    if im.mode == "RGB":
        # RGB convert to RGBA
        im.putalpha(alpha=255)
    elif im.mode == "L" or im.mode == "P":
        # Grayscale and Index Color convert to RGBA
        im = im.convert("RGBA")

    raw_image = im.tobytes()

    ttype = GL_RGBA
    if im.mode == "RGB":
        ttype = GL_RGB
    elif im.mode == "RGBA":
        ttype = GL_RGBA

    id = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, id)

    # glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4)

    # set texture
    glTexImage2D(
        GL_TEXTURE_2D,      # target
        0,                  # MIPMAP level
        ttype,              # texture type (RGB, RGBA)
        w,                  # texture image width
        h,                  # texture image height
        0,                  # border width
        ttype,              # texture type (RGB, RGBA)
        GL_UNSIGNED_BYTE,   # data is unsigne char
        raw_image,          # texture data pointer
    )

    glClearColor(0, 0, 0, 0)
    glShadeModel(GL_SMOOTH)

    # set texture repeat
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)

    # set texture filter
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
    # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
    # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
    # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND)

    return id


def InitGL(width, height):
    glClearColor(0.15, 0.6, 0.3, 0.0)  # background color
    glClearDepth(1.0)  # Enables Clearing Of The Depth Buffer
    glDepthFunc(GL_LESS)  # The Type Of Depth Test To Do
    glEnable(GL_DEPTH_TEST)  # Enables Depth Testing
    glShadeModel(GL_SMOOTH)  # Enables Smooth Color Shading

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()  # Reset The Projection Matrix

    # Calculate The Aspect Ratio Of The Window
    gluPerspective(PANGLE, float(width) / float(height), 0.1, 100.0)

    glMatrixMode(GL_MODELVIEW)


def ReSizeGLScene(width, height):
    # Prevent A Divide By Zero If The Window Is Too Small
    if height == 0:
        height = 1

    # Reset The Current Viewport And Perspective Transformation
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(PANGLE, float(width) / float(height), 0.1, 100.0)
    glMatrixMode(GL_MODELVIEW)


def DrawGLScene():
    global rotx, roty, textures

    glClearColor(0.15, 0.6, 0.3, 0.0)  # background color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()  # Reset The View

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glEnable(GL_BLEND)
    glEnable(GL_TEXTURE_2D)

    r = 1.05
    poslist = [
        [-r, r],
        [r, r],
        [-r, -r],
        [r, -r],
    ]

    ry = roty
    for i, tex_id in enumerate(textures):
        tx, ty = poslist[i]

        glPushMatrix()

        # change texture
        glBindTexture(GL_TEXTURE_2D, tex_id)

        # translate
        glTranslatef(tx, ty, -6.0)

        # Rotate
        glRotatef(ry, 0.0, 1.0, 0.0)
        glRotatef(rotx, 1.0, 0.0, 0.0)

        glColor4f(1.0, 1.0, 1.0, 1.0)  # color

        # draw quads
        x, y, z = 1.0, 1.0, 0.0
        glBegin(GL_QUADS)

        glTexCoord2f(0.0, 0.0)  # set u, v
        glVertex3f(-x, y, z)  # Top Left

        glTexCoord2f(1.0, 0.0)
        glVertex3f(x, y, z)  # Top Right

        glTexCoord2f(1.0, 1.0)
        glVertex3f(x, -y, z)  # Bottom Right

        glTexCoord2f(0.0, 1.0)
        glVertex3f(-x, -y, z)  # Bottom Left

        glEnd()

        glPopMatrix()

        ry *= 1.2

    glDisable(GL_TEXTURE_2D)
    glDisable(GL_BLEND)

    glutSwapBuffers()


def on_timer(value):
    global rotx, roty
    rotx = 0.0
    roty = roty + 1.0
    glutPostRedisplay()
    glutTimerFunc(int(1000 / FPS), on_timer, 0)


def keyPressed(key, x, y):
    # If escape is pressed, kill everything.
    ESCAPE = b"\x1b"
    if key == ESCAPE or key == b'q':
        if glutLeaveMainLoop:
            glutLeaveMainLoop()
        else:
            sys.exit()


def main():
    global window

    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)

    glutInitWindowSize(SCRW, SCRH)
    # glutInitWindowPosition(0, 0)

    window = glutCreateWindow(b"Draw textures")

    glutDisplayFunc(DrawGLScene)
    glutReshapeFunc(ReSizeGLScene)
    # glutFullScreen()

    # glutIdleFunc(DrawGLScene)
    glutTimerFunc(int(1000 / FPS), on_timer, 0)

    glutKeyboardFunc(keyPressed)
    InitGL(SCRW, SCRH)

    for _, fn in IMGS:
        id = load_texture(fn)
        textures.append(id)
        glEnable(GL_TEXTURE_2D)

    glutMainLoop()


if __name__ == "__main__":
    print("Hit ESC key to quit.")
    main()

使用画像は以下。

_img_rgb.png
_img_rgba.png
_img_grayscale.png
_img_palette.png


少し解説。

glBindTexture() を使えば、何番目のテクスチャを利用してその後の処理をするかを指定できる。

ただ…。複数のテクスチャ画像を描画した後で気づいたけれど、考えてみたら、テクスチャ画像を1枚だけ渡して、そのテクスチャ内のここからここまで切り出して利用、といった形でも済むのだよな…。特にゲーム関係はそんな感じでテクスチャを利用しているはずだし…。いやまあ、テクスチャをリピートさせて描画する時は、またちょっと違ってくるのだろうか。

以上、1 日分です。

過去ログ表示

Prev - 2022/09 -
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project