2009年12月25日金曜日

strtok_sで文字列を分割する

std::stringでは速度的に不安が出てきたのでstrtok_sを使用することにした。
strtokだとコンパイラに怒られるので、ここは素直にMSの指示に従おう。
01#include <stdio.h>
02#include <string.h>
03 
04#ifndef null_ptr
05#define null_ptr 0
06#endif
07 
08char str[] ="asdc,fghm jkl;";//分割したい文字列
09char *delim = ", ";//デリミタ(複数渡せる)ここではカンマと空白
10char *ctx;//内部的に使用するので深く考えない
11 
12//実行例
13int main()
14{
15    char *next = strtok_s(str, delim, &ctx);
16    while(next){
17        printf("%s\n", next);
18        next = strtok_s(null_ptr, delim, &ctx);
19    }
20    // 出力結果 --------------------------------
21    // >> asdc
22    // >> fghm
23    // >> jkl;
24    return 0;
25}

GUIDを生成する

インクルードガード用にGUIDを生成するデスクトップアプリを作ろうと画策中。既にいろいろ便利なものはありますが、自作ってのにこだわってみたい。車輪の再発明大歓迎。JavaでもいいけどせっかくC++メインで組んでるので頑張ってみるか…。
01#include <windows.h>
02#include <tchar.h>
03 
04#ifndef null_ptr
05#define null_ptr 0
06#endif//null_ptr
07 
08/**
09* エントリポイント
10*/
11int WINAPI _tWinMain(
12                     HINSTANCE hInst,
13                     HINSTANCE,
14                     LPTSTR,
15                     int
16                     )
17{
18    GUID guid;
19    if(SUCCEEDED(CoInitializeEx(null_ptr, COINIT_APARTMENTTHREADED))){
20        CoCreateGuid(&guid);//GUIDを生成
21        CoUninitialize();//CoInitializeとセットで必ず呼び出すこと
22    }
23    return 0;
24}
CoInitializeEx()とCoUninitialize()はセットで呼び出す必要があるそうだ。new, deleteみたいなもんか? COMコンポーネントが未だに何なのかよく分からない。DirectXAPI関数を使用している時も似たような書き方を行うものが多いが…。

ここまで書いといてなんだが、やっぱりJavaでいいような気がしてきた…('A`)
参考サイト

2009年12月22日火曜日

std::stringを分割する



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


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

01#include <iostream>
02#include <vector>
03#include <string>
04 
05typedef std::string String;
06typedef std::vector<String> StringList;
07 
08/**
09* 文字列を分割してコンテナに格納する
10* @param dest 分割後の文字列格納先へのポインタ
11* @param src 分割したい文字列への参照
12* @param delim デリミタ文字列への参照
13* @return 分割後の文字列格納先へのポインタ
14*/
15StringList *SpritString(
16                        StringList *dest,
17                        const String &src,
18                        const String &delim
19                        )
20{
21    String::size_type start = 0;//デリミタを検索するインデクス番号
22    while(true){
23        //デリミタが現れる最初のインデクスを求める
24        String::size_type end = src.find(delim, start);
25 
26        //デリミタが見つかった場合
27        if(end != String::npos){
28            dest->push_back(src.substr(start, end - start));
29        }
30        //デリミタが見つからなかった場合
31        else{
32            //文末までを格納して返す
33            dest->push_back(src.substr(start, src.length() - start));
34            break;
35        }
36        //次の開始地点へ移動
37        start = end + delim.length();
38    }
39    return dest;
40}
41 
42/**
43* 呼び出し側
44*/
45int main(){
46    using namespace std;
47     
48    String src("hoge piyo fuga");//区切りたい文字列
49    String delim(" ");//デリミタ(区切り文字)
50    StringList out;//区切られた文字が配列となって帰ってくる
51    SpritString(&out, src, delim);//空白文字で区切る
52     
53    //出力
54    for(String::size_type i=0; i<out.size(); ++i){
55        cout << out[i] << endl;
56    }
57    /* 出力結果---------------------------
58    > hoge
59    > piyo
60    > fuga
61    */
62    return 0;
63}
参考先のサイトでは関数内部で作成したリストオブジェクトをそのまま返していたので、軽くする目的でポインタ経由での受け渡しにしてみた。そもそも軽くしたいならstd::stringなんか使うなって話だが…。便利なんだもん。

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

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

2009年12月16日水曜日

透過処理の実装

昨日に引き続きレイトレーシング。今日は透過処理を実装した。残念ながら屈折までは手が回らなかった、明日以降に引継ごう。屈折を行わない透過処理の場合、マテリアルの要素として環境光、拡散反射光、鏡面反射光に加えて、透過係数(transmit)を加える。
01/**
02* floatであらわされる色構造体
03*/
04struct FRGB{
05    float r,b,g;
06    static FRGB *Set(FRGB *out, unsigned char r, unsigned char g, unsigned char b);
07};
08 
09/**
10* 自作マテリアル構造体
11*/
12struct Material{
13    FRGB diffuse;//拡散反射光係数
14    FRGB ambient;//環境光係数
15    FRGB specular;//鏡面反射光係数
16    FRGB transmit;//透過係数
17};
透過係数は、0~1で定義される透明度。0の時は光を一切通さず、1の時は完全に光を通す(=透明となる)ものとする。
  1. 始点から画素方向へ向かって飛ばしたレイが、空間内に配置されたオブジェクトと衝突するかどうか調べる。
  2. 衝突しない場合は、画素に背景色を適用する。
  3. 衝突する場合は、始点に最も近い交点の座標を求め、再度交点からレイを発射する。
  4. 再発射したレイがオブジェクトと衝突している場合はここで、環境光・拡散反射光を求める。この値にレイを発射したオブジェクトの透過率を掛けて最終的な画素色とする。
以上のパターンでレンダリングした結果が以下の通り。


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

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

2009年12月15日火曜日

フォンシェーダの実装

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


 『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月13日日曜日

Javaでディレクトリ内のテキストファイル(*.txt)だけをリストアップする

昨日友人がやり方が分からないと嘆いていたので、ちょっと作ってみた。開発環境はnetbeans6.5.1です。
01import java.io.File;
02import java.io.FilenameFilter;
03 
04/**
05 * エントリポイント
06 */
07public class Main {
08    public static void main(String[] args) {
09        File dir = new File("C:\\");//Cドライブのルートディレクトリを開く
10         
11        //フィルタリングをかけてファイル名を列挙
12        String []files = dir.list(new MyFilter());
13         
14        //出力
15        for(int i=0; i<files.length; ++i){
16            System.out.println(files[i]);
17        }
18    }
19}
20 
21/**
22 * フィルタリングクラス
23 */
24class MyFilter implements FilenameFilter{
25    public boolean accept(File dir, String name) {
26        int index = name.lastIndexOf(".");//拡張子の"."を探す
27         
28        //"."以下の文字列を取り出して全て小文字に
29        String ext = name.substring(index+1).toLowerCase();
30 
31        //拡張子が"txt"と一致すれば取り出す
32        if(ext.equals("txt") == true) {return true;}
33 
34        //それ以外のファイルはリストアップしない
35        return false;
36    }
37}
FilenameFilterクラスは、インターフェースとして定義されておりまして、実際に使うときは自分でフィルタリングの真偽を判定するacceptメソッドを実装しなければなりません。まさに、インターフェースのお手本のようなクラスですねー。

今作ってるレイトレーサのシェーダクラスもこの作り方で切替えとかできそうです。OOPLに限らず、いろんな言語を使うことで設計について学べることがあって楽しい。関数型言語をもっと触らねばと思う今日この頃。

2009年12月11日金曜日

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

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


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

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

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

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

2009年12月10日木曜日

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

『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の時は割り算を行わないという処理をやっていたのですが…。
01Vector3 &Vector3::normalize()
02{
03    float len = x*x+y*y+z*z;//ノルムの平方
04    //以下len = sqrt(len);と同じ
05    __asm{
06        fld len
07        fsqrt
08        fstp len//お行儀が悪いがlenを使いまわす
09    }
10 
11    //0割防止
12    if(len < FLT_EPSILON){
13        x=y=z=0;
14    }
15    else{
16        float denom = 1.f / len;
17        x *= denom;
18        y *= denom;
19        z *= denom;
20    }
21    return *this;
22}
上記のif文の評価式をlen >= EPSILONにして、ブロックを入れ替えた方がジャンプが減る分確率的に速くなる…気がする。てかここでごっそり速くなった。
1if(len >= FLT_EPSILON){
2    float denom = 1.f / len;
3    x *= denom;
4    y *= denom;
5    z *= denom;
6}
7else{
8    x=y=z=0;
9}

2009年12月3日木曜日

SLAX覚え書き

今できること
  1. CDからSLAXを立ち上げる
  2. USBを手動でマウントして、WLANドライバをndiswrapperにインストールする
  3. ndiswrapperを手動で実行する
  4. HDDを汚さないお手軽自宅サーバの完成(・▽・)

TODO:
  1. USBドライブのマウントを自動化
  2. 無線LAN子機の認識までの設定をUSBに保存しておく

上記を達成するには
  1. Linuxのコマンドの基本的なところをしっかり学習
  2. Windowsでいうbatファイルに当たるshファイルの作成を勉強しなくては…