《Unity着色器圣经》4.0.4 | HLSL函数的结构

目录索引

译文

与C#中的函数一样,在HLSL中,函数声明可以返回空(void)或者是一个值。我们必须使用依赖于函数类型的“声明”;它们确定一个值是对应于输入(in)、输出(out)、全局变量(uniform)还是常数值(const)。我们将使用以下语法来声明(定义)一个无返回值函数:

void functionName_precision (declaration type arg)
{
    float value = 0;
    arg = value;
}

要声明一个无返回值函数,我们从void命名法开始,然后是函数名称,再加上精度和最终参数。正如我们在前面的例子中看到的,在函数内部,我们编写要执行的算法或操作。通常,在输入变量中,我们必须定义这些是输入还是输出。

为了理解这个概念,假设我们想创建一个函数来计算对象中的照明,为此,我们需要的属性之一是法线,通过它我们可以确定光源将从哪个方向照射我们的对象。因此,法线将是我们无返回值函数中的“计算输入”。

void FakeLight_float (in float3 Normal, out float3 Out)
{
    float[n] operation = Normal;
    Out = operation;
}

这个函数没有实现任何特定的功能,但我们将使用它来理解前面提到的概念。这个函数被称为“FakeLight”,“_float”对应于它的精度,它可以是浮点型或半浮点型,因为正如我们所知,这些都是HLSL兼容的格式。我们必须始终为空函数添加精度,否则,它将无法在程序中编译。然后在参数中,我们可以看到对象的法线(float3 Normal)已经通过“in”声明声明为输入,同样,还有一个名为“out”的输出,它将是操作的最终值。

要在另一个函数中使用这个函数,我们必须在函数本身之前在代码中声明输入和输出。让我们在片段着色器阶段内模拟我们的FakeLight函数,就好像它将实际运行一样

// create our function
void FakeLight_float (in float3 Normal, out float3 Out)
{
    float[n] operation = Normal;
    Out = operation;
}
half4 frag (v2f i) : SV_Target
{
    // declare normals.
    float3 n = i.normal;
    // declare the output.
    float3 col = 0;
    // pass both values as arguments.
    FakeLight_float (n, col);

    return float4(col.rgb, 1);
}

在上面的例子中,以下三点需要注意。首先,因为GPU从上到下读取我们的代码, 所以“FakeLight”函数必须在“frag”函数之前声明。然后,在片段着色器阶段,我们创建了一个名为“n”的三维向量和另一个名“col”的三维矢量。在这种情况下,两者都是三维向量类型,这是因为我们将在FakeLight_float函数中使用这两个向量作为参数,该函数要求三维输入和输出向量。最后,第一个参数与对象的正常输入相对应,第二个参数与FakeLight_float函数中正在执行的操作的结果相对应。

矢量col初始值为零,这意味着它的红、绿、蓝(RGB)颜色为“0”,默认情况下对应于黑色,但是,由于它已被声明为输出,它现在在FakeLight_float函数中被重新赋值。最后,我们返回了一个四维向量,其中前三个值对应于RGB中的col向量,“1”对应于Alpha。为什么我们要返回一个四维向量?这是因为函数frag是half4类型,即四维向量。现在我们将分析返回值的函数的结构。本质上,它们非常相似,不同之处在于在这种情况下,不需要添加精度函数。为了说明这一点,我们将使用相同的FakeLight函数,但这次它将返回一个值。

// create our function
half3 FakeLight (float3 Normal)
{
    float[n] operation = Normal;
    return operation;
}
half4 frag (v2f i) : SV_Target
{
    // declare normals
    float3 n = i.normal;
    float3 col = FakeLight_float (n);

    return float4(col.rgb, 1);
}

与空函数不同,我们只添加Normal参数,因为它不需要输出。同样,在片段着色器阶段,我们使向量“col”等于该函数,因为它返回的维度数与该向量所拥有的维度数相同。


原文对照

As in C#, in HLSL we can declare empty functions (void) or functions that return a value (return).
We have to use “declarations” that depend on the function type; these determine if a value corresponds to an input (in), output (out), global variable (uniform) or a constant value (const).
We will start by analyzing an empty function using the following syntax:

void functionName_precision (declaration type arg)
{
    float value = 0;
    arg = value;
}

To declare an empty function, we start with the void nomenclature, then the name of the function accompanied by precision and final arguments. As we can see in the previous example, inside the function field we write the algorithm or operation to be performed. Generally, in the arguments, we must define whether these will be inputs or outputs.
How can we know if they have a declaration? Everything will depend on the functions that we want to pass as arguments. To understand this concept, let’s suppose we want to create a function to calculate the illumination in an object, for this, one of the properties that we need are the normals, with this we can identify from which direction the light source will be illuminating our object. Therefore, the normals would be an “input to calculate” inside our empty function.

void FakeLight_float (in float3 Normal, out float3 Out)
{
    float[n] operation = Normal;
    Out = operation;
}

This function does not fulfil any specific functionality, but we will use it to understand the concepts mentioned before it.
The function is called “FakeLight” and “_float” corresponds to its precision, this can be of float or half type since, as we know, these are HLSL compatible formats.
We must always add precision to an empty function, otherwise, it cannot be compiled within our program. Then in the arguments, we can see that the object’s normals (float3 Normal) have been declared as input through the “in” declaration, likewise, there is an output called “out” which will be the operation’s final value.

To use this type of function within another, we must declare both inputs and outputs in our code before the function itself.
Let’s simulate our FakeLight function inside the fragment shader stage as if it will actually operate

// create our function
void FakeLight_float (in float3 Normal, out float3 Out)
{
    float[n] operation = Normal;
    Out = operation;
}
half4 frag (v2f i) : SV_Target
{
    // declare normals.
    float3 n = i.normal;
    // declare the output.
    float3 col = 0;
    // pass both values as arguments.
    FakeLight_float (n, col);

    return float4(col.rgb, 1);
}

In the example above, there are several situations that are occurring. First, the “FakeLight” function has been declared before the “frag” function because the GPU reads our code from top to bottom. Then, in the fragment shader stage, we have created a three-dimensional vector called “n” and another three-dimensional vector called “col”. In this case, both are three-dimensional vector types, this is because we will use both vectors as arguments in the FakeLight_float function, which asks for three-dimensional input and output vectors. Then, the first argument corresponds to the normal input of the object and the second, to the result of the operation that is being carried out within the FakeLight_float function.

The col vector has been started at “zero”, this means that it has “0” for the red, green and blue (RGB) color, which corresponds to black by default, however, since it has been declared as output, it is now taking place inside the FakeLight_float function.
Finally, we return to a four-dimensional vector, where the first three values correspond to the col vector in RGB and “one” to the Alpha.
Why are we returning a four-dimensional vector? This is because the function frag is a half4 type, that is, a four-dimensional vector.
Now we will analyze the structure of a function that returns a value. In essence, they are very similar, with the difference that in this case, it will not be necessary to add the precision function. To illustrate this, we are going to use the same FakeLight function, but this time it will return a value.

// create our function
half3 FakeLight (float3 Normal)
{
    float[n] operation = Normal;
    return operation;
}
half4 frag (v2f i) : SV_Target
{
    // declare normals
    float3 n = i.normal;
    float3 col = FakeLight_float (n);

    return float4(col.rgb, 1);
}

Unlike the empty function, we just add the Normal argument given that it does not require output, likewise, in the fragment shader stage we made the vector “col” equal to this function because it returns the same number of dimensions that this vector possesses.

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

请登录后发表评论

    暂无评论内容