Gポイントポイ活 Amazon Yahoo 楽天

無料ホームページ 楽天モバイル[UNLIMITが今なら1円] 海外格安航空券 海外旅行保険が無料!

log

2013.05.19 C++:すごく今更な法線マッピング

ファイル 433-1.jpgファイル 433-2.jpg

今現在はライティング計算はある程度それっぽいものの頂点単位の法線だと限界が
あるなと思い始めたので、法線マッピングを試験的に実装させてみました。

「今になって法線マップの実装かよ!」っていわれそうですね笑。
ちなみに接線と従法線はここの方法で求め、法線マップはNVidiaのPhotoshopプラグイン
サクっと作成してみました。
ワンタッチ生成なので法線の精度は雑ですが、理論的にはこれであっている感じでしょうか。
ぶっちゃけると、プログラム側の実装よりもどうやって法線マップを作成するかの方が
手間がかかるんですよねぇ苦笑。

追記:
ネット上にある法線マップを使った球の画像も追加してみました。
こちらの方が分かりやすいかな。

追記2:
動画にしてみました。

2013.05.15 C++:深度プリパスの導入

今回は色々ポストエフェクトを導入して速度が落ちてきたので深度プリパスを使用することで
速度が向上できるのではないかと、ちょっとした実験をしてみました。

その前に深度プリパスについて少しだけ説明でもしておきます。
通常はカラーバッファ+深度バッファで描画し、深度値が描画されている深度値より
手前の場合にバッファを更新するというのが古典的な手法ですが。これだと複雑なシーンだと
無駄なアウトプットがかなり増えてしまいます。例えばビルや家や木が複雑に立ち並ぶ
シーンだと、酷いケースで地面と空の描画で1~2ピクセル、ビルや家や木の描画でもう
5~10ピクセル、キャラクターの描画でさらに3~5ピクセルといった感じで同じピクセルに
10数回以上描画するケースが発生したりします。けれど不透明なピクセル群ならば実際に
必要な色は最後に描画する1回だけでいいはずです。
これを解決する手法が深度プリパスであり、深度値の書き込みとカラーの書き込みをそれ
ぞれ別のパスに分け、最初に深度値だけ書き込み、次のパスでカラーを書き込む際は
深度値が深度バッファの値と同一の場合にだけ書き込むようにすることでカラーバッファ
への描画がピクセル当たり1~2ピクセル程度に抑えることが出来るようになります。

マルチレンダーターゲットはアウトプットが大きくなるため、そこで今回は深度プリパスで
高速化できるのかどうか検証してみましたが。レンダーターゲット3枚で36FPS程度のシーン
が40FPSまで上がるようになりました。いやぁ、思ったより速度が上がりました笑。
マルチレンダーターゲットが1~2枚の頃に試した時は速度が上がるどころか少し落ちた
ような記憶があるのですが、今回深度プリパスは意外と使えることが分かりました。

2013.05.14 C++:FXAA 3 Quality

ファイル 431-1.pngファイル 431-2.png

今までは通常のFXAA3でアンチエイリアシングを行っていましたが、今回はその上位版(?)
であるFXAA3Quality(FXAA3Q)を実装してみました。

FXAA3は他でもよく言われている通り、全体的に少しボケた感じの画になりますが
FXAA3Qの方はよりくっきりした感じの画になりました。それとQualityという名前がついて
いるのでAA自体の品質も上がるのかなと思いましたが、自分の目で確かめる限りでは
FXAA3と同程度(8xAA)な感じですね。ただしボケが無くなると鳥瞰でみてもキャラクターが
くっきり視認できるのでメリットは結構大きいなと思いました。

それと以前にライティングを改良しましたが、1つのシーンだけであれこれやっても、
いざ他のシーンになった時に不自然なライティングになりそうだなと思ったので今回は
シーンを変えて試してみましたが。…やっぱり少しおかしくなってました笑。
今まではPRT色・自己反射色・半球色を1:1:1で合成していましたが、今回の画像では
1:0.5:1.3に改良することになりました。
一番いいのは自身の制作部屋の3Dモデルを作ってライティングが同じになって見えるか
確認する方法だと思いますが、、自分の部屋は汚いしこれはこれで大変だ苦笑。

2013.05.12 C++:アウトラインの色分け

ファイル 430-1.jpg

今まではモデルのアウトラインを描画する場合、同じ色でしかを描画できない制限があった
のですが、今回はそれを改善させてマテリアルごとにアウトラインの色を分けて描画
できるようにしてみました。ちなみに以前、RGB3成分を1つにまとめる手法を実装した
ことをほんの少しだけ書きましたが、それはこれがやりたかったからなのでした。

何故わざわざ圧縮する必要があるかというと、DirectX9ではマルチレンダーターゲットは
最大4枚までですが、今現在は以下のように3枚使用しており、
・カラー+アルファ  (A8R8G8B8)
・深度+グロー強度 (R16B16)
・法線         (A8R8G8B8)
将来的には速度マップを4枚目に格納して2.5Dモーションブラーを搭載したいため
アウトラインの色がピクセルごとに乗せれないなぁ、と悩んでいて、モーションブラーを
切り捨てたり、アウトライン描画をモデル押し出しの手法に切り替えるといったことも
考えましたが。今回ふと思いついた「切り捨てるんだったらアウトラインの色の方を減色
して法線のバッファに格納すればよくない?」という逆転の発想に落ち着きました。

そして今回は実装しながら「我ながらいいアイデアだなあ」なんて思ったのでした笑。

2013.05.10 C++:被写界深度


被写界深度を実装させてみました。

実装方法はネットで載せられている方法と同じなので、これといって説明することも無い
ですが。今回は工夫として、被写界深度用にぼかした画像をサンプリングする際にRGB
ごとにサンプリング先をずらすことで、色収差っぽい表現になるようにしてみました。
分かりやすく例えてみると、メガネを掛けていて右か左をみると色がずれて見えるアレです。
メガネを掛けている人なら「あぁ、これね」ってわかると思いますが、分からない方は
動画を拡大してみて、境界部分が青くなっていたりオレンジ色になっている部分があったら
それだと思って下さい。まぁ、実際はグレア部分でもそうなってるんですけどね苦笑。
現象を言葉で説明するって難しいな。

それにしても、被写界深度以前に動画のエンコードの時点でボケてしまっているのが
何とかならないものか…。アップロード前はほとんど大丈夫なのに、YouTubeの奴め。

2013.05.07 C++:続・ライティングに関する真面目な考察

ファイル 428-1.jpgファイル 428-2.jpgファイル 428-3.jpg

前回の記事を書いた後で、「そういえばスペキュラ部分の説明を忘れていたな」と気が付いた
ので、今回は補足としてスペキュラの実装もメモしておきます。
といっても、今回はかなりザックリとした説明にしますが苦笑。

スペキュラ(鏡面反射)とは、そのなの通りライトの光が面で反射した光のことです。
なのでディフューズと違い、その光量は反射ベクトルの向きによって変化します。
まぁ、分かりにくい人は鏡かガラスを想像してみてください。鏡は全ての光を反射
しますが、やすりでこすってザラザラにすると曇ってにぶい反射になると思います。
物理等では主に前者を鏡面反射光と呼ぶと思いますが、3DCGではもっぱら後者の
方を指し、前者は環境マッピングと呼んでいますがここではこの後者の鏡面反射を説明
することにします(何だかややこしいな)。
スペキュラの実装方法についてもphongやblinn-phong等色々な方法がありますが
ここでは(面倒なので)それらについて説明せず、どう合成するのかだけにしたいと思います。

広く使われる一般的なスペキュラの合成方法は以下のような加算による方法です。
Color = saturate( Color + SpecularColor * SpecularAmount );
この方法で実装したのが画像の2枚目です。昔の3DCGはこんな感じでスペキュラが
ギラギラしたものが多かったような気がしますが、おそらくこの方法で実装しているため
だろうと思います。この方法は現実のスペキュラと異なる点があり、それは"加算で実装
するためライトの色より強くなる"問題が発生するということです。現実世界ではどんな角度
から眺めてもライトの色よりスペキュラ部分が明るくなることは絶対に無い(はず)です。

ここからは自分の考察と勝手な結論なのですが、これはエネルギー保存則によって
スペキュラとして発する光が大きくなるほどディフューズとして発する光が小さくなる
ためライトから受ける光以上の明るさにならないようになっているのだろうと思います
(例えば3Dで鏡を実装する場合、ディフューズは0に設定しますが、これはこういった
理由からなんだろうな)。
つまり、現実世界のスペキュラを再現するためには以下の式のようにスペキュラの分だけ
ディフューズを暗くすればよいということになります。
SpecularColor -= Color;
Color = saturate( Color + SpecularColor * SpecularAmount );
式ではディフューズではなくスペキュラ色を減衰させていますが、Colorとの合成が加算
処理なので結局は同じ意味となります。この処理で実装したのが画像の3枚目ですが、
2枚目では肌の部分のスペキュラが不自然な感じに見えますが、3枚目の方は自然な
感じで見えるようになりました。

ちなみにリムライティング色についても、SpecularAmountに上乗せする形で実装させる
ようにしました。これはリムライティングはスペキュラから派生した表現という考察の結果
からなのですがこれもいい具合に馴染んだのでした。後はスペキュラ色についてですが、
以下のように反射ベクトルR先のGI色とライト色を元にしています。
float3 LightingColor = saturate(
lerp( (half3)0, InLightColor, saturate(dot(-R,LightDir)) ) + GetPRTColor( R )
) - InOutColor;

全部でこんな感じだろうか。これで明部分・暗部分ともにある程度正確なライティングに
なりましたが。結局はこれでも疑似的な手法に過ぎないんだよなーと思ったりするのでした。
まぁリムライティング自体は非現実なライティングなものの、見た目がいいかなという
個人的な理由で実装させているので、ある程度の嘘はやっぱり必要なのかもしれない。

2013.05.06 C++:ライティングに関する真面目な考察

ファイル 427-1.jpgファイル 427-2.jpgファイル 427-3.jpgファイル 427-4.jpg

今回は今までいい加減な実装だったライティング部分を修正してみました。
実装方法についてはまず自然現象を観察してそこから実装したのですが、さてさてその
実装方法についてある程度以下にまとめてみたいと思います。
3Dやプログラミングに興味が無くても絵描きの方でも参考になるようにまとめてみた
予定ですが、…どうだろうなあ;

まずはライティングというものを大雑把に考えてみますが、"ライトの光の光が当たる
部分は明るくライトの光が当たらない部分は真っ暗になる"、これが超大雑把なライティ
ングの説明です笑。この理屈だけだと3番目の画像の左のように影の部分は真っ黒に
なって何も見えません。が、実際は影の部分も光を受けており、例えば青空の下では
青っぽい影となり夕焼けの下では赤っぽい影となります。
これは太陽以外の物体や粒子が受けた光が反射・散乱して影部分も照らしているから
ですが。ここでは3DCGでの呼び方にのっとり"Global Illumination(GI)"と呼ぶこと
にします。上の説明だけを鵜呑みにすると、「何だ、空の色を影の色にすればいいのか」と
考えるでしょう。つまり、HLSLでは以下のような式でライティングを計算します。
float3 LighingColor = lerp( SkyColor, SunColor, LightingAmount );

上の式でも、まぁある程度それっぽいライティングになります(2番目の画像)。ただ現実世界を
みてみると、例えば陸橋の下の面のライティングは陸橋下の草むらの影響を強く受けて
いるし、分かりやすい例だと自分の左手を右手に近づけたり離したりすると、近づけた時に
お互いに色を影響し合っているのが観察できると思います。つまり実際はライティングを計算
する点をカメラに見立てて、そのカメラから見える景色の色の総和がGI色(正確にはGI+
ライト色)になっていることが想像できます。

ライティングポイントから見えるシーンは遅延ライティングを行うことで一応実装可能な
テクではありますが、画面の画素数分レンダリングするのは今現在のところは非現実的です;
そこで頭のいい方がHDRライティングなりPRTなりを思いついて今に至る訳ですが、自分は
初めてPRTとシャドウマップを実装したときはどう合成していいか分からず、今回になるまで
以下のような方法で実装させていました。
// 旧式その1
float3 LighingColor = lerp( SkyColor, SunColor, LightingAmount ) * PRTColor;
// 旧式その2
float3 LighingColor = min( lerp( SkyColor, SunColor, LightingAmount ), PRTColor );
ちなみにこの手法でライティングした画像を用意しようと思いましたが2番目の画像と対して
変わらなかったので画像は省いています。

旧式その1では暗い部分が暗くなりすぎるし、その2の方では影部分がPRTの影響をほとんど
受けない画になります。
理由としては、その1ではSkyColorはそもそもPRTColorと本質は同じ(同じGI色)なので
GI色同士で乗算して暗くなっているためです。その2の方では暗い部分を選択することで
乗算で暗くなる問題を回避しているが、結局SkyColorの色を選択した場合はGI色とは
呼べない感じになってしまいます。

ここで、仮に現実世界のライトから受ける色を除くGI色をGIColorとすると以下の式で
完全なライティングが再現できると仮定できます。
float3 LightingColor = saturate( lerp( (float3)0, SunColor, LightingAmount ) + GIColor );
何故この式が正しいか証明する方法が思いつかないため、とりあえず矛盾が起きず
現実のライティングと似た挙動になるという点を以下にまとめてみます。
・現実世界では影部分だけ青くなり、明るい部分は青くならない現象が起こるが、これは上の
 式でいうところの白色から飽和することによって明るい部分は青くならないということが説明
 できる。
・現実世界では曇った窓ガラス越しに透過する光を受けた色は白色ではなく青っぽくなって
 いるが、これも同様にSunColorがガラスを通して減衰することでGIColorの色が飽和せず
 に影響しているということで説明できる。
こんな感じかな。「いやいや間違ってるぜ」という意見も出そうですが苦笑。
さて、では今度はどういう風にGIColorを実装したかという点を説明したいと思います。

今現在、自身のプログラムでは背景のような静止物体のみのGIしかPRTで実装できていない
のですが、これだけだと以下のような問題が起こります。
・そもそもPRTやHDRではサンプリング先の色しか分からない。マルチサンプルで実際の
 GI色に近づけることも出来るが、非現実的。(問題1)
・キャラクターから受けるGI色が全く反映されない。(問題2)

この2点を解決するために、自身のプログラムでは以下の2点の仮想GI色をしてみました。
・PRTサンプル先以外の色にみたてた半球ライティングによる固定GI色(問題1解決用)
・自身の色をキャラクターの自己反射にみたてた色(問題2解決用)
HLSLの式にすると以下のようになります。
const half3 SkyColor, HorizonColor;// 真上の空の色,地平線の空の色
float3 HemisphereColor;
if( Normal.y > 0 ) HemisphereColor = lerp( HorizonColor, SkyColor, Normal.y );
else HemisphereColor = lerp( HorizonColor, PRTColor, -Normal.y );
float3 GIColor = SunColor * ( PRTColor + SelfColor.xyz + ShadowColor ) / 3;
PRTの色だけでなく仮想GI色2色を合わせた3色の平均をGI色に見立てるといった感じです。
PRTはHDRより低周波なので問題1は目立たなくなるのですが、空の色もボケて
弱くなってる感じだったのでHemisphereColorも利用しています。
なので場合によってはHemisphereColor部分はいらないかもしれません。
それとライトが暗くなったときにGI色だけ明るくなり過ぎないようにSunColorで掛けて
ます。この方法によるライティングの画像が1番目のものになりますが、2番目の画像と
比べて結構それっぽくなっているかなと思います。位置ごとに3色の合成度合いを修正
した方がより理想的なんだろうけど、そのために必要だと思われるAO用PRTを今はまだ実装
してないのでした。

説明が長くて文章にするのにとっても疲れました。最後の"それっぽい"ライティングを説明
するのにどんだけ書いてるんだか。。そういえば他のサイトだと複数記事に分けて
書いてたりしますが、まとめてあった方が個人的には読みやすいんですよね。
後、実は他にも実験的にR8G8B8の3成分を8-bit1成分にまとめたり取り出したりする
手法も実装してたりしますが、長くなりすぎるので今回はこの辺にしておこうかと思います。

追記:
より分かりやすいように1番目の画像のキャラ部分をモノクロ化した画像もアップしてみました。

2013.03.21 C++:GLSLの移植

ファイル 425-1.jpg

去年当たりから、定期的にGLSLSandboxを徘徊していたりするのですが、今回は
そんなGLSLのエフェクトをHLSLに移植したりしていました。

実装したのはノイズ生成各種、ボロノイ、疑似コースティクス、太陽シェーダといった感じです。
ちなみに画像はGLSLSandboxではお馴染みのSimplex Noiseというものを適応
させてみたものです。テクスチャでノイズなんて実装できるのに何故わざわざノイズ生成
なんて実装しているのかと聞く人がいそうですが、テクスチャからサンプリングするより
レジスタ内だけで処理できるノイズ生成の方が高速化する場合があるんじゃないかな、
と思った訳です。というか現に前の記事のSSAOでは既に使ってたりするんですが笑。

それと今回、HLSLに移植していて気付いたことですが、どうもGLSLのmod()とHLSLの
fmod()では結果が異なることがあることに気が付いたのでネットで調べてみると、
どうやら負値の場合に結果が変わるようです(注:英文)。

他にもsaturate()しなくてもいいはずの所をHLSLではしなければならなかったり。微妙に
違いが発生するのが謎だったりします。GLSLというよりWebGLとの違い…なのかなあ。

2013.02.25 C++:ブルーム等々

ファイル 424-1.jpgファイル 424-2.jpg


年賀状に続いて、久しぶりの動画投稿となりますが。間が開いてしまって色々と新しく実装
した点が多いので、以下にざっとリストアップしてみます。

・動画用にマウスによるカメラルックアップを実装
・お蔵入りしていたSSAOを使用出来る品質に改良
・ブルームエフェクトを新しく実装
・フィードバックブラーを新しく実装(ただし動画では未使用)
・FXAAを新しく実装
・テクスチャのベクタライズの実装
・剣エフェクトの改良(重なり部分の加算を防止)

こんな感じでしょうか。ちなみに今回はポストエフェクト系の追加が多かったため、何も考え
無しに実装したところ30FPSまで下がってしまったのでした。
そこで、今までソフトシャドウ用に8サンプリングだったのを4サンプリングに下げたり、SSAOを
1/2のテクスチャサイズで計算したりすることでとりあえず55FPSまで出せるようになりました。
カツカツのチューニングこの上無いですが、、ライティングは前より大分マシになったかなあ。

ちなみにテクスチャのベクタライズというのは動画内では箱部分で使用しています(画像を
クリックすると箱部分のテクスチャのアップ画像が見れます)。
箱のテクスチャは64x64のサイズのテクスチャなのですが、カメラが近づいてもテクスチャの
ラインがくっきりしているのが分かるでしょうか。何故こんな技術を実装したかといいますと、
セル調のゲームのプレイ動画とか見ていて、キャラがアップになった時にテクスチャのガタ
ガタが気になってしまい「テクスチャがベクター画像だったらいいのにな」と思ったのが原因
だったりします。まぁ実際にベクタライズしている訳ではないのですが、64x64でも1024x
1024と同程度の品質に見える上に、携帯端末のようなVRAMメモリやキャッシュが小さい
環境だと高解像度のテクスチャを使用する場合に比べて高速化する可能性があるので結構
おいしい技術なんじゃないかと思います。

追記:
剣エフェクトの改良も動画を見ただけだと分かりにくいかと思ったので、アップ画像の方も
用意してみました。内容としては、ステンシルバッファを利用して一度描画した部分のピク
セルでは2回目以降剣エフェクトの描画をしないようにしているといった感じになっています。
この方法でも2回目以降のピクセルの描画を省いている分、剣エフェクトが少しガタガタして
いますが、、不自然さはほとんど無いのでとりあえず自分はこれでOKにしてます。

2012.12.27 C++:物理演算


以前考えたソフトボディのアルゴリズムを自作プログラム内で試したいなと思いましたが
まず先に剛体の物理演算を実装した方がいいかなと思ったので、余所様のサイトを参考に
剛体の物理演算を実装させてみました。

現在は参考にしたソースコードを大体そのまま流用しているだけなので、キャラクターとの
衝突判定すらしていない状態ですが…。クロスの時と同様、パラメータがおかしいとガタガタ
とオブジェクトが揺れたり、衝突してもそのまますり抜けたり、まだまだ課題が多いです。

ちなみに以下のサイトのソースコードを参考にさせて頂きました(英文)。
http://www.xbdev.net/physics/index.php
剛体の衝突応答についてはt-potでも日本語で詳しく紹介されているのでおススメです。