mieki256's diary



2022/10/03(月) [n年前の日記]

#1 [python] PyOpenGLでSkyboxを描画

Windows10 x64 21H2 + Python 3.9.13 64bit + PyOpenGL 3.1.6 で、Skybox を描画できるかテスト。

以下のような見た目になった。マウスカーソルの位置に応じてカメラ角度が変わるようにしてみた。それっぽい感じになったような気がする。

ソース。 :

ソースは以下。

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

IMG_NAME = "montorfano_cubemap.png"
# IMG_NAME = "alps_field_cubemap.png"
# IMG_NAME = "bell_park_cubemap.png"

# SCRW, SCRH = 1600, 900
SCRW, SCRH = 512, 512
FPS = 60
BOXW = 16

scr_w, scr_h = SCRW, SCRH
window = 0

mouse_x = 0
mouse_y = 0
cam_rot_x = 0.0
cam_rot_y = 0.0
cam_pos = (0, 0, 0)

texture = 0


def load_texture():
    global texture

    # load image by using PIL
    im = Image.open(IMG_NAME)
    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
        print("Set GL_RGB")
    elif im.mode == "RGBA":
        ttype = GL_RGBA
        print("Set 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 draw_skybox():
    glEnable(GL_BLEND)
    glEnable(GL_TEXTURE_2D)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glColor4f(1.0, 1.0, 1.0, 1.0)
    glDisable(GL_ALPHA_TEST)

    w = BOXW

    glPushMatrix()

    m = glGetDoublev(GL_MODELVIEW_MATRIX)
    m[3][0] = m[3][1] = m[3][2] = 0.0
    glLoadMatrixd(m)

    va = 0.0005
    # va = 0.0

    pntlist = [
        # x, y, z
        [-1, +1, -1],  # 0
        [+1, +1, -1],  # 1
        [+1, +1, +1],  # 2
        [-1, +1, +1],  # 3
        [-1, -1, -1],  # 4
        [+1, -1, -1],  # 5
        [+1, -1, +1],  # 6
        [-1, -1, +1],  # 7
    ]

    pos = [
        [
            # index, u, v
            [0, 0.25, 0.50 + va],
            [1, 0.50, 0.50 + va],
            [5, 0.50, 0.75 - va],
            [4, 0.25, 0.75 - va],
        ],
        [
            [1, 0.50, 0.50 + va],
            [2, 0.75, 0.50 + va],
            [6, 0.75, 0.75 - va],
            [5, 0.50, 0.75 - va],
        ],
        [
            [2, 0.75, 0.50 + va],
            [3, 1.00, 0.50 + va],
            [7, 1.00, 0.75 - va],
            [6, 0.75, 0.75 - va],
        ],
        [
            [3, 0.00, 0.50 + va],
            [0, 0.25, 0.50 + va],
            [4, 0.25, 0.75 - va],
            [7, 0.00, 0.75 - va],
        ],
        [
            [3, 0.25 + va, 0.25 + va],
            [2, 0.50 - va, 0.25 + va],
            [1, 0.50 - va, 0.50 - va],
            [0, 0.25 + va, 0.50 - va],
        ],
        [
            [4, 0.25 + va, 0.75 + va],
            [5, 0.50 - va, 0.75 + va],
            [6, 0.50 - va, 1.00 - va],
            [7, 0.25 + va, 1.00 - va],
        ],
    ]

    for poly in pos:
        glBegin(GL_QUADS)
        for idx, u, v in poly:
            x, y, z = pntlist[idx]
            glTexCoord2f(u, v)  # set u, v
            glVertex3f(w * x, w * y, w * z)
        glEnd()

    glPopMatrix()


def draw_gl():
    global cam_rot_x, cam_rot_y, cam_pos

    glClearColor(0.2, 0.4, 0.8, 0.0)  # background color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glLoadIdentity()  # Reset The View

    # move camera
    r = 8.0
    rr = r * math.cos(math.radians(cam_rot_x))
    ey = r * math.sin(math.radians(cam_rot_x))
    ex = rr * math.cos(math.radians(cam_rot_y + 90.0))
    ez = rr * math.sin(math.radians(cam_rot_y + 90.0))
    tx, ty, tz = 0.0, 0.0, 0.0
    gluLookAt(ex, ey, ez, tx, ty, tz, 0, 1, 0)
    cam_pos = (ex, ey, ez)

    draw_skybox()

    glDisable(GL_TEXTURE_2D)
    glDisable(GL_ALPHA_TEST)

    glutSwapBuffers()


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

    scr_w, scr_h = width, height

    # Reset The Current Viewport And Perspective Transformation
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()  # Reset The Projection Matrix

    # Calculate The Aspect Ratio Of The Window
    # gluPerspective(fovy, aspect, zNear, zFar )
    gluPerspective(60.0, float(width) / float(height), 0.1, 100.0)
    glMatrixMode(GL_MODELVIEW)


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

    init_viewport_and_pers(width, height)


def resize_gl(width, height):
    init_viewport_and_pers(width, height)


def on_timer(value):
    global cam_rot_x, cam_rot_y
    # cam_rot_y += 0.25
    glutPostRedisplay()
    glutTimerFunc(int(1000 / FPS), on_timer, 0)


def key_pressed(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 mouse_move(x, y):
    global mouse_x, mouse_y, cam_rot_x, cam_rot_y, scr_w, scr_h

    dw = scr_w / 2
    dh = scr_h / 2
    mouse_x = (x - dw) / dh
    mouse_y = (y - dh) / dh

    cam_rot_x = 90.0 * mouse_y
    cam_rot_y = 180.0 * mouse_x


def main():
    global window

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

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

    window = glutCreateWindow(b"Draw Skybox")

    glutDisplayFunc(draw_gl)
    glutReshapeFunc(resize_gl)
    glutKeyboardFunc(key_pressed)
    glutPassiveMotionFunc(mouse_move)
    # glutFullScreen()

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

    InitGL(SCRW, SCRH)

    load_texture()

    glutMainLoop()


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


使用画像は以下。512 x 512 x 6枚の画像を 2048 x 2048 x 1枚に収めている。

_montorfano_cubemap.png (2.93MB)
_alps_field_cubemap.png (3.41MB)
_bell_park_cubemap.png (3.06MB)


実行は以下。
python 01_draw_skybox.py


ちなみに、画像は、Poly Haven でCC0で公開されている、正距円筒図法のHDRIを元にして作成した。ありがたや。

_Poly Haven
_正距円筒図法
_(Equirectangular projection)

作り方は以下でメモしてある。

_Skybox用のテクスチャを作成

覚書。 :

Skyboxは、カメラを取り囲むような大きな箱を表示して、その箱に遠景相当のテクスチャを貼ることで実現するけれど。カメラが移動して箱との位置関係が変わってしまうとちょっと困る。

そのあたりは、箱を描画する前に設定する変換行列に対して、平行移動成分のみを 0.0 にすることで対処する。これで、カメラの角度は反映されるけど、位置関係は常時変わらない状態になる。

OpenGL (PyOpenGL) + GLUT で、マウスカーソルが移動したことを検出するには、glutPassiveMotionFunc() を使う。指定した関数には x と y の引数が渡されるけど、この x, y には、ウインドウ内におけるマウスカーソルの座標値が入ってる。

箱の境界部分に黒い線が入ってしまって悩んだけれど、テクスチャの座標指定値をほんの少し弄ってみたら消えてくれた。draw_skybox() 内の va = 0.0005 が弄ってる量。本当にこういう対処でいいのかどうか分からんけど…。ちなみに、以下が黒い線が入ってしまっている状態。

ss_01_draw_skybox_nogood.png

以上です。

過去ログ表示

Prev - 2022/10 - Next
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 31

カテゴリで表示

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


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

Powered by hns-2.19.6, HyperNikkiSystem Project