An Introduction to HLSL Programming

Information
Step 1
In this lesson we'll learn a new programming language called HLSL!

Note: I will be using Unity 2017.4.28.f1 for this Tutorial.

You will learn how to control pixels, vertices and how to do some basic 3D math!

Challenge
Step 2
CheckList

This is where the power of the fill() function from Javascript comes in handy. It's recommend that you know how it works from JavaScript.

We will also be moving vertices around so knowing the basics of Blender would be good also.

Planning
Step 3
Let's review the fill() function from JavaScript!

From the fill function, we have 3 spaces for 3 numbers.
fill(R,G,B)
These 3 numbers are Red, Green, and Blue values.

To use the full color we use the value 255, to not use the color we use 0 value.

To use half the color we use the value 255/2

If there is only 1 space, that means it's all the RGB.
Meaning 1 value will be all values.
So this color will always range from white to black, with gray in the middle.

If we have a 4th number, then that means Alpha.
Alpha means transparency, so our colors are either see-through or not.

Step 4
Let's jump into it! Start a brand new scene in Unity with only a plane.
Step 5
Select any seamless texture from google image, import it and create a material with it to place on your plane.

If you want my rock image. Right Click and save the image below.

Planning
Step 6
What we'll try to create is we'll try to do something that the Standard shader can't do

The Standard shader is great for most purposes but there are other very interesting shaders that can do all kinds of interesting things!

Note: You can browse other interesting shaders that people has made on https://www.shadertoy.com/ for fun and inspiration!

However, it's all written in GLSL which is not compatible with Unity but can be easily converted to HLSL through tutorial videos.

Step 7
Next we want to create a Subsurface shader. So Right click in your assets, create, shader, standard subsurface shader.

Give it any name you want, I'll name mines as "First Shader".

Step 8
Double click your Shader to open the code and... Don't be alarmed, it's a lot, but we'll break it down.
Information
Step 9
Here is how the basic structure is like for a Shader
Shader "CategoryName/ShaderName" {
    Properties {
        //Properties that are shown on the Unity Inspector

    }
    SubShader {

        CGPROGRAM

        #pragma vertex MyVertexProgram
        #pragma fragment MyFragmentProgram

        //Input
        struct Input {

        };

        //Variables here (Usually is connected to Properties)
        fixed4 _Color;

        //Vertex program to manipulate vertices or know where to put color
        void MyVertexProgram () {

        }

        //Fragment program on how to make the color appear on your vertices
        void MyFragmentProgram () {

        }


        ENDCG
    }
}

Step 10
So first thing's first, we want to use the shader that we're working with.

In line 1, our shader is placed in "Custom" the category, and "FirstShader" which is the name of the shader.

Check Your Work
Step 11
As you can see, by using our custom shader, nothing has changed but this time we have more control over it!

Check to make sure that you're using the shader you made and not any other shader.

Information
Step 12
We'll take a look at the properties first.

You notice that the color is set to (1,1,1,1) as default

(1,1,1,1) is just like fill(255,255,255,255) from JavaScript

In HLSL, 1 would be the maximum of a color value, where 255 is the maximum in JavaScript.

Step 13
[Optional] Try changing a name, save the script and then see what shows in the inspector!
Information
Step 14
Next we have another block called Subshader, this is where everything happens
SubShader {

        CGPROGRAM
            // All shader code happens Between CGPROGRAM and ENDCG
        ENDCG
    }

The Tags and LOD is telling us when and how to render our shader in relation to other objects. It's not important right now so we'll skip over this for now.

Information
Step 15
The next part is using a "template" from the Standard shader of how we take in light and shadows.

At the moment it gets very complicated if you want to create custom light directions and how normal maps show on your material. So we'll leave this as is.

Step 16
The next section in our code is our variables and structs! Let's reorganize the code so that it's easier to see.

Remember our variable names in the Property block? We have to define them here, the names must match.

Information
Step 17
Variable types

In C# we have float, int, Vector2, Vector3, etc. And we use them according to what we need them for.

In HLSL we'll be mostly working with color and textures.

Here's a quick list of what each will be used for.

sampler2D - Used for textures
float - Hold a single number, (Can be a large number)
half - Hold a single number, (Can hold medium sized numbers around up to 60,000)
fixed - Hold a single number, (Can hold simple number between -2.0 and 2.0), used for something like simple color data.

float2, float3, float4 - The same as float, but can hold 2, 3 or 4 numbers.

The above also applies to half2, half3, half4, fixed2, fixed3, fixed4, etc

More information here: https://docs.unity3d.com/Manual/SL-DataTypesAndPrecision.html

Information
Step 18
The Input data

Structs are used to package things.
In fact you've used something very similar to structs in JavaScript! Here's one example from JavaScript.

var player = {
xpos:580,
ypos:350,
width:25,
height:50,
img:loadImage("ellen.png"),
};

For our Input, we only have 1 thing in there, which is taking in the main texture's uv's. But it's possible to later input more thing and add more to our Input.

The next section we can skip, it's optional but we won't be using any instancing.

Step 19
Lastly, the surface shader function! We'll mostly code in this function!

Let's try something.

Instead of o.Albedo = c.rgb;, let's change that to o.Albedo = float3(1,0,0);
Save the script and see if it changed anything in your scene

Step 20
Playing with the Albedo!

From the previous step, we turned our plane to become red!
Let's try float3(1,1,0); for yellow!

Now let's try multiplying two float 3's:
float3(1, 1, 0) * float3(1, 0, 0);

It turned back to red! Why is that?

When we multiple both of them, the 1st, 2nd, and 3rd spaces is multipled by both float3's.
1 times 1 is 1,
1 times 0 is 0,
and 0 times 0 is 0

Next let's try addition. Use this example:
float3(1, 1, 0) + float3(0, 0, 1);

This time we get white! Because

float3(1, 1, 0) + float3(0, 0, 1) = float3(1, 1, 1);

Note: if you had (1, 0, 0) + (1, 0, 0), that would equal (1, 0, 0) and not (2, 0, 0).

Because any value that's higher than 1, stays at 1.

Just how you can't have a value higher than 255 in the fill() function.

Step 21
Playing with the texture color values

Let's put this back to normal.

o.Albedo = c.rgb;

c.rgb is using all 3 channels, the red, blue and the green. So what would happen if we only used the blue channel?

o.Albedo = c.b;

It looks slightly darker, but what c.b means is that we only use the blue value for all parameters.

o.Albedo is always needing 3 numbers. Just like fill(use,use,use).

If we did c.rgb, that would be like fill(c.r, c.g. c.b)

but if we only use c.b when 3 numbers are needed, that would become

fill(c.b, c.b, c.b)

So whenever all numbers in the fill() function is the same, it'll always be either black, white, or somewhere in between.

fill(0,0,0) / fill(255,255,255) / fill(193, 193, 193) / etc

Step 22
Let's change our texture to another texture with more colors.

I'm going to choose this brick texture, if you want to use it you can Right click to save image as.

Return our o.Albedo = c.b, back to o.Albedo = c.rgb;

Step 23
Negative colors

You know how some camera filters have a negative color filter?

What the camera does is 1 minus all the colors in the picture

1 - (all colors)

In our code, we would try o.Albedo = 1 - c.rgb;

It's negative! Brilliant!

Let's return our code back to o.Albedo = 1 - c.rgb; for the next part

Information
Step 24
The Lerp() function

For the next part we'll learn a new function called Lerp, which means linear interpolation between 2 values based on the 3rd value.
(Aka. Similar to adjusting a volume knob on the radio)

Here's an example of mixing yellow and blue paint to make green paint

So in Summary:

lerp(yellow, blue, 0) = yellow
lerp(yellow, blue, 1) = blue
lerp(yellow, blue, 0.5) = green

Step 25
Next step we want to blend between black/white and fully colored using a "click and drag" button.

To get an average black and white grayscale. We have to add all 3 red, blue, green channels and divide it by 3 to get the average.

(c.r + c.g + c.b) / 3

Let's type o.Albedo = lerp((c.r + c.g + c.b)/3, c, 0); into our code. Save it and see the results.

We got grayscale and not full color because our 3rd value is 0

Step 26
Creating a reference variable

We want to be able to change this in the Unity Inspector.

To create a new variable, we have to make it a property first. We'll call this Saturation

Then we have to declare it where our other variables are.

Note: I used "half" because saturation is going to be a low number

Then lastly, in our lerp function's 3rd number, instead of 0 or 1, we'll use the _Saturation variable here.

Check Your Work
Step 27
Let's test this out in the Unity Inspector!
Information
Step 28
You are done with this lesson! The next lesson will then introduce how to use UV's and vertices in HLSL!
An Introduction to HLSL Programming Info

Account

MVCode

Created By

tina nguyen

Course:

Introduction to HLSL Graphics

Access Level

public

For Teachers and Schools

Teach coding to your students with MVCode Teach

MVCode offers an integrated product designed to teach coding to students.

Learn more about MVCode Teach