続・トゥーンシェーダ

前回上手くいかなかった輪郭線の抽出をフィルタを使ってやってみた。
t-potさんの本を参考にフィルタを作成。
一度レンダリングした画像の深度バッファからテクセルの差分をとって輪郭線を書く。
という方法で実装した。

元がアセンブラのコードだったので、HLSLで書いてみた。

fxファイルより一部抜粋

float du = 1.0f / 640.0f / 2; // テクセル幅 / 2
float dv = 1.0f / 480.0f / 2;

sampler Samp0 : register(s0);

// エッジを抽出する
float4 EdgePS(float2 Tex : TEXCOORD0) : COLOR
{
    float4 Out = (float4)0;

    float2 Tex1 = Tex; // 隣のテクセル
    Tex1.x -= du;
    Tex1.y -= dv;
    float2 Tex2 = Tex;
    Tex2.x -= du;
    Tex2.y += dv;
    float2 Tex3 = Tex;
    Tex3.x += du;
    Tex3.y += dv;
    float2 Tex4 = Tex;
    Tex4.x += du;
    Tex4.y -= dv;
    float4 Sub1 = tex2D(Samp0, Tex1) - tex2D(Samp0, Tex3);
    float4 Sub2 = tex2D(Samp0, Tex2) - tex2D(Samp0, Tex4);
	
    Out.rgba = 1.0f - 128*(Sub1.a*Sub1.a + Sub2.a*Sub2.a);

    return Out;
}

もっとスマートな書き方がありそうだが、動いているので気にしない方向で。

今更だけど

トゥーンシェーダを実装してみた。
まず、色の階調化だけど、これは簡単にできた。

ライトのベクトルと法線の内積をとって、UV座標を計算。ベクトルは正規化しないとU値が1より大きくなるので注意。このままでは、値が0以下のこともありえるので、0から1の範囲になるように調整する。

次は、輪郭線の抽出。さっきと同じで、視線のベクトルと法線の内積をUV座標のV値に代入する。

後は計算したUVを元にトゥーン用のテクスチャから色を拾って描画。これで、おしまい。

描画してみた

何か輪郭線が汚い。上手く抽出できてないようだ。


以下、fxファイルの中身

float4x4 mWVP;
float3 vLight = {0.557f, -0.557f, 0.557f}; // ライトの方向
float3 vEye;

// 頂点シェーダー出力
struct VS_OUTPUT
{
    float4 Pos : POSITION;  // 座標
    float2 Tex : TEXCOORD0; // テクスチャUV
};

VS_OUTPUT ToonVS(float4 Pos : POSITION, float4 Normal : NORMAL)
{
    VS_OUTPUT Out = (VS_OUTPUT)0;

    Out.Pos = mul(Pos, mWVP); // 座標変換
    float3 N = normalize(Normal.xyz); // 法線を正規化

    Out.Tex.x = max(dot(vLight, N), 0) * 0.5f + 0.5f; // 0から1の範囲に修正

    float3 E = normalize(vEye - Pos.xyz); // 視線のベクトル
    Out.Tex.y = dot(E, N); // vを計算、内積が小さいとき、輪郭抽出を行う

    return Out;
}

technique ToonShader
{
    pass P0
    {
        VertexShader = compile vs_2_0 ToonVS();
    }
}

また、しょうもないことで時間を無駄にした

描画バッファをスワップするたびに、謎のちらつきが発生。
ソースを読み返したら深度バッファ使ってないのに、深度バッファのクリアフラグが書かれているのを発見。
消去したら直ったが、これで30分くらい無駄になった・・・。

アライメントの関係でBitmapは扱いづらいので、自前のフォーマットを作成。といっても、ヘッダを縦幅と横幅のみして、ピクセルデータをRGBAにしただけ。また、少しだけ読み込みが速くなった。

width & height RGBA RGBA RGBA

全部4byte境界だから扱い易い

Spriteで画像表示

PSPSDKのGuを使用して画像表示するとこまでやった。
サンプルではテクスチャを事前にbin2oでオブジェクトファイルにして読み込んでいるが、いちいち画像を追加するたびにMakefileを書き直さなくてはいけないし、rawファイルしか使えないので面倒すぎる。データの取り扱いにも困るのでビットマップを読み込めるようにした。

あと、メモステから読み込むときのバッファを増やしたら、更に読み込みが速くなった。

PSPSDKのfreadはゆっくりしすぎ

PSPSDKのfreadを使って画像を読み込もうとしたのだが、512*512のビットマップをロードするのに軽く30秒以上かかる。遅すぎる。
いろいろと調べていたら、ファイルの読み込みは、sceIoRead関数で4byte境界で行ったほうが、早くなるらしいとのこと。とりあえず、そのように実装してみたら3秒位で読み込めた。

あと、14byteの構造体をsizeofでサイズ取得したら16byteになってた。こっちも4byte境界なのかもしれない。

cygwinて名前は聞いたことあるけど何なのか知らなかった

PSP修理の失敗談を友人Mにしたら、PSPを貸しても良いと言ってくれたので、有難く借りてきた。
まずはPSPSDKのインストールからはじめた。が、俺はlinuxなどさっぱり分からないので、解説サイトを見ながら試行錯誤する羽目に。。一日掛けて何とかインストールに成功したが、気力が尽きたのでその日はそれで終わり。

以上がGW以前の話。帰省を終えて自宅に戻ってきたので、現在PSPSDKについてるサンプルを読んでいる。