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
使用画像は以下。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)
実行は以下。
ちなみに、画像は、Poly Haven でCC0で公開されている、正距円筒図法のHDRIを元にして作成した。ありがたや。
_Poly Haven
_正距円筒図法
_(Equirectangular projection)
作り方は以下でメモしてある。
_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 が弄ってる量。本当にこういう対処でいいのかどうか分からんけど…。ちなみに、以下が黒い線が入ってしまっている状態。
そのあたりは、箱を描画する前に設定する変換行列に対して、平行移動成分のみを 0.0 にすることで対処する。これで、カメラの角度は反映されるけど、位置関係は常時変わらない状態になる。
OpenGL (PyOpenGL) + GLUT で、マウスカーソルが移動したことを検出するには、glutPassiveMotionFunc() を使う。指定した関数には x と y の引数が渡されるけど、この x, y には、ウインドウ内におけるマウスカーソルの座標値が入ってる。
箱の境界部分に黒い線が入ってしまって悩んだけれど、テクスチャの座標指定値をほんの少し弄ってみたら消えてくれた。draw_skybox() 内の va = 0.0005 が弄ってる量。本当にこういう対処でいいのかどうか分からんけど…。ちなみに、以下が黒い線が入ってしまっている状態。
[ ツッコむ ]
以上です。
