Heliodor 2019/03/18 19:37

シェーダー内での計算精度

前々回のピクセルシェーダーの話で思い出した話。

HLSLでの浮動小数精度...なんですが、ときどきハマるんですよコレ。
時間経過で変化するようなエフェクトをピクセルシェーダーで記述するときに、タイマーの値を使って云々というのはワリとよくあることだと思うのですが、問題は時間変化する値の渡し方です。

私が作っているゲームはすべて固定フレームで動いているので、時間変化する値としてそのままフレームカウンタを渡していました。
つまり1フレームで1インクリメントされる値です。ゲーム開始後から1秒後には60になり、1分後には3600になり、10分後には36000になっています。
このように、一定の速さで時間変化する値をピクセルシェーダーに渡していたわけです。

myPixelShader->SetParameterInt("u_Time", myFrameCount);

みたいな感じです。そんでもってピクセルシェーダー内で

uniform float u_Time; // 外部から与えられる時間値。アニメーション用

...

float t = u_Time * 0.001;
float4 color = tex2D(sampler, float2(uv.x, uv.y+frac(t)));


みたいな感じにして、適当な精度で時間変化する値 t を手に入れていました。ところがこれ、最初のうちはうまくいくんですが、これをしばらく動かしているうちにだんだんと t の値がとびとびというか、カクカクになってくるんですよ。
最初は1フレームにつき 0.001 刻みだったものが、そのうち 0.01 刻みになり、さらに時間がたつと 0.1 刻みになる...みたいな。
もちろん浮動小数での計算なので Timer の値がすごく大きくなると t の相対的な精度がどんどん悪くなるのは当たり前なのですが、それにしても、精度の悪化速度が速すぎるんですよね。具体的には1分ぐらい動かしていると... つまり Timer に代入しているフレームカウンタの値が4000とか5000とか、そのぐらいになると、もうすでに t の精度が悪すぎてアニメーションがカクカクになるんです。

これに最初に気づいたのは、ヴィータ大脱出の背景(砂漠ステージでの赤っぽい背景とか、異空間ステージでの青っぽい背景など)のスクロールが、最初は滑らかに動いていたのに次第にカクカク動くようになっていく、という現象でした。

DirectX では浮動小数点の演算の精度を意図的に落として速度を稼いでいるという話は聞いたことがありますが、こんなにも悪くなるもんなんですかね??

まあそんなわけなので、HLSL側では4桁を超える整数と少数同士の演算はなるべく控えるようにして、GPUの外側で、つまり C++ プログラム側で10未満程度の値に落としてからシェーダーに渡すようにしました。

myPixelShader->SetParameterInt("u_Time", myFrameCount * 0.0001f);

みたいな感じですね。こうすると時間がたっても精度があまり落ちなくなります。ただ、「あまり落ちない」ってだけで確実に落ちてはいくので、

myPixelShader->SetParameterInt("u_Time", (myFrameCount % 3600) / 3600.0f);

のようにして、一定周期で 0 から 1 へ変化するようなタイマー値にしておきます。もちろん、1 から 0 に戻る瞬間と
エフェクト計算の繰返し周期を一致させておいて、1から0にタイマー値が飛んでもエフェクトが滑らかにつながるようにしておきます。



余談ですがこの背景の一番奥(右側の画像は例によって手前のキャラを消した状態)もやはりピクセルシェーダーで実現しています。
元はPhotoShopの「雲模様」で作った画像を、無限書庫と同じくピクセルシェーダーで歪めているだけですね。
(なんとなくゼク○クスの背景みたいなの出来るかなーとテストしたら上手くいったのでそのまま採用w)

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索