PyOpenGLでHDRピクセル
久々に開発ネタです. 最近仕事で必要になり,10年ぶりくらいにOpenGLを触っています.PythonからPyOpenGL経由です.GPU出現以降のmodern OpenGLは真面目に書いたことがなかったので,勉強すること盛り沢山. PyOpenGL — The Python OpenGL Binding PyOpenGLでHDRIを扱う時にちょっと引っかかった問題があったので,忘備録を兼ねてここにまとめます.数年前からリアルタイムでもHDRレンダリングが流行っていたので,知っている人には今更な話題ですが... 解決にあたって@K240さんに多大なる助言を戴きました.有難うございます. さて,今時のVFXたるもの,ピクセル値はHDRがディフォルトです.[0, 1.0]ということはまずない.ライティングや反射マップにはほぼ必ずHDRIが使われるので,ハイライト部分は1.0を超えます.OpenGLでも普通に1.0を超える値は使えますし,GLSLシェーダでもfloating pointで計算ができます. ところが,一度ディスプレイバッファに値が書き込まれてしまうと,どうやらピクセル値は[0, 1.0]にclampされてしまうようなのです.具体的には,glReadPixels()を使って得られる値がclampされています.glCopyTexImage2D()なんかも同じ.これは困る. さらに調べを進めると,どうやらmodern OpenGLにはframe buffer object(FBO)という物があり,オフスクリーンの描画用バッファとして使え,更にデータタイプとして32-bit floating pointが指定できます.つまり,一度このFBOに描画することでシェーダを実行し,そこから値をコピーすることで生の計算結果を取得できます.ただしこのFBOはオフスクリーンなので,結果を画面に表示するには再度ディスプレイバッファに描画してやる必要があります. 計算結果をコピーするにはglGetTexImage()という関数を使うのですが,C言語ではこの関数は値を返さず,結果の書込み先としてポインタを渡すことになっています.しかしPythonにはポインタがありませんから,関数が値を返します.つまり次のように書きます.glGet*()やglGen*()系の関数は大抵こういう仕様になっています. result = glGetTexImagef(GL_TEXTURE_2D, 0, GL_RGBA) そして,ここでハマったのですが,上記で得たresultは三次元配列で,そのレイアウトは[y][x][c](cはチャンネル)となっています.てっきり[x][y][c]だと思い込んでいたので,場所が一致せずに悩んでしまいました... 以下にサンプルコードを置いておきます.これは@K240さんがC++で書いて下さったコードをPythonに移植したものです.実行して表示される画面を左クリックすると,その場所の生ピクセル値がコンソールに出力されます. hdr_pixels.py 今回,GPUで色の計算(カラースペース変換とか)をやってみたのですが,さすがに速いですね.大きな画像だと普通にやるよりマジで何十倍も速いです.Pythonだと何百倍じゃないでしょうか.昔から3Dをリアルタイムでやる事にあまり興味はないのですが,2Dの画像処理を高速化するためにGPUを使うのは楽しそうだと思っています.コンプ作業が楽しくなりそう. 最近だとOpenCLとか使うんでしょうね?もう少し重たい計算もやってみたくなりました. [2011/4/6 追記] どうやらPyOpenGLのglGetTexImage()にはバグがあるようで,テクスチャが正方形でない時に関数が返す配列がおかしいです.ご注意下さい.