ToyBox2012 について




まえがき

ToyBox はレイトレーシング法の学習用に C# で書かれたレイトレーサです (学習用の文書はこちらにあります: 「C# で学ぶレイトレーシング」)。

この ToyBox は、そこそこの機能を、そこそこの実用度で、分かりやすく 実装する事に重きをおいたレンダラです。 必要最低限の機能しか実装されておらず実行速度も速くはありません。 簡単に言ってしまえば実用レベルのレンダラでは無いという事です。 (より現実的なレンダラに向けてのロードマップはこちら:準備中)。

一連の説明文書の No.12 までで作られるレンダラを ToyBox 2012 として ここに公開します。 どのようなレンダラが作れるようになるのか?その点が気になる方は この ToyBox2012 を試してみて下さい。

目次

ダウンロード(ToyBox2012Demo.zip)

ToyBox での遊び方
ToyBox のプログラム構造
レンダラの設定
シーンの構築

リファレンス

ToyBox を使って画像を生成するには以下のファイルが必要となります:

・ToyBox.dll - ToyBox 本体
・TinyMT.dll - 質の良い乱数の生成ライブラリ TinyMT
・OpenExrCsWrapper.dll - 以下に挙げる OpenEXR ライブラリの C# ラッパ
・Half.dll, Iex.dll, IlmImf.dll, IlmThread.dll, Imath.dll - OpenEXR 関連

メモ:C# 用の TinyMT および OpenEXR 関連にご興味のある方へ
これらのファイルに関しても説明ページの方で順次説明していきますが、 基本的にそれぞれのオリジナルプログラムを C# 用にラップしただけです。 お急ぎの場合は DllImport アトリビュート等のキーワードで検索して頂ければ 分かりやすい解説ページがいくつか見つかるかと思います。

ToyBox で画像を生成する場合、プログラムは大きく分けると 2つの部分に分けられます。 ひとつはレンダラの設定部分であり、 もうひとつはシーンの記述部分です。

レンダラの設定部分には、何というファイル名で画像を保存するか、 画角はどれくらいにするのか、レンズ長はどれくらいにするのか、etc といった 事をレンダラに指定するコードを書きます。

シーンの記述部分では、形状データの読み込みおよび配置や、 視点、注目点の指定などを行います。

最初にレンダラのインスタンスを生成します。
// レンダラ生成の例
const int kMaxRayDepth=3;
const int kNumOfJitterCellSqrtForPrimaryRay=4;
const int kNumOfJitterCellSqrtForGlossy=4;
RGB backgroundColor=new RGB(0,0,8.0f/255);
SimpleDRT drt=new SimpleDRT(kMaxRayDepth,
							kNumOfJitterCellSqrtForPrimaryRay,
							kNumOfJitterCellSqrtForGlossy,
							backgroundColor);
SimpleDRT は単純な分散レイトレーシング(Distributed Ray Tracing)の 機能を提供するクラスです。

kNumOfJitterCellSqrtForPrimaryRay および kNumOfJitterCellSqrtForGlossy は ジッタする時のセル数の平方根を整数で指定します。 上の例では、それぞれに 4 が指定されていますので、 1次レイおよび光沢面での反射屈折のレイは 16 本(=4^2) が生成される事を 意味します。

SimpleDRT のインスタンスを生成したら、そのインスタンスに対して 出力ファイル名や解像度などの情報を設定していきます。
// 出力ファイル名の設定
// 拡張子は .ppm もしくは .exr を指定して下さい
drt.SetDisplay("ToyBox2012.ppm");

// 出力画像の解像度の設定
const int kImageWidth =640;
const int kImageHeight=350;
const float kPixelAspectRatio=1.0f;
drt.SetFormat(kImageWidth,kImageHeight,kPixelAspectRatio);

// レンズの設定とピント位置の指定
const float kFStop=30.0f;		   // レンズの F 値
const float kFocalLength=1.0f;	   // レンズの焦点距離
const float kFocusDistance=40.0f;  // 視点からピント位置までの距離
drt.SetDepthOfField(kFStop,kFocalLength,kFocusDistance);

// 画角(水平)の設定
const float kHorizontalFov=20.0f/180*(float)Math.PI;
drt.SetFovHorizontal(kHorizontalFov);

次に、レンダリングするシーンの設定を行います。 シーンデータは形状情報の集まりです。

それぞれの形状に、

・どのようなライトを当てるか
・どのようなシェーダを割り当てるか
・反射時に作用する形状情報(=シーンデータ)は何か
・屈折時に作用する形状情報(=シーンデータ)は何か

を設定します。

シーンデータの実態は SceneElementList と呼ばれるもので、 具体的には次のように記述します:
SceneElementList scene=new SceneElementList();
	:
ShapeElement redTeapot=new ShapeElement(...);
scene.Add(redTeapot);
SceneElementList のインスタンスを生成し、そのインスタンスに形状データの 派生型である ShapeElement のインスタンスを追加しています。

ShapeElement 型はコンストラクタ引数として、次のような値をとります:
public ShapeElement(Shape inShape,Matrix44 inTransformMat,
					RGB inOs,RGB inColor,
					float inKr,float inKt,float inAbsRefractiveIndex,
					Shader inSurfaceShader,Light[] inLightArray,
					SceneElementList inSceneElementListForShadow,
					SceneElementList inSceneElementListForReflect =null,
					SceneElementList inSceneElementListForTransmit=null)
第1引数の Shape 型は形状そのものを表す引数です。 Shape 型自体は抽象データ型であり、具体的な形状データには 以下のようなクラスが用意されています:

・SimpleMeshWithOctree - .ply フォーマットから生成される形状
・Rectangle - 四角形
・Sphere - 球

第2引数の inTransformMat は平行移動や回転、拡大縮小など、 シーンデータ内に形状データを配置する為の行列情報です。 通常、この行列データは MatrixStack を用いて指定されます。

第3引数の inOs は不透明度を表します。 これは RGB それぞれのチャネルで不透明度を表す事ができます。 完全に不透明の場合は RGB.One という値を指定します。 逆に完全に透明なものは RGB.Zero という値を指定します。

第4引数の inColor は形状データの色を表します。 形状の色は最終的にはシェーダによって定義されますが、 ToyBox では形状データに色データが含まれているので、 独自のシェーダを書かれる場合は活用して下さい。

第5引数の inKr および第6引数の inKt は それぞれ反射係数と透過係数を表します。

第7引数の inAbsRefractiveIndex は材質の屈折率を表します。 不透明物体など特に屈折率が必要でない場合は Util.AbsRefractiveIndex.Air を 指定しておいて下さい。

第8引数から第10引数までの inSceneElementListForShadow, inSceneElementListForReflect, inSceneElementListForTransmit は、それぞれ 影を落とす形状のリスト、反射時に作用する形状のリスト、 屈折時に作用する形状のリストを指定します。

このうち最低限必要なものは inSceneElementListForShadow のみで、 他のものは指定しなければ、この inSceneElementListForShadow を 使用します。


形状データをシーンに追加する場合の具体的なコードは 次のようになります:
SceneElementList scene=new SceneElementList();
	:
List<Light> lightList=new List<Light>();
	:
Light[] lights=lightList.ToArray();
	:
PlasticShader plasticShader=new PlasticShader(...);
	:
MatrixStack matrixStack=new MatrixStack();
	:
// .ply データの読み込み
const int kMaxOctreeLevel=5;
SimpleMeshWithOctree teapotMesh=PLY.LoadSmoothedSimpleMeshWithOctree(
										@"teapot.ply",kMaxOctreeLevel);

// 読み込んだ .ply データを用いて ShapeElement を作る。
const float kTeapotKr=0.5f;
const float kTeapotKt=0;
const float kTeapotAbsRefractiveIndex=Util.AbsRefractiveIndex.Air;
ShapeElement redTeapot=new ShapeElement(teapotMesh,matrixStack.Top,
										RGB.One,RGB.Red,
										kTeapotKr,kTeapotKt,
										kTeapotAbsRefractiveIndex,
										plasticShader,lights,scene);
scene.Add(redTeapot);
上の例では redTeapot エレメント自体が格納される scene を redTeapot の inSceneElementListForShadow に指定しています (24 行目および 25 行目)。 これにより自分自身の影(セルフシャドウ)が正しく表現されます。

ライトやシェーダに関する説明は後ほどしますが、とりあえず このようにして構築された SceneElementList を、先程設定した SimpleDRT のインスタンスの Render メソッドの引数として渡せば レンダリングを開始します。 コードで書くと次のような感じです。
SimpleDRT drt=new SimpleDRT(...);
	:
SceneElementList scene=buildScene();
drt.Render(scene);


SceneElement(ShapeElement の親抽象クラス)で使用するライト情報は ライト情報を表す Light 抽象クラスの単なる配列です。 実際のコーディングでは、例えば平行光源を表す DirectionalLight クラス等の インスタンスを生成して使用します。 実際のコードでは次のようになります。
List<Light> lightList=new List<Light>();
lightList.Add(new DirectionalLight(
						new Vector3(0.5f,-1,1),	// 光の進行方向
						RGB.White				// 光の色
				  )
			 );
Light[] lights=lightList.ToArray();


シェーダに関してはインスタンスを生成して、それを使用するだけです。
PlasticShader plasticShader=new PlasticShader(
									Ks:1.0f,
									Kd:0.8f,
									Ka:0.3f,
									Hardness:300
								);


最後はマトリックススタックについてですが、 こちらは OpenGL や RenderMan 等と同様の仕組みです。 MatrixStack インスタンスを生成しておき、 それに対して各種行列を Push / Pop するという方法です。 マトリックススタックの一番上の行列は Top という プロパティで取得する事が可能です。
MatrixStack matrixStack=new MatrixStack();	// 最初は単位行列

Vector3 cameraPos=new Vector3(0,1,-10);	// 視点の位置
Vector3 targetPos=Vector3.Zero;			// 注目点の位置

// 上方向ベクトルの計算
Vector3 upVector=Vector3.Cross(targetPos-cameraPos,
							   Vector3.XAxis);

// 視点座標系からの変換マトリックスをプッシュ
matrixStack.PushLookAt(cameraPos,targetPos,upVector);
  // 平行移動
  matrixStack.PushTranslate(0,-160,900);
	// 回転
	matrixStack.PushRotate((float)(20*Math.PI/180),	// 回転量
						   0,1,0	// 回転軸
						  );
		:
		// matrixStack.Top には、回転および並行移動、
		// カメラ座標系からワールド座標系への変換を表す
		// 行列が格納されている。
		SceneElement redTeapot=new ShapeElement(...
												matrixStack.Top
												...
											   );
		:
	matrixStack.Pop();
  matrixStack.Pop();
matrixStack.Pop();
このような感じでシーンデータを構築していき、 それを SimpleDRT の Render メソッドに渡すことで レンダリングを行うことができます。

画像関連クラス
数学関連クラス
ユーティリティクラス
ライト関連クラス
シェーダ関連クラス
形状関連クラス
MatrixStack クラス
SimpleDRT クラス
ShapeElement クラス