楽天モバイル[UNLIMITが今なら1円] ECナビでポインと Yahoo 楽天 LINEがデータ消費ゼロで月額500円〜!


無料ホームページ 無料のクレジットカード 海外格安航空券 解約手数料0円【あしたでんき】 海外旅行保険が無料! 海外ホテル

log

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.04.17 3D:ミノタウロス

ファイル 426-1.jpg

聖剣2のボス、ミノタウロスをsculptrisで制作してみました。
毎回思うことですが、味方やMOBキャラはドットが小さいのでどう3D化していいか苦戦
するのですが、ボスキャラの大きいドットサイズだとそのままモデリングしやすいです。
ただ、MOTHTER2のようなシンプルなドットでも3D化しやすいのでMOTHERのキャラを
モデリングしてみたいなとも思ったり。うーん横道それてるけど、息抜きにいいかな。

それと以前ポストエフェクトを各種実装して、あれから色々と高速化でいじってるうちに
いつの間にか古いXPの方のPCで正常に描画されない問題が起こっていたので
色々調べてみましたが。どうもX8R8G8B8フォーマットのテクスチャをレンダーターゲットに
した場合、描画されなくなる(マルチレンダーターゲットの場合は他のターゲットにも描画
されない)という問題だということが分かったので、A8R8G8B8フォーマットに変更する
ことで解決することが出来たのでした。

レンダーターゲットで作成できるのに描画できないのが不思議。バックバッファもX8R8G8B8
で作成していますし。一応ネットで調べてみるとGeforceとRadeonでX8R8G8B8テクスチャ
サンプリング時の挙動が違うようですが、今回はサンプリングじゃなく描画の方だしなあ。

古いGPUだと仕様が決定していないのか、毎度よく分からん動作になることが多くて
困りものです。かといって非対応として切り捨てたりしたくないし、大変だな。

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にしてます。

2013.02.25 イラスト:年賀状

2013.年賀 by たけ on pixiv


更新が大分遅れてしまいましたが、新年あけましておめでとうございます。

さて、今年も例年通りpixivに年賀イラストを投稿してみました。今年は自分で3D化したい
イラストを意図的に考えてみたことと、後はテーマとしてブィネット風に描いてみました。
…それにしても、気がつくと自分2011年から年賀状しか投稿してないですね;
イラスト月間とか考えた方がいいかもしれないなあ。

2012.12.27 C++:物理演算


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

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

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

2012.12.10 LW:メルトモーション


今回はLWでスライムが溶けるメルトモーションを作ってみました。
これを作るきっかけとなったのはTrueMeltというプラグインを最近発見して、
「これだったら自前で実装できるな」と思ったためでした。

さてさて、具体的にどういう実装かというと
・頂点座標のY軸座標値が0以下の場合は0より下にめり込んだ範囲を取得。
・めり込んだ範囲だけ上にもどし、さらにY軸成分を0に除去して正規化したXZ平面だけ
 を考慮した法線を取得し、その法線方向にめりこんだ範囲だけ移動させる。

これだけです笑。今回はLightwaveのディスプレイスメントノードで設計しましたが、HLSLでも
すぐに実装できそうな手軽さです。ただ問題があって、潰した部分はポリゴンが密集するので
Zファイティングが起きてしまっています(目や口の辺り)。ためしに他のキャラクタでも
試してみましたが結構悲惨なことに…。これスライムのようなシンプルな形状専用ですね;

他にもモーフやボーンで同じように表現することも出来そうですが、複数のモーフやボーンが
必要で管理が難しくなりそうだし。やはりシェーダで処理した方が無難だろうなあ。

追記:
今までは「ソフトボディってどんなアルゴリズムでやるんだろ」って考えたりすることが
あっても、結局思いつかなかったなんてことがありましたが;この手法を改良すれば出来
ますね。具体的には以下の方法です。
・コリジョンとの衝突深度と衝突面の法線を取得
・めり込んだ範囲だけ衝突面法線方向にもどし、衝突面方向成分を除去して正規化した
 頂点法線を取得し、その法線方向にめりこんだ範囲だけ移動させる(ただし衝突面法線
 と頂点法線の向きが逆向きに同じ場合は処理させない)。
・あとは粘性パラメータ等を決めて衝突面の法線方向にオブジェクトを反射させたり、
 衝突しなくても動きに応じて形が変化するように速度に応じて衝突深度をとってみたり。

こんなところでしょうか。ソフトボディのアルゴリズムを解説している国内サイトはほとんど
無いみたいなので、今回の記事は珍しく割と有用なことを書いたのかも笑。
ちなみにソフトボディはシェーダでやるにも頂点単位で衝突深度情報がいるので少し
難しいというか、クロスと同様にバッファの動的作成のリスクを考える必要がありますが。
やろうと思えばすぐに出来る手法ですね。…そういえば、ソフトボディって調べるとだいたい
みんな似たようなものを揺らしてるなあ笑。

2012.12.08 お気に入り:Lightwave11.5の新機能について



近々公開予定のLightwave11.5の新機能Genomaの動画を見てみましたが凄く良さそうだ。

今まではスケルゴンという機能を使ってモデラーでボーンのセットアップが出来ましたが
IKやコンストレイントの設定はレイアウトでしか出来ず、毎回キャラクターを新しくレイアウト
に持っていくごとにちまちまリグのセットアップする必要がありました。。しかしそれも
Genomaによっておさらば出来そうな感じです。

動画を見る限り、モデラー側で3DSMaxのリグのような様々な形状のボーンが用意されて
いてIKのセットアップはおろか、スパインリグやマッスルリグまで標準で組める模様。
今までのバージョンアップはモデラーだけやレイアウトだけが進化するような感じが
多かった気がしますが、今回はちゃんとLightwaveがバージョンアップしている感じで
かなり期待出来るなと思いました。
もう、「モデラーはいいけどレイアウトがなぁ…」とは言わせないつもりでしょうか笑。

さらに11.0のBulletはソフトボディやクロスが出来ませんでしたが、11.5ではソフトボディ・
クロスも出来るようになってるみたいです。これも良さげだなぁ。

2012.12.07 C++:ミップマップに関するお話

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

今回はポロンを作ってみましたが、まぁ別に話すことも無いので笑。打って変わって今回は
ミップマップについて少しお話してみようと思います。

ミップマップを使用すると、通常通りだと斜めのポリゴンがぼけた感じになってしまいます。
これはなぜこうなるか説明するために、まずサンプリング方法について説明してみます。
一般的に使用されるサンプリングはバイリニアサンプリングというやつですが、これは
描画するピクセル位置のUV点の周囲4ピクセルを使用して滑らかにサンプリングする
という手法です。

バイリニアサンプリングだけだと画面上に描画するポリゴンサイズよりテクスチャサイズが
小さい場合、サンプリングするピクセルが飛び飛びになるため、遠景の描画がガタガタに
なる訳で。これを解決するためにミップマップが存在します。

ミップマップは描画するポリゴンサイズ(というより描画するピクセルで使用するUV範囲)に
応じて使用するテクスチャのサイズを変更することで、サンプリングが飛び飛びになる問題を
防ぐ手法です。ピクセルで使用する
UV範囲が正方ならば綺麗に描画されるのですが、UV範囲が長方形の場合は範囲が
大きい方を基準に小さいテクスチャを選ぶため、もう片方の範囲が小さい軸方向では
ボケてしまいます(おそらくUV範囲が小さい方を基準にした場合はもう片方の軸方向で
サンプリングが飛び飛びになるため、とりあえずこの仕様になっていると思われる)。
さてこれを解決するにはどうすればいいだろう?

解決方法の1つに異方性フィルタリングでサンプリングする方法がある。このサンプリングの
アルゴリズムについて詳しくのっているサイトが見つからなかったため、仕様の予測でしか
無いのですが、バイリニアサンプリングでは4ピクセルで補間するのに対し異方性フィルタ
リングでは描画するピクセルのUV範囲に応じてm*nの範囲のピクセルを使用して補間
することで正確な描画を行っているんだろうと思います。

ただこのサンプリングだと昔のGPUでは重いため、一般的に使用される解決方法が
「んじゃミップマップレベルをシフトすればいいじゃない?」というものです。
描画するテクスチャのミップレベルのシフトは固定パイプライン、HLSL共にMipMapLodBias
で出来ます。MipMapLodBiasに-1を設定することで描画するテクスチャが1段階大きい
テクスチャにシフトするので、ボケた感じがこれである程度解消できます。

ちなみに画像の2番目が通常通りのミップマップ、3番目がMipMapLodBiasに-1を設定した
場合のものです。サムネイルをクリックで拡大してみることで、効果が分かるかと思います。
ネットで調べてみると-0.5に設定してる人もいましたが、あまり効果が無かったので自分は
-1にしています。

実は今まではMipMapLodBiasにマイナスを設定できるとは考えてもいなかったので、
設定していなかったのでした笑。試してみると、ボケが解消されたというより、テクスチャの
ガタカタが解消されました。それよりもポポイの口のラインがようやく少し見えるようになった
のが嬉しい!遠景の地面辺りは相変わらずボケボケなので、異方性フィルタリングに変更
しようかなとも思いますが、とりあえずはこの辺で終わりにします。

ふぅ、今回の記事を書くのに少し疲れた;

追記:
異方性フィルタリングも試してみました(画像の4枚目)。
試してみて気づきましたが、テクスチャがガタついてたのは縮小フィルタにニアレストポイント
を指定していたからで、遠景のボケを少しでも防ぐために設定していたのが仇となっていて、
中景のテクスチャがガタガタになってたのでした;

さて、そこで拡大/縮小/ミップフィルタ全てに異方性フィルタを設定する場合と縮小フィルタ
だけニアレストポイントのままにする場合を調べてみましたが。
前者の場合はテクスチャは全ての場合と比べて一番滑らかになりましたが、結局遠景は
同じようにボケた感じでした。対して、後者は遠景のボケはあまりありませんが中景の
キャラのテクスチャがガタガタになってしまいました。
ちなみに4枚目の画像は前者の方法のものです。後者の方は3枚目と4枚目の中間の
ような感じだと思って下さい。2,3枚目は縮小フィルタにニアレストポイントを設定していた
時のものなので、4枚目よりも遠景がはっきりしてるのが分かるかと思います。

結論としては遠景の見た目よりも近景やキャラの見た目が大切だと思うのと、速度の面でも
バイリニアの時とほとんど差がなかったため、これからは3フィルタ全てに異方性フィルタを
設定する方向性でいこうと思います。