交差判定ができるようになったら、いよいよレイトレースをして見ましょう。
今回は、反射や屈折する物体はありません。また、Blinn-Phong の鏡面反射則を使ってレイトレースをします。
今回のプログラムは、次のものです。
まぁ、いつものように適当にファイルが入っています。
APP WIZARD から出力されるフレームワークのファイルは紹介を省かせていただきます。
render.cpp | レイトレ用の描画関数群 |
render.h | レイトレ用の描画関数群 |
mainDlg.cpp | ダイアログを管理するクラスのメソッドが書かれたファイル |
mainDlg.h | ダイアログを管理するクラスのヘッダ |
あと、実行ファイル、リソースファイル、プロジェクトファイルが入っています。
今回の方法ですが、最初に視線からレイを飛ばして、レイがどこかにぶつかれば、
その点に関するBlinn-Phongの鏡面反射則による明るさを求めて、色を塗ります。
従って、今回の光源は、面光源に見えますが、実は点光源です。
実際に交差を調べ部分ですが、メッシュ単位でクラスCObjを作り、
そこから派生させた各オブジェクトに関して交差判定を調べる関数 IsAcross を作って、
レイの始点と方向から衝突したかどうかを検索します。
IsAcross の引数には、衝突する限界の距離を与えます。
IsAcross 内では、オブジェクトに衝突した位置が衝突する限界の距離よりも遠ければ、衝突していないものとみなします。
実際に使うときには、最初に非常に大きな値を与えておいて、衝突が発生したところで新しい衝突の距離と置き換えます。
この方法によって、衝突した点で一番近いオブジェクトを検索します。
また、IsAcross には、衝突した位置ベクトルと法線ベクトルを格納するポインタを与え、
衝突した後の照明計算や今後の反射計算ができるように設計しました。
render.cpp 0707: D3DXVECTOR3 *GetColor(D3DXVECTOR3 *dest, D3DXVECTOR4 *x, D3DXVECTOR4 *v) 0708: { 0709: D3DXVECTOR4 light_pos = D3DXVECTOR4(278.f, 548.8f, 279.5f,1);// 光源の位置 0710: D3DXVECTOR3 light_color = D3DXVECTOR3( 10.f, 9.0f, 5.0f); // 光源の色 0711: 0712: float ttmp; 0713: D3DXVECTOR4 ptmp; 0714: D3DXVECTOR4 ntmp; 0715: int i; 0716: 0717: float t = CObject::INFINTY_DIST; // 最近接オブジェクトとの距離 0718: D3DXVECTOR4 p; // 最近接点 0719: D3DXVECTOR4 n; // 最近接点での法線 0720: CObject *pObj = NULL; // 最近接オブジェクト 0721: 0722: // 一番近くにあるオブジェクトを検索する 0723: ttmp = sphere.IsAcross(&ntmp, &ptmp, x, v); 0724: if(0<=ttmp && ttmp<t){ 0725: t = ttmp; 0726: n = ntmp; 0727: p = ptmp; 0728: pObj = &sphere; 0729: } 0730: ttmp = tphere.IsAcross(&ntmp, &ptmp, x, v); 0731: if(0<=ttmp && ttmp<t){ 0732: t = ttmp; 0733: n = ntmp; 0734: p = ptmp; 0735: pObj = &tphere; 0736: } 0737: for(i = 0; i<12; i++){ 0738: ttmp = room[i].IsAcross(&ntmp, &ptmp, x, v); 0739: if(0<=ttmp && ttmp<t){ 0740: t = ttmp; 0741: n = ntmp; 0742: p = ptmp; 0743: pObj = &room[i]; 0744: } 0745: } 0746: 0747: for(i = 0; i<10; i++){ 0748: ttmp = blocks[i].IsAcross(&ntmp, &ptmp, x, v); 0749: if(0<=ttmp && ttmp<t){ 0750: t = ttmp; 0751: n = ntmp; 0752: p = ptmp; 0753: pObj = &blocks[i]; 0754: } 0755: } 0756: for(i = 0; i<10; i++){ 0757: ttmp = blockt[i].IsAcross(&ntmp, &ptmp, x, v); 0758: if(0<=ttmp && ttmp<t){ 0759: t = ttmp; 0760: n = ntmp; 0761: p = ptmp; 0762: pObj = &blockt[i]; 0763: } 0764: } 0765: 0766:
レイが衝突しているオブジェクトが分かったら、当たった場所と光源の位置から光源ベクトルやハーフベクトルを求めて、
光源ベクトルと法線ベクトルの内積 LN や、ハーフベクトルと法線ベクトルの内積 HN 計算して、物体の色を求めます。
目に見える色は、光の色に左右されるので、物体の色にさらに光の色をかけたり、距離の2乗で薄めたりします。
また、今回は、点光源といっても特別な照明にしました。光源の強さをy軸と光線ベクトルのcosθに比例させています。
これは、床は非常に明るく、天井は暗い光源を実現するための設定です。全くといってよいほど見た目の問題で、気に入らなければ 798 行目の項は入れる必要はありません。
レイが全ての物体と当たらなかったら、背景色を返します。
render.cpp 0767: if( pObj ){ 0768: // 当たり 0769: 0770: // オブジェクトの色をBlinn-Phongの鏡面反射で計算する 0771: D3DXVECTOR4 l = light_pos-p; 0772: float L2 = D3DXVec3Dot((D3DXVECTOR3 *)&l, (D3DXVECTOR3 *)&l); 0773: D3DXVec3Normalize((D3DXVECTOR3 *)&l, (D3DXVECTOR3 *)&l); 0774: 0775: D3DXVECTOR3 dir, H; 0776: // 視線の計算 0777: camera.GetFrom(&dir); 0778: dir = dir - *(D3DXVECTOR3 *)&p; 0779: D3DXVec3Normalize(&dir, &dir); 0780: // ハーフベクトル 0781: H = dir+*(D3DXVECTOR3 *)&l; 0782: D3DXVec3Normalize((D3DXVECTOR3 *)&H, (D3DXVECTOR3 *)&H); 0783: 0784: float LN = D3DXVec3Dot((D3DXVECTOR3 *)&l, (D3DXVECTOR3 *)&n); 0785: float HN = D3DXVec3Dot((D3DXVECTOR3 *)&H, (D3DXVECTOR3 *)&n); 0786: if(LN<0) LN=0; 0787: if(HN<0) HN=0; 0788: 0789: pObj->GetColor(dest, LN, HN); 0790: 0791: // 光源の色の反映 0792: dest->x *= light_color.x; 0793: dest->y *= light_color.y; 0794: dest->z *= light_color.z; 0795: 0796: // 光の強さの適当な補正 0797: *dest *= min(1.5f, 500000.0f/(10000.0f+L2)); // 距離による補正 0798: *dest *= min(1, l.y+0.1f); // 光の向きをcosθの関数にする 0799: 0800: }else{ 0801: // 外れ 0802: *dest = BG_COLOR; 0803: } 0804: 0805: 0806: return dest; 0807: }
今回、コーネル部屋をレンダリングするにあたって、オブジェクトの数が増えてきたので、オブジェクトクラスを作りました。
実際には、基底クラス CObject を作って、派生クラスとして、球を持つクラス CSphere と、3角形を持つクラス CTriangle を持ちました。
基本クラスは、とりあえず、次の構成にしました。
render.h 0081: class CObject 0082: { 0083: public: 0084: enum { 0085: INFINTY_DIST = 10000, // 無限大とみなせる程度の距離 0086: }; 0087: int m_type; // OBJ_TYPE_???? 0088: Material m_material; // 質感 0089: 0090: // 初期化 0091: void Init(OBJ_DATA *pData); 0092: // 交差したか 0093: float IsAcross(D3DXVECTOR4 *n, D3DXVECTOR4 *p, const D3DXVECTOR4 *x, const D3DXVECTOR4 *v){return INFINTY_DIST;} 0094: // 質感データから、オブジェクトの色を得る 0095: D3DXVECTOR3 *GetColor(D3DXVECTOR3 *dest, float LN, float HN ); 0096: };
ここで、m_type は、基底クラスから派生クラスを判定するための型情報を格納します。
とりあえず、球と3角形しかないので、
render.h 0045: enum { 0046: OBJ_TYPE_SPHERE = 0, 0047: OBJ_TYPE_TRIANGLE, 0048: };
だけが有効です。
質感の情報 Material は、次の構造としています。
D3DMATERIAL9 等を参考にするのも良いでしょう。
render.h 0053: // 質感 0054: typedef struct{ 0055: float COLOR_AMB[4]; // 環境色 0056: float COLOR_DIF[4]; // 拡散色 0057: float COLOR_SPE[4]; // 鏡面反射色 0058: float speq_power; // 鏡面反射の強さ 0059: }Material;
今回は、球でも3角形でも、質環情報が同じなら同じ色を返すので、 基底クラスで、色を求める関数を定義しています。
render.cpp 0579: D3DXVECTOR3 *CObject::GetColor(D3DXVECTOR3 *dest, float LN, float HN ) 0580: { 0581: *dest = *(D3DXVECTOR3*)&this->m_material.COLOR_AMB 0582: + *(D3DXVECTOR3*)&this->m_material.COLOR_DIF * LN 0583: + *(D3DXVECTOR3*)&this->m_material.COLOR_SPE * powf(HN, this->m_material.speq_power); 0584: 0585: return dest; 0586: }
球の派生クラス CSphere は、次のように基底オブジェクトを拡張しました。
球の持つ幾何学的な情報は、球の半径とその中心ですが、計算に便利なように、半径の2乗と球の中心をパラメータにしました。
初期化関数 Init や交差判定の関数 IsAcross も、球を扱えるように拡張しています。
render.h 0097: // --------------------------------------------------------------------------- 0098: // 球 0099: // --------------------------------------------------------------------------- 0100: class CSphere : public CObject 0101: { 0102: private: 0103: D3DXVECTOR4 center; // 中心 0104: float radius_sq; // 半径 0105: 0106: public: 0107: CSphere(); 0108: 0109: void Init(OBJ_DATA *pData); 0110: float IsAcross(D3DXVECTOR4 *n, D3DXVECTOR4 *p, const D3DXVECTOR4 *x, const D3DXVECTOR4 *v); 0111: };
3角形のクラスは、次のようになります。
3角形の場合は、各頂点と法線ベクトルを持つようにしました。
render.h 0113: // --------------------------------------------------------------------------- 0114: // 3角形 0115: // --------------------------------------------------------------------------- 0116: class CTriangle : public CObject 0117: { 0118: private: 0119: D3DXVECTOR4 pos[3]; // 頂点座標 0120: D3DXVECTOR4 normal; // 法線ベクトル 0121: 0122: public: 0123: CTriangle(); 0124: 0125: void Init(OBJ_DATA *pData); 0126: float IsAcross(D3DXVECTOR4 *n, D3DXVECTOR4 *p, const D3DXVECTOR4 *x, const D3DXVECTOR4 *v); 0127: }; 0128: 0129: };// namespace Render
これらのオブジェクトを初期化するために、初期化用の共通構造体を定義しました。
といっても、球用の変数と3角形用の変数を両方持っているので、効率はすごく悪いです。
これは何とかしたいなぁ・・・
render.h 0061: typedef struct{ 0062: int type; 0063: 0064: Material material; 0065: 0066: struct{ 0067: // 3角形 0068: float x0[3]; // 座標1 0069: float x1[3]; // 座標2 0070: float x2[3]; // 座標3 0071: } triangle; 0072: struct { 0073: // 玉用 0074: float center[3];// 中心 0075: float radius; // 半径 0076: } sphere; 0077: 0078: }OBJ_DATA;
実際のデータは次のようになります。
球を初期化するときには、3角形の初期化の変数が球の変数の前にきますので、適当なダミーデータを流します。
ちなみに、今回のメインのファイルである render.cpp は、半分以上がこのオブジェクトのデータになっています。
render.cpp 0057: OBJ_DATA sphere_data[] = { 0058: {// 小さいのに乗ってるほう 0059: OBJ_TYPE_SPHERE, 0060: { 0061: {0.01f, 0.05f, 0.02f }, 0062: {0.01f, 0.05f, 0.02f }, 0063: {0.10f, 0.10f, 0.10f }, 0064: 16.0f, 0065: },{{0,0,0},{0,0,0},{0,0,0}},{ 0066: {185.5, 165+100,169}, 0067: 100.0f, 0068: } 0069: },{// 大きいのに乗ってるほう 0070: OBJ_TYPE_SPHERE, 0071: { 0072: {0.02f, 0.01f, 0.05f}, 0073: {0.02f, 0.01f, 0.05f}, 0074: {0.10f, 0.10f, 0.10f }, 0075: 8.0f, 0076: },{{0,0,0},{0,0,0},{0,0,0}},{ 0077: {368.5, 330+100,351}, 0078: 100.0f, 0079: } 0080: }}; 0081: // --------------------------------------------------------------------------- 0082: // 部屋 0083: OBJ_DATA room_data[12] = { 0084: {// ライト1 0085: OBJ_TYPE_TRIANGLE, 0086: { 0087: {100.0f, 100.0f, 100.0f}, 0088: { 0.0f, 0.0f, 0.0f}, 0089: { 0.0f, 0.0f, 0.0f}, 0090: 0.0f, 0091: },{ 0092: {213.0, 548.799f, 227.0}, 0093: {213.0, 548.799f, 332.0}, 0094: {343.0, 548.799f, 227.0}, 0095: } 0096: },{// ライト2 0097: OBJ_TYPE_TRIANGLE, 0098: { 0099: {100.0f, 100.0f, 100.0f}, 0100: { 0.0f, 0.0f, 0.0f}, 0101: { 0.0f, 0.0f, 0.0f}, 0102: 0.0f, 0103: },{ 0104: {343.0, 548.799f, 227.0}, 0105: {213.0, 548.799f, 332.0}, 0106: {343.0, 548.799f, 332.0}, 0107: } 0108: },{// 左1 0109: OBJ_TYPE_TRIANGLE, 0110: { 0111: {0.05f, 0.01f, 0.01f}, 0112: {0.05f, 0.01f, 0.01f}, 0113: {0.00f, 0.00f, 0.00f}, 0114: 32.0f, 0115: },{ 0116: {552.8f, 0.0f, 0.0f}, 0117: {556.0f, 548.8f, 0.0f}, 0118: {549.6f, 0.0f, 559.2f}, 0119: } 0120: 0121: },{// 左2 0122: OBJ_TYPE_TRIANGLE, 0123: { 0124: {0.05f, 0.01f, 0.01f}, 0125: {0.05f, 0.01f, 0.01f}, 0126: {0.00f, 0.00f, 0.00f}, 0127: 32.0f, 0128: },{ 0129: {549.6f, 0.0f, 559.2f}, 0130: {556.0f, 548.8f, 0.0f}, 0131: {556.0f, 548.8f, 559.2f}, 0132: } 0133: 0134: },{// 右1 0135: OBJ_TYPE_TRIANGLE, 0136: { 0137: {0.01f, 0.01f, 0.05f}, 0138: {0.01f, 0.01f, 0.05f}, 0139: {0.00f, 0.00f, 0.00f}, 0140: 32.0f, 0141: },{ 0142: {0.0f, 0.0f, 0.0f}, 0143: {0.0f, 0.0f, 559.2f}, 0144: {0.0f, 548.8f, 0.0f}, 0145: } 0146: 以下省略
実際に使うときには、これらのクラスを必要な数だけ用意して、初期化関数や交差判定関数を実行します。
render.cpp 0049: CTriangle room[12]; // 部屋 0050: CTriangle blocks[10]; // 低い箱 0051: CTriangle blockt[10]; // 高い箱 0052: CSphere sphere; // 低いところにある玉 0053: CSphere tphere; // 高いところにある玉 0514: void Init() 0515: { 0516: int i,j; 0517: 0518: // フレームバッファの初期化 0519: for(j=0;j<RENDER_HEIGHT;j++){ 0520: for(i=0;i<RENDER_WIDTH ;i++){ 0521: s_data[4*(j*RENDER_WIDTH+i)+0]=(char)255;// R 0522: s_data[4*(j*RENDER_WIDTH+i)+1]=(char)(i*256/RENDER_WIDTH );// G 0523: s_data[4*(j*RENDER_WIDTH+i)+2]=(char)(j*256/RENDER_HEIGHT);// B 0524: } 0525: } 0526: 0527: // カメラの設定 0528: camera.SetFrom (&D3DXVECTOR3(278,273,-800)); 0529: camera.SetLookAt(&D3DXVECTOR3(278,273,0)); 0530: camera.SetUp (&D3DXVECTOR3(0,1,0)); 0531: camera.SetFovY (D3DX_PI/4); 0532: camera.SetAspect(1.0f); 0533: camera.SetNear (0.01f); 0534: camera.SetFar (100.0f); 0535: camera.Update(); 0536: 0537: // オブジェクトの初期化 0538: sphere.Init(&sphere_data[0]); 0539: tphere.Init(&sphere_data[1]); 0540: for(i=0;i<12;i++){ 0541: room[i].Init(&room_data[i]); 0542: } 0543: for(i=0;i<10;i++){ 0544: blocks[i].Init(&Short_block[i]); 0545: } 0546: for(i=0;i<10;i++){ 0547: blockt[i].Init(&Tall_block[i]); 0548: } 0549: }
このままでは、普通にレンダリングするのとなんら換わりありません。
この後、色々な項を追加して、グローバルイルミネーションぽくしていきましょう。