ラベル RTRT の投稿を表示しています。 すべての投稿を表示
ラベル RTRT の投稿を表示しています。 すべての投稿を表示

2010年2月12日金曜日

slaxでレイトレーサ

Check
自作のレイトレーサをslaxに移植して「もらった」。学校の先生が空いた時間を使ってやってくださったようだ。
これにより、企業への作品提出にOSごと送りつけるという、面白いことができるわけだ(迷惑かな?)。

ただ、VC++とG++の違いを意識しなくてはならなくなった。具体的にはインラインアセンブラの構文や一部の関数が異なり、現時点では_MSC_VERマクロの有無で切り分けるようにしている。
float Vec::length() const
{
    float len = x*x+y*y+z*z;
#ifdef _MSC_VER
    __asm
    {
        fld    len
        fsqrt
        fstp   len
    }
#else
    len = sqrt(len);
#endif
    return len;
}
速度重視のため、アセンブラ部分はかなりの部分をベタ書きのハードコーディングにしていたが、インライン関数に置き換えるなどしないとメンテしづらいことがわかった。

wxWidgetsはソースコードレベルでの互換性を保障しているわけだが、実際には結構な部分に手を加えなければならなかった。今後は、移植を前提とした書き方に慣れる必要があるだろう。

C#やJavaなどのVM上で動く言語が主流になるのも頷ける。

2010年2月4日木曜日

GUIでレイトレーサ

Check
面接練習やら、企業への作品提出やらでちと更新が滞り気味。作品制作はぼちぼち進んでいるというった具合で、ようやくwxWidgetsの扱い方に慣れてきた。
 
[Render->Start]を選択すると、今まで作ってきたレイトレーサが画面に描画してくれるという仕組み。こうやって形にすると、いろいろ追加したいことが出てくる。とりあえず、メタセコイアオブジェクトの読み込みとレンダリングは入れておきたい。さらにオブジェクトの設定をGUI側でできるようになりたい。

現在レンダリング速度が情けないこと(4~500ms)になっているので、事前処理として空間分割を行うことで、レンダリング時間の短縮を狙う。さらに、ここからOpenCLによるGPGPUを使用した並列レンダリング(?)にチャレンジ。ここからはハードウェアのパワーを借りようというわけだ。

2010年1月14日木曜日

wxWidgets事始め

Check
いよいよレイトレーサをGUIアプリケーションとしてまとめることになった。どうせやるならクロスプラットフォームということで、開発にはwxWidgetsを使うことに。

とりあえず導入までに参考にしたサイトを列挙。
以上を参考にして作成したのが以下のアプリケーションだ。


中身は空のハリボテだが、C++でここまで楽にGUIアプリケーションが構築できるのには正直驚いた。オブジェクト指向を取り入れてあるのでJavaのSwingよろしく組みやすい。

2009年12月22日火曜日

std::stringを分割する

Check


レイトレーサはほぼ完成。実行速度は一気に落ちて300ms@Releaseともはやどこがリアルタイムだか分からない結果に。オブジェクト数が少ないと100msを切れるのだが、屈折・透過・鏡面反射をするオブジェクトが複数あれば計算量は指数的に増えるわけで…。とはいえそろそろフォトンマッピングが見えてきた、かな?


今日からはメタセコイアデータ(MQOフォーマット)を読み込んでレイトレーシングで描画するビューアの作成に取り掛かっている。さて、メタセコのデータを読むにあたって、テキストファイルを解釈しなければならない。そんな時にstd::stringクラスを任意のデリミタ(分割子)で分割できると便利なので、早速作ってみた。こちらのサイトが非常に参考になった。

#include <iostream>
#include <vector>
#include <string>

typedef std::string String;
typedef std::vector<String> StringList;

/**
* 文字列を分割してコンテナに格納する
* @param dest 分割後の文字列格納先へのポインタ
* @param src 分割したい文字列への参照
* @param delim デリミタ文字列への参照
* @return 分割後の文字列格納先へのポインタ
*/
StringList *SpritString(
                        StringList *dest,
                        const String &src, 
                        const String &delim
                        )
{
    String::size_type start = 0;//デリミタを検索するインデクス番号
    while(true){
        //デリミタが現れる最初のインデクスを求める
        String::size_type end = src.find(delim, start);

        //デリミタが見つかった場合
        if(end != String::npos){
            dest->push_back(src.substr(start, end - start));
        }
        //デリミタが見つからなかった場合
        else{
            //文末までを格納して返す
            dest->push_back(src.substr(start, src.length() - start));
            break;
        }
        //次の開始地点へ移動
        start = end + delim.length();
    }
    return dest;
}

/**
* 呼び出し側
*/
int main(){
    using namespace std;
    
    String src("hoge piyo fuga");//区切りたい文字列
    String delim(" ");//デリミタ(区切り文字)
    StringList out;//区切られた文字が配列となって帰ってくる
    SpritString(&out, src, delim);//空白文字で区切る
    
    //出力
    for(String::size_type i=0; i<out.size(); ++i){
        cout << out[i] << endl;
    }
    /* 出力結果---------------------------
    > hoge
    > piyo
    > fuga
    */
    return 0;
}
参考先のサイトでは関数内部で作成したリストオブジェクトをそのまま返していたので、軽くする目的でポインタ経由での受け渡しにしてみた。そもそも軽くしたいならstd::stringなんか使うなって話だが…。便利なんだもん。

デリミタ自体もベクタオブジェクトに突っ込んで、複数のデリミタに対応するとかいろいろ出来そう。

MQOローダ自体は以前も作ったのだが、設計が気に食わなかったので最初から作り直している。その際に『ゲームプログラマになる前に覚えておきたい技術(通称:セガ本)』の「XMLモドキを読む」という項が大変参考になった。MQOファイル自体はXML形式ではないので若干の改造が必要になるが、基本となる考え方はセガ本からまるまるパクッている。明日には完成させたい。

2009年12月16日水曜日

透過処理の実装

Check
昨日に引き続きレイトレーシング。今日は透過処理を実装した。残念ながら屈折までは手が回らなかった、明日以降に引継ごう。屈折を行わない透過処理の場合、マテリアルの要素として環境光、拡散反射光、鏡面反射光に加えて、透過係数(transmit)を加える。
/**
* floatであらわされる色構造体
*/
struct FRGB{
    float r,b,g;
    static FRGB *Set(FRGB *out, unsigned char r, unsigned char g, unsigned char b);
};

/**
* 自作マテリアル構造体
*/
struct Material{
    FRGB diffuse;//拡散反射光係数
    FRGB ambient;//環境光係数
    FRGB specular;//鏡面反射光係数
    FRGB transmit;//透過係数
};
透過係数は、0~1で定義される透明度。0の時は光を一切通さず、1の時は完全に光を通す(=透明となる)ものとする。
  1. 始点から画素方向へ向かって飛ばしたレイが、空間内に配置されたオブジェクトと衝突するかどうか調べる。
  2. 衝突しない場合は、画素に背景色を適用する。
  3. 衝突する場合は、始点に最も近い交点の座標を求め、再度交点からレイを発射する。
  4. 再発射したレイがオブジェクトと衝突している場合はここで、環境光・拡散反射光を求める。この値にレイを発射したオブジェクトの透過率を掛けて最終的な画素色とする。
以上のパターンでレンダリングした結果が以下の通り。


見事に影を落とすのを忘れてた。加えて、透過の回数を1回までとしているため、半透明のオブジェクトの先にさらに半透明のオブジェクトが存在した場合の処理が不自然になっている。画像の緑→黄色→赤の順に並んだ球体で、黄色の後ろに赤の玉が透けて見えないといけないのだが、うまくいっていない。

明日はこのあたりの改善+屈折に入っていこう。苦手な再帰呼び出しをいかに克服するかがポイントになりそうだ。

2009年12月15日火曜日

フォンシェーダの実装

Check
今日はプラスチックや金属の光沢感を出すためのフォンシェーダにチャレンジ。


 『CによるCGレイトレーシング』によると、フォンのモデルの求め方は以下の通り。
入射光をIp
鏡面反射係数をks
レイの正反射方向と視線のなす角をa
レイの正反射方向ベクトルをL'
視線へのベクトルをV
と置いたときの表面輝度Isは
Is = ks*Ip*cosna = ks*Ip*(dot(L', V))n
となる
この時、自然数nの値を大きくすればするほど、ハイライトの広がりは抑えられて、シャープな印象になるそうです。
n=1のとき

n=16のとき



L'ってのは早い話が、ライトからの光が物体の表面に正反射した時の反射方向ベクトルですから、求め方は以下の通り。
入射ベクトルをL
表面の法線をNとすると
L' = 2*(dot(N,V))*N - L
最終的な表面の輝度は【環境光+拡散反射光+鏡面反射光】になるので、ランバートシェーダの処理のあとに上記の処理を入れるだけでフォンのモデルは実装できちゃうわけですね。

実装がお手軽なわりには、なかなか効果が出てるかなと思いました。とはいえ速度的に半分の32ms@Releaseとそろそろ不安が…。計算とか工夫すれば、まだいける…はず。

明日はいよいよ屈折をやります。

2009年12月11日金曜日

レイトレーシングで複数オブジェクトを描画

Check
 昨日に続きレイトレーシングを。


今日は2種類(無限平面と球体)のオブジェクトを複数描画することにチャレンジ。Celeron 2.4GHz で平均43ms@Release と昨日の倍以上の速度で動いてくれました。Core2Duo E6600 2.4GHzでは14ms@Releaseとまだまだ余裕。

PlaneクラスとSphereクラスは共通のインターフェースObjectDataを継承しており、このおかげで当たり判定などをスマートに記述することができました。この辺はOOPLの旨みを活かせたかなと思います。仮想関数呼び出しは重いかなぁと心配だったのですが、switch文と大して速度は変わらなかったので良しとしよう。

明日は影付けですかね…。屈折やテクスチャリングなど、どんどこ重くなる要素てんこ盛りですが、30fpsはキープできるようにしたい…。

小ネタ:
空のコンストラクタはヘッダファイルに記述すると、コンパイラが最適化の際に呼び出し命令を省略してくれることもあるそうです。→参考になったサイト

2009年12月10日木曜日

リアルタイムレイトレーシング事始め

Check
『Javaではじめるレイトレーシング入門』という面白い書籍を見つけたので、レイトレーシングに挑戦。大まかな仕組みをつかんだところで、C++で書き起こしてみることに。先生にお借りした『CによるCGレイトレーシング』がかなり参考になった。

とりあえず、カメラとスクリーンはワールド空間に固定して、球体を描画。シェーディングはランバートで。画素数は500*500です。



我が家のポンコツマシン(Northwood Celeron 2.4GHz)で実行したところ、Debugで600ミリ秒、Releaseで93ミリ秒というなんとも情けない結果に終わった。FPS換算だと1~10FPSとかちょっと…。というかまだオブジェクト1個だし、屈折とかテクスチャリングとかやってないし!

言い訳すると有り合わせのクラスをパチ組みしただけなので、無駄な呼び出しというか、OOPっぽい書き方になってるのが遅れの原因か。ローカルに作ったワーク変数が、空のコンストラクタを呼び出してたりいろいろ無駄がありそう。

とはいえ、一番の原因はアルゴリズム部分でしょうね。きっとまだ無駄な判定やってるに違いない、今から見なおそう。

TODO:
飽和加算とか。

<追記 2009.12.10@2:10am>
320*240で10fps@Debug, 60fps@Releaseを達成できました。
500*500は3fps@Debug, 13fps@Releaseが今のところ限界…。

ベクトルの正規化がかなり足を引っ張っていたようです。0割防止のため、ノルムが0の時は割り算を行わないという処理をやっていたのですが…。
Vector3 &Vector3::normalize()
{ 
    float len = x*x+y*y+z*z;//ノルムの平方
    //以下len = sqrt(len);と同じ
    __asm{
        fld len
        fsqrt
        fstp len//お行儀が悪いがlenを使いまわす
    }

    //0割防止
    if(len < FLT_EPSILON){
        x=y=z=0;
    } 
    else{
        float denom = 1.f / len;
        x *= denom;
        y *= denom;
        z *= denom;
    }
    return *this;
}
上記のif文の評価式をlen >= EPSILONにして、ブロックを入れ替えた方がジャンプが減る分確率的に速くなる…気がする。てかここでごっそり速くなった。
    if(len >= FLT_EPSILON){
        float denom = 1.f / len;
        x *= denom;
        y *= denom;
        z *= denom;
    } 
    else{
        x=y=z=0;
    }