Search Unity

Writing a unlit shader for a basic Skydome (My Journey so far)

Discussion in 'Shaders' started by Zaine7673, Dec 16, 2019.

  1. Zaine7673

    Zaine7673

    Joined:
    Feb 15, 2018
    Posts:
    238
    Hi all,

    I'm in the process of making a new game and with every new game, I like to push things a little more than I did last time so I thought I'd go ahead and learn some shader stuff.

    One thing I came to realise is that, as far as I'm concerned, there is not enough info about what goes on in a shaders. A lot (not all) of Youtube tutorials just seem to say "do this and do this, put these numbers here and tada!" This made it incredibly hard to learn (still learning). I also struggled to find a reference for the language used. Most of the info I've found on google was just people giving out codes. This is of-course very useful but it didn't help me to learn.

    However, with a simple push in the right direction from @bgolus I've come so far in just a week. If you are reading this I am incredibly grateful for the time you gave to respond to my posts.

    My journey into shaders started with the need of a way to make a decent enough sky dome for my game. I've managed to cut a spehere in half, flip the uv's ... ah damn. How will I display all those beautiful moving clouds, the sky, mountains etc? I went online and couldn't find a free shader to accomodate to my needs and the one's that did were all so costly. In this case, I did what every sane person would ... I started to write my own :)

    So what is my shader doing?

    It takes four textures (Foreground, Background, Clouds, Mask) and lerps them using the alpha value in order to be displayed on top of one other (thanks @bgolus and @Rahd ) The order in which you lerp is important as it defines which texture is on top of which.

    Code (CSharp):
    1. col = lerp(col, tex1, tex1.a);
    Before I do this, I do a few other things first.

    The cloud texture is moved along the x axis using _Time.x multiplied by the users preference to create cloud movement

    Code (CSharp):
    1. o.uv3.x += (_Time.x * Speed);
    If the "Add cloud layer" box is checked then the texcoord for uv3 is set to a new uv3Tile with a simple

    Code (CSharp):
    1. float2 uv3Tile : uv3;
    then the uv3Tile x and y are offset and animated as the original with the desired amount from the user. I thought this would give more depth to the clouds

    Code (CSharp):
    1. o.uv3Tile.x = o.uv3.x * _XAxis;
    2. o.uv3Tile.y = o.uv3.y * _YAxis;
    3. o.uv3Tile.x += (_Time.x * Speed2);
    If the "Mask" box is checked then a new set of texcoord is set to uv4. It is then moved on the x axis using the _Time.x and user preference

    Code (CSharp):
    1. o.uv4 = TRANSFORM_TEX (v.texcoord, _Mask);
    2. o.uv4.x += _Time.x * _MaskSpeed;
    I'm still not sure what TRANSFORM_TEX does properly, couldn't find any documentations on it but only looked for a few minutes. it works though

    If the "Mask Invert" Box is checked then instead of an increment, a decrement operation takes place to the alpha channel of the mask texture. The alpha is replaced with the values from the red. From a Black and white image, this will show the white and not the black and as red is set to 1 in white and 0 in black. (confusing myself now lol)

    Code (CSharp):
    1. #ifdef MaskInvert
    2.     tex4.a -= tex4.r;
    3. #else
    4.     tex4.a *= tex4.r;
    5. #endif
    Finally for the Atmosphere, I've taken three colours and lerped them together. however it took me the longest time to figure this out for some reason. so here is what I did in the end. I created a new pos2 using v.texcoord (still not too sure about texcoord and what it is but I assume it is the texture co ordinates)

    Code (CSharp):
    1. o.pos2 = v.texcoord;
    The I used lerp to blend color 1 to 3, and again blend 2 to 3. Then I simply lerped those two together based on the y of pos2 divied by the users preference for mixing the colours.

    Code (CSharp):
    1. fixed4 gradient = lerp(_Sky3, _Sky1, i.pos2.y / _Mix3);
    2. fixed4 gradient2 = lerp(_Sky2, _Sky1, i.pos2.y / _Mix);
    3. gradient = lerp(gradient, gradient2, i.pos2.y / _Mix2);
    I really hope that this helps someone else to understand what the hell is going on in shaders. I'm sorry I couldn't be more extensive. Please note that I am merely a beginner myself and am just putting what I have learned so far out there so that someone may improve on it or correct me where I've wrong.

    If you have made it this far the you deserve the full shader to use in your own projects!!

    As I found something like this so hard or costly to come across, I thought I would post this up here for anyone who needs it. However, I encourage you to learn rather than just copy and paste the code. Try to figure out how it works and maybe even come back to tell me how to do it better ;-)

    I would like to keep this updated and improve on it so I can re-use it on my games. When I update it, I will try and remember to upload the new version.

    Thank you for your time.
     

    Attached Files:

  2. Zaine7673

    Zaine7673

    Joined:
    Feb 15, 2018
    Posts:
    238


    This is a preview of the shader so far and how it looks.

    I'm not using great textures and also using the shader on a sphere rather than a dome (half a sphere) but still kind of shows what it does
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    It applies the tiling and offset settings for that texture. This line:
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

    Is identical to writing:
    o.uv = v.texcoord * _MainTex_ST.xy * _MainTex_ST.zw;

    Where
    _MainTex_ST.xy
    is the scaling, or "tiling" value, and the
    _MainTex_ST.zw
    is the offset.

    It's also faster on most modern hardware to just pass the single base texcoord unmodified and apply the
    TRANSFORM_TEX
    (if wanted) in the fragment shader. Older and slower mobile hardware doing it in the vertex shader is cheaper.

    Other notes:
    Code (csharp):
    1. float2 uv3Tile : uv3;
    That's not a valid semantic. Windows shader compilers are fairly forgiving and will probably look at that and go "eh, close enough, we'll replace that with a valid one for you", but most other platforms won't. That really should be a
    TEXCOORD#
    value. It's also a good idea to pack float2 values into a single float4 if you're going to continue that route as each semantic has a fixed cost and a
    float val : TEXCOORD0;
    costs the same as a
    float4 val : TEXCOORD0;
    in Direct3D.

    Code (csharp):
    1. o.uv3Tile.x += (_Time.x * _Speed2);
    Use a
    frac()
    call here.
    Code (csharp):
    1. o.uv3Tile.x += frac(_Time.x * _Speed2);
    Just adding
    _Time
    to a UV will eventually cause issues with floating point accuracy causing the texture to pixelate in weird ways as the
    _Time
    value gets big enough. Using
    frac()
    helps mitigate this, though eventually the scrolling won't be smooth as the
    _Time
    value itself won't have enough accuracy left to update every frame.
     
    Last edited: Dec 20, 2019
  4. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I'd been wondering about this recently - I assumed that Unity did some kind of intelligent wrapping on the Time values, but from what you say that's not the case?
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Nope. The value in
    _Time.y
    is exactly the same as the c# Time.timeSinceLevelLoad. _Time.x is that value / 20.

    Just adding
    _Time.y
    directly to a UV will mean in just over and hour and 8 minutes a 1024x1024 texture will start to look a little funny when zoomed in on it. After a little over 4 1/2 hours it’ll start to look point sampled. After 9 hours you’ll start to have less precision than there are pixels in the texture.

    If you multiply that time value so it pans faster, all the above issues only happen sooner. If you use
    frac()
    instead, you won’t have the same issues with the UV precision, though the panning itself has the same precision limitations, but that’s harder to notice. It’ll take over 12 days before you might start to notice the panning doesn’t update every frame when running at 60hz.
     
  6. Zaine7673

    Zaine7673

    Joined:
    Feb 15, 2018
    Posts:
    238

    Thanks buddy, I've made the changes. I understand the whole thing about using frac and not just time but had some questions about some of the other things you've mentioned.

    Considering what you said about TRANSFORM_TEX, I've moved it to the frag shader as i intend my game to run on a fairly modern PC. Not sure if how I've done it is correct but it works.

    Am I right in assuming there are up to 8 TEXCCORDs?

    I've noticed that there are some variables that need _ST at the end. what exactly are these?

    Apologies for all the questions but if I ask once, hopefully i wont have to ask again.
     

    Attached Files:

  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    By TEXCOORDs I assume you’re specifically referring to the TEXCOORD semantic used for passing data from a mesh to a vertex shader, or from a vertex shader to a fragment shader.

    A mesh can have up to 8 unique Vector4 UV sets / float4 TEXCOORDs, which means 16 unique float2 UVs if you store one UV in the xy and one UV in the zw (only possible with script generated meshes, not imported ones which only get the xy values per UV set). Or any other arbitrary data you want to store per vertex (like what Unity’s particle system does with the custom vertex streams).

    Between the vertex and fragment shader how many TEXCOORDs you can pass depends on the platform, with most modern devices allowing for up to 16 or 32 float4 TEXCOORDs. Again, this is for any arbitrary data and not just UVs.
    https://docs.unity3d.com/Manual/SL-ShaderSemantics.html

    If by TEXCOORD you’re just referring to texture UVs used by the tex2D calls, with modern GPUs you can have effectively an unlimited number of UVs, especially if they’re all calculated from the single base UV set. So apart from Sampler limits, you could have 1000 TRANSFORM_TEX lines in your shader. You probably don’t want to do that for performance reasons, but it is technically possible.


    Read the first paragraph of the post you quoted?