レイトレース:MFC


~ Ray Tracing : MFC ~







■はじめに

いいかげん、レイトレを勉強しようかなぁと思っていたところ、急に盛り上がってしまったので、適当に実装して見ました。
古典的なレイトレは、DirectXを使わないので、自分で画面に表示する必要があります。
今回は、FCを使って、ダイアログに画像を表示するプログラムを組みました。

今回のプログラムは、次のものです。

まぁ、いつものように適当にファイルが入っています。
APP WIZARD から出力されるフレームワークのファイルは紹介を省かせていただきます。

mainDlg.cppダイアログを管理するクラスのメソッドが書かれたファイル(これ大事)
mainDlg.hダイアログを管理するクラスのヘッダ
main.hアプリケーションの基本クラス(アプリケーションウィザードが勝手に作ったファイル)
main.cppアプリケーションの基本クラス(アプリケーションウィザードが勝手に作ったファイル)
StdAfx.hほとんどのファイルがインクルードするファイル(アプリケーションウィザードが勝手に作ったファイル)
StdAfx.cpp何もありません(アプリケーションウィザードが勝手に作ったファイル)
resource.hリソースの管理IDが収められたヘッダ(アプリケーションウィザードが勝手に作ったファイル)

あと、実行ファイル、リソースファイル、プロジェクトファイルが入っています。

■はじめの一歩

DirectX を使わないプログラムというと、標準的な Windows プログラムになるのですが、 今回は、MFCを使って、画像を表示します。

MFCのプログラムを作るのにも、アプリケーションウィザードを使って、手を抜きましょう。
[ファイル]-[新規作成]-[プロジェクト]を選択して、アプリケーションウィザードを立ち上げます。
立ち上げたら、「MFC AppWizard(exe)」を指定して、プロジェクト名やファイルを作る場所を指定してください。

次に、ダイアログベースにするか、メモ帳のようなSDIタイプにするかを指定します。
ここでは、ダイアログベースを選びます。

次に、どの程度の機能を自動的にソースに埋め込むのかを選択します。
特に何もいらないので、適用にいらないものをはずします。

次は、コメントを自動的につけるかどうかの選択です。
あったほうが、どこを修正すればいいのかが明確になるので、つけておきましょう。

あとは、どんなファイルが生成されるかを説明してくれるので、 「終了」を押して、ファイルを実際に作成してもらいます。

基本的には、これだけでプログラムは完成です。実行すれば、ダイアログが表示されるでしょう。

■画像の描画

さて、プログラムが動いたら、次はダイアログに直接画像を描画してみましょう。
ダイアログに点を打つには、デバイスコンテキストと呼ばれるオブジェクトを所得して、デバイスコンテキストの点を打つメソッド「CDC::SetPixel(x,y,色)」を呼び出します。
デバイスコンテキストは、ダイアログプログラムならダイアログクラスのメンバーに含まれているので、GetDC()を使って所得することができます。
たとえば、画面の左上から100x100の大きさに黒い点を打つなら次のようになります。

CDC *pCdc= this->GetDC();// デバイスコンテキスト

for(int j=0;j<100;j++){
    for(int i=0;i<100;i++){
        pCdc->SetPixel(i,j,RGB(0,0,0);
    }
}

ReleaseDC(pCdc);// デバイスコンテキストは使い終わったら開放する

実際の MFC のプログラムでは、ダイアログの描画メソッド「CMainDlg::OnPaint()」に独自に描画する部分を追加します。

mainDlg.cpp 
0139: const int RENDER_WIDTH  = 512;
0140: const int RENDER_HEIGHT = 512;
0141: char s_data[4*RENDER_WIDTH*RENDER_HEIGHT];// 仮想フレームバッファ
0142: 
0143: void CMainDlg::OnPaint() 
0144: {
0145:     if (IsIconic())
0146:     {
              中略
0161:     }
0162:     else
0163:     {
0164:         CDialog::OnPaint();
0165: 
0166:         RECT r;
0167:         int offset = 10;//上下左の余白
0168:         int x = 120;
0169:         CDC *pCdc= this->GetDC();//デバイスコンテキストの所得
0170:         this->GetClientRect(&r);//表示領域の大きさを調べる
0171:         int h = r.bottom-r.top - 2*offset;//高さの決定
0172:         int w = r.right-r.left - offset - x;//幅の決定
0173:         if(h<0)h=0;//高さが負にならないように
0174:         if(w<0)w=0;//幅が負にならないように
0175:         if(h<w)w=h;else h=w;//正方形に表示する
0176: 
0177:         for(int j=0;j<h;j++){
0178:         for(int i=0;i<w;i++){
0179:             int no = (j*RENDER_HEIGHT/h)*RENDER_WIDTH
0180:                     + i*RENDER_WIDTH/w;
                  //点を打つ
0181:             pCdc->SetPixel(i+x,j+offset,RGB(
0182:                 s_data[4*no+0],//赤
0183:                 s_data[4*no+1],//緑
0184:                 s_data[4*no+2],//青
0185:             ));
0186:         }
0187:         }
0188:         ReleaseDC(pCdc);
0189:     }
0190: }

本番では、もう少し複雑に「s_data」というRENDER_WIDTH x RENDER_HEIGHTのサイズの2次元の画像イメージを連想させる配列を用意して、s_data を対応するピクセルに描画しました。
ただし、描画する大きさは、ダイアログの大きさによって変化するように作ったので、

0179:             int no = (j*RENDER_HEIGHT/h)*RENDER_WIDTH
0180:                     + i*RENDER_WIDTH/w;

の式を使って、描画するピクセルから配列の位置への変換を行っています。
ちなみに、今回の方法は完全ではなく、ダイアログのサイズを小さくしたときにはごみが残ってしまいますが、お許しください。

■ボタンの追加

次に、レンダリングの開始をユーザーが決められるように、ボタンを追加してみましょう。
リソースファイル(.rc)を編集しようとするとダイアログの見た目等のリソースを編集する画面になります。
ここで、「Dialog」フォルダのIDD_MAIN_DIALOGをクリックすると、ダイアログを編集できるようになります。
右の方を見るとツールボックスが見えます。
このツールボックスのボタンをクリックして、次にダイアログ上で再びクリックすると、ボタンがダイアログに追加されます。

このボタンをドラッグしたり、ボタンの上で右クリックするとでるポップアップメニューの「プロパティ」からボタンの名前を編集すると、次のように見た目を整えることができるでしょう。
(「TODO: ダイアログのコントロールをここに配置」のスタティックテキストも消しましょう)

次に、ボタンを押したときの振る舞いをプログラムします。
ダイアログのボタンをダブルクリックしてください。
「メンバ関数の追加」ダイアログが表示されるので、いい感じの名前に変更して、関数を追加します(ここでは、OnButtonRenderにしました)。すると、mainDlg.cpp 等に自動的にメソッドが追加されます。

このメソッドがクリックされたときに呼ばれるメソッドなので、 押されたときの振る舞いを適当に記述します。
今回は、画像イメージ「s_data」を適当なグラデーションで塗りつぶしました。
なお、配列を更新した後に、OnPaint()を呼ばないと、画面に反映されません。

mainDlg.cpp 
0199: void CMainDlg::OnButtonRender() 
0200: {
0201:     // フレームバッファの初期化
0202:     for(int j=0;j<RENDER_HEIGHT;j++){
0203:     for(int i=0;i<RENDER_WIDTH ;i++){
0204:         s_data[4*(j*RENDER_WIDTH+i)+0]=(char)255;// R
0205:         s_data[4*(j*RENDER_WIDTH+i)+1]=(char)(i*256/RENDER_WIDTH );// G
0206:         s_data[4*(j*RENDER_WIDTH+i)+2]=(char)(j*256/RENDER_HEIGHT);// B
0207:     }
0208:     }
0209: 
0210: 
0211:     this->OnPaint();
0212: }

これで、とりあえずはボタンを押したときに画面に絵(次以降はレンダリングの結果)が表示されるようなプログラムが完成しました。

■最後に

以外と、こういうところで手間取るんですよね。
今回の方法は、レイトレに限らず、ダイアログに絵を描くときに一般的に使えるので、 押さえておくと便利ですね。





もどる

imagire@gmail.com