《Unity着色器圣经》3.2.7 | 数据类型

目录索引

译文

在继续学习编写着色器之前,我们需要先了解一下数据类型以及数据类型在 Cg 与 HLSL 之间的微小差异。

当我们在当前版本的Unity中创建着色器时,我们可以找到以下几种精度不同的浮点数类型:

  • float.
  • half.
  • fixed.

使用 Cg 语言编写的着色器能够完美地编译上面的三种类型,但 HLSL 无法编译“fixed”类型。因此,当我们使用 HLSL 编程时需要把所有 fixed 类型都换成 half 或 float。

“Float” (Cg / HLSL) 是一种 32 位的高精度数据类型,通常使用在计算世界空间位置、纹理映射以及涉及复杂函数(例如三角函数或指数函数)的计算。

“Half” (Cg / HLSL) 是一种 16 位的中等精度数据类型,最常用在计算较小向量、方向、模型空间位置和高动态范围(HDR)色彩。

“Fixed” (Cg) 是一种仅 11 位的低精度数据类型,用于计算一些简单的操作(例如基本的颜色存储)。

在使用向量时经常会遇到的一个问题是:如果我们所有的变量都使用 float 类型会怎么样?实际上这是可行的,但我们必须考虑到 float 是一种高精度数据类型,这意味着它有更多的小数点,因此 GPU 将花费更长的时间来计算,增加运算时间并发热。正确使用恰当的数据类型,这样我们才能优化程序并减少 GPU 的图形负载。

另外几种在Cg和HLSL中都能找到的常用数据类型是 Int,sampler2D 和 samplerCube。

采样器(Sampler)”指纹理的采样状态。在该数据类型中我们可以存储纹理及其 UV 坐标。

一般来说,当我们要在着色器中处理纹理时,必须使用“Texture2D”类型来存储纹理,并为其创建一个“采样状态(SamplerState)”来进行采样。“采样器(Sampler)”数据类型允许我们在一个变量中同时存储纹理和采样状态。为了详细了解采样器的功能,我们将进行以下操作:

// 将纹理_MainTex声明为全局变量 
Texture2D _MainTex; 
// 将纹理_MainTex的采样器声明为全局变量 
SamplerState sampler_MainTex; 
// 片元着色器
half4 frag(v2f i) : SV_Target 
{ 
    // 用_MainTex.Sample通过UV坐标对纹理进行采样
    half4 col = _MainTex.Sample(sampler_MainTex, i.uv); 
    // 返回纹理颜色
    return col;
}

上述操作可以被简化为使用“采样器”:

// 为_MainTex声明采样器 
sampler2D _MainTex; 
// 片元着色器
half4 frag(v2f i) : SV_Target 
{ 
    // 用tex2D()函数采样纹理的UV坐标 
    half4 col = tex2D(_MainTex, i.uv) 
    // 返回纹理颜色
    return col; 
}

这两个例子都会返回相同的值,该值对应于我们在属性中声明的纹理,我们可以在 Unity 的材质检查器中为其赋值。

在我们的着色器程序中,我们还可以使用标量、向量和矩阵。

标量是那些返回实数的数值,可以是整数或小数(如 1、0.4、4 等)。要在着色器中声明标量,我们需要写“数据类型”,再写“标量名称”,最后初始化其默认值。

声明标量的语法如下所示:

float name = n; // e.g. float a = 0; 
half name = n; // e.g. half b = 1.456; 
fixed name = n; // e.g. fixed c = 2.5;

向量指的是返回的值多于一个维度的类型(如 XYZW)。要在着色器中声明向量,同样的我们需要先写“数据类型”,然后写上“维数”和“向量名称”,最后初始化其默认值。

声明向量的语法如下所示:

float2 name = n; // e.g. float2 uv = float2(0.5, 0.5); 
half3 name = n; // e.g. half3 position = float3(0, 0, 0); 
fixed4 name = n; // e.g. fixed4 color = float4(1, 1, 1, 1);

矩阵指的是那些以行和列存储数值、具有多个维度的类型。矩阵主要用于剪切、旋转和改变顶点位置。

要在着色器中声明矩阵,同样的,我们首先需要写“数据类型”。接着是 “维度数量乘以自身”,然后是“矩阵名称”,最后初始化其默认值,同时考虑到坐标 Xx、Yy 和 Zz 将等于 1。

声明矩阵的语法如下所示:

// 三行三列矩阵
float3x3 name = float3x3 
( 
    1, 0, 0, 
    0, 1, 0, 
    0, 0, 1 
); 

// 两行两列矩阵
half2x2 name = half2x2 ( 
    1, 0, 
    0, 1 
); 

// 四行四列矩阵
fixed4x4 name = fixed4x4 
( 
    1, 0, 0, 0, 
    0, 1, 0, 0, 
    0, 0, 1, 0, 
    0, 0, 0, 0 
); 

如果您是 Unity 着色器的初学者,可能目前还无法完全理解前面所讲解的内容。不过别担心,在本章稍后我们将详细回顾这些参数,并学习片元着色器的工作原理。


原文对照

Before continuing to define the properties and functions of our shader, we must look at data types because there is a small difference between Cg and HLSL.

When we create a default shader in current versions of Unity, we can find floating-point numbers that differ in precision, among them:

  • float.
  • half.
  • fixed.

A shader written in Cg can compile perfectly in these three types of precision, however, HLSL is not capable of compiling the “fixed” data type, so, if we work with this language, we will have to replace all the variables and vectors of this type either with half or float.

“Float” (Cg and HLSL) is a high-precision data type, it is 32 bit and generally used in calculating positions in world-space, texture coordinates (UV), or scalar calculations involving complex functions such as trigonometry or exponentiation.

“Half” (Cg and HLSL) is half-precision, is 16 bit and is mostly used in the calculation of low magnitude vectors, directions, object-space positions, and high dynamic range colors.

“Fixed” (Cg) is low precision, it is only 11 bits and is mainly used in the calculation of simple operations (e.g. basic color storage).

A question that commonly arises in the vector use is, what would happen if we only use a floating data type (Float) for all our variables? In practice this is possible, however, we must consider that float is a high-precision data type, which means that it has more decimals, therefore, the GPU will take longer to calculate it, increasing times and generating heat.

It is essential to use vectors and/or variables in their required data type, thus we can optimize our program, reducing the graphic load on the GPU.

Other widely used data types that we can find in both languages are “Int, sampler2D and samplerCube”.

“Sampler” refers to the sampling state of a texture. Within this type of data, we can store a texture and its UV coordinates.

Generally, when we want to work with textures in our shader, we must use the “Texture2D” type to store the texture and create a “SamplerState” to sample. The data type “sampler” allows us to store both the texture and the sampling status in a single variable. To understand in detail the function of a sampler, we will do the following:

// declare the _MainTex texture as a global variable 
Texture2D _MainTex; 
// declare the _MainTex sampler as a global variable 
SamplerState sampler_MainTex; 
// go to the fragment shader 
half4 frag(v2f i) : SV_Target 
{ 
    // inside the col vector sample the texture in UV coordinates. 
    half4 col = _MainTex.Sample(sampler_MainTex, i.uv); 
    // return the color of the texture 
    return col;
}

The above process can be optimized by simply using a “sampler”.

// declare the sampler for _MainTex 
sampler2D _MainTex; 
// go to the fragment shader stage 
half4 frag(v2f i) : SV_Target 
{ 
    // sampler the texture in UV coordinates using the function tex2D(). 
    half4 col = tex2D(_MainTex, i.uv) 
    // return the color of the texture. 
    return col; 
}

Both examples return the same value that corresponds to the texture that we generate in our properties and will be assigned from the Unity Inspector.

We can use scalar values, vectors and matrices in our program.

Scalar values are those that return a real number, either an integer or decimals (e.g. 1, 0.4, 4, etc.) and to declare them in our shader we must first add the “data type”, then the “scalar value name” and finally initialize its default value.

Its syntax is as follows:

float name = n; // e.g. float a = 0; 
half name = n; // e.g. half b = 1.456; 
fixed name = n; // e.g. fixed c = 2.5;

Vectors are those that return a value with more than one dimension (e.g. XYZW) and to declare them in our shader we must first add the “data type”, then “dimension number”, then “vector name” and finally initialize its default value.

Its syntax is as follows:

float2 name = n; // e.g. float2 uv = float2(0.5, 0.5); 
half3 name = n; // e.g. half3 position = float3(0, 0, 0); 
fixed4 name = n; // e.g. fixed4 color = float4(1, 1, 1, 1);

Matrices are those that have values stored in rows and columns, they have more than one dimension and are used mainly for shearing, rotation and change of vertex position.

To declare a matrix in our shader we must first add the “data type”, then the “dimension quantity multiplied by itself”, then the “matrix name” and finally “initialize its default values” taking into consideration that the coordinates Xx, Yy & Zz will be equal to 1.

Its syntax is the following:

// three rows and three columns 
float3x3 name = float3x3 
( 
    1, 0, 0, 
    0, 1, 0, 
    0, 0, 1 
); 

// two rows and two columns 
half2x2 name = half2x2 ( 
    1, 0, 
    0, 1 
); 

// four rows and four columns 
fixed4x4 name = fixed4x4 
( 
    1, 0, 0, 0, 
    0, 1, 0, 0, 
    0, 0, 1, 0, 
    0, 0, 0, 0 
); 

If you are starting in the world of Unity shaders, you may well not fully understand what has been explained. Don’t worry, later in this chapter we will review all these parameters in detail and see how the fragment shader stage works.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容