現在、VC++とDXライブラリ、プログラマブルシェーダを書く際にDXライブラリ制約下でのHLSLを用いてゲーム開発を行なっており、
その一環として質感のクオリティアップのための実験も兼ねて別プロジェクトにて、「水面の描画とシミュレーション」を行なっているのですが、どうしても自分一人では解決の糸口が掴めないためここを利用させていただこうと考えました。
結論から言いますと、水面を描画する上で、反射成分と屈折成分のそれぞれの像をうまく辻褄が合うように用意できずに困っています。
試行錯誤の内容を書きつつ、状況を説明していきます。本題は§3にありますので状況が分かれば他は読み飛ばしてくださって結構です。冗長な部分もあるかと思いますがよろしくお願いします。
§0 目標とする実装の内容
・水面波を波動方程式を解く形でシミュレーションし、頂点単位で動かして描画(ハイトマップを用いた実装は後の課題に)する(参考:ttp://www.geocities.jp/supermisosan/secondwaveequation.html)←解決済み(簡単なメッシュのみの動画:[youtube][/youtube])
・上のものはDXライブラリのシェーダを用いたプリミティブの描画関数を用いて描画するためシェーダを書くことが出来る
・各頂点の法線を考慮して反射・屈折した画像を得て(←ここが未解決)、フレネル反射の式に則って合成率を決定、描画する(←解決済み)
(以降はコースティクスを実装したりもしたいですが今回の話題とは逸れますので置いておきます)
また、動画のようなイメージのままでテッセレーションなどは一切行っていません。
よって以下は反射・屈折の像を得るために試行錯誤した内容を記します。
§1 環境マップによる実装(参考:ttp://marupeke296.com/DXPS_PS_No6_EnvMap.html)
まず最初に思いつくのが環境マップを用いた実装ですが、利点としてHLSLの組み込み関数のreflect、refract関数を用いることができるので「水面の法線を考慮した」像を得ることが比較的簡単です。
これの実装には6方向のテクスチャを得るためのカメラ位置を決める必要があります。これが私にとっては難問でした。
1-1.カメラを水面の中心に置く
鏡面反射を入れたオブジェクトの場合、オブジェクト中心に置くのが通例のようですが、
これでは例えば(0,0,0)の位置においた場合、(0,0,0)付近にオブジェクト(以下、球)が近づいた場合、水面全体が球の表面でいっぱいに埋め尽くされてしまい、現実とは大きくかけ離れてしまいます。
1-2.(テクスチャを得るための)カメラを、画面描画に使うカメラと同じ位置に置く
要は視点からの光景をテクスチャにするわけで、これが正しいと思うのですが
ピクセルシェーダの処理を以下のように実装すると(話題以外の部分は端折っています)
vReflect = normalize( reflect( PSInput.viewVecW, PSInput.normalW ));
float4 color_refl = texCUBE(dynamicCubeTex, vReflect); //反射分(Ref le ction)
vRefract = normalize( refract( normalize(PSInput.viewVecW), normalize(PSInput.normalW),0.86));
float4 color_refr = texCUBE(dynamicCubeTex, vRefract); //屈折分(Ref ra ction )
PSOutput.Color0 = color_refr *(1-color_refl_alpha) + color_refl *color_refl_alpha ;
PSOutput.Color0.a = 1;
PSInput.normalW:水面の各頂点の法線方向
color_refl_alpha:フレネル反射を考慮した反射像と屈折像の合成率
PSOutput.Color0:最終的に返すテクスチャの色 となっています。
以上のように実装すると動画のようになってしまいます。(実装動画:[youtube][/youtube])
注目すべき点は
・水面近くの時には辻褄が合っているように見える
・水面との角度を大きくするほど反射像が高い部分しか見えなくなる
・屈折像が角度(高さ)がそのままでも平面上を動いた時の像の動き方も明らかにおかしい
これらの点から、当初はテクスチャを取るカメラの位置が(画面描画に使うカメラと同じ位置に置くのは)適切でないと考えていましたが、
現在はどうやら問題は別のところにあったと考えるに至ったので質問の状況説明としてこのセクションを書きました。
本題は§3に続きます。
§2 同次座標を用いたテクスチャの抽出による鏡面効果の実装(参考:ttp://homepage2.nifty.com/natupaji/DxLib/program/dxprogram_Mirror.html)
一度環境マップを用いた実装が手詰まりになったと考えたので他の手法で実装することを考えました。
理解が怪しい部分もありますので参考先を見ていただきたいのですが、
(テクスチャを得る)カメラとその注視点を水面に対して(画面描画に使うカメラと)線対称な位置に置き、そこから描画される画像をテクスチャとし、
そのテクスチャから見た水面の範囲にある部分を切り取って、水面の反射テクスチャとして使うものです。透過(屈折)分も同じように得られます。(実装動画:[youtube][/youtube])
ただし、これでは恐らく水面を波打たせてもテクスチャが上下するだけで、「法線を考慮した反射」は行われていないと思います。(あくまで平らな鏡面としてのサンプルプログラムであり、一方向から見た光景をテクスチャとしているので気付いてみると当然ですが)
また、透過した水底も揺らぎ、それっぽくは見えますが屈折はこのままでは行われていません。
しかしこちらの方法では水面に突き刺さるオブジェクトも(比較的)反射像、屈折像のズレが少ないです。(が、残念ながら確かにズレは確認されます。動画後半やこの画像ttp://i.imgur.com/eZnqjbo.jpg?1を参照。)
§1とは反対に水面近くで角度を小さくすると同次座標の精度問題でテクスチャが乱れます。
この実装方法では、本題である「法線を考慮した反射」や屈折は原理的に難しいと判断したため、また、これによって環境マップは必須であると考えなおさせられたため、この方向での実装はやめて§1の問題を解決する方向に動きました。
§3 環境マップがうまくいかない原因を考える
本題です。
色々考えた結果、§1の実装ではtexCUBEに渡したベクトルは水面からの反射(屈折)ベクトルでした。
これではキューブマップの中心から反射(屈折)ベクトル方向を見てしまい、おかしくなるのは明白です。(キューブマップの中心はカメラ座標なので水面はキューブマップの中心からずれた場所にある)
よってtexCUBEに渡すベクトルはビューベクトル+反射(屈折)ベクトルとなると考えられます。
その上での問題点を図に示してみました。非常にイメージしやすいのでマルペケさんの画像を使わせて頂いていますがまずそうなら消します。
ttp://i.imgur.com/BHFCek1.png?1
以上の通りです。
どなたか関数の仕様について分かる方、もしくは根本的に実装方法が悪いのでしたらその方法について教えていただける方、どうかご助力お願いします。
画像、動画の張り方も分からずリンクを張るだけとなってしまいました。
初めてなので色々至らない点はあるとは思いますが、どうかよろしくお願いします。