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)にテクスチャを貼り付けることができている。
環境は、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
使用画像は以下。
_img_rgb.png
_img_rgba.png
_img_grayscale.png
_img_palette.png
_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) を呼ぶ。
テクスチャ画像の読み込みには、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
使用画像は以下。
_img_rgb.png
_img_rgba.png
_img_grayscale.png
_img_palette.png
少し解説。
glBindTexture() を使えば、何番目のテクスチャを利用してその後の処理をするかを指定できる。
ただ…。複数のテクスチャ画像を描画した後で気づいたけれど、考えてみたら、テクスチャ画像を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枚だけ渡して、そのテクスチャ内のここからここまで切り出して利用、といった形でも済むのだよな…。特にゲーム関係はそんな感じでテクスチャを利用しているはずだし…。いやまあ、テクスチャをリピートさせて描画する時は、またちょっと違ってくるのだろうか。
[ ツッコむ ]
以上です。