やまだです。人生の何割かをGPUを酷使することに捧げています。
スマートフォンゲームにおいてはPCで開発し、スマートフォンに転送して実行することが多いと思います。
特にシェーダを書かれる方はPCではうまく動くが、スマートフォンではうまく動かないという経験があるのではないでしょうか。
その中でも私の周りで比較的トラブルとしてよく耳にするのが浮動小数点演算の精度です1。
結果が異なる例
百聞は一見にしかずということで例を見てみましょう。
以下のシェーダを実行してみてください、どのような結果になったでしょうか?
http://glslsandbox.com/e#39354.3
PCから実行した場合は以下のような結果になります。
一方でiPhone6で実行した場合は次のような結果になります。
この結果の違いはPCとスマートフォンの浮動小数点数の精度の違いによるものです。
GPUの世界での浮動小数点数
スマートフォンではシェーダ言語としてGLSL ESが使用されます。
GLSL ESではフラグメントシェーダの冒頭に以下のような記述がされていることがあります。
precision mideump
これは、浮動小数点数の演算精度をすべて中精度で行うことを宣言しています。
ソースの先頭に記述するほかに mediump float v;
のように変数に個別に精度を与えることもできます。
精度はhighp
, mediump
, lowp
の3種類あり、それぞれ高精度, 中精度, 低精度で浮動小数点演算を行うことを意味します。
これらは最低要件が定義されています2。
範囲 | 精度 | |
---|---|---|
highp | $(-2^{62}, 2^{62})$ | 浮動小数点数 $2^{-16}$ |
mediump | $(-2^{14}, 2^{14})$ | 浮動小数点数 $2^{-10}$ |
lowp | $(-2, 2)$ | 固定小数点数 $2^{-8}$ |
注目すべきポイントとして highp
および mediump
は浮動小数点数ですが lowp
は固定小数点数となっている点です。
加えて lowp
は範囲も mediump
と比較して著しく狭いため、値のオーバーフローには注意が必要です。
一般的に低精度浮動小数点数は高精度浮動小数点数と比較してバンド幅を節約できるため、演算が高速に行えます。
そのため複雑な演算は highp
を使用し、それほど精度が必要でない演算は mediump
や lowp
を積極的に使用するなどの使い分けが必要です。
前述のとおり、これらは最低要件であり、実際はこれ以上の精度を持つことがあります。
先程のシェーダは帯が下に進めば進むほど数値が大きくなり、 highp
で計算したものを赤色で、 mediump
は緑色で、 lowp
が青色で出力しています。
PCではすべて白い帯で描画されていますが、iPhone6の結果は赤い帯のみが下にのびています。
これはPCにおいては highp
, mediump
, lowp
の精度が全て同じ高精度であることを意味し、
iPhone6では mediump
と lowp
の精度が同じで、 highp
がそれらよりも高い精度であること意味しています3。
UnityのShaderLabではhighp
, mediump
, lowp
がそれぞれ float
, half
, fixed
にあたります。
Unityのマニュアルに浮動小数点の精度と使い分けについて詳しく書かれています。
最後に
GPUによって浮動小数点数の精度が異なるのがスマートフォンのシェーダプログラミングの難しさのひとつです。
PCで動作したから大丈夫、iPhoneで動作したから大丈夫、というわけではないということを意識してシェーダを書くようにしていきましょう!
-
浮動小数点数のトラブルはCPUの世界でもよく耳にします。我々は永遠に戦い続ける。 ↩
-
OpenGL ES Shading Languageの33ページ付近より ↩
-
近年では、PC向けGPUも精度よりも演算速度が重要な機械学習分野の需要の高まりもあり、低精度浮動小数点演算にも注目が集まっています。
もちろんmediump
とlowp
が異なる精度であるスマートフォンもありますし、スマートフォンによって微妙に精度が異なることもあります。
これがスマートフォンのGPUのシェーダプログラミングの難しさのひとつでもあります。 ↩