PythonでOpenGLを理解する

Muhammad Junaid Khalid によるこの記事では、OpenGL の基本的なコンセプトとセットアップについて説明しましたが、今回は、より複雑なオブジェクトの作成方法と、それらのアニメーションの方法について見ていきます。

OpenGLは非常に古く、その正しい使い方や理解のためのチュートリアルは、オンラインではあまり見かけません。

なぜなら、すべてのトッププレイヤーは、すでに新しい技術にどっぷり浸かっているからです。

現代のOpenGLのコードを理解するには、まず、賢明なマヤのゲーム開発者が石板に書いた古代の概念を理解する必要があります。

この記事では、あなたが知っておく必要があるいくつかの基本的なトピックに飛びます。

  • 基本的なマトリックス操作
  • 合成変換
  • 参照点を含む変換
  • モデリング デモンストレーション

最後のセクションでは、PythonのライブラリであるPyGameとPyOpenGLを使って実際にOpenGLを使用する方法を見ていきます。

次回は、Pythonと上記のライブラリでOpenGLを使う方法について、さらに詳しく見ていきます。

行列の基本操作

OpenGLの多くの関数を適切に使用できるようにするためには、いくつかの幾何学が必要です。

空間内のすべての点は、デカルト座標で表すことができます。

座標は、X、Y、Zの値を定義することによって、任意の点の位置を表します。

座標は1×3の行列というか、3次元のベクトルとして使うことになります(行列については後ほど説明します)。

以下にいくつかの座標の例を示します。

a=(5,3,4) b=(9,1,2)a=(5,3,4) b=(9,1,2)a=()
5,のように
3,のように
4の場合
)。

のようになります。

b=()
9,のように
1,の
2)となります。

</math
abは空間上の点で、その x 座標はそれぞれ59、y 座標は31` 、といった具合になります。

コンピュータグラフィックスでは、通常のデカルト座標の代わりに同次座標を使うことが多くなっています。

これは基本的に同じものですが、ユーティリティ・パラメータが追加されており、簡単のために常に 1 とします。

つまり、a の正規の座標が (5,3,4) であれば、それに対応する同次座標は (5,3,4,1) となります。

これには多くの幾何学的理論が隠されていますが、この記事にはあまり必要ないでしょう。

次に、幾何学的な変換を表現するのに不可欠な道具として、行列があります。

行列は基本的に2次元の配列(この場合サイズはn*n、行と列の数が同じであることが非常に重要です)です。

行列の演算は、多くの場合、足し算、引き算など、とても簡単です。

しかし、もちろん、最も重要な操作は、最も複雑なものである乗算でなければなりません。

それでは、基本的な行列演算の例を見てみましょう。

A=⎡⎢⎣125619552⎤⎥⎦−Example matrix ⎡⎢⎣125619552⎤⎥⎦+⎡⎢⎣25101221810104⎤⎥⎦=⎡⎢⎣37151832715156⎤⎥⎦−Matrix addition ⎡⎢⎣24101221810104⎤⎥⎦−⎡⎢⎣125619552⎤⎥⎦=⎡⎢⎣125619552⎤⎥⎦−Matrix subtraction A=[125619552]-Example matrix [125619552]+[25101221810104]=[37151832715156]-Matrix addition [24101221810104]-[125619552]=[125619552]-Matrix subtraction A=
<モ[<モ
1
</mtd
2の場合
</mtd
5の場合
</mtd
</mtr
6</mn
</mtd
1となります。

</mtd
9</mn
</mtd
</mtr
5</mn
</mtd
5</mn
</mtd
2の場合
</mtd
]のように
-のようになります。

行列の例。

のようになります。

[のように
1
</mtd
2の場合
</mtd
5の場合
</mtd
</mtr
6</mn
</mtd
1となります。

</mtd
9</mn
</mtd
</mtr
5</mn
</mtd
5</mn
</mtd
2の場合
</mtd
]のように
+の場合
[の場合
のようになります。

2
</mtd
5となります。

</mtd
10の場合
</mtd
</mtr
12</mn
</mtd
2の場合
</mtd
18</mn
</mtd
</mtr
10</mn
</mtd
10の場合
</mtd
4の場合
</mtd
]となります。

=の場合
[の場合
のようになります。

3
</mtd
7となります。

</mtd
15の場合
</mtd
</mtr
18</mn
</mtd
3の場合
</mtd
27の場合
</mtd
</mtr
15</mn
</mtd
15</mn
</mtd
6の場合
</mtd
</mtr
]のように

-のように
行列の加算。

のようになります。

[の場合
2
</mtd
4となります。

</mtd
10の場合
</mtd
</mtr
12</mn
</mtd
2の場合
</mtd
18</mn
</mtd
</mtr
10</mn
</mtd
10の場合
</mtd
4の場合
</mtd
]となります。

-のように
[の場合
のようになります。

1
</mtd
2の場合
</mtd
5の場合
</mtd
</mtr
6</mn
</mtd
1となります。

</mtd
9</mn
</mtd
</mtr
5</mn
</mtd
5</mn
</mtd
2の場合
</mtd
]のように
=の場合
[の場合
のようになります。

1
</mtd
2の場合
</mtd
5の場合
</mtd
</mtr
6</mn
</mtd
1となります。

翻訳

移動とは、文字通り設定されたベクトルによってオブジェクトを移動させる行為です。

変換の影響を受けるオブジェクトは、その形状や向きが変わることはなく、空間内で移動するだけです(そのため、平行移動は移動変換に分類されます)。

並進は次のような行列形式で記述することができます。

T=⎡⎢


⎢⎣100tx010ty001tz0001⎤⎥


⎥⎦T=[100tx010ty001tz0001]T=[の場合
1
</mtd
0の場合
</mtd
0の場合
</mtd

txの
</mtd
</mtr
0となります。

</mtd
1の場合
</mtd
0の場合
</mtd

tyの
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
1の場合
</mtd

tzの
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように

</math
t-sは、オブジェクトのx,y,z` の位置の値がどれだけ変化するかを表します。

つまり、任意の座標を並進行列 T で変換すると、次のようになります。

[x,y,z]∗T=[tx+x,ty+y,tz+z][x,y,z]∗T=[tx+x,ty+y,tz+z]
[x,y,z]*T=[t_x+x,t_y+y,t_z+z]

変換は以下のOpenGL関数で実装する。

void glTranslatef(GLfloat tx, GLfloat ty, GLfloat tz);


このように、並進行列の形がわかれば、OpenGLの関数を理解するのは非常に簡単で、これはOpenGLのすべての変換に当てはまります。

GLfloat`は気にしないでください、これはOpenGLが複数のプラットフォームで動作するための賢いデータ型です、このように見ることができます。

typedef float GLfloat;
typedef double GLdouble;
typedef someType GLsomeType;


これは、たとえば char の記憶領域がすべてのシステムで同じであるとは限らないので、必要な措置です。

ローテーション

回転は、2つの要素に依存するという単純な理由から、少し複雑な変換です。

  • ピボット。3次元空間のどの線(または2次元空間の点)を中心に回転させるか。
  • 量。量:回転させる量(度またはラジアン単位

このため、まず2次元空間での回転を定義する必要があり、そのためにちょっとした三角法が必要になります。

以下に簡単なリファレンスを示します。

となります。

これらの三角関数は、直角三角形(角の1つが90度でなければならない)の内部でのみ使用できます。

2次元空間の物体を頂点 (0,0) の周りに角度 A だけ回転させるための基本回転行列は次のようになる。

⎡⎢⎣cosA-sinA0sinAcosA0001⎤[cosA-sinA0sinAcosA0001]は次のようになります。

[のように
cosA
</mtd
-の
sinA
</mtd
0の場合
</mtd
sinA
</mtd
cosA
</mtd
0の場合
</mtd
0となります。

繰り返しますが、3行目と3列目は、他の変換の上に変換を積み重ねたい場合(OpenGLではそうします)のためのもので、なぜそれがあるのか、今すぐには理解できなくてもかまいません。

合成変換の例では、物事が明確になるはずです。

ここまでが2D空間での話ですが、次は3D空間に移りましょう。

3D空間では、オブジェクトを任意の線の周りに回転させることができる行列を定義する必要があります。

ある賢者が言いました。

“Keep it simple and stupid!” 幸いなことに、数学の魔術師たちは、かつてそれを単純かつ愚直なまでにやり遂げた。

直線を中心とした回転は、いくつかの変換に分解することができます。

  • x軸の周りの回転
  • y軸の周りの回転
  • z軸周りの回転
  • ユーティリティの並進(これは後で触れます)

つまり、3次元回転のために構成する必要があるのは、角度 A による x, y, z 軸周りの回転を表す行列の3つだけなのです。

Rx=⎡ ⎢。


⎢⎣10000cosA−sinA00sinAcosA00001⎤⎥

⎥⎦Ry=⎡⎡⎢。


⎢⎣cosA0sinA00100−sinA0cosA00001⎤⎥

⎥⎦⎡⎢⎦

⎢⎣cosA−sinA00sinAcosA0000100001⎤⎥

⎥⎦Rx=[10000cosA−sinA00sinAcosA00001]Ry=[cosA0sinA00100−sinA0cosA00001]Rz=[cosA−sinA00sinAcosA0000100001]の場合
Rの場合
xのようになります。

=[の場合
のようになります。

1
</mtd
0の場合
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
cosA
</mtd
-の
sinA
</mtd
0の場合
</mtd
0となります。

</mtd
sinA
</mtd
cosA
</mtd
0の場合
</mtd
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように

Ryの
=[の場合
のようになります。

cosA
</mtd
0の場合
</mtd
sinA
</mtd
0の場合
</mtd
0となります。

</mtd
1の場合
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
-sinA
</mtd
0の場合
</mtd
cosA
</mtd
0の場合
</mtd
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように

Rzの
=[の場合
のようになります。

cosA
</mtd
-の
sinA
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
sinA
</mtd
cosA
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
1の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように

</math
3次元回転は、以下のOpenGLの関数で実装されています。

void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);


  • 角度の単位は0〜360です。
  • x,y,z`: 回転が実行されるベクトル

スケーリング

スケーリングとは、対象となるオブジェクトの任意の寸法にスカラーを乗じることです。

このスカラーは、オブジェクトを縮小したい場合は &lt;1 となり、オブジェクトを拡大したい場合は &gt;1 となります。

スケーリングは次のような行列形式で記述することができる。

S=⎡⎢


⎢⎣sx0000sy0000sz00001⎤⎥


⎥⎦S=[sx0000sy0000sz00001]S=
<モ[<モ
sxの
</mtd
0</mn
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
syの
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd

szの
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように

</math
sx, sy, sz は、対象オブジェクトの x, y, z の値と掛け合わされるスカラーです。

任意の座標をスケーリングマトリクス S で変換すると、次のようになります。

[x,y,z]∗S=[sx∗x,sy∗y,sz∗z][x,y,z]∗S=[sx∗x,sy∗y,sz∗z]が得られます。

[のようになります。

xの場合
,のように
y,のように
z]のようになります。

*S=[のようになります。

の場合
sxのようになります。

*x,sy*y,sz*z]のようになります。

この変換は、オブジェクトを係数kで拡大縮小する(つまり、結果のオブジェクトが2倍になる)ときに特に有用で、これはsx=sy=sz=kと設定することで実現されます。

[x,y,z]∗S=[sx∗x,sy∗y,sz∗z][x,y,z]∗S=[sx∗x,sy∗y,sz∗z]を設定することで実現できます。

[のようになります。

xの場合
,のように
y,のように
z]のようになります。

*S=[のようになります。

の場合
sxのようになります。

*x,sy*y,sz*z]のようになります。

</math
拡大縮小の特殊なケースとして、反射が知られています。

これは、sx、sy、または sz のいずれかを -1 に設定することで実現します。

これは、オブジェクトの座標の1つの符号を反転させることを意味します。

もっと簡単に言うと、オブジェクトを x, y, z 軸の反対側に置くのです。

この変換は、どのような平面の反射に対しても働くように修正することができますが、今のところ本当に必要ではありません。

void glScalef(GLfloat sx, GLfloat sy, GLfloat sz);


合成変換

合成変換とは、1つ以上の基本的な変換(上に挙げたもの)からなる変換のことです。

変換 AB は、対応する変換行列 M_aM_b を行列乗算することで合成されます。

これは非常にわかりやすいロジックのように思えますが、しかし、混乱させることもあります。

  • 行列の乗算は commutable ではない。

A∗B≠B∗A A と B は行列A∗B≠B∗A A と B は行列 である。

A*とする。

B≠とする。

B*Aのようになります。

A、Bは行列である。

</math
* これらの変換の一つ一つに逆変換があります。

逆変換とは、元の変換を打ち消すような変換のことです。

T=⎡⎢

⎢⎣ 100a010b001c0001⎤⎥

⎥⎦T-1=⎡⎢

⎢⎣100−a010−b001−c0001⎤⎥

⎥⎦E=⎡⎢

⎢⎣1000010000100001⎤⎥

⎥⎦ T∗T−1=ET=[100a010b001c0001]T−1=[100−a010−b001−c0001]E=[1000010000100001] T∗T−1=ET=[の場合
1
</mtd
0の場合
</mtd
0の場合
</mtd
a
</mtd
</mtr
0
</mtd
1の場合
</mtd
0の場合
</mtd
b
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
1の場合
</mtd
c
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように
Tの場合。

-となります。

1
</mrow

=の場合
[の場合
1
</mtd
0の場合
</mtd
0の場合
</mtd
-の場合
a
</mtd
</mtr
0
</mtd
1の場合
</mtd
0の場合
</mtd
-の場合
b
</mtr
0となります。

</mtd
0の場合
</mtd
1の場合
</mtd
-の場合
c
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように
E=の場合
[の場合
のようになります。

1
</mtd
0の場合
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
1の場合
</mtd
0の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
1の場合
</mtd
0の場合
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように
のようになります。

</mtext
のようになります。

T*の場合
T-のようになります。

1
</mrow

=E
</math
* 合成変換の逆変換を行う場合は、利用する要素の順序を変更する必要がある。

(A∗B∗C)-1=C-1∗B-1∗A-1(A∗B∗C)-1=C-1∗B-1∗A-1となります。

()
A*B*とする。

Cの場合
)となります。

の場合。

-1
</mrow

=の場合
の場合
C (日本語)
-のように
1
</mrow

*となります。

の場合
Bの場合。

-のようになります。

1
</mrow

*となります。

の場合
A (日本語)
-のようになります。

1
</mrow

</math
要は、行列利用の位相的順序は、ビルのある階まで昇るのと同じように、非常に重要だということです。

もしあなたが1階にいて、4階に行きたいなら、まず3階に行き、次に4階に行く必要があります。

しかし、もし2階まで降りたければ、3階まで行ってから2階へ行かなければなりません(トポロジーの順序が逆です)。

参照元を含む変換について

前述したように、ある空間内の特定の点を基準にして変換を行う必要がある場合、例えば、原点 O=(0,0,0) ではなく、3次元空間の参照点 A=(a,b,c) の周りを回転する場合、すべてを T(-a,-b,-c) だけ移動して、その参照点 AO に変換する必要があります。

そして、必要な変換を行い、それが終わったら、すべてを T(a,b,c) だけ平行移動させ、元の原点 O が再び座標 (0,0,0) となるようにします。

この例の行列形式は

T∗M∗T-1=⎡⎢となります。


⎢⎣100−a010−b001−c0001⎤⎥

⎥⎦∗M∗⎡⎢

⎢⎣ 100A010B001C0001⎤⎥

⎥⎦T∗M∗T−1=[100−a010−b001−c0001]∗M∗[100a010b001c0001]TMTの場合。

-のようになります。

1
</mrow

=の場合
[の場合
1
</mtd
0の場合
</mtd
0の場合
</mtd
-の場合
a
</mtd
</mtr
0
</mtd
1の場合
</mtd
0の場合
</mtd
-の場合
b
</mtr
0となります。

</mtd
0の場合
</mtd
1の場合
</mtd
-の場合
c
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように
*M*[のように
のようになります。

1
</mtd
0の場合
</mtd
0の場合
</mtd
a
</mtd
</mtr
0
</mtd
1の場合
</mtd
0の場合
</mtd
b
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
1の場合
</mtd
c
</mtd
</mtr
0となります。

</mtd
0の場合
</mtd
0の場合
</mtd
1の場合
</mtd
]のように

</math
ここで、Mはオブジェクトに対して行いたい変換です。

これらの行列演算を学ぶことの要点は、OpenGLがどのように動作するかを完全に理解できるようになることです。

モデリング・デモンストレーション

それでは、簡単なモデリングデモを見てみましょう。

PythonでOpenGLを使うには、PyGameとPyOpenGLという2つのモジュールを使用します。

$ python3 -m pip install -U pygame --user
$ python3 -m pip install PyOpenGL PyOpenGL_accelerate


なぜなら、3冊のグラフィック理論書を自分に課すのは冗長なので、PyGameライブラリを使うことにします。

これは、プロジェクトの初期化から実際のモデリングとアニメーションまでのプロセスを本質的に短縮するものです。

まず始めに、OpenGLとPyGameの両方から必要なものをインポートする必要があります。

import pygame as pg
from pygame.locals import *


from OpenGL.GL import *
from OpenGL.GLU import *


次の例では、型にはまらないオブジェクトをモデリングするために必要なのは、複雑なオブジェクトをより小さく単純な断片に分解する方法だけであることがわかります。

これらの関数のいくつかはまだ何をするのかわからないので、OpenGLがどのように使われるのかがわかるように、コード自体に表面的な定義をしておきます。

次回の記事では、これらすべてを詳しく説明します。

これは、OpenGLを使った作業がどのようなものか、基本的なアイデアを提供するだけのものです。

def draw_gun():
    # Setting up materials, ambient, diffuse, specular and shininess properties are all
    # different properties of how a material will react in low/high/direct light for
    # example.
    ambient_coeffsGray = [0.3, 0.3, 0.3, 1]
    diffuse_coeffsGray = [0.5, 0.5, 0.5, 1]
    specular_coeffsGray = [0, 0, 0, 1]
    glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_coeffsGray)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_coeffsGray)
    glMaterialfv(GL_FRONT, GL_SPECULAR, specular_coeffsGray)
    glMateriali(GL_FRONT, GL_SHININESS, 1)


# OpenGL is very finicky when it comes to transformations, for all of them are global,
    # so it's good to seperate the transformations which are used to generate the object
    # from the actual global transformations like animation, movement and such.
    # The glPushMatrix() ----code----- glPopMatrix() just means that the code in between
    # these two functions calls is isolated from the rest of your project.
    # Even inside this push-pop (pp for short) block, we can use nested pp blocks,
    # which are used to further isolate code in it's entirety.
    glPushMatrix()


glPushMatrix()
    glTranslatef(3.1, 0, 1.75)
    glRotatef(90, 0, 1, 0)
    glScalef(1, 1, 5)
    glScalef(0.2, 0.2, 0.2)
    glutSolidTorus(0.2, 1, 10, 10)
    glPopMatrix()


glPushMatrix()
    glTranslatef(2.5, 0, 1.75)
    glScalef(0.1, 0.1, 1)
    glutSolidCube(1)
    glPopMatrix()


glPushMatrix()
    glTranslatef(1, 0, 1)
    glRotatef(10, 0, 1, 0)
    glScalef(0.1, 0.1, 1)
    glutSolidCube(1)


glPopMatrix()


glPushMatrix()
    glTranslatef(0.8, 0, 0.8)
    glRotatef(90, 1, 0, 0)
    glScalef(0.5, 0.5, 0.5)
    glutSolidTorus(0.2, 1, 10, 10)
    glPopMatrix()


glPushMatrix()
    glTranslatef(1, 0, 1.5)
    glRotatef(90, 0, 1, 0)
    glScalef(1, 1, 4)
    glutSolidCube(1)
    glPopMatrix()


glPushMatrix()
    glRotatef(8, 0, 1, 0)
    glScalef(1.1, 0.8, 3)
    glutSolidCube(1)
    glPopMatrix()


glPopMatrix()


def main():
    # Initialization of PyGame modules
    pg.init()
    # Initialization of Glut library
    glutInit(sys.argv)
    # Setting up the viewport, camera, backgroud and display mode
    display = (800,600)
    pg.display.set_mode(display, DOUBLEBUF|OPENGL)
    glClearColor(0.1,0.1,0.1,0.3)
    gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
    gluLookAt(5,5,3,0,0,0,0,0,1)


glTranslatef(0.0,0.0, -5)
    while True:
        # Listener for exit command
        for event in pg.event.get():
            if event.type == pg.QUIT:
                pg.quit()
                quit()


# Clears the screen for the next frame to be drawn over
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        ############## INSERT CODE FOR GENERATING OBJECTS ##################        draw_gun()
        ####################################################################        # Function used to advance to the next frame essentially
        pg.display.flip()
        pg.time.wait(10)


このコードの束から、私たちは次のようなものを得ました。

結論

OpenGLは非常に古く、それを正しく使い、理解するためのチュートリアルは、オンラインではあまり見かけません。

なぜなら、すべてのトップドッグはすでに新しい技術に膝まで浸かっているからです。

OpenGLを正しく使うには、OpenGLの関数による実装を理解するために、基本的な概念を把握する必要があります。

今回は、基本的な行列操作(平行移動、回転、拡大縮小)、合成変換、参照点を伴う変換について説明しました。

次回は、PyGameとPyOpenGLを使って、プロジェクトの初期化、オブジェクトの描画、アニメーションなどを行う予定です!

タイトルとURLをコピーしました