0
\$\begingroup\$

I'm working on a small psychedelic-style game that needs to be done in Unity, with which I have no prior experience.

I'd like to achieve an effect where the material of my objects can change colors over time. The end result would look similar to what is happening in this video:

Hue shifting example

How can I achieve something like this?

\$\endgroup\$
1
  • \$\begingroup\$ What render pipeline are you using? \$\endgroup\$
    – Kevin
    Commented Sep 14, 2022 at 2:23

2 Answers 2

3
\$\begingroup\$

Here's how to write a shader that applies this effect.

I'm assuming you want lighting, so I started this shader by right-clicking in the Project view and selecting Create -> Shader -> Standard Surface Shader. If you don't need lighting, use the "Unlit" shader template instead.

Our strategy will be:

  1. Read the colour of the texture on the material as usual.

  2. Convert that colour to HSV, or Hue-Saturation-Value colour space.

  3. Add an offset to the hue (.x) part of our HSV triple, proportionate to the amount of time that has passed - we can get that from Unity's built-in shader uniform _Time

  4. Convert back to RGB colour space, and use that shifted colour in place of the original.

I'm using the colour space conversion formulas shared by Chilliant / Ian Taylor.

Shader "Custom/PsychedelicHue"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        // Use this to control the speed of the animation.
        _Speed ("Hue Rotation Speed", float) = 2.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        // Expose the speed parameter to use in our code.
        half _Speed;

        // Colour conversion functions.
        float3 hsv_to_rgb(float h, float s, float v) {
            h = 6.0f * frac(h);

            float3 hue = saturate(float3(
                abs(h-3.0f) - 1.0f,
                2.0f - abs(h - 2.0f),
                2.0f - abs(h - 4.0f)
            ));

            return ((hue - 1.0f) * s + 1.0f) * v;
        }

        // These functions are from https://chilliant.com/rgb2hsv.html
        const float EPSILON = 1e-10;
 
        float3 rgb_to_hcv(in float3 rgb) {
            // Based on work by Sam Hocevar and Emil Persson
            float4 p = (rgb.g < rgb.b) ? float4(rgb.bg, -1.0, 2.0/3.0) : float4(rgb.gb, 0.0, -1.0/3.0);
            float4 q = (rgb.r < p.x) ? float4(p.xyw, rgb.r) : float4(rgb.r, p.yzx);
            float c = q.x - min(q.w, q.y);
            float h = abs((q.w - q.y) / (6 * c + EPSILON) + q.z);
            return float3(h, c, q.x);
        }

        float3 rgb_to_hsv(float3 rgb) {
            float3 hcv = rgb_to_hcv(rgb);
            float s = hcv.y / (hcv.z + EPSILON);
            return float3(hcv.x, s, hcv.z);
        }

        // Original surface function, with modifications...
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Read the colour from the texture, and apply material tint.
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;

            // Convert colour to HSV colour space.
            float3 hsv = rgb_to_hsv(c.rgb);      
           
            // Shift the hue proportional to time, and convert back.
            o.Albedo = hsv_to_rgb(hsv.x + _Time.x * _Speed, hsv.y, hsv.z);

            // The rest we leave as-is in the default shader.
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
\$\endgroup\$
1
\$\begingroup\$

You would first want to get the material attached to the gameObject.

// If you have a reference to the GameObject called "myGameObject":
Renderer renderer = myGameObject.GetComponent<Renderer>();
// If the object you want is the one this script is attached to:
Renderer renderer = GetComponent<Renderer>();
 
// This creates a special material just for this one object. If you change this material it will only affect this object:
Material mat = renderer.material;
 
// This gets the shared material used by this renderer. If there are other renderers that use the same material, changing this material will also affect them.
Material mat = renderer.sharedMaterial;

Note: Use .sharedMaterial if you want to change the material's color on all game objects with this material. Note 2: If you have multiple scripts all accessing the same .sharedMaterial, what each one does will stack (meaning if you increase hue, it will increase faster).

Then, later in the script you can increase hue, by doing:

public foat speed;

float h, s, v;
void Update()
{
    Color color = mat.color;

    Color.RGBToHSV(color, out h, out s, out v);
    h += Time.deltaTime * speed;
    h = h % 1f; //Max value it can be. For non-hdr, it is 1
//I don't know what it is for hdr, maybe try 255f
    mat.color = Color.HSVToRGb(h, s, v, false);
//set the bool to if it is hdr

In the line that goes h = h % 1f, set 1f to 1f it is not hdr. (I do not know what hdr max value is for HSV values, as the scripting api was not very clear) if i had to guess though, it would be 1f or 255f.

In the final line of code, set the bool to if it should be hdr.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ This example uses a C# script to set the tint color of a material. It's also possible to create a custom shader which changes the hue entirely on the GPU. \$\endgroup\$
    – Kevin
    Commented Sep 14, 2022 at 2:06
  • \$\begingroup\$ @Kevin I would like to know how you do that. Please post an answer :) \$\endgroup\$
    – gbe
    Commented Sep 14, 2022 at 2:20
  • \$\begingroup\$ @Walter DMGregory posted an answer which demonstrates how this can be done in a much more performant way by using a shader. \$\endgroup\$
    – Philipp
    Commented Sep 14, 2022 at 15:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .