Unity uses the standard Shader language HLSL and supports general HLSL data types. However, Unity handles some data types differently from HLSL to provide better support on mobile platforms.
Shaders carry out the majority of calculations using floating point numbers (also called as float
in regular programming languages like C#). In Unity’s implementation of HLSL, the scalar floating point data types are float
, half
, and fixed
. These data types differ in precision and, consequently, performance or power usage. There are also several related data types for vectors and matrices such as half3
and float4x4
.
float
This is the highest precision floating point data type. On most platforms, float
values are 32 bits like in regular programming languages.
Full float
precision is typically useful for world space positions, texture coordinates, or scalar calculations that involve complex functions such as trigonometry or power/exponentiation. If you use lower precision floating point data types for these purposes, it can cause precision-related artifacts. For example with texture coordinates, a half
doesn’t have enough precision to accurately represent 1-texel offsets of larger textures.
half
This is a medium precision floating point data type. On platforms that support half
values, they’re generally 16 bits. On other platforms, this becomes float
.
half
values have a smaller range and precision than float
values.
Half precision is useful to get better shader performance for values that don’t require high precision such as short vectors, directions, object space positions, and high dynamic range colors.
fixed
This is only supported by the OpenGL ES 2.0 Graphics API. On other APIs it becomes the lowest supported precision (half
or float
).
This is the lowest precision fixed point value and is generally 11 bits. fixed
values range from –2.0 to +2.0 and have a precision of 1/256.
Fixed precision is useful for regular colors (that are typically stored in regular textures) and performing simple operations on them.
Unity’s shader compiler ignores floating point number suffixes from HLSL. Floating point numbers with a suffix therefore all become float
.
This code shows a possible negative impact of numbers with the h
suffix in Unity:
half3 packedNormal = ...;
half3 normal = packedNormal * 2.0h - 1.0h;
Since the h
suffix is ignored, the shader compiler generates code that executes these steps:
1. Calculate an intermediary normal
value in high precision (float3
)
2. Convert the intermediary value to half3
.
This reduces your shader’s performance.
This code is more efficient because it only uses half
values in its calculations:
half3 packedNormal = ...;
half3 normal = packedNormal * half(2.0) - half(1.0);
Unity’s shader compiler ignores floating point number suffixes from HLSL. Floating point numbers with a suffix therefore all become float
.
This code shows a possible negative impact of numbers with the h
suffix in Unity:
half3 packedNormal = ...;
half3 normal = packedNormal * 2.0h - 1.0h;
Since the h
suffix is ignored, the shader compiler generates code that executes these steps:
1. Calculate an intermediary normal
value in high precision (float3
)
2. Convert the intermediary value to half3
.
This reduces your shader’s performance.
This code is more efficient because it only uses half
values in its calculations:
half3 packedNormal = ...;
half3 normal = packedNormal * half(2.0) - half(1.0);
Integers (int
data type) are often used as loop counters or array indices, and typically work fine across various platforms.
Depending on the platform you’ve chosen, your GPU might not support integer types. For example, OpenGL ES 2.0 GPUs only operate on floating point data, and might need complicated floating point math instructions to emulate simple-looking integer expressions that involves bit or logical operations.
Direct3D 11, OpenGL ES 3, Metal, and other modern platforms have proper support for integer data types, so using bit shifts and bit masking works as expected.
HLSL has built-in vector and matrix types are created from the basic types. For example, float3
is a 3D vector with .x, .y, .z components, and half4
is a medium precision 4D vector with .x, .y, .z, .w components. Alternatively, you can index vectors using .r, .g, .b, .a components, which is useful when working on colors. For example:
float4 myColor = ...
float redValue = myColor.r;
Matrix types are built in a similar way; for example float4x4
is a 4x4 transformation matrix. However, some platforms like OpenGL ES 2.0 only support square matrices.
Typically, you declare textures in your HLSL code in the following way:
sampler2D _MainTex;
samplerCUBE _Cubemap;
For mobile platforms, these translate into low precision samplers
, that is, the textures should have low precision data in them.
You can change the default sampler precision for the whole Unity project in the Player Settings using the Shader precision model dropdown.
If you know your texture contains HDR colors, you might want to use half precision sampler:
sampler2D_half _MainTex;
samplerCUBE_half _Cubemap;
Or if your texture contains full float precision data depth texture, use a full precision sampler:
sampler2D_float _MainTex;
samplerCUBE_float _Cubemap;
As PC (Windows/Mac/Linux) GPUs are always of high precision, it doesn’t matter whether you write float
, half
or fixed
data types in your shaders. They always calculate everything in full 32-bit floating point precision.
The half
and fixed
types only become relevant when targeting mobile GPUs, where these types primarily exist for power and performance constraints. Make sure to test your shaders on mobile to check for any precision/numerical issues.
モバイルの GPU であっても、GPU ファミリーによって精度のサポートにさまざまな違いがあります。以下は、各モバイル GPU ファミリーが各浮動小数点型を処理する方法の概要です (消費されるビット数で表示)。
GPU グループ | float | half | fixed |
---|---|---|---|
PowerVR Series 6/7 | 32 | 16 | |
PowerVR SGX 5xx | 32 | 16 | 11 |
Qualcomm Adreno 4xx/3xx | 32 | 16 | |
Qualcomm Adreno 2xx | 32 (頂点)、24 (フラグメント) | ||
ARM Mali T6xx/7xx | 32 | 16 | |
ARM Mali 400/450 | 32 (頂点)、16 (フラグメント) | ||
NVIDIA X1 | 32 | 16 | |
NVIDIA K1 | 32 | ||
NVIDIA Tegra 3/4 | 32 | 16 |
最近良く使われるモバイルのほとんどの GPU は、実際には 32 ビット数 (float
型に使用) または 16 ビット数 (half
と fixed
型の両方に使用) のいずれかしかサポートしていません。一部の古い GPU では、頂点シェーダーとフラグメントシェーダーの計算で精度が異なります。
低精度を使用すると高速になることがよくあります。それは、GPU レジスターの割り当てが改善されたため、または特定の低精度数学演算用の特別な “高速パス” 実行ユニットによるためです。直接のパフォーマンス上の利点がない場合でも、低い精度を使用すると GPU の消費電力が少なくなるため、バッテリー寿命が長くなります。
Unity recommends to start with half precision for everything except positions and texture coordinates. Increase precision only if half precision isn’t enough for some parts of the computation.
Support for special floating point values can be different depending on which GPU family (mostly mobile) you’re running.
All PC GPUs that support Direct3D 10 support well-specified IEEE 754 floating point standard. This means that float numbers behave exactly like they do in regular programming languages on the CPU.
Mobile GPUs can have slightly different levels of support. On some, dividing zero by zero might result in a NaN (“not a number”); on others it might result in infinity, zero or any other unspecified value. Make sure to test your shaders on the target device to check they’re supported.
GPU には、そのパフォーマンスや能力に関してベンダーによる詳しいガイドがあります。詳細は、それらを参照してください。