LuaJITでお手軽3Dプログラミング (4) (2013/12/23)
前回の「LuaJITでお手軽3Dプログラミング (3)」から半年も空いてしまいましたが、価格のわりに強力な GPU を積んでいる Raspberry Pi の上で、LuaJIT という非常に高速なスクリプト言語を使って、3Dプログラミングを手軽に楽しもうというシリーズの4回目です。前回は、 物体が存在する空間となる Spaceeクラス、物体の位置や姿勢をコントロールする Nodeクラス 、物体の形状を決定する Shapeクラス の概要を紹介しました。これまでのサンプルは地味な物体を表示していましたが、今回は物体にテクスチャを貼ってもう少し見映えを良くしてみます。
Raspberry Pi の公式ディスクイメージの Raspbian には最初から LuaJIT が入っているため、新たに何もインストールする必要もなくプログラミングを始められます。 サンプルプログラムを試すには LjESを解凍したディレクトリでデモのスクリプトを実行するだけです。コンソールから以下のように入力すると、ダウンロード、解凍、デモの実行ができます。
curl -O https://www.mztn.org/rpi/ljes-1.00.tar.gz tar zxf ljes-1.00.tar.gz cd ljes-1.00 cd examples luajit demo_spheres.lua
テクスチャマッピング
テクスチャ(texture)とは「手ざわり、きめ、表面の感じ」といった「物の質感」をあらわす言葉です。 3次元コンピュータグラフィックスでは、物体の表面に画像を貼り付けることで、例えば単純な直方体をレンガや木片のように見せかける方法をテクスチャマッピングと呼びます。 3次元の物体は、その表面をたくさんの三角形に分割して立体に見えるようにしています。それらの三角形の頂点1つ1つに対してテクスチャ画像の位置を指定する必要があります。ちょっと考えるだけでも大変な作業になりますが、そういった作業 (UV展開) は、3D モデリング専用のソフトウェア(例えば blender)に比較的にできる機能を使って行います。 ここでは、球、円筒、立方体などの幾何学的な形状に対して自動的にテクスチャマッピングする機能を中心に試してみます。
テクスチャを使ったサンプルコード
これまで使ってきたサンプルにテクスチャを追加します。 テクスチャについては赤で示した 5 行を追加するだけです。 今回のサンプルは光の当たらない部分が見えるように光源の位置を右後ろに置いています。 コード中の (A) から (S) については 前の解説を参照して下さい。
package.path = "../LjES/?.lua;" .. package.path -- (A) local demo = require("demo") -- (B) require("Texture") -- テクスチャクラスをロード demo.screen(0, 0) -- (C) demo.backgroundColor(0.2, 0.2, 0.4) -- (D) local aSpace = demo.getSpace() -- (E) local eye = aSpace:addNode(nil, "eye") -- (F) eye:setPosition(0, 0, 30) -- (G) local tex = Texture:new() -- Texture クラスのインスタンス tex:readImageFromFile("num512.png") -- テクスチャファイルのロード local shape = Shape:new() -- (H) shape:donut(8, 3, 16, 16) -- (I) shape:endShape() -- (J) shape:shaderParameter("use_texture", 1) -- テクスチャを使う shape:shaderParameter("texture", tex) -- テクスチャを設定 shape:shaderParameter("color", {1.0, 1.0, 1.0, 1.0}) -- (K) 物体の色 shape:shaderParameter("light", {100, 0.0, 100, 1.0}) -- 光源の位置 local node = aSpace:addNode(nil, "node1") -- (L) node:setPosition(0, 0, 0) -- (M) node:addShape(shape) -- (N) function draw() -- (O) node:rotateX(0.1) -- (P) aSpace:draw(eye) -- (Q) end demo.loop(draw, true) -- (R) demo.exit() -- (S)
実行例
上のサンプルコードを ljes-1.00/examples に保存して実行します。
cd ljes-1.00/examples curl -O https://www.mztn.org/rpi/rpi25tex1.lua luajit rpi25tex1.lua
テクスチャを追加するとこれまでのサンプルと比べて、かなり見映えがするものとなります
サンプルコードの説明
物体の形状を表すShapeクラスのインスタンスに対してテクスチャを指定することで、物体に画像を貼り付けます。テクスチャは Texture クラスを定義した LjES/Texture.lua をモジュールとして読み込んで、Shapeクラスのインスタンスが持つシェーダに対して画像ファイルを設定します。
- 3行目にある「require("Texture")」は、Texture というクラスを読み込みます。LjES では 大文字から始まるファイル名の場合は、同じ名前のクラスがグローバル名前空間に登録されます。
- 「local tex = Texture:new()」で Texture というクラスを表すグローバル変数の new メソッドを呼び出すと、Texture クラスのインスタンスが返るため、それをローカル変数の tex に代入しています。
- 「tex:readImageFromFile("num512.png")」で tex に num512.png というPNG形式の画像ファイルをテクスチャとして読み込みます。
- 「shape:shaderParameter("use_texture", 1)」では、Shapeクラスのインスタンス shape が持つシェーダに対してテクスチャを使うことを指示します。
- 「shape:shaderParameter("texture", tex) 」では、Shapeクラスのインスタンス shape が持つシェーダにテクスチャ画像を設定します。
以上の手順で物体にテクスチャが貼られます。readImageFromFile で指定する画像ファイルは PNG形式のファイルで、画像のサイズが 2048 x 2048 より小さい任意のサイズが指定できます。256 x 256 などの正方形で2の累乗のサイズである必要はありません。
物体の色
テクスチャを貼ると物体の色は関係ないのでしょうか? LjESではテクスチャの色は Shape が使っているシェーダが決めています。デフォルトのシェーダでは、物体の色とテクスチャの色は赤(R)、緑(R)、青(B)の成分ごとに乗算されます。したがって、物体の色を白(1.0, 1.0, 1.0) とした場合はテクスチャの色がそのまま反映されます。
黄色にした例
物体の色を指定している次の行を変更して実行すると物体の色のフィルムを上から重ねたようにテクスチャの色が変わります。
shape:shaderParameter("color", {0.8, 0.8, 0.0, 1.0}) -- (K)
テクスチャマッピング関連のクラスとメソッド
Texure クラスのメソッド
Texureクラスはテクスチャ用のファイルをロードしたり、単色に塗りつぶしたり、点を打ったりする機能を持っています。 ほとんどがLjESが内部的に使うメソッドのため、上の例のように new() と readImageFromFile() の2つのメソッドだけ使います。
メソッド | 機能 |
---|---|
Texure:new () | Textureクラスのインスタンスを返す。 |
Texure:readImageFromFile (textureFile) | png形式の画像ファイルを読み込みます。 |
Texure:fillTexture (r, g, b, a) | r, g, b, a で指定した色でテクスチャを塗りつぶします。 |
Texure:point (x, y, color) | テクスチャの(x, y)の位置に点を打ちます。色は {r, b, g, a} で指定します。 |
Texure:writeImageToFile () | テクスチャを画像ファイルとして書き出します。ファイル名は "tex日時.png" になります。 |
Shapeクラスのテクスチャ関連メソッド
Shapeクラスのメソッドのうちでテクスチャに関連するメソッドです。 Shape:shaderParameter 以外のメソッドは Shape:endShape() の前に実行する必要があります。endShape()の後に実行しても効果はありません。 Shape:shaderParameter はいつでも何度でも実行できます。
メソッド | 機能 |
---|---|
Shape:setTexture (texture) | テクスチャクラスのインスタンスを設定する。 |
Shape:setTextureMappingMode (mode) | テクスチャマッピングのモード(0:球面、1:投影)を指定する。 |
Shape:setTextureMappingAxis (axis) | テクスチャの貼り付け軸を指定する。軸の指定には整数を指定し、球面マッピングの場合は、0, 1, -1 : Y軸周り、 2, -2 : X軸周り、 3, -3 : Z軸周りとなる。投影マッピングの場合は、0, 1, -1 : XY面(Z軸に直角)、 2, -2 : YZ面(X軸に直角)、 3, -3 : XZ面(Y軸に直角)となる。 |
Shape:setTextureScale (scale_u, scale_v) | テクスチャの拡大率を設定する。値が大きいと貼られたテクスチャは拡大して見える。テクスチャのサイズは(1.0, 1.0)で形状のサイズが1より大きいと複数枚くり返し貼られる。 |
Shape:shaderParameter (key, value) | シェーダのパラメータを設定する。 |
シェーダのパラメータの設定
Shape:shaderParameter (キー, 値) は Shapeクラスのインスタンスに設定したシェーダのパラメータを設定するメソッドです。サンプルコードで使っている demo モジュールではデフォルトのシェーダとしてPhong シェーダ(Phong.lua) を指定しています。 Phong シェーダで設定できるパラメータの種類と初期値、意味を以下の表で示します。Phong シェーダは基本的な光の性質を設定できます。光源からの光が直接当たらない部分の明るさ(環境光)、物体の表面と光源からの光の角度で明るさが変わる部分(1.0 - 環境光強度)、反射光の強さと鋭さです。
シェーダのパラメータは Shape 毎に設定できます。また、1フレームの描画毎に設定できるので、徐々に明るくしたり、色を変化させたりすることができます。
キー | 初期値 | 意味 |
---|---|---|
color | {0.8, 0.8, 1.0, 1.0} | 物体の色をしていする。 |
light | {0.0, 0.0, 100.0, 1} | 光源の位置を指定する。 |
emissive | 0 | 発光(照明計算をしない)する場合は 1 を指定。 |
ambient | 0.3 | 環境光(光源の光が当たらない部分の明るさ)の強さを指定する。 |
specular | 0.6 | 反射光の強さを指定する。 |
power | 40 | 反射の鋭さを指定する。値が大きいとなめらかな表面。 |
use_texture | 0 | テクスチャを使う場合は 1 を指定する。 |
tex_unit | 0 | テクスチャユニットの番号 |
サンプルコードを見ると以下のように、テクスチャを使うことを指定した後に、使うテクスチャをシェーダに設定しています。また、物体の色もシェーダパラメータの color キーを使って指定しています。
shape:shaderParameter("use_texture", 1) -- テクスチャを使う shape:shaderParameter("texture", tex) -- テクスチャを設定 shape:shaderParameter("color", {1.0, 1.0, 1.0, 1.0}) -- 色の設定 shape:shaderParameter("light", {100, 0.0, 100, 1.0}) -- 光源の位置
環境光の強度を 0 にするコードを追加すると、光源の反対側は非常に暗くなります。太陽以外光源のない宇宙空間のような感じになります。
shape:shaderParameter("ambient", 0.0) -- 環境光を 0.0 に
まとめ
テクスチャを貼ると 3次元コンピュータグラフィックスらしい感じになってきました。テクスチャに使う画像ファイルを変えたり、色や光の設定を変えて、いろいろ試してみてください。