Unity ShaderGraph Procedural Skybox Tutorial Pt.1

Procedural skybox demo picture showing the skybox used in a demo game scene.

Unity ShaderGraph Procedural Skybox (Tutorial)

Create the sun and the sky,
And the stars and the clouds,
Add them all up,
And turn day into night


Turns out..it’s surprisingly easy to start creating your own custom skybox shaders with Unity + ShaderGraph…
In this tutorial we’ll build up a fully customisable skybox by creating functions in ShaderGraph for each different layer the skybox consists of, starting with the colors of the sky, then we’ll add the sun and a layer for the clouds and the stars and blend between day and night by the rotation of the directional light in the scene.

This tutorial was written using Unity 2019.2.4f1 using the LWRP template and later upgraded to work with Unity 2019.4.18f1 LTS and the URP.

The only real difference between the LWRP and the URP shader graph is that you have to enable ‘Two Sided’ on the Unlit Master output node.. See step 1.5.

1. Creating the Skybox Material

1.1 Create a new unlit ShaderGraph

Right click in the Project window and select Create > Shader > Unlit Graph

1.2 Create a new material

Create a new material in the Project window and drag the ShaderGraph on top of it to assign it to the material.

1.3 Assign the material to the skybox

Open Window > Rendering > Lighting Settings and drag the material into the Skybox Material slot in the Environment settings.

1.4 Open the ShaderGraph

Double click on the Skybox ShaderGraph file in the project window to open the graph…

1.5 Unlit Master Node Settings URP

If you are using Unity 2019.3 and the URP then you have to enable ‘Two Sided’ in the unlit master node settings by clicking on the cogwheel icon:

ShaderGraph master node settings for the Universal Render Pipeline (URP).

1.6 Simple gradient sky Test

Just to make sure everything works we can create a very simple gradient skybox by using the green channel (Y-axis) of the normalised world position and feeding that into a sample gradient node. The normalised world position ranges from -1 at the bottom of the world to 1 at the top center of the world so in order to plug that value into the time input of the gradient it needs to be remapped to a 0 to 1 range first:

Image of a shader graph skybox node setup that creates a gradient in the sky.
Image displaying the shader graph skybox material added to the scene's lighting settings.

It would be really great if we could use gradients throughout the entire skybox but at the moment gradients in ShaderGraph cannot be turned into exposed properties so for the sake of not having to open the graph to make adjustments and so that we can use the same shader for different materials we’ll use three separate color properties instead and blend between those..

2. Adding a Sky Layer

This ShaderGraph nodes setup blends three exposed HDR color properties for the sky, horizon and ground. The softness of the gradient towards the horizon from the ground and from the sky can be adjusted with the Exponent1 and Exponent2 properties, the overall brightness with the Intensity property. By using HDR colors we can make the sky emit light coming from the middle horizon color for instance. Using HDR colors combined with a Bloom post-processing effect on the camera can make it look even better!:

Image of a shader graph skybox node setup that creates a gradient in the sky from three color properties.
Image of the shader graph skybox node setup that creates a gradient in the sky from three color properties used in the scene.

3. Adding the Sun

To add a sun to a skybox that has its position based on the direction of a directional light in your scene, a custom ‘Main Light’ node that gets the direction of the Main Light in the scene has to be created first..
You can find detailed instructions on how to create this custom main light node (and custom nodes in general) in this Unity blog post (opens in a new tab).: blogs.unity3d.com/2019/07/31/custom-lighting-in-shader-graph…

The best way to create a custom node that we can reuse in other graphs as well is to create a Custom Function node first and then convert it into a Subgraph.
Both the custom node function .hlsl file and the SubGraph can be downloaded from here (see the Subgraphs and CustomNodes folders): github.com/Timanious/MyShaderGraphs…

Or if you want to create this custom node yourself, you can follow along with these instructions to create the Main Light node…

3.1 Create a Custom Function node

Right click in the ShaderGraph working area and create a new Custom Function node, you can find it under ‘Utility’:

Image showing a newly created shader graph custom function node.

3.2 Add input and output parameters

Click on the settings cogwheel from the Custom Function node and add the following input and output parameters:

Image of a shader graph custom function node with input and output properties added.

3.3 Function name

The name of the function inside of the .hlsl file that this node is going to use is called MainLight, type that into the Name field:

Image of a shader graph custom function node showing the name of the node which is MainLight with capital M and L.

3.4 Adding an HLSL include file

As you can see the Custom Function node has the option to use a inline function if you switch the type to string, (see the Unity blogpost for more information about that) but using a separate HLSL include file gives us more flexibility, it can contain more complicated functions and you can keep them all organised in one place.

At the time of writing this, Unity doesn’t have a convenient menu item for creating an HLSL include file asset, so you’ll have to create it yourself, for example by creating a normal unlit .shader or .cs file and changing the file extension to .hlsl and removing the code from it..
Create a file named CustomLighting.hlsl, paste the code from below into it and save it : 


void MainLight_float(float3 WorldPos, out float3 Direction, out float3 Color, out float DistanceAtten, out float ShadowAtten)
    Direction = float3(0.5, 0.5, 0);
    Color = 1;
    DistanceAtten = 1;
    ShadowAtten = 1;
    float4 clipPos = TransformWorldToHClip(WorldPos);
    float4 shadowCoord = ComputeScreenPos(clipPos);
    float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
    Light mainLight = GetMainLight(shadowCoord);
    Direction = mainLight.direction;
    Color = mainLight.color;
    DistanceAtten = mainLight.distanceAttenuation;
    ShadowAtten = mainLight.shadowAttenuation;

void MainLight_half(float3 WorldPos, out half3 Direction, out half3 Color, out half DistanceAtten, out half ShadowAtten)
    Direction = half3(0.5, 0.5, 0);
    Color = 1;
    DistanceAtten = 1;
    ShadowAtten = 1;
    half4 clipPos = TransformWorldToHClip(WorldPos);
    half4 shadowCoord = ComputeScreenPos(clipPos);
    half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
    Light mainLight = GetMainLight(shadowCoord);
    Direction = mainLight.direction;
    Color = mainLight.color;
    DistanceAtten = mainLight.distanceAttenuation;
    ShadowAtten = mainLight.shadowAttenuation;

void DirectSpecular_float(float3 Specular, float Smoothness, float3 Direction, float3 Color, float3 WorldNormal, float3 WorldView, out float3 Out)
    Out = 0;
    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    Out = LightingSpecular(Color, Direction, WorldNormal, WorldView, float4(Specular, 0), Smoothness);

void DirectSpecular_half(half3 Specular, half Smoothness, half3 Direction, half3 Color, half3 WorldNormal, half3 WorldView, out half3 Out)
    Out = 0;
    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    Out = LightingSpecular(Color, Direction, WorldNormal, WorldView,half4(Specular, 0), Smoothness);

void AdditionalLights_float(float3 SpecColor, float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView, out float3 Diffuse, out float3 Specular)
    float3 diffuseColor = 0;
    float3 specularColor = 0;

    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    int pixelLightCount = GetAdditionalLightsCount();
    for (int i = 0; i < pixelLightCount; ++i)
        Light light = GetAdditionalLight(i, WorldPosition);
        half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
        diffuseColor += LightingLambert(attenuatedLightColor, light.direction, WorldNormal);
        specularColor += LightingSpecular(attenuatedLightColor, light.direction, WorldNormal, WorldView, float4(SpecColor, 0), Smoothness);

    Diffuse = diffuseColor;
    Specular = specularColor;

void AdditionalLights_half(half3 SpecColor, half Smoothness, half3 WorldPosition, half3 WorldNormal, half3 WorldView, out half3 Diffuse, out half3 Specular)
    half3 diffuseColor = 0;
    half3 specularColor = 0;

    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    int pixelLightCount = GetAdditionalLightsCount();
    for (int i = 0; i < pixelLightCount; ++i)
        Light light = GetAdditionalLight(i, WorldPosition);
        half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
        diffuseColor += LightingLambert(attenuatedLightColor, light.direction, WorldNormal);
        specularColor += LightingSpecular(attenuatedLightColor, light.direction, WorldNormal, WorldView, half4(SpecColor, 0), Smoothness);

    Diffuse = diffuseColor;
    Specular = specularColor;


3.5 Assigning the .hlsl file

Drag the CustomLighting.hlsl file into the text asset Source slot to assign it to the custom function node:

Image of a shader graph custom function node with with the CustomLighting.hlsl file added to the Source slot.

3.6 Convert to Sub-graph

Right-click on the Custom Function node and select ‘Convert to Sub-graph’. Name the sub-graph MainLight:

Image displaying the right click menu pointed at the Convert To Sub-graph option of the Custom Function node.

3.7 Add Sub-graph Outputs

Open the new Subgraph and Connect the WorldPos input from the Custom Function node to a Position node. Also create 4 new inputs on the Output node corresponding with the outputs from the Custom Function node:Screenshot 2019-09-03 at 14.22.20.png

3.8 Organising the Sub-graph node

If you look at the Blackboard, you’ll see that underneath the name of the Subgraph in a darker tint grey you can specify where the subgraph is organised in the node creation menu. If you change it to Input/Lighting it will be sorted with the other Input/Lighting nodes, so it will be easy to find:

Image displaying the shader graph name on the blackboard and the organisation name beneath.
Screenshot 2019-09-03 at 14.31.38.png

3.9 Positioning the Sun

Now that we can get the direction of the Main Light in the scene, we can use it for positioning a sun:

Screenshot 2019-09-03 at 14.47.30.png

This graph works by getting the dot product from the view direction and the inverse direction of the main light. The size, intensity and softness of the sun can be adjusted with the radius, intensity and exponent variables. The color of the Main Light in the scene is also used to give the sun color.

3.10 Add the Sun to the Sky

The last step is to simply add the output from the Sun to the sky color with a Add node:

Image displaying the shader graph with the sun and the sky layers added with an add node.
Image displaying the shader graph sun used in the game scene.

4.Adding a clouds layer

Building up a clouds layer has a lot of steps, but it starts simple, so for this I think it’s best if we build it up step by step, then you can decide how much information your clouds layer needs.

For the purpose of better showing you what’s going on this tutorial uses an RGB circles testing image created with Photoshop for most of the examples as well as a noise/clouds texture + normal map created with CatlikeCoding’s NumberFlow Unity plugin (which is also a node based application but for creating procedural textures).
You can download the textures used in this tutorial from this blogpost: https://timcoster.wordpress.com/2019/09/09/tileable-clouds-texture/

Or if you are going to use your own textures, then what probably works best is using an actual transparent .PNG file instead of a black and white one. This way you won’t get clouds with grey edges but with transparent white edges instead. Make sure to set the Alpha Source to Input Texture Aplha in the import settings and also make sure the texture has an Alpha channel, you can see if it has one in the preview window if you see a white A after RGB :

Screenshot 2019-09-06 at 12.43.27

4.1 Adding a clouds layer

Image displaying the clouds layer for the skybox shader with a debug color circles image used.

The node setup below generates an infinitely large flat layer for a clouds texture:

Image displaying the clouds layer shader graph nodes setup.

We can blend the transparent clouds texture with the sky colors and the sun, using a blend node. By using the Alpha channel from the texture for the opacity of the Blend node and setting the Blend node to overwrite, we can get a nice clean result:

Screenshot 2019-09-06 at 13.02.41.pngScreenshot 2019-09-06 at 12.57.38

4.2 Adding far tiling

By adding a property for far tiling we can make it look like the horizon is a little bit closer.
for the RGB circles example it was set to 0.1 and for the clouds to 0.25:Screenshot 2019-09-06 at 13.08.31.png

4.3 Adding Texture Tiling

Adding a texture tiling property gives us control over the size of the texture, here it was set to .05:

Image displaying the shader graph node setup to add texture tiling to the clouds layer.

4.4 Adding Texture Offset

Adding in a Vector2 clouds texture offset property will be useful for animating the clouds later on:

Image displaying the shader graph node setup to add texture offset to the clouds layer.

4.5 Adding Cutoff

Adding Distance and Cutoff properties to control fading out the clouds towards the horizon. For the examples below the distance was set to 0.8 and the cutoff to 25:

Image displaying the shader graph node setup for how to add distance and cutoff to the clouds layer.

4.6 Adding Opacity

Opacity can be added by Multiplying the output of the Alpha channel of the clouds texture with a new Vector1 clouds opacity property. The example shows the opacity at 0.5:

Image displaying the shader graph node setup to add texture opacity to the clouds layer.

4.7 Adding a Normal Map

Adding support for a normal map texture to suggest depth based on the direction of the light. This Makes it look like shadows are at the bottom of the clouds when the directional light shines straight down. When adding the Sample Texture node for the normal map, make sure to set the node type to Normal. Also make sure to set the clouds normal map texture property to ‘Bump’ mode on the blackboard.
The custom Main Light function node is used outside of the subgraph here because it uses the tangent position instead of the world position. You can simply copy the custom node from the Main Light subgraph.

Normal map strength was set to 0.8 for the example:

Image displaying the shader graph node setup to add a normal map texture to the clouds layer.

4.8 Adding Brightness

Add a new Vector1 property for clouds brightness, and a new add, subtract and multiply node.
Subtract 1 from the brightness value and then add it to the output of the blend node.
Subtracting one from the brightness will make it so that a brightness value of 1 will be normal and a value of 0 will be no brightness at all, instead of zero being the normal brightness.
Also multiply the normal map strength by the brightness property before plugging it back into the blend node, to make it dependent on the brightness value.
Left and right examples show brightness values of 0.1 and 1.5:

Image displaying the shader graph node setup to add brightness to the clouds layer.

4.9 Adding Directional Light Color

Multiplying the texture by the Main Light color output will make the color of the clouds dependant on the color of the directional light in the scene. For the examples the light color was set to light orange and light blue:

Image displaying the shader graph node setup to add directional light color the clouds.

4.10 Adding Clouds Color

Now that the hard bits for the clouds layer are done, let’s add in something a bit more easy, which is the basic clouds color. For this, make a new Color property called Clouds Color and drag it onto the graph, also make a new Blend node. Blend the Main Light color with the Clouds Color before plugging it into the multiply node at the end.
For the example below the sun color was set to light orange and the Clouds Color to light blue HDR with a high intensity:

Image displaying the shader graph node setup to add a clouds color property to the graph.

4.11 Reorganise Layers

To make the adding of the different layers make more sense we can rearrange the graph so the order will be Sun + Sky + Clouds.
This way what is closest by will be added last:

5. Adding a Night-Sky

Now that we have a sun sky and some basic clouds we can start reaching for the stars!…(yeah, yeah..)…
But in order to see those stars better when we add them it will be handy if we can automatically transition from day-sky colors to night-sky colors using the direction of the sun for the blending between them.
We can do this by using almost the same position input that we use for the sun to Lerp from color a to b, and while we’re at it we can do the same with the sky exponent 1 and 2 properties to make the horizon look thinner at night.

So before we add the stars we’ll have to revisit the sky colors layer..

5.1 Adding night-sky properties

Add three new Color properties for the night-sky colors and add two new Vector1 properties for the night-sky exponents:

Screenshot 2019-09-08 at 15.05.59.png

5.2 Lerping Colors

Swap the three color and two exponent property nodes with Lerp nodes and connect them in the way that you see in the diagram below.
First image is the old sky and the second is the new sky layer with all the new nodes selected so they show a blue line around them:

Screenshot 2019-09-08 at 15.03.46
Screenshot 2019-09-08 at 14.57.17.png

6. Adding Stars

Now that we can transition to dark night colors we can start adding the stars layer to the skybox. This tutorial uses a very simple black and white stars texture, but you can go as fancy as you want of course:


6.1 Sphere Mapping Space

The following node setup gives us a nice spherical mapping for a stars texture. There are some weird visual artefacts from the texture not being made to wrap around a sphere but that’s not really a problem because the texture will be faded out towards the horizon:

Screenshot 2019-09-08 at 16.36.06.png

6.3 Add Intensity

Multiply the RGB output from the stars texture by an intensity value to control how bright they shine:

Screenshot 2019-09-08 at 16.43.05.png
Screenshot 2019-09-08 at 17.21.23

6.4 Add Texture Offset

Adding a Vector2 offset value to the UV coordinates before plugging it in the texture gives us control the position of the stars texture, useful for animating later on:

Screenshot 2019-09-08 at 16.44.28.png

6.5 Add Horizon Fade-out

To gently fade out the stars towards the horizon we can multiply the texture again by using a power node and a fadeout property:

Screenshot 2019-09-08 at 16.46.04.png
Screenshot 2019-09-08 at 17.23.53

6.6 Add Day/Night Transition

Finally add a day to night transition by multiplying again by a Lerp node using the dot product of the light direction for the Lerp node’s time input. Now the stars will be completely visible when the directional light is shining straight up and completely invisible when shining straight down:

Image displaying the shader graph node setup to add a day night transition to the stars layer.

6.7 Adding the Stars Layer

Rearrange the graph so the stars come on top. They are farthest away so the stars layer is drawn first.

The complete Procedural Skybox ShaderGraph:

Large image displaying the complete procedural skybox shader graph with all the layers.

Adding everything up and using it on a small testing scene with a floor, exponential fog with a very low value of about 0.0006 and some post processing effects enabled it already starts to look pretty game-ee!:

Animated GIF displaying the changes of the sky color etc. of the day night cycle while the sun is rotating.

7.0 Finally

Want to learn how we can access the properties of this Skybox ShaderGraph from a C# script to create a Day/Night Cycle for a game next?? Then click on the link below to go to part 2!:

Unity ShaderGraph Procedural Skybox Tutorial Pt.2 (Day/Night Cycle)

p.s Feel free to post any comments or suggestions in the comments and let me know if there’s anything wrong!



Donate $5 to buy me a coffee so I have the fuel I need to keep producing great tutorials!




  1. Hi I loved this tutorial it’s well explained and I created my own shader from this. However after trying to tweak the values and adding a cubemap for the night sky instead of a 2DTexture the shader broke, I don’t know how to describe it but it looks like there is a green layer over the sky shader, and i can’t find where it came from, even after removing the cubemap part it’s still broken :/ I’m using Unity 2020.1 with URP


    • Glad you loved it Raphael!:D I’m not sure why it broke for you but if you mail (timcoster@gmail.com) or dropbox or something me the .shadergraph file and the cubemap then I’ll have a look..I guess you want to blend between a day and a night cubemap? or something else?


  2. Hi Tim, great tutorial! I’m following from the beginning and when I normalize the world position I get basically just a brighter version of the same UV. It create the defined point in the middle, and after splitting and remapping all I’m left with is a vertical white to black gradient, without the shape shown in yours. Is there some setting that I’m missing on the normalize node that would fix this?


    • Hi Brenton! Thanks I’m glad you like it!:)
      I understand the confusion,.. I don’t know with what version Unity changed it exactly but I just reopened the skybox project in a newer LTS version and I see that Unity changed the way that the preview images in the nodes are shown, from 2D to 3D previews, but the functionality is still the same. The vertical black to white gradient from top to bottom, 1 to 0, is exactly what is needed to sample the gradient with.
      The normalisation of the position vector is needed for two reasons, the first is because the actual points in world space on the surface of the skybox may be further away than 1 so by normalising we make sure the vector always has a magnitude/length of 1. The second reason is because the shape of the skybox is a box but we want to render the sky like a sphere so we need the y-value from the points on the surface of a sphere, not on the surface of a box where the y would be -1 (zero after remapping) for the whole ‘floor’ face of the box..Best I can explain it is with this 2D illustration where the y-component of vector b before normalization is -1 and after normalization it is -0.707 :

      You can debug it from a script like this:
      Debug.Log(“Normalised: ” + new Vector2(-1,-1).normalized);
      Debug.Log(“Magnitude: ” + new Vector2(-1,-1).magnitude);
      Debug.Log(“Normalised magnitude: ” + new Vector2(-1,-1).normalised.magnitude);

      After normalisation the length of all three vectors is 1 where before the length of vector b would be more than 1. Which is handy for instance to make sure a character doesn’t move faster diagonally when both horizontal and vertical input axes return 1 or -1, then you can put the horizontal and vertical axes in a vector2, normalise it and multiply the players position with it etcetera..
      But what matters for the shader is that we get a gradual change in the y position from the bottom center to the top center of a sphere and not just on the sides/walls of a box:)

      Hope it explains it a bit but I’m not a mathematician:) I just checked the skybox in the latest LTS version and it still works correctly as far as I can tell but let me know if you run into more problems.


  3. Hey, I really enjoyed following along with your tutorial for my school project! The look is great! There is a question I am left with; If I would want my clouds to move slowly Should the value for cloud offset just go up the entire time? I can’t imagine that’s the way, I tried combining the offset value with a time node with some clamps and multiplications, but the best I can get to is the clouds going back and forth. Any suggestions?

    Liked by 1 person

    • Hey Jasper! I’m really glad to read that you enjoyed the tutorial!:D Maybe post a link here to your results if you put it online somewhere, would be great to see.
      It is a good question that you have because it is a bit counter-intuitive at first to keep adding an increasing value to the offset, but it is a proper way to do it and it’s also used in every texture offset example that i can find, for instance this texture offset example from the Unity API: https://docs.unity3d.com/ScriptReference/Material.SetTextureOffset.html

      Keep in mind that the time node returns the time since the start of the game so it gets reset every time the game starts, so the player would have to play the game for a very long time before it could cause any precision problems:

      You can however use a fraction node after the time node to make the value go from 0 to 1 repeatedly instead of ping-ponging like you describe, but it would still use the time value that gets increasingly larger in the calculation, so it’s best to just use it directly I think.
      So if you want to set the offset from within the shader it is fine to add regular time and let the offset increase.
      If you want it to look a bit smoother then I recommend adding Time.Deltatime to the offset from a C# script, which will make the smoothness of the animation frame rate independent. This is the code that I use for that in the second part of the skybox tutorial:

      public Vector2 cloudsSpeed = new Vector2(1,-1);

      void Update()

      void MoveClouds()
      RenderSettings.skybox.SetVector(“_CloudsOffset”, (Vector2)RenderSettings.skybox.GetVector(“_CloudsOffset”) + Time.deltaTime * cloudsSpeed);

      You can see it in its context in step 7 from the tutorial:

      I try to keep the code examples very plain but maybe you could add some if statements to the MoveClouds method to check if the offset added gets too large and then reset it or something similar..
      Hope it helps! Cheers


      • Hey, thanks for the in-depth answer! Here’s a little gif of my results: https://gyazo.com/190e21087539a89627cb1738c7af3f67
        Eventually, I found out you can call on the exposed parameters of a shader by the reference code in the blackboard. So I ended up just adding a value over time and make it really small:P. \

        public Material skyBoxShaderMaterial;
        public float woosh,wooshKeer;
        void Update()
        woosh += Time.time *wooshKeer;
        skyBoxShaderMaterial.SetFloat(“Vector1_B483EFBD”, woosh);
        Might play around with the solution with nodes. Thanks again and best of luck!


        • You’re welcome and nice gif, if already looks great!
          Also good that you already found a solution!
          In a lot of games the clouds layer from the skybox shader is used for the high clouds layer and a clouds particle system is used for the mid and low clouds layers, to make it look more realistic. So maybe try adding a particle system (or VFX-graph) as well:)
          Have fun and good luck with the school project!✌️


  4. How would you go about adding a moon texture so you can have a moon at night. Adding a circle like the sun isn’t hard, how would you switch that to a texture?


    • Hi Chris!
      To use a texture for a moon you can use a method like the one below, which basically draws the texture on two sides of the skybox cube by using the fragment/pixel position and then it rotates the position with the Rotate About Axis node.
      The Sphere Mask node is just there to mask the second moon on the other side of the cube. You can adjust the size of the moon with the tiling and offset node. The moon can rotate by adding time multiplied by some speed to the rotation angle and you could set the speed from a script if you want it to be the same as the sun.
      You can just add the moon method to the graph in between the sun and the sky layers and you have to set the moon texture wrap mode to clamped in the import settings:

      I thought I had found a way to have the moon rotate using the direction of the sun as well but that is proving to be harder. The best I have for that so far is using the suns direction for the rotate axis but that makes the moon rotate too fast:

      So I haven’t found the perfect solution for that problem yet, but if I figure it out I’ll post it here:) I Hope it helps!

      Greetings, Tim


      • Thanks for the quick reply Tim.

        First off I want to say thank you for these tutorials. I followed this one and have another bookmarked for later on. I forgot to mention that in my first post, love these blog tutorials better than videos.

        I tried both ways to add the moon and both end in the same result, a giant white circle where my moon should be. Texture is set to clamp, rotation set to degrees and using object position.

        On the main preview it shows it working with a bit of a streak of white behind it like a trail. Currently trying to fix the first method, as I can tweak that with a script easily, the moon isn’t exactly tied to the sun movement.

        Not sure what to do, my graph is identical to yours, well the nodes any way :D.


        • Hi Chris! I’m glad to read that you enjoy this tutorial and hopefully also the next😃
          I’m not exactly sure why you only get to see a white circle but it might be because of the texture’s alpha channel. The white streaks are maybe the pixels of the edge of the image that are being stretched out because the texture is clamped. So it helps to leave some small edges/space around the moon in the texture so it only stretches the transparant edge pixels. Maybe try it with the one that I’ve used:

          It’s just a regular texture of a moon with the background removed in Photoshop.
          It may also have something to do with the sphere mask node so you can try it without the mask.
          It may also be some other variable that is different in your graph vs mine so here is the complete skybox demo package plus the moon bit in a separate folder.
          It is a large package so it’s best to create a new empty URP project first and import it into that:

          Hope it will work 🙂 and if all else fails then there is always the option to use a quad with a moon texture 😀
          But I will spend some more time on it myself too because it is really nice to have the moon in a skybox shader! 🙂


          • So I feel like an idiot. I added the texture as a preview in the shader graph but didn’t add it to the material. So that was the reason why it was showing a big white sphere. Works great now that I added the texture.


            • Haha yeah that one gets me every time as well..But don’t worry about it! Even after years of working with Unity I still feel like a coding chimpansee sometimes. I’m just glad I never met a chimpansee smarter than me 😀 But I’m glad its something small and that you have the moon working now! 🙌

              Enjoy the shader coding and feel free to post any links here if you’ve made something cool with it🙂


              • Forgot to mention something..You can open a second Inspector tab and have it locked with the ShaderGraph material selected, so you can easily keep your eye on the material properties while working on a graph.


                • I’ve been working with Unity for 6+ years and still make simple mistakes, pull my hair out for a couple of hours then go, “$%&#, what was I thinking!” Good suggestion on the second locked inspector window, and emphasis on the SECOND window, too many times with it being locked and wondering “why isn’t this working?”

                  Just so you know, I am using 2020.2.0 and the hlsl file gives an error messages about a define parameter. To fix this replace #if SHADERGRAPH_PREVIEW with #if defined(SHADERGRAPH_PREVIEW) and all other #if’s that are checking for a shader define. #ifndef doesn’t need to be changed though.

                  The streak with the moon was exactly what you said it was, gave the texture a bit of a transparent border and no more streak. I also rotated the uv’s 90 degrees so my moon looks correct when coming up over the horizon, it is quite stylized so it didn’t look right not up right. I also reversed the rotation and used the sun’s rotation speed in the script to rotate the moon for now.


                  • Haha yeah it is kind of the same for me!😅Also one of the reasons why I have trimmed hair, so I can’t grasp onto it easily. Don’t think it’s going to change unless maybe if I lose all my enthusiasm, but that would suck..

                    Anyway, thanks for pointing out the 2020 #if defined error! I will add a note to the tutorial about it and I’m glad that you got the moon exactly how you want now.😃
                    Maybe it’s overkill for your project but you can also create some pretty decent moons and planet type stuff procedurally with noise and the spherize nodes. I’ve been messing with it some more and here are some examples that you can try out, but they’re not completely tested etc..🙂:

                    The custom layered noise node can be downloaded from here:


  5. Great rouses thank you for publishing!
    I was wondering if you have managed to find a way to rotate the moon texture and have it align to a direction?
    The best thing i manage is to use a rotation matrix but that’s quite taxing on performance, What I’m trying to do is have the moon texture align with a direction vec3 that I’m updating throw c#.


    • Hi Hanabi! You’re very welcome and thank you for reading! I really wish I could say yes to your question because that would make me look like a genius😅 I’ve been trying to find a solution but the best I have so far is something like this, but it only half works..:

      With this setup it is probably be easiest to just specify the axis in the Rotate about axis node instead of using the light direction for it and then only update the rotation degrees value from a script or with a Time node.
      The moon texture can also be rotated up and down with the normal Rotate node.

      I’ll update if I find a more elegant solution but I guess I need to learn more about matrixes and quaternions etcetera.
      The problem is that the direction doesn’t change when rotating the object around the direction axis so the forward direction of the light doesn’t change when rotating the light around the z-axis. If you managed to use a rotation matrix then I’m curious how you did it because I’m still learning how to work with matrixes:)


  6. Hey Tim! Thanks so much for the tutorial. It’s all working great, but I’m having some trouble with the custom subgraph (MainLight). I’m getting errors saying that there’s an “Invalid conditional expression” in the CustomLighting.hlsl file. And “undeclared identifier” in the MainLightCustomFunction. Do you have any ideas on how I might get it working? Thank you!!


  7. Excellent tutorial thus far! However, I am using the most recent version of Unity and cannot manage to get the sun texture to appear. I scoured the reference image to see if any of the nodes are incorrectly placed, but I can’t see a problem. The only thing I think could be the problem is the custom shader code for the MainLight node. Do you have an updated script?


    • Hey 👋 Jack! I’m not sure if it will solve the problem but Chris from the comments pointed out that, quote:
      “Just so you know, I am using 2020.2.0 and the hlsl file gives an error messages about a define parameter. To fix this replace #if SHADERGRAPH_PREVIEW with #if defined(SHADERGRAPH_PREVIEW) and all other #if’s that are checking for a shader define. #ifndef doesn’t need to be changed though.”
      I forgot to change it in the tutorial but it may be what causes it to not work for you. Lemme know if it solves it! Cheers 🍻


      • Hi there! I did indeed try that after seeing all of the errors, and once it compiled, no errors were found. I am still not seeing a sun though. :/ Does any part of the code rely on a GameObject having a specific name? If that’s the case, it would make sense, because I have changed the name of the main light.

        PS – I wasn’t sure if I would hear any response to my question, but I was delighted to find that you had replied so quickly. I really appreciate it!


        • Hmmm..🤔 weird,.. but we’ll get it to work, it always works in the end. Changing the name of the GameObject shouldn’t be a problem but it does need to have the ‘Main Light’ tag 🏷 assigned in the inspector if I member correctly. I will do some checkups and updates to the tutorial and the package tomorrow. Have you tried downloading the finished tutorial package from the bottom of the tutorial to see if that works? Best to try it in an empty new project and if it works you can compare it to your own setup. I’ve been doing some home improvement stuff lately but I always try to answer as soon as I can 🙂


          • Thank you for the response. I’ll check on the tag. If it still isn’t working, I’ll try downloading the finished project.


          • I’m looking at the CustomLighting.hlsl code here. I am not experienced at all in this type of code, but it’s similar to C#, so I’ll try to make sense of it.

            It looks like the code takes the parameter WorldPos. WorldPos is used to get clipPos, clipPos is used to get shadowCoord, and shadowCoord is used to get mainLight. I don’t really know how this works, but I am guessing WorldPos is important; by association, mainLight is dependent on WorldPos.

            But where does the WorldPos value come from? I can see in the subgraph that the World Pos node is plugged into the MainLight node, to provide it with the WorldPos parameter. But how does the World Pos node know what world position to look for?



      • I just noticed something fascinating. While I cannot see any sun in the skybox, a beautiful sun seems to reflect perfectly off of my reflective ocean tiles, as though there is actually a sun in the sky. I am not entirely sure why this is, but I’ll try to investigate to see if I can’t get the skybox to work.


        • Hahaha 😂 ok 👍 that just cheers me up 😁 I’m going to pour myself sum coffee and see what’s up on my end..Might just be a Unity bug 🐜 or something, who knows..I’m a total discord newbie but I you have it you can add me: Timanious #0424 .Or I will get back to you here.


        • Hey 👋 Jack. So I’ve been updating my master shader graph project to Unity 2020.3.9f1 to see if the sun still worked and it does,.. thankfully so that’s one relief. I also installed Unity 2020.3.15f2 and downloaded the complete skybox package from up here☝️ into it and it also imported without problems and in the demo scene the sun also works. So I don’t think it is a Unity bug that is causing the grief. I noticed that the default ‘Main Light’ tag that unity used to put on the default directional light isn’t there anymore so that’s not necessary either. Which kind of leaves me clueless to what’s causing it to not show in your setup..Maybe if you don’t mind you can mail me a link to your entire project so I can have a look into it for you. In any case it seems like it is a solvable problem that you’re facing so we can figure this sh*t out 🙂. Take your time if you want to solve it alone but my guess is that it’s something small that’s causing the problem so I don’t think you’ll have to get into the (unnecessarily difficult) meta language that is HLSL. I think the whole HLSL documentation is a bit lacking.. (I can never get a grip on it myself and I tried a couple of times) If you want to get into the ‘traditional’ shader coding I recommend starting with some GLSL coding, maybe follow along with some tutorials from ‘The Art of Code’ YouTube channel by Martijn, those are really good. The only thing really different between GLSL and HLSL are the datatypes which are easy to convert but HLSL has it’s own Unity-built-in variables and keywords that are a bit mysterious if you ask me.. I haven’t gotten around to really learning how the rasterisation process works on the background but I guess that’s where the shadowcoord and worldpos variables come from.. Maybe if you watch the tutorials from OLC (One Lonely Coder) about creating a 3D game engine (https://www.youtube.com/watch?v=ih20l3pJoeU) then it will shed some light on how the rasterisation process and the lighting etcetera works, but it is all in C++.. Anyway, lemme know about your process and try to leave your hair intact 😅 Cheers! ✌️


      • Greetings again. I am getting very close to a breakthrough here. I can finally see the shape of a sun in the sky!! I needed to set the Main Light as the Sun in the Lighting Settings pane for it to work. However, the new problem is a particularly odd one: The “sun” in the sky is actually just a circle of skybox, and the skybox is now the color the sun is supposed to be.

        Upon further investigation, I find that the sun and skybox swap textures only if Sun Radii A and B are the same. Why? I have no clue. If, for example, Sun Radius A = 0.1, and Sun Radius B = 0.11, the sun texture is fine.

        I’ll keep you updated.


        • Ahh I see, I guess setting the main light as the sun in the lighting settings is a new setting that I didn’t knew about. It probably didn’t cause a problem for me because it was fixed automatically by Unity upon updating to the newer versions, but I’m glad you figured it out for me😉. That is another thing to note about in the tutorial for new readers… The glitch you get from setting the radiuses a & b to the same value is to be expected just because ‘it’ doesn’t know what radius to choose when they’re both the same, but that could be fixed with either an extra IF-statement in the code or by adding a very small amount (Epsilon) like 0.005 or something to radius A or B. But that’s an improvement that I think should be done only by you or other readers, but that would make the graph ‘look’ overly complicated for the tutorial’s sake in my opinion🙂 Anyhow, good work!!


          • Well, I am just so happy it’s resolved now. My game is looking so unbelievably gorgeous right now… I just completed an ocean shader tutorial from PolyToots and it’s looking so much better than I ever could have guessed. And even with just half of the first part of the day cycle shader complete, I am very happy with the look of the sky. There are obviously some weird details, but they’ll probably be resolved at some point by the end of the day cycle tutorial.

            Thanks for keeping in contact. I’ll let you know if I run into any more issues. 🙂

            Liked by 1 person

            • Hey 👋 Jack. I’m happy when you’re happy man!😃 I really mean that. And I’m also super curious now to what you’re creating so feel free to post a link to a screenshot or recording over here or trough my e-mail if you want. The sun in the shader is by far the most iffy part of the skybox so I don’t expect you’ll run into anymore hard problems with the rest of it. One more thing I should note is that the stars texture for the stars layer used by me contains a very ugly watermark so it’s best to look for a better texture for that..enjoy your hard work 😓 and feel free to shoot me any questions related or unrelated to the shader. GRTZ && ✌️!


  8. if you’re doing this on a procedural terrain, one of the main issues is that you can’t control the objects in the horizon, which makes the stars have a visible tearing on the horizon.

    a quick and easy fix is to absolute the green value before hooking it to the clamp node; it’s still not perfect as it’s still just one texture on both nadir and zenith, but with the gradient it gives a easy fix to the rather dominant texture stretching issue below the horizon.

    of course you could also check if the value is negative, and give it a different texture below the horizon similar to how the sky atmosphere is done. this probably wouldn’t work well with part 2 of the tutorial though – haven’t checked, and didn’t tinker with it… i personally want complete sphere mapping with actual procedural stars anyway – so as such i file that under the not my problem folder.

    yet. that being said, excellent tutorial. ^^


  9. Hi Tim,
    great tutorial thanks a lot for the work. I also experience a small problem. The sun itself is rendered correctly when in play mode but is nowhere to be found when in editor mode. I sometimes can see a “flicker” of the sun somewhere in reflective surfaces (again, in edit mode) but I’m quite sure it’s not coming from the shader but is some remnant of the build-in sun. My first question would be: Is this normal behavior (sun not visible in editor mode) and if yes, is there a way to make it visible there as well?


    • Hi Heurazio,
      It should also work in edit mode. It might be that you have to add the directional light that you use as the sun in the lighting settings window so the engine knows what light is supposed to be the sun. Other than that I don’t know what causes the problem. Hope it helps!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.