PyOpenGLするにはここを抑えておかなければなりますまい。
OpenGLはShader移行期に旧APIをすっぱり切り捨てなかったので、
新旧の方法を混ぜて書く方法や暗黙のデオフォルト値に頼った不明瞭なコードディングが横行してしまいました。
Cでも十分にハマれるが、Pythonではもっと分かりにくいぞ。
レベル0: glBegin
いまや、obsoleteですけど三角形を描くにはこいつが一番楽ちん。
# coding: utf-8
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import sys
s=0.5
vertices=[
-s, -s, -s,
s, -s, -s,
s, s, -s,
-s, s, -s,
-s, -s, s,
s, -s, s,
s, s, s,
-s, s, s,
]
colors=[
0, 0, 0,
1, 0, 0,
0, 1, 0,
0, 0, 1,
0, 1, 1,
1, 0, 1,
1, 1, 1,
1, 1, 0,
]
indices=[
0, 1, 2, 2, 3, 0,
0, 4, 5, 5, 1, 0,
1, 5, 6, 6, 2, 1,
2, 6, 7, 7, 3, 2,
3, 7, 4, 4, 0, 3,
4, 7, 6, 6, 5, 4,
]
#
# 描画関数 glBegin
#
def draw_cube0():
glBegin(GL_TRIANGLES)
for i in range(0, len(indices), 3):
index=indices[i]*3
glColor3f(*colors[index:index+3])
glVertex3f(*vertices[index:index+3])
index=indices[i+1]*3
glColor3f(*colors[index:index+3])
glVertex3f(*vertices[index:index+3])
index=indices[i+2]*3
glColor3f(*colors[index:index+3])
glVertex3f(*vertices[index:index+3])
glEnd()
def initialize():
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
def resize(Width, Height):
# viewport
if Height == 0:
Height = 1
glViewport(0, 0, Width, Height)
# projection
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
yaw=0
pitch=0
def draw():
global yaw, pitch
# clear
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# view
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
yaw+=0.39
pitch+=0.27
glTranslatef(0.0, 0.0, -2.0)
glRotatef(yaw, 0, 1, 0)
glRotatef(pitch, 1, 0, 0)
# cube
draw_cube0()
glFlush()
##############################################################################
# glut driver
##############################################################################
def reshape_func(w, h):
resize(w, h == 0 and 1 or h)
def disp_func():
draw()
glutSwapBuffers()
if __name__=="__main__":
glutInit(sys.argv)
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
glutInitWindowSize(256, 256)
glutCreateWindow(b"vbo")
glutDisplayFunc(disp_func)
glutIdleFunc(disp_func)
glutReshapeFunc(reshape_func)
initialize()
glutMainLoop()
レベル1: glVertexPointer without VBO
配列が使える。別に速くは無い。
def draw_cube1():
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(3, GL_FLOAT, 0, colors)
glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, indices);
glDisableClientState(GL_COLOR_ARRAY)
glDisableClientState(GL_VERTEX_ARRAY);
レベル2: glVertexPointer with VBO without Shader
GPUに配列を転送する。速い。が、分かりにくい。
pythonの素の配列は受け付けてもらえないので、ctypesでバイト列を作ってAPIに渡す。
このやり方さっき初めて知りました。
buffers=None
def create_vbo():
buffers = glGenBuffers(3)
glBindBuffer(GL_ARRAY_BUFFER, buffers[0])
glBufferData(GL_ARRAY_BUFFER,
len(vertices)*4, # byte size
(ctypes.c_float*len(vertices))(*vertices), # 謎のctypes
GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, buffers[1])
glBufferData(GL_ARRAY_BUFFER,
len(colors)*4, # byte size
(ctypes.c_float*len(colors))(*colors), # 謎のctypes
GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2])
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
len(indices)*4, # byte size
(ctypes.c_uint*len(indices))(*indices), # 謎のctypes
GL_STATIC_DRAW)
return buffers
def draw_vbo():
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glVertexPointer(3, GL_FLOAT, 0, None);
glBindBuffer(GL_ARRAY_BUFFER, buffers[1]);
glColorPointer(3, GL_FLOAT, 0, None);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2]);
glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None);
glDisableClientState(GL_COLOR_ARRAY)
glDisableClientState(GL_VERTEX_ARRAY);
def draw_cube2():
global buffers
if buffers==None:
buffers=create_vbo()
draw_vbo()
レベル3: glVertexPointer with VBO with Shader
古いAPIとShaderを混ぜた書き方。古いコードがこうなっている場合があるかも。
シェーダー内の変数(gl_XXX)はすべてBuiltInで定義されるものを使っているので、
記述量は減っているが暗黙の部分が多い。
以前やったときは、新しいOpenGL関数を使うのに
http://glewpy.sourceforge.net/
が必要で準備がいろいろあったのだが、何もせずともglCreateProgram等の新しい関数が使えるようになってる。
class Shader(object):
def initShader(self, vertex_shader_source, fragment_shader_source):
# create program
self.program=glCreateProgram()
print('create program')
printOpenGLError()
# vertex shader
print('compile vertex shader...')
self.vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(self.vs, [vertex_shader_source])
glCompileShader(self.vs)
glAttachShader(self.program, self.vs)
printOpenGLError()
# fragment shader
print('compile fragment shader...')
self.fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(self.fs, [fragment_shader_source])
glCompileShader(self.fs)
glAttachShader(self.program, self.fs)
printOpenGLError()
print('link...')
glLinkProgram(self.program)
printOpenGLError()
def begin(self):
if glUseProgram(self.program):
printOpenGLError()
def end(self):
glUseProgram(0)
shader=None
def draw_cube3():
global shader, buffers
if shader==None:
shader=Shader()
shader.initShader('''
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_FrontColor = gl_Color;
}
''',
'''
void main()
{
gl_FragColor = gl_Color;
}
''')
buffers=create_vbo()
shader.begin()
draw_vbo()
shader.end()
レベル4: glVertexAttribPointer
今風のやつ。GLESとかWebGLはこれしか無いのだっけ。
が、Shader変数のLocationを取るやつと、uniform変数のsetter、glu, glMatrixの廃止による自前関数の準備等が長くなるので次回に送る。
ここら辺のセットアップをやってくれるユーティリティがあれば、PythonがShaderで遊ぶのに一番ライトな環境になりうる気がしてきた。
まとめ
毎回忘れて調べなおすので、自分の備忘録も兼ねてPyOpenGLによるVBOをまとめてみました。