読者です 読者をやめる 読者になる 読者になる

人工知能に関する断創録

人工知能、認知科学、心理学、ロボティクス、生物学などに興味を持っています。このブログでは人工知能のさまざまな分野について調査したことをまとめています。最近は、機械学習・Deep Learningに関する記事が多いです。



ライティング

Pygame

ライティングとは3次元空間に光源を置いてオブジェクトを照らすことをいいます。光が当たると陰ができて3次元っぽく見えるようになります。ライティングしてないティーポット(2008/9/6)と比べてみると違いは明らかだと思います。また、3次元オブジェクトのマテリアルを設定して、表面に色を付けたり、光沢を出したりできます。

lighting.py
f:id:aidiary:20100814110124p:plain

サンプルスクリプト

#!/usr/bin/env python
#coding:utf-8
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import sys

# 照明
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 = [2.0, 2.0, 1.0, 1.0]   # 照明の位置

# マテリアル
no_mat = [0.0, 0.0, 0.0, 1.0]           # 反射しない
mat_ambient = [0.0, 0.0, 0.3, 1.0]      # 環境光の青成分だけ少し反射
mat_diffuse = [1.0, 0.0, 0.0, 1.0]      # 拡散光の赤成分を全反射
mat_specular = [1.0, 1.0, 1.0, 1.0]     # 鏡面光の全成分を全反射
mat_emission = [0.3, 0.3, 0.2, 0.0]     # 放射の色
no_shininess = [0.0]                    # 鏡面反射しない
low_shininess = [5.0]                   # 弱い鏡面反射
high_shininess = [100.0]                # 強い鏡面反射

def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
    glutInitWindowSize(300, 300)
    glutInitWindowPosition(100, 100)
    glutCreateWindow("ライティング")
    glutDisplayFunc(display)
    glutReshapeFunc(reshape)
    init(300, 300)
    glutMainLoop()

def init(width, height):
    glClearColor(0.0, 0.0, 0.0, 1.0)
    glEnable(GL_DEPTH_TEST)
    
    # ライティングの設定
    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)    # 0番目の照明を有効にする
    
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(width)/float(height), 0.1, 100.0)

def display():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
    
    # マテリアルの設定
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse)
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat)
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess)
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat)
    
    # Teapotの描画
    glutSolidTeapot(1.0)
    
    glutSwapBuffers()

def reshape(width, height):
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(width)/float(height), 0.1, 100.0)

if __name__ == "__main__":
    main()

ライティング

まず、3次元の世界に光源を追加します。この処理はinit()内で行っています。

# 照明
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 = [2.0, 2.0, 1.0, 1.0]   # 照明の位置

# ライティングの設定
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)    # 0番目の照明を有効にする

OpenGLの光源には、

  • 環境光(ambient)
  • 拡散光(diffuse)
  • 鏡面光(specular)

の3種類があります。環境光は光源は見えないけど何かぼやーっと明るい感じです。曇りの日の明るさみたいな。拡散光は、一般的な意味での照明で一方向から当てられる光です。鏡面光は拡散光と同じく一方向から当てられる光ですが、物体に当たったとき鏡面反射します。光が鏡面反射すると光沢のある金属やプラスチックのようにテカって見えます。

上の設定では、この3つの光をglLightfv()で設定しています。照明は少なくとも8個(GL_LIGHT0から GL_LIGHT7)まで使えますが、ここではGL_LIGHT0だけ使っています。glEnable()で有効化するのを忘れずに!また、ライティング機能を使うために同じくglEnable()でGL_LIGHTINGを有効化する必要があります。これは忘れやすいので注意です。光源の位置は GL_POSITIONで設定します。上の例では、(2.0, 2.0, 1.0)の位置に置いています。

3つの光の色はすべて白色にしています。白色光は赤成分(R)、緑成分(G)、青成分(B)のすべてを含んだ光のことです。つまり、[1.0, 1.0, 1.0]です(第4引数はとりあえず無視)。太陽の光はまさに白色光ですべての成分を含んでいます。小学校で習いますがニュートンがプリズムを使って太陽の光がすべての色を含むのを示したのは有名な話です。虹を見ても太陽の白色光がすべての色を含んでいることがわかりますね。

f:id:aidiary:20100814110125j:plain

マテリアル(環境反射+拡散反射)

物体がどう見えるかは当てる光だけではなく、物体が当たった光をどのように反射する性質を持っているかにもよります。この性質をマテリアルと呼びます。ライティングをオンにすると今まで使っていたglColor3f()による色付けは無視されます。代わりにマテリアルによって色を設定するわけです。

f:id:aidiary:20100814110126p:plain

# マテリアル
no_mat = [0.0, 0.0, 0.0, 1.0]           # 反射しない
mat_ambient = [0.0, 0.0, 0.3, 1.0]      # 環境光の青成分だけ少し反射
mat_diffuse = [1.0, 0.0, 0.0, 1.0]      # 拡散光の赤成分を全反射
mat_specular = [1.0, 1.0, 1.0, 1.0]     # 鏡面光の全成分を全反射
mat_emission = [0.3, 0.3, 0.2, 0.0]     # 放射の色
no_shininess = [0.0]                    # 鏡面反射しない
low_shininess = [5.0]                   # 弱い鏡面反射
high_shininess = [100.0]                # 強い鏡面反射

# マテリアルの設定
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient)
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse)
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat)
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess)
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat)

# Teapotの描画
glutSolidTeapot(1.0)

マテリアルの設定は、glMaterialfv()でセットします。光の種類ごとにどのようにその光を反射するのか設定します。マテリアルを一度設定したらその後に作る3次元オブジェクトはすべてそのマテリアルを持った物体になります。つまり、直後で作っているTeapotは設定したマテリアルを持ちます。

まず、3種類の各光をどのように反射するかを定義します。no_mat = [0.0, 0.0, 0.0](第4引数は無視)は、RGBのすべての値が0なので当たった光をまったく反射しません。光を反射しないというのはどういうことかわかりますか?これは、物体がまったく見えないということです。物が見えるというのは物体に当たった光が反射して目に届くから見えるわけです。物体が光を反射しなければ物は見えません。つまり、no_matに設定した光は無視されます。上の例では、鏡面光は無視してますね。

例では、環境光の反射成分をmat_ambient = [0.0, 0.0, 0.3]としています。これは、環境光の赤成分と緑成分はまったく反射しないが、青成分を0.3だけ反射することを意味しています。環境光は白色だったので白色の光を当てると青成分だけ0.3反射します。これはどういうことかわかりますか?白色光の赤と緑はまったく反射せずに青成分だけ反射するので目には青色が入ってくることになります。つまり、物体は青色に見えるわけです。別の例として、環境光を赤色にするとどうなるでしょう?この物体は赤は反射せず、環境光に青成分は含まれていないため物体は光を反射しなくなります。つまり、見えません。このように物体が最終的に何色に見えるかは当てる光とマテリアルによって決まります。マテリアルを青色にしてもいつも青色に見えるわけではないので注意です。

拡散光の反射成分はmat_diffuse = [1.0, 0.0, 0.0] としています。これは、拡散光のうち赤成分だけすべて反射します。つまり、目には反射された赤色だけ見えます。環境光が反射した青色と拡散光が反射した赤色がまざって見えるので最終的にスクリーンショットのような紫っぽい毒々しい(笑)ティーポットになります。

マテリアル(拡散反射+鏡面反射+低い鏡面指数)

次に鏡面反射を使ってみます。あと環境光の青色が入ると毒々しいので環境光はno_matにして無視します。

f:id:aidiary:20100814110124p:plain

    # マテリアルの設定
    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse)
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular)
    glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess)
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat)
    
    # Teapotの描画
    glutSolidTeapot(1.0)

拡散光はさっきと同じく赤色だけ反射するmat_diffuseをセットしています。これとは別に鏡面光を反射するmat_specular = [1.0, 1.0, 1.0]としているので当たった光をすべて反射します。鏡面光は白色白なので鏡面反射はそのまま白色になります。鏡面反射の場合は、 GL_SHININESS(鏡面指数)を設定することで鏡面反射のサイズと強さを設定できます。これは、0.0から128.0の範囲の数値を指定でき、値が大きいほど小さくて明るい鏡面反射になります。ここでは、5.0なのでかなり小さい方です。スクリーンショットを見るとわかりますが、全体的に白色でてかったような感じになります。金属っぽいですね。

マテリアルの追加(拡散反射+鏡面反射+高い鏡面指数)

次にGL_SHININESSを大きな値にしてみます。下のスクリーンショットのように小さくて明るい鏡面反射になります。

f:id:aidiary:20100814110127p:plain

    # マテリアルの設定
    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat)
    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)
    
    # Teapotの描画
    glutSolidTeapot(1.0)

マテリアルの追加(拡散反射+放射)

最後に放射です。放射は光が当たって光るのではなく、自ら光るオブジェクトに設定します。ここでは、拡散光と放射を設定しています。放射はmat_emission = [0.3, 0.3, 0.2, 0.0]というちょっと微妙な色です。拡散光の赤と合わせて何かぼーっと光ったような感じがします。

f:id:aidiary:20100814110128p:plain

    # マテリアルの設定
    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse)
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat)
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess)
    glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission)
    
    # Teapotの描画
    glutSolidTeapot(1.0)

法線ベクトル

オブジェクトが反射するためには法線ベクトルを各面に設定する必要があります。たとえば、立方体の描画(2008/8/31)で作ったように自分で面を定義して組み立てたオブジェクトは法線ベクトルを設定しないとライティングしてもダメです。一方、 glutSolidTeapot()のようにGLUTコマンドで作れるデフォルトのオブジェクトはあらかじめ法線ベクトルを設定してくれているためライティングできます。法線ベクトルの設定方法は別の機会に取り上げます。