2022/09/22(木) [n年前の日記]
#1 [python] PyOpenGLでビルボードにフォグをかけてみた
Python + PyOpenGL (OpenGL) を使ってビルボード関係の実験中。環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + PyOpenGL 3.1.6。
今回は、ビルボードにフォグをかけることができるのかどうかを試してみた。フォグというのは霧のこと。この場合、カメラとの距離に基づいてポリゴンの色を変えていくことで、遠近感があるように感じさせつつ、遠くのものははっきり見えない状態にして見た目を誤魔化す手法をフォグと呼んでいる。
動作させた結果は以下。フォグがかかっていることが分かるかと。
ちなみに、このサンプルは、ビルボードにとっては厳しい感じのカメラの動かし方をしている。回転させたり、上から見たりすると、立て看板らしさが実にハッキリと分かる…。小さいウインドウで見てる分には気にならないかもしれないけど、スクリプトを動かして、全画面表示にして眺めてみると、なかなか厳しいものが…。
今回は、ビルボードにフォグをかけることができるのかどうかを試してみた。フォグというのは霧のこと。この場合、カメラとの距離に基づいてポリゴンの色を変えていくことで、遠近感があるように感じさせつつ、遠くのものははっきり見えない状態にして見た目を誤魔化す手法をフォグと呼んでいる。
動作させた結果は以下。フォグがかかっていることが分かるかと。
ちなみに、このサンプルは、ビルボードにとっては厳しい感じのカメラの動かし方をしている。回転させたり、上から見たりすると、立て看板らしさが実にハッキリと分かる…。小さいウインドウで見てる分には気にならないかもしれないけど、スクリプトを動かして、全画面表示にして眺めてみると、なかなか厳しいものが…。
◎ ソース。 :
ソースは以下のような感じになった。python 05_draw_billboard_trees.py で実行できる。
_05_draw_billboard_trees.py
使用画像は以下。CC0 / Public Domain ってことで。前述のスクリプトと同じ場所に置いておくこと。
_tex.png
_05_draw_billboard_trees.py
import sys
import math
import random
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from PIL import Image
IMG_NAME = "tex.png"
# SCRW, SCRH = 1600, 900
SCRW, SCRH = 512, 512
FPS = 60
FLOOR_W = 10.0
TREES_MAX = 256
# USE_DEPTHMASK = True
USE_DEPTHMASK = False
# GET_DISTANCE = True
GET_DISTANCE = False
scr_w, scr_h = SCRW, SCRH
window = 0
# Rotation angle for the Quads
rotx = 0.0
roty = 0.0
objs = []
texture = 0
# lighting
light_ambient = [1.0, 1.0, 1.0, 1.0]
light_diffuse = [1.0, 1.0, 1.0, 1.0]
light_specular = [1.0, 1.0, 1.0, 1.0]
light_position = [5.0, 5.0, 10.0, 1.0]
# material
no_mat = [0.0, 0.0, 0.0, 1.0]
mat_ambient = [0.05, 0.1, 0.25, 1.0]
mat_diffuse = [0.0, 1.0, 0.0, 1.0]
mat_specular = [0.5, 0.5, 0.5, 1.0]
mat_emission = [0.2, 0.2, 0.2, 0.0]
no_shininess = [0.0]
low_shininess = [5.0]
high_shininess = [100.0]
class Obj:
def __init__(self, x, y, z, scale, kind):
self.x = x
self.y = y
self.z = z
self.scale = scale
uv = [
(0.5, 0.0), (0.75, 0.0), (0.5, 0.25), (0.75, 0.25),
(0.5, 0.5), (0.75, 0.5), (0.5, 0.75), (0.75, 0.75),
]
self.u, self.v = uv[kind]
def draw(self):
glAlphaFunc(GL_GREATER, 0.5)
glEnable(GL_ALPHA_TEST)
glEnable(GL_BLEND)
glEnable(GL_TEXTURE_2D)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glDisable(GL_LIGHTING)
glColor4f(1.0, 1.0, 1.0, 1.0) # color
glPushMatrix()
glTranslatef(self.x, self.y, self.z) # translate
m = glGetDoublev(GL_MODELVIEW_MATRIX)
m[0][0] = m[1][1] = m[2][2] = 1.0
m[0][1] = m[0][2] = 0.0
m[1][0] = m[1][2] = 0.0
m[2][0] = m[2][1] = 0.0
glLoadMatrixd(m)
w = 1.0 * self.scale
u, v = self.u, self.v
uw = 0.25
glBegin(GL_QUADS)
glTexCoord2f(u, v)
glVertex3f(-w, w * 2, 0) # Top Left
glTexCoord2f(u + uw, v)
glVertex3f(w, w * 2, 0.0) # Top Right
glTexCoord2f(u + uw, v + uw)
glVertex3f(w, 0.0, 0.0) # Bottom Right
glTexCoord2f(u, v + uw)
glVertex3f(-w, 0.0, 0.0) # Bottom Left
glEnd()
glPopMatrix()
glDisable(GL_ALPHA_TEST)
glDisable(GL_TEXTURE_2D)
def init_objs():
global objs
w = FLOOR_W
n = 4.0
kind = 0
i = 0
while i < TREES_MAX:
x = random.uniform(-w, w)
y = 0
z = random.uniform(-w, w)
if (x * x + z * z <= n * n):
continue
scale = random.uniform(1.0, 1.5)
objs.append(Obj(x, y, z, scale, kind))
kind = (kind + 1) % 4
i += 1
def draw_objs():
global objs
for obj in objs:
obj.draw()
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_floor():
"""Draw floor quads."""
w = FLOOR_W
u, v = 0.0, 0.0
uw = 0.5
glDisable(GL_LIGHTING)
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) # color
glPushMatrix()
glBegin(GL_QUADS)
glTexCoord2f(u, v)
glVertex3f(-w, 0.0, -w) # Top Left
glTexCoord2f(u + uw, v)
glVertex3f(w, 0.0, -w) # Top Right
glTexCoord2f(u + uw, v + uw)
glVertex3f(w, 0.0, w) # Bottom Right
glTexCoord2f(u, v + uw)
glVertex3f(-w, 0.0, w) # Bottom Left
glEnd()
glPopMatrix()
def draw_cube():
""" Draw cube."""
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient)
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse)
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular)
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess)
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat)
glPushMatrix()
glDisable(GL_TEXTURE_2D)
glColor3f(0.0, 1.0, 0.0)
if False:
glTranslatef(0.0, 2.0, 0.0)
glutSolidCube(4.0)
else:
glTranslatef(0.0, 1.2, 0.0)
glutSolidTeapot(2.0)
glPopMatrix()
def draw_gl():
global rotx, roty
glClearColor(0.85, 0.85, 0.85, 0.0) # background color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# set light
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient)
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse)
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular)
glLightfv(GL_LIGHT0, GL_POSITION, light_position)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glLoadIdentity() # Reset The View
# move camera
r = 16.0
ex = r * math.cos(math.radians(roty + 90.0))
ey = 6.0 + 4.0 * math.cos(math.radians(roty * 3))
ez = r * math.sin(math.radians(roty + 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_floor()
draw_cube()
draw_objs()
glutSwapBuffers()
def init_viewport_and_pers(width, height):
# 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(45.0, float(width) / float(height), 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
def InitGL(width, height):
glClearColor(0.85, 0.85, 0.85, 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
# setiing fog
fog_color = [0.85, 0.85, 0.85]
glEnable(GL_FOG)
glFogi(GL_FOG_MODE, GL_LINEAR)
glFogfv(GL_FOG_COLOR, fog_color)
glFogf(GL_FOG_DENSITY, 0.1)
glHint(GL_FOG_HINT, GL_DONT_CARE)
glFogf(GL_FOG_START, 8.0)
glFogf(GL_FOG_END, 25.0)
init_viewport_and_pers(width, height)
def resize_gl(width, height):
init_viewport_and_pers(width, height)
def on_timer(value):
global rotx, roty
rotx = 0.0
roty = roty + 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 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(draw_gl)
glutReshapeFunc(resize_gl)
glutKeyboardFunc(key_pressed)
# glutFullScreen()
# glutIdleFunc(draw_gl)
glutTimerFunc(int(1000 / FPS), on_timer, 0)
InitGL(SCRW, SCRH)
load_texture()
init_objs()
glutMainLoop()
if __name__ == "__main__":
print("Hit ESC key to quit.")
main()
使用画像は以下。CC0 / Public Domain ってことで。前述のスクリプトと同じ場所に置いておくこと。
_tex.png
◎ 覚書。 :
フォグをかけるように指定している部分は以下。
# setiing fog
fog_color = [0.85, 0.85, 0.85]
glEnable(GL_FOG)
glFogi(GL_FOG_MODE, GL_LINEAR)
glFogfv(GL_FOG_COLOR, fog_color)
glFogf(GL_FOG_DENSITY, 0.1)
glHint(GL_FOG_HINT, GL_DONT_CARE)
glFogf(GL_FOG_START, 8.0)
glFogf(GL_FOG_END, 25.0)
- glEnable(GL_FOG)、glDisable(GL_FOG) で、フォグの有効/無効を切り替えられる。
- fog_color がフォグの色。[1.0, 1.0, 1.0] なら真っ白になるし、[0.0, 0.0, 0.0] なら真っ黒になる。
- glFogfv(GL_FOG_COLOR, fog_color) で、フォグの色を指定している。
- glFogi(GL_FOG_MODE, GL_LINEAR) で、フォグのかかり方の種類を指定している。
- フォグのかかり方が GL_LINEAR の場合、GL_FOG_START、GL_FOG_END で、フォグがかかり始める距離を指定できるらしい。
- GL_FOG_DENSITY の値は、フォグのかかり方 (GL_FOG_MODE) が、GL_EXP、GL_EXP2 の場合に絡んでくるらしい。今回は GL_LINEAR にしているから、関係してこないっぽい。
◎ 画像の入手先について。 :
今回、以下のblogで、CC0 として公開されてる木の画像を、テクスチャとして使わせてもらった。ありがたや。blenderでレンダリングした画像らしい。
_Tree 01 Free CC0 Image | Graphics Learning
_Tree 02 Free CC0 Image | Graphics Learning
_Tree 03 Free CC0 Image | Graphics Learning
_Tree 04 Free CC0 Image | Graphics Learning
余談。一応自分も木の画像を公開していたりするのだけど、見た目のスタイルがバラバラなものだから、自分で作っておきながら使い辛くて…。
_trees - OneDrive
_mieki256's diary - 木のテクスチャをレンダリング中
_Tree 01 Free CC0 Image | Graphics Learning
_Tree 02 Free CC0 Image | Graphics Learning
_Tree 03 Free CC0 Image | Graphics Learning
_Tree 04 Free CC0 Image | Graphics Learning
余談。一応自分も木の画像を公開していたりするのだけど、見た目のスタイルがバラバラなものだから、自分で作っておきながら使い辛くて…。
_trees - OneDrive
_mieki256's diary - 木のテクスチャをレンダリング中
◎ 参考ページ :
[ ツッコむ ]
#2 [gimp] GIMP 2.10の全レイヤーをレガシーモードにしたい
Windows10 x64 21H2 + GIMP 2.10.32 Portable samj 版で、テクスチャ画像の加工修正をしているのだけど、GIMP 2.10 は GIMP 2.8 とレイヤーの実装が変わったので、そのままだと .psd で保存(エクスポート)した際などにレイヤーが全部統合された状態でエクスポートされてしまう。
.psd でエクスポートしても、各レイヤーをバラバラの状態にしたいなら、各レイヤーを「デフォルト」モードから「レガシー」モードに変更して、GIMP 2.8時代のレイヤーの状態にしてからエクスポートしないといけない。
しかし、一つ一つ、レイヤーをレガシーモードに切り替えていくのが面倒臭い。一気に切り替えることはできないものか。
ググってみたら、全レイヤーをレガシーモードに切り替えてくれるプラグイン(Python-Fuスクリプト)を公開してくれてる方がいらっしゃった。ありがたや。使わせてもらおう…。
_To Legacy Mode Script/Plug-in - GIMP LEARN
to_legacy_modes(2).zip をダウンロード/解凍して、to_legacy_modes.py を GIMPユーザーディレクトリ/plug-ins/ 以下にコピーした。
オリジナル版は、Python-Fu というメニュー項目が増えるのだろうか…。自分の環境に合わせて、以下のように修正して使うことにした。レイヤーに対して処理をするのだから、「レイヤー」の下にあったほうが迷わなくて済むよな、みたいな。
ちなみに、GIMP 2.10 のレイヤーの、「デフォルト」「レガシー」は、互換性がない合成モードがいくつかあるらしいので…。安易にモードを切り替えると見た目が違ってしまう可能性があることに注意。まあ、例えば「標準」で重ねてるだけの凝ったことをしていない画像であれば、問題無さそうではあるけれど…。
さておき。件のスクリプトの作者様は、他にも色々なスクリプトを公開してくれているようで。ありがたや。そのうちじっくり眺めてみよう…。
_GIMP 2.10 Scripts/Plug-ins - GIMP LEARN
.psd でエクスポートしても、各レイヤーをバラバラの状態にしたいなら、各レイヤーを「デフォルト」モードから「レガシー」モードに変更して、GIMP 2.8時代のレイヤーの状態にしてからエクスポートしないといけない。
しかし、一つ一つ、レイヤーをレガシーモードに切り替えていくのが面倒臭い。一気に切り替えることはできないものか。
ググってみたら、全レイヤーをレガシーモードに切り替えてくれるプラグイン(Python-Fuスクリプト)を公開してくれてる方がいらっしゃった。ありがたや。使わせてもらおう…。
_To Legacy Mode Script/Plug-in - GIMP LEARN
to_legacy_modes(2).zip をダウンロード/解凍して、to_legacy_modes.py を GIMPユーザーディレクトリ/plug-ins/ 以下にコピーした。
オリジナル版は、Python-Fu というメニュー項目が増えるのだろうか…。自分の環境に合わせて、以下のように修正して使うことにした。レイヤーに対して処理をするのだから、「レイヤー」の下にあったほうが迷わなくて済むよな、みたいな。
"<Image>/Python-Fu/To Legacy Modes...", #Menu path
↓
"<Image>/Layer/All Layers/To Legacy Modes...",
ちなみに、GIMP 2.10 のレイヤーの、「デフォルト」「レガシー」は、互換性がない合成モードがいくつかあるらしいので…。安易にモードを切り替えると見た目が違ってしまう可能性があることに注意。まあ、例えば「標準」で重ねてるだけの凝ったことをしていない画像であれば、問題無さそうではあるけれど…。
さておき。件のスクリプトの作者様は、他にも色々なスクリプトを公開してくれているようで。ありがたや。そのうちじっくり眺めてみよう…。
_GIMP 2.10 Scripts/Plug-ins - GIMP LEARN
[ ツッコむ ]
以上、1 日分です。