Shader based rounded rectangle for ui

I created this small shader that renders rounded rectangles for ui. Some notable features are:

  • Set corner radius
  • Border, with thickness
  • Built in shadows, including soft shadows
  • Sprite support
  • Default, URP and HDRP support

It’s based on the buildin Image component so it also shares most of the features from that like masking, button tint, button sprite swap etc.

Watch the video below for a demonstration:

Download here: https://bitbucket.org/laitch/proceduraluishapes/

How to use:

  • Place the folder “ProceduralUIShapes” somewhere in you asset folder
  • Right click on the canvas in the Hierarchy and select UI -> RoundedRect
  • Make sure that the shader channels TexCoord1, -2 and -3″ are enabled on the canvas component under “Additional Shader Channels”

Experiments with async/await and Task to run multithreaded code

A couple of years ago when unity started supporting .NET 4.x we got access to newer C# features, including async/await. Today I decided to see if I could use this to easily run multithreaded code in unity.

First I would like to point out that asynchronous does not mean multithreaded. Just having an async method is more akin to having a coroutine method like we are used to in Unity. But in an async method we can start (and wait on) a task on a separate thread using something like this:

private void Start()
{
    Test();
}

private async void Test()
{
    string result = "";
    await Task.Run(() => {
        Thread.Sleep(1000);
        result = "This is some result";
    });
    Debug.Log(result);
}

With this in mind I created a static helper class where you can run any method on a separate thread and get the result sent back to the main thread via a callback action

using System;
using System.Threading.Tasks;

public static class RunOnSeparateThread
{
    public static async void Run<TResult>(Func<TResult> task, Action<TResult> onDone)
    {
        TResult result = default;
        await Task.Run(() => result = task());
        onDone?.Invoke(result);
    }

    public static async void Run<T1, TResult>(Func<T1, TResult> task, Action<TResult> onDone, T1 arg1)
    {
        TResult result = default;
        await Task.Run(() => result = task(arg1));
        onDone?.Invoke(result);
    }

    public static async void Run<T1, T2, TResult>(Func<T1, T2, TResult> task, Action<TResult> onDone, T1 arg1, T2 arg2)
    {
        TResult result = default;
        await Task.Run(() => result = task(arg1, arg2));
        onDone?.Invoke(result);
    }

    public static async void Run<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> task, Action<TResult> onDone, T1 arg1, T2 arg2, T3 arg3)
    {
        TResult result = default;
        await Task.Run(() => result = task(arg1, arg2, arg3));
        onDone?.Invoke(result);
    }
}

The class has methods for taking in a System.Func delegate, a callback method with the return value of the delegate, and then all the arguments for the delegate. It’s worth noting that System.Func can take up to 16 arguments, I only implemented overrides for up to 3 as I feel like that’s enough for proof of concept. I then created a little test script and attached to to a gameobject in the scene:

using System.Threading;
using UnityEngine;

public class Example : MonoBehaviour
{
    private void Start()
    {
        RunOnSeparateThread.Run(SlowTask, OnSlowTaskDone, 10, 1000);
    }

    private void Update()
    {
        //Illustrate that main thread is still running
        transform.Rotate(Vector3.up, Time.deltaTime * 10);
    }
    private void OnSlowTaskDone(string result)
    {
        LogWithThreadId(result);
    }

    private string SlowTask(int count, int wait)
    {
        for (int i = count; i >= 0; i--)
        {
            LogWithThreadId(i);
            Thread.Sleep(wait);
        }
        return "Result: 42";
    }

    private void LogWithThreadId(object content)
    {
        Debug.Log("ThreadId " + Thread.CurrentThread.ManagedThreadId + ":\t" + content);
    }
}

This script just starts a slow method that counts down and writes to the console, by the end the method returns a result that a callback method prints that result to the console. I also made the gameobject rotate in the Update method just to illustrate that the main thread was still running.

Everything seems to be working, but I want to point out that I have only tested this in the Editor on windows so no guarantee that this will work on all platforms

Procedurally generated space skybox

Hey all,

I made a space skybox shader using Amplify shader editor. I have exposed a bunch of the parameters to the inspector so it’s modifiable without going into the source. The sun on the skybox matches the direction and color of the directional light in the scene like the standard skybox in unity does.

The source is available for download, it’s originally made with Amplify shader editor but you don’t need it to use the shader.

Disclaimer: The shader is completely untested in any production environment.

Super simple Easing script

This is a script I wrote a long time ago, and I use it all the time. It’s a super simple bare-bones easing script containing the most used and most useful easing methods.

The methods are all static members of the class Ease and all follow the signature ‘EaseName(float start, float end, float t)’ just like Mathf.Lerp do.

using UnityEngine;

public static class Ease
{
    //Linear. Pretty much the same as Mathf.Lerp
    public static float Linear(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, t);
    }

    //Sinusodial ease functions
    public static float SinIn(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, 1f - Mathf.Cos(t * (Mathf.PI / 2f)));
    }

    public static float SinOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, Mathf.Sin(t * (Mathf.PI / 2f)));
    }

    public static float SinInOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, -0.5f * (Mathf.Cos(Mathf.PI * t) - 1));
    }

    //Quadratic ease functions
    public static float QuadIn(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, t * t);
    }

    public static float QuadOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, -1f * t * (t - 2));
    }

    public static float QuadInOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        t *= 2f;

        float calculatedT;

        if (t < 1f)
        {
            calculatedT = 0.5f * t * t;
        }
        else
        {
            t--;
            calculatedT = -0.5f * (t * (t - 2) - 1);
        }

        return ApplyEase(start, end, calculatedT);
    }

    //Exponential ease functions
    public static float ExpoIn(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, Mathf.Pow(2, 10 * (t - 1)));
    }

    public static float ExpoOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        return ApplyEase(start, end, -Mathf.Pow(2, -10 * t) + 1);
    }

    public static float ExpoInOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        t *= 2f;

        float calculatedT;

        if (t < 1)
        {
            calculatedT = 0.5f * Mathf.Pow(2, 10 * (t - 1));
        }
        else
        {
            t--;
            calculatedT = 0.5f * (-Mathf.Pow(2, -10 * t) + 2);
        }

        return ApplyEase(start, end, calculatedT);
    }

    //Elastic ease functions (over shoots and go back)
    public static float ElasticIn(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        const float p = 0.3f;
        return ApplyEase(start, end, -(1 * Mathf.Pow(2, 10 * (t -= 1f)) * Mathf.Sin((t - p / 4f) * (2 * Mathf.PI) / p)));
    }

    public static float ElasticOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        const float p = 0.3f;
        return ApplyEase(start, end, Mathf.Pow(2, -10 * t) * Mathf.Sin((t - p / 4f) * (2 * Mathf.PI) / p) + 1);
    }

    public static float ElasticOutIn(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);

        t *= 2f;
        const float p = 1 * (0.3f * 1.5f);
        if (t < 1f)
        {
            return ApplyEase(start, end, -0.5f * (Mathf.Pow(2, 10 * (t -= 1f)) * Mathf.Sin((t * 1 - p / 4f) * (2 * Mathf.PI) / p)));
        }
        else
        {
            return ApplyEase(start, end, Mathf.Pow(2, -10 * (t -= 1)) * Mathf.Sin((t * 1 - p / 4f) * (2 * Mathf.PI) / p) * 0.5f + 1);
        }
    }

    //Bounce ease functions (not the most elegant implementation, but I couldn't find any better)
    public static float BounceOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);

        float calculatedT;

        if (t < (1 / 2.75f))
        {
            calculatedT = 7.5625f * t * t;
        }
        else if (t < (2 / 2.75f))
        {
            calculatedT = 7.5625f * (t -= (1.5f / 2.75f)) * t + 0.75f;
        }
        else if (t < (2.5f / 2.75f))
        {
            calculatedT = 7.5625f * (t -= (2.25f / 2.75f)) * t + 0.9375f;
        }
        else
        {
            calculatedT = 7.5625f * (t -= (2.625f / 2.75f)) * t + 0.984375f;
        }

        return ApplyEase(start, end, calculatedT);
    }

    public static float BounceIn(float start, float end, float t)
    {
        return BounceOut(end, start, 1f - t);
    }

    public static float BounceInOut(float start, float end, float t)
    {
        t = Mathf.Clamp(t, 0f, 1f);
        t *= 2f;

        if (t < 1f)
        {
            return BounceIn(start, Linear(start, end, 0.5f), t);
        }

        t--;
        return BounceOut(Linear(start, end, 0.5f), end, t);
    }

    private static float ApplyEase(float start, float end, float t)
    {
        return start + (end - start) * t;
    }
}

It is intentionally left as bare-bones as possible so it’s not cluttered with stuff you don’t need. You can easily add more stuff on top of this script if you need to.

Tex2Pbr – Single texture to PBR material

A few months ago I started work on a plugin for Unity3D that could take in any texture and then (with some image processing magic) spit out various maps used for physical based rendering such as heightmaps and normalmaps. I never quite finished this project, maybe I will try again at some point down the line, but for now I’m done with this project.

But not all is lost! The tool is actually working so I have decided to release it as open source in it’s current state. Think of it as a technical preview, or early beta. Get it here on GitHub. And if you feel like it, you can view an introduction on how to use the plugin on YouTube.

Hello world!

Hello Internet, my name is Lars Hagen. I’m a professional videogame developer. In my spare time I like to experiment with various ideas and concepts related to game development, and my tool of choice is Unity3D. I created this blog to have a central place to show my stuff. Enjoy.