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
{
float3 n = i.normal; // declare the normals.
float3 col = 0; // declare the output.
FakeLight_float (n, col); // pass both values as arguments.
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 the 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.