ラベル プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル プログラミング の投稿を表示しています。 すべての投稿を表示

2010年6月7日月曜日

std::mapの使い方

Check
Collada読み込みにおいて、stlのmapを使ったほうがよさそうな場面に出くわしたので、使い方をチェック。キーはstd::stringが手っ取り早そう。要素の挿入・参照方法は、イテレータを使用しないシンプルなものを採用した。

2010年5月25日火曜日

セミナーに参加します

Check
5月27日(木) ADOBE CREATIVE SUITE5 デザインセミナーツアー
サイバーコネクトツーによるフォトショ、イラレの活用事例。デザイナーではないので、どれほど身になるかはわからないが、行ってみる価値は十分にありそう。

5月28日(金) C/C++ セキュアコーディングセミナー 2010 @福岡
こちらはC/C++プログラマ向けのコーディングセミナー。よりセキュアーなコーディングを目指して。
ゲームプログラミングでどれだけ役に立つかはわからないが、学生気分を抜けるにはいい機会だと思う。

28日は名刺を持ってくるように指定があったので、知り合いのデザイナーさんにお願いして作ってもらった。思っていた以上の出来だったので大満足! 

//TODO: 写真を加工してあとで貼る。

今年も半分が過ぎようとしていますが、これからじゃんじゃか動いていきます!

2010年5月5日水曜日

GWやったこと

Check
いろいろやりすぎてブログぜんぜん書いてねえ・・・。

以下、GW中の生活の様子を簡単にまとめる。

読書
PC・プログラム
  • PuTTYでWindowsからLinux鯖にアクセスしてそこでemacs使って簡単なプログラムをビルドできるようになった。
  • Makefileを書いて、作業ディレクトリで"make"と打つだけでビルドできるようになった。
  • glutを使用したウィンドウをlinux上で作成することができるようになった。
  • Debian(lenny)上で日本語の表示(jfbterm)と入力(uim-fep+anthy)ができるようになった。
  • flexなる字句解析用のジェネレータ?を使ってみた。
  • プライベート用のWinXPマシンが死亡。原因は不明だが、このままパーツだけ取ってさよならになりそう。
私生活
  • 京都のゲーム会社に4月から入社した友人がGW休暇で帰ってきたのでいろいろ面白い話をきいた。
  • 部屋を掃除して、本を50冊ほど売ってきた。
とにかく継続して何かを続けること、学生であるこの1年はがっつりプログラミングに身を捧げることを誓った連休でした。

2010年4月13日火曜日

VC++のキーワードハイライト機能を拡張する

Check
VC++2008のお話。

"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE"フォルダ下にある"usertype.dat"を開く。なければ作成。

登録したいキーワードを追加する。記述の仕方はいたって簡単で、ハイライトしたいキーワードを1行ずつ追加するだけ。以下の例はGLSLの組み込み型。
次にVCのメニューバーから"ツール"->"オプション"->"テキストエディタ"->"ファイル拡張子"を選択。追加したい拡張子と、編集用のエディタを選択する。
私の場合はバーテクスシェーダファイルは"vert"、ピクセルシェーダファイルは"frag"で登録しました。

最後にVCを再起動して終了。

これだけだとほかのブログにもあるので、さらに小ネタを追加。先ほど追加した"usertype.dat"ですが、新しい型が出てくるたびにフォルダから探して編集するのが面倒なので、ショートカットを作成しちゃいましょう。
最後にショートカットの"プロパティ"->"リンク先"をテキストエディタに変更して、もともとのリンク先はそのまま引数として使う。
画像の1番が追加する使用したいエディタへのパス。2番がもともと記述されていた"usertype.dat"へのパスです。ショートカットのアイコンがエディタのものに変わっていれば成功。このショートカットをデスクトップや、プロジェクトのディレクトリに放り込んでおけばいつでも1クリックで追加したい型を編集可能になります。

2010年4月3日土曜日

PythonでFizzBuzz

Check
4月→新入社員→研修→FizzBuzz!!

ということでやってみた。社会人じゃないけど。
一番苦労したのがprintだと改行が上手くいかないので最後にカンマをつけるってところ。

※下記のプログラムには誤りがあります!
for i in range(1, 100):
    if(i % 3 == 0) or (i % 5 == 0):
        print 'fizz',
        if(i % 5 == 0):
            print 'buzz',
    else:
        print i,
    print '\n',
この手のアルゴリズムネタはスクリプト言語でやるのが楽しいね。

(追記)
とか格好つけて嘘っぱちなコードをネット上に晒すとかアホもいいとこだ。改めて実行すると上記のアルゴリズムじゃダメダメですね。即効で直したものを上げておこう。あー恥ずかしい!

for i in range(1, 100):
    if(i % 3 == 0) or (i % 5 == 0):
        if(i % 3 == 0):
            print 'fizz',
        if(i % 5 == 0):
            print 'buzz',
    else:
        print i,
    print '\n',
参考サイト

2010年3月31日水曜日

ウィンドウ表示とメッセージ処理

Check
そろそろ新学期ということで、かわいいかわいい後輩も入ってくるそうです。

放課後にプログラムの部活みたいなのをやれればいいねーみたいな話になったので、そのときちょこっとだけ教えられるようにとWIN32API周りを復習しております。

何度やっても初期化のあたりは忘れるので、今超適当に組んだコードを保存しておく。今後はこいつのコピペで済ませよう…。
#include <windows.h>
#include <tchar.h>

//定数など
namespace
{
    const TCHAR* APPNAME = _T("01CreateWindow");
    const int APP_W = 400;
    const int APP_H = 300;
}

//メッセージ処理
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch(msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWnd, msg, wp, lp);
}

//エントリポイント
int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE, TCHAR*, int)
{
    //ウィンドウクラスの登録
    WNDCLASS wc;
    memset(&wc, 0, sizeof(wc));
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.hCursor = (HCURSOR)LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = (HICON)LoadIcon(NULL, IDI_APPLICATION);
    wc.lpfnWndProc = MyWndProc;
    wc.hInstance = hInst;
    wc.lpszClassName = APPNAME;

    //クラス登録
    if(!RegisterClass(&wc))
    {
        //メッセージなどを出して終了
        exit(1);
    }

    //ウィンドウサイズの調整
    RECT r;
    SetRect(&r, 0, 0, APP_W, APP_H);
    AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);

    //ウィンドウの作成
    HWND hWnd = CreateWindow(APPNAME, APPNAME,
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
        r.right - r.left, r.bottom - r.top,
        NULL, NULL, hInst, NULL);

    //作成できない場合のエラー処理
    if(!hWnd)
    {
        //メッセージなどを出して終了
        exit(1);
    }

    //ウィンドウの表示
    ShowWindow(hWnd, SW_SHOW);

    //メインループ
    while(true)
    {
        //メッセージ処理
        MSG msg;
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            DispatchMessage(&msg);
        }
        //終了時はループからぬける
        if(msg.message == WM_QUIT) break;

        //この辺にメインロジックを書く
    }
    return 0;
}
こんなのが出ます。
PeekMessageを使っているのは、今後このスケルトンをゲーム用に拡張していくため。GetMessageとかは使わないです。ポーリングも自前でできればいいけど、面倒なときはメインループの末端にSleep(1)とか入れとけばいい。次回はDirect3D9の初期化でもやるか。

2010年2月26日金曜日

OpenCLはじめました

Check
大阪・東京と移動が多い…。ようやく、じっくりとCLを触れるようになったので実験。

参考文献はインプレスジャパンの『OpenCL入門 マルチコアCPU・GPUのための並列プログラミング

 
128個の配列に入った実数を2倍するだけのプログラム。

以下『OpenCL入門』で印象に残った部分覚え書き。
  1. OpenCLは、マルチコアCPUやGPUなど異なるアーキテクチャを持つ環境(ヘテロジニアス)に適した並列プログラミングのためのフレームワークである。
  2. OpenCLプログラミングは、GPUなどの演算機上で動作するカーネルとそのカーネルを制御するホストの2種類のプログラムを記述する。カーネルはOpenCL C言語というCライクな専用言語で書き、ホスト側はいつもどおりC/C++での記述となる。

2010年2月12日金曜日

C++でnullポインタをdeleteしても安全

Check
タイトルの通りです。
意外と知らない人が多いので書いてみた。C++の規約レベルで安全は保障されているので大丈夫。
(中略)if the value of the operand of delete is the null pointer the operation has no effect.
ただし、(中略)と太字強調は筆者による。
//冗長なdelete
if(ptr)
{
    delete ptr;
}

//楽ちんなdelete
delete ptr;
上記のコードはどちらも安全。delete[]についても同様。
VC++のoperator deleteの中身を見てみたら、内部でnullチェックを行っているようだ。

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年1月14日木曜日

wxWidgets事始め

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

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


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

2010年1月13日水曜日

C++で経路探索

Check
事の始まりは某所のC#スレで見かけたこの記事。ちょこっと引用すると、
内容は、壁とスペースで構成された迷路が与えられたとき、スタート地点からゴール地点に至る最短経路を求めよ、というものです。
たとえば、S:スタート G:ゴール *:壁 $:解答の経路 としたとき、
**************************
*S* *                    *
* * *  *  *************  *
* *   *    ************  *
*    *                   *
************** ***********
*                        *
** ***********************
*      *              G  *
*  *      *********** *  *
*    *        ******* *  *
*       *                *
**************************
とのこと。ふむ、なかなか面白そうじゃない! 独りでやるのも寂しいので、学校の先生と競争してみることに。
#include <iostream>
#include <string>
#include <fstream>
#include <vector>

typedef unsigned char byte;
typedef unsigned int uint;
typedef std::vector<std::string> StringList;

using namespace std;

/**
* 2次元配列の位置などを表す構造体
*/
struct POS{
    uint x, y;
};

//定数
const char *fname = "dat.txt";//ファイルデータパス
const byte WALL = -1;//壁を表す定数

//
// 再帰的にスタートからゴールへ向かって最短距離を求める
// @param now 現在位置
// @param from 一つ前の位置
// @param dat 書き換えるデータ配列
// @param width データ配列幅
// @param start スタート地点
// @param goal ゴール地点
// @return none
//
void SearchPath(
                uint count,
                const POS &now,
                const POS &from,
                byte *dat,
                uint width,
                const POS &start,
                const POS &goal
                )
{
    //再帰の終了をチェック
    if(now.x == goal.x && now.y == goal.y){
        dat[now.x+now.y*width] = count+1;
    }

    //次に移動する座標を決定する
    POS next[4] ={
        {now.x, now.y-1},//上
        {now.x, now.y+1},//下
        {now.x-1, now.y},//左
        {now.x+1, now.y},//右
    };
    for(uint i=0; i<4; ++i){
        uint idx = next[i].x + next[i].y*width;//次に調べる位置

        //次の位置が壁なら調べない
        if(dat[idx] == WALL){
            continue;
        }

        //次に調べる位置が既に調べてあり
        //かつ、すでに最短パスが出ていたらこれ以上調べない
        if(dat[idx] > 0 && dat[idx] < count){
            continue;
        }

        //現在の位置にこれまでの歩数を記録する
        dat[now.x+now.y*width] = count;

        //次の経路を再帰的に調べる
        SearchPath(count+1, next[i], now, dat, width, start, goal);
    }
}

//
// ゴール地点からスタート地点へ向かって最短経路を描画する
// @param sl 書き出す文字列リスト
// @param now 現在位置への参照
// @param start スタート地点
// @param goal ゴール地点
// @param dat ステップ数が記述されたデータ配列(壁は0xff)
// @param width データ配列幅
//
void FindPath(
              StringList *sl,
              const POS &now,
              const POS &start,
              const POS &goal,
              const byte *dat,
              uint width
              )
{
    //再帰打ち切りの条件
    //すなわち、現在地がスタート地点なら終了
    if(now.x == start.x && now.y == start.y){
        return;
    }

    //次に移動する座標を決定する
    POS next[4] ={
        {now.x, now.y-1},//上
        {now.x, now.y+1},//下
        {now.x-1, now.y},//左
        {now.x+1, now.y},//右
    };

    //現在のステップ数
    uint nowStep = dat[now.x+now.y*width];

    for(uint i=0; i<4; ++i){
        uint idx = next[i].x + next[i].y*width;//次に調べる位置

        //次の位置が壁なら調べない
        if(dat[idx] == WALL){
            continue;
        }

        //次の位置が現在のステップ数より小さいときは
        //再帰的に調べる
        if(dat[idx] == nowStep-1){
            //経路マークをつける
            char mark = '$';
            if(now.x == goal.x && now.y == goal.y){
                mark = 'G';
            }
            sl->at(now.y).at(now.x) = mark;

            //次の経路を再帰的に調べる
            FindPath(sl, next[i], start, goal, dat, width);
            return;
        }
    }
}

//
//エントリポイント
//
int main(){
    //
    // テキストからデータを読み込んで配列に格納
    //
    std::fstream fs(fname);
    StringList sl;
    while(!fs.eof()){
        string tmp;
        getline(fs, tmp, '\n');
        sl.push_back(tmp);
    }

    //数値に変換して配列に格納
    uint height = sl.size();//配列高さ
    uint width = sl[0].size();//配列幅
    byte *dat = new byte [width*height];//作業用配列

    POS start = {0,0};//スタート
    POS goal = {0,0};//ゴール
    POS now = {0,0};//現在地

    for(uint y=0; y<height; ++y){
        for(uint x=0; x<width; ++x){
            uint idx = x+y*width;
            dat[idx] = 0;
            switch(sl[y][x]){
                case '*':
                    dat[idx] = WALL;
                    break;
                case 'G':
                    goal.x = x;
                    goal.y = y;
                    break;
                case 'S':
                    start.x = now.x = x;
                    start.y = now.y = y;
                    break;
            }
        }
    }

    //
    // 経路探索
    // 上下左右に対して再帰的に検索をかける
    //
    SearchPath(0, start, start, dat, width, start, goal);

    //探索したパスを元に最短経路を描画する
    FindPath(&sl, goal, start, goal, dat, width);

    //
    //デバグ用表示
    //
    for(uint y=0; y<sl.size(); ++y){
        cout << sl[y] << endl;
    }

    //
    //ファイル書き出し
    //
    ofstream ofs("out.txt");
    for(uint y=0; y<sl.size(); ++y){
        ofs << sl[y] << endl;
    }
    ofs.close();

    //あとしまつ
    delete [] dat;
    dat = 0;

    return 0;
}
結果は惨敗。相手は1時間ちょっとで終わっていたのに対して、私は4時間かけてようやくといったところ。先生はPythonを使われていいたようです。てかこの手の問題でC++を使う時点でダメだろ俺…。Pythonなどの高級言語や、関数型言語ならもっともっと美しく簡潔に書けたでしょうね。とにかく自分の無能さが身に染みてわかった。くやしい!

自分のアホさにげんなりした問題でした。さて、寝よう(:D)┼─┤

2010年1月7日木曜日

std::fstreamでファイルサイズを取得

Check
メタセコイア読み込みに関連して、std::fstreamからデータを読み込む際に必要なことのまとめ。
#include <iostream>
#include <fstream>

typedef unsigned int uint;
const char *fname = "C:/hoge/piyo.txt";//ファイルパス

int main()
{
    using namespace std;

    ifstream fs(fname);//ファイルオープン。読み込み形式は指定なしのときはテキストモードになる。

    fs.seekg(0, fstream::end);//ファイル末尾を探す
    uint eofPos = fs.tellg();//ファイル末尾インデクスを取得

    fs.clear();//先頭にもどるために一度clear()をかける。これをしないと次のseekg()でコケるときがある。

    fs.seekg(0, fstream::beg);//ファイル先頭に戻る
    uint begPos = fs.tellg();//ファイル先頭インデクスを取得

    uint size = eofPos - begPos;//末尾-先頭でファイルサイズを計算

    char *buf = new char [ size ];//サイズ分の領域を確保
    memset(buf, 0, size);//0クリアしておく

    fs.read(buf, size);//ファイル先頭からバッファへコピー

    fs.close();//ファイルを明示的に閉じてみる

    cout << buf << endl;//確認のため出力

    SAFE_DELETE_ARRAY(buf);//バッファを削除
    return 0;
}
基本は上記のとおり。

例外処理も入れるべきだが、使い方を間違えると正しく読み込めているのに例外が送出されてしまうので注意。
下記のリストに挙げた『std::ifstream::read()の使い方がツライ』の記事を参考されたい。
参考

2009年12月25日金曜日

strtok_sで文字列を分割する

Check
std::stringでは速度的に不安が出てきたのでstrtok_sを使用することにした。
strtokだとコンパイラに怒られるので、ここは素直にMSの指示に従おう。
#include <stdio.h>
#include <string.h>

#ifndef null_ptr
#define null_ptr 0
#endif

char str[] ="asdc,fghm jkl;";//分割したい文字列
char *delim = ", ";//デリミタ(複数渡せる)ここではカンマと空白
char *ctx;//内部的に使用するので深く考えない

//実行例
int main()
{
    char *next = strtok_s(str, delim, &ctx);
    while(next){
        printf("%s\n", next);
        next = strtok_s(null_ptr, delim, &ctx);
    }
    // 出力結果 --------------------------------
    // >> asdc
    // >> fghm
    // >> jkl;
    return 0;
}

GUIDを生成する

Check
インクルードガード用にGUIDを生成するデスクトップアプリを作ろうと画策中。既にいろいろ便利なものはありますが、自作ってのにこだわってみたい。車輪の再発明大歓迎。JavaでもいいけどせっかくC++メインで組んでるので頑張ってみるか…。
#include <windows.h>
#include <tchar.h>

#ifndef null_ptr
#define null_ptr 0
#endif//null_ptr

/**
* エントリポイント
*/
int WINAPI _tWinMain(
                     HINSTANCE hInst,
                     HINSTANCE,
                     LPTSTR,
                     int
                     )
{
    GUID guid;
    if(SUCCEEDED(CoInitializeEx(null_ptr, COINIT_APARTMENTTHREADED))){
        CoCreateGuid(&guid);//GUIDを生成
        CoUninitialize();//CoInitializeとセットで必ず呼び出すこと
    }
    return 0;
}
CoInitializeEx()とCoUninitialize()はセットで呼び出す必要があるそうだ。new, deleteみたいなもんか? COMコンポーネントが未だに何なのかよく分からない。DirectXAPI関数を使用している時も似たような書き方を行うものが多いが…。

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

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

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

Check
昨日友人がやり方が分からないと嘆いていたので、ちょっと作ってみた。開発環境はnetbeans6.5.1です。
import java.io.File;
import java.io.FilenameFilter;

/**
 * エントリポイント
 */
public class Main {
    public static void main(String[] args) {
        File dir = new File("C:\\");//Cドライブのルートディレクトリを開く
        
        //フィルタリングをかけてファイル名を列挙
        String []files = dir.list(new MyFilter());
        
        //出力
        for(int i=0; i<files.length; ++i){
            System.out.println(files[i]);
        }
    }
}

/**
 * フィルタリングクラス
 */
class MyFilter implements FilenameFilter{
    public boolean accept(File dir, String name) {
        int index = name.lastIndexOf(".");//拡張子の"."を探す
        
        //"."以下の文字列を取り出して全て小文字に
        String ext = name.substring(index+1).toLowerCase();

        //拡張子が"txt"と一致すれば取り出す
        if(ext.equals("txt") == true) {return true;}

        //それ以外のファイルはリストアップしない
        return false;
    }
}
FilenameFilterクラスは、インターフェースとして定義されておりまして、実際に使うときは自分でフィルタリングの真偽を判定するacceptメソッドを実装しなければなりません。まさに、インターフェースのお手本のようなクラスですねー。

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

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;
    }