33
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Unity】Radeonで描画が乱れないシェーダーの書き方

Last updated at Posted at 2022-11-19

今回はRadeon環境でのシェーダーの挙動を見てみます。今回の記事はRadeonVRの会からRadeon機を提供していただき執筆しています。

また、本記事はシェーダー作者やUnityで開発を行っている方、VRChat等のワールドクリエイター向けとなっているため、一般ユーザーの方はやまなみさんの以下の記事が参考になるかと思います。

まず結論ですが、一部の例外を除いてシェーダーに関しては基本的にNVIDIAと同じ挙動をします。便宜上記事内ではNVIDIA環境・AMD環境という書き方をしていますが、ドライバのバージョンやモバイル・デスクトップなどの差異、シェーダーAPIの差異などで異なる特性を示す可能性があります。

【2023/04/02追記】
検証環境を更新しました。基本的に以前の検証結果との違いはありませんが、検証中に気づいた点を追記しています。

項目 詳細
CPU AMD Ryzen 7 5800X3D
GPU AMD Radeon RX 6700 XT 7900 XT
AMD Software Adrenalin Edition 22.5.1 23.3.2
Unity 2021.3.5f1
シェーダーAPI DirectX 11

描画が乱れるパターン

何もしないシェーダー

環境 結果
NVIDIA 何も描画されていないように見えます。
AMD 一見問題無いように見えますが、頂点シェーダーでSV_POSITIONが初期化されずランダムな位置に頂点が描画され、かなり激しい乱れ方をします。

問題の発生するコード

void vert(){}
void frag(){}

置き換え例: 明示的に初期化を行います。

float4 vert() : SV_POSITION {return float4(0.0,0.0,0.0,0.0);}
float4 frag() : SV_Target {return float4(0.0,0.0,0.0,0.0);}

一部ポリゴンが描画されない

環境 結果
NVIDIA 予期した通りに描画されます。
AMD 一部ポリゴンが描画されません。

私が制作したものではないためコードは不明で、現在調査中です。該当シェーダーではテッセレーションやジオメトリシェーダーを使用している可能性があります。

巨大なポリゴン

ピクセルシェーダー実行前の補間に違いがあるためか、極端に大きなポリゴン (スケールが1000000倍などのレベル) でノイズが発生する場合があります。程度の違いこそありますが、NVIDIA環境でも浮動小数点の精度の問題でUVが乱れたり面がガタガタ揺れるような現象が発生するため極端に大きなポリゴンは使用すべきではなく、いくらか面が分割されたPlaneを利用する必要があります。VRChatにおいては無限遠に広がっている(ように見える)水面などで遭遇するケースがあります。ハードウェア依存の問題なのでシェーダー側の対応としては「ピクセルシェーダーの入力値に極端な値をセットしないように注意する」ぐらいですが、よほど特殊なことをしない限り遭遇しないと思います。

【2023/04/02追記】
小さなスケールでも極端に接近した際にStandard ShaderのSpecularやReflectionがちらついたり線のようなものが見えることを確認しました。そこから更に近づくとノイズが見えてきます。スケールと距離が反比例しているので、SV_POSITIONが極端に大きくなった場合に補間の精度が落ちているように見えます。解像度を変えて確認しましたが結果に違いはありませんでした。また、NVIDIAではちらつきのみ発生し、ノイズは発生しませんでした。

Quadのスケール ちらつき始める距離 ノイズが出る距離
1倍 0.00001 N/A
10倍 0.0001 0.00001
100倍 0.001 0.0001
1000倍 0.01 0.001
10000倍 0.1 0.01

四則演算・組み込み関数

以下については特に差異がありませんでした。コンパイラによる最適化を回避するために値は全て変数として渡しています。また、これらの関数はピクセルシェーダー上で実行しています。

加算

返り値
NaN + -INF NaN
NaN + -1 NaN
NaN + 0 NaN
NaN + 1 NaN
NaN + INF NaN
NaN + NaN NaN
INF + -INF NaN
INF + -1 INF
INF + 0 INF
INF + 1 INF
INF + INF INF

乗算

返り値
NaN * -INF NaN
NaN * -1 NaN
NaN * 0 NaN
NaN * 1 NaN
NaN * INF NaN
NaN * NaN NaN
INF * -INF -INF
INF * -1 -INF
INF * 0 NaN
INF * 1 INF
INF * INF INF
-INF * -INF INF
-INF * -1 INF
-INF * 0 NaN
-INF * 1 -INF

除算

返り値
-1 / 0 -INF
0 / 0 NaN
1 / 0 INF

acos

返り値
acos(-1.5) NaN
acos(-1) 3.14159
acos(0) 1.57073
acos(1) 0.00000
acos(1.5) NaN

asin

返り値
asin(-1.5) NaN
asin(-1) -1.57080
asin(0) 0.0000675916
asin(1) 1.57080
asin(1.5) NaN

atan

返り値
atan(0,0) NaN
atan(1,0) 1.57080
atan(-1,0) -1.57080
atan(0,1) 0.00000
atan(0,-1) 3.14159

clamp

返り値
clamp(-INF,-1,1) -1.00000
clamp(INF,-1,1) 1.00000
clamp(NaN,-1,1) -1.00000

fmod

返り値
fmod(-1,0) NaN
fmod(0,0) NaN
fmod(1,0) NaN

lerp

lerp(a,b,c)a+c*(b-a)として実行されます。

返り値
lerp(0,INF,-1) -INF
lerp(0,INF,0) NaN
lerp(0,INF,1) INF
lerp(-INF,INF,-1) -INF
lerp(-INF,INF,0) NaN
lerp(-INF,INF,1) NaN
lerp(-INF,INF,2) NaN

log

返り値
log(-1) NaN
log(0) -INF
log10(-1) NaN
log10(0) -INF
log2(-1) NaN
log2(0) -INF

max

返り値
max(NaN,NaN) NaN
max(NaN,-1) -1.00000
max(NaN,0) 0.00000
max(NaN,1) 1.00000
max(-1,NaN) -1.00000
max(0,NaN) 0.00000
max(1,NaN) 1.00000

min

返り値
min(NaN,NaN) NaN
min(NaN,-1) -1.00000
min(NaN,0) 0.00000
min(NaN,1) 1.00000
min(-1,NaN) -1.00000
min(0,NaN) 0.00000
min(1,NaN) 1.00000

pow

返り値
pow(-1,-1) NaN
pow(-1,0) NaN
pow(-1,1) NaN
pow(0,-1) INF
pow(0,0) NaN
pow(0,1) 0.00000
pow(1,-1) 1.00000
pow(1,0) 1.00000
pow(1,1) 1.00000

rcp

返り値
rcp(-1) -1
rcp(0) INF

rsqrt

返り値
rsqrt(-1) NaN
rsqrt(0) INF

saturate

返り値
saturate(-INF) 0.00000
saturate(NaN) 0.00000
saturate(INF) 1.00000

sign

返り値
sign(-1) -1.00000
sign(0) 0.00000
sign(1) 1.00000
sign(NaN) 0.00000

sqrt

返り値
sqrt(-1) NaN

step

返り値
step(-INF,NaN) 0.00000
step(-1,NaN) 0.00000
step(0,NaN) 0.00000
step(1,NaN) 0.00000
step(INF,NaN) 0.00000
step(NaN,NaN) 0.00000
step(NaN,-INF) 0.00000
step(NaN,-1) 0.00000
step(NaN,0) 0.00000
step(NaN,1) 0.00000
step(NaN,INF) 0.00000

normalize

normalizeは内部でrsqrtを使用しているため、入力が0の場合は0*INFでNaNになります。

返り値
normalize(float3(0,0,0)) NaN

私の所感

テッセレーションの「AMDの最適化」など、Radeonは初期設定そのままで運用すると安定しない印象があるため、初期値を安定したものにしてくれると良いかなと思います。 また、何もしないシェーダーで描画が乱れる件はドライバ側で0で初期化するか、初期化されていないSV_POSITIONを含むポリゴンをラスタライズしないようにしてくれると良さそうかなと思います。AMDはハードウェア面ではNVIDIAより優秀な印象がありますが、ソフトウェア面の不安定さが足かせとなっている印象です。この辺が改善され安定して利用できるようになるとRyzenのように利用率が上がるかなと思います。

【2023/05/12追記】
テッセレーションを用いたシェーダーが「AMDの最適化」設定でも安定して動作するようになっていました。現時点ではこの設定で壊れるシェーダーには遭遇していません。

33
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?