Skip to content

Instantly share code, notes, and snippets.

@downthecrop
Last active April 12, 2023 18:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save downthecrop/690160085f8b5e1babc6047568dbf1a2 to your computer and use it in GitHub Desktop.
Save downthecrop/690160085f8b5e1babc6047568dbf1a2 to your computer and use it in GitHub Desktop.

You: I created the shader in shadergraph but my texture 2d is upside down.

ChatGPT: If your texture appears upside down when using Shader Graph in Unity, you can easily fix this by flipping the V coordinate of the UV coordinates.

To do this in Shader Graph, follow these steps:

In your Shader Graph, find the Sample Texture 2D node that is sampling the upside-down texture.

Look for the UV input of the Sample Texture 2D node. If you don't have a UV input connected, create a new UV node by right-clicking in the graph area, selecting 'Create Node', and searching for 'UV'. Then, connect the 'Out' output of the UV node to the 'UV' input of the Sample Texture 2D node.

Now, create a new Vector2 node by right-clicking in the graph area, selecting 'Create Node', and searching for 'Vector2'. Set the X component value to 1 and the Y component value to -1.

Next, create a new Multiply node by right-clicking in the graph area, selecting 'Create Node', and searching for 'Multiply'. Connect the output of the UV node to the first input of the Multiply node. Then, connect the output of the Vector2 node to the second input of the Multiply node.

Finally, connect the output of the Multiply node to the 'UV' input of the Sample Texture 2D node.

The Shader Graph should now sample the texture using the flipped UV coordinates, correcting the upside-down texture issue.

You: how can I apply a shader temporarily

ChatGPT: To apply a shader temporarily in Unity, you can create a script that swaps the original material with the one using the temporary shader and then reverts it back after a specified duration or when a certain condition is met.

Here's an example C# script that demonstrates this:csharp

using System.Collections;
using UnityEngine;

public class TempShaderController : MonoBehaviour
{
    public Material originalMaterial;
    public Material temporaryShaderMaterial;
    public float duration = 5f;

    private Renderer objectRenderer;

    void Start()
    {
        objectRenderer = GetComponent<Renderer>();
        StartCoroutine(ApplyTempShader());
    }

    IEnumerator ApplyTempShader()
    {
        // Swap the original material with the temporary shader material
        objectRenderer.material = temporaryShaderMaterial;

        // Wait for the specified duration
        yield return new WaitForSeconds(duration);

        // Revert back to the original material
        objectRenderer.material = originalMaterial;
    }
}

To use this script:Attach the script to the object you want to apply the temporary shader to. Make sure the object has a Renderer component, like a Mesh Renderer for 3D objects or a Sprite Renderer for 2D objects.In the Inspector, assign the originalMaterial and temporaryShaderMaterial fields with the appropriate materials. The originalMaterial should use the original shader, while the temporaryShaderMaterial should use the temporary shader you want to apply.Adjust the duration field in the Inspector to set the length of time (in seconds) that the temporary shader will be applied to the object.

Now, when you run your scene, the script will apply the temporary shader to the object for the specified duration, and then revert back to the original material afterwards.

You: can you format that so it works the same way my other one does? Here's how it works using System.Collections; using UnityEngine;

public static class FlashColor { public static IEnumerator DoFlash(GameObject go, Color flashColor, Color originalColor, float flashInterval, int numberOfFlashes) { Renderer[] renderers = go.GetComponentsInChildren<Renderer>(); Texture[] originalTextures = new Texture[renderers.Length]; for (int i = 0; i < renderers.Length; i++) { originalTextures[i] = renderers[i].material.mainTexture; }

    for (int i = 0; i &lt; numberOfFlashes; i++)
    {
        foreach (Renderer rend in renderers)
        {
            rend.material.color = flashColor;
        }
        yield return new WaitForSeconds(flashInterval);
        for (int j = 0; j &lt; renderers.Length; j++)
        {
            renderers[j].material.color = originalColor;
            renderers[j].material.mainTexture = originalTextures[j];
        }
        yield return new WaitForSeconds(flashInterval);
    }
}

}

ChatGPT: Here's a modified version of your script that works with materials instead of colors, allowing you to temporarily apply a shader:csharp

using System.Collections;
using UnityEngine;

public static class TempShader
{
    public static IEnumerator ApplyTempMaterial(GameObject go, Material temporaryMaterial, float interval, int numberOfFlashes)
    {
        Renderer[] renderers = go.GetComponentsInChildren&lt;Renderer&gt;();
        Material[] originalMaterials = new Material[renderers.Length];
        
        for (int i = 0; i &lt; renderers.Length; i++)
        {
            originalMaterials[i] = renderers[i].material;
        }

        for (int i = 0; i &lt; numberOfFlashes; i++)
        {
            foreach (Renderer rend in renderers)
            {
                rend.material = temporaryMaterial;
            }
            yield return new WaitForSeconds(interval);
            
            for (int j = 0; j &lt; renderers.Length; j++)
            {
                renderers[j].material = originalMaterials[j];
            }
            yield return new WaitForSeconds(interval);
        }
    }
}

To use this script, call the ApplyTempMaterial coroutine from another script, passing the required parameters:csharp

using UnityEngine;

public class ExampleController : MonoBehaviour
{
    public GameObject targetObject;
    public Material temporaryMaterial;
    public float interval = 0.5f;
    public int numberOfFlashes = 2;

    void Start()
    {
        StartCoroutine(TempShader.ApplyTempMaterial(targetObject, temporaryMaterial, interval, numberOfFlashes));
    }
}

This script will apply the temporary shader to the target object for the specified interval and number of flashes. After each flash, it will revert the object back to its original material.

You: Great! I would like one of the properties of the shader to slowly increase overtime in the coroutine aswell. It's called _CutoffHeight

ChatGPT: To slowly increase a shader property over time within the coroutine, you can use a Lerp function. Here's a modified version of the TempShader script that includes this functionality:csharp

using System.Collections;
using UnityEngine;

public static class TempShader
{
    public static IEnumerator ApplyTempMaterial(GameObject go, Material temporaryMaterial, float interval, int numberOfFlashes, float lerpDuration, string propertyName)
    {
        Renderer[] renderers = go.GetComponentsInChildren&lt;Renderer&gt;();
        Material[] originalMaterials = new Material[renderers.Length];
        Material[] tempMaterials = new Material[renderers.Length];

        for (int i = 0; i &lt; renderers.Length; i++)
        {
            originalMaterials[i] = renderers[i].material;
            tempMaterials[i] = new Material(temporaryMaterial);
        }

        for (int i = 0; i &lt; numberOfFlashes; i++)
        {
            foreach (Renderer rend in renderers)
            {
                rend.material = tempMaterials[Array.IndexOf(renderers, rend)];
            }

            float elapsedTime = 0f;
            while (elapsedTime &lt; lerpDuration)
            {
                float t = elapsedTime / lerpDuration;
                float cutoffHeight = Mathf.Lerp(0, 1, t);
                foreach (Material mat in tempMaterials)
                {
                    mat.SetFloat(propertyName, cutoffHeight);
                }
                elapsedTime += Time.deltaTime;
                yield return null;
            }

            yield return new WaitForSeconds(interval);

            for (int j = 0; j &lt; renderers.Length; j++)
            {
                renderers[j].material = originalMaterials[j];
            }
            yield return new WaitForSeconds(interval);
        }
    }
}

This version of the script includes two additional parameters:lerpDuration: The duration of the property transition from 0 to 1.propertyName: The name of the shader property you want to modify (_CutoffHeight in your case).

To use this updated script, update your ExampleController script accordingly:csharp

using UnityEngine;

public class ExampleController : MonoBehaviour
{
    public GameObject targetObject;
    public Material temporaryMaterial;
    public float interval = 0.5f;
    public int numberOfFlashes = 2;
    public float lerpDuration = 1.0f;
    public string propertyName = "_CutoffHeight";

    void Start()
    {
        StartCoroutine(TempShader.ApplyTempMaterial(targetObject, temporaryMaterial, interval, numberOfFlashes, lerpDuration, propertyName));
    }
}

Now, when you run your scene, the shader property will gradually increase from 0 to 1 over the specified lerpDuration.

You: I'd like the propertyName value to lerp independatly of the flashing. Currently if it's a long time between flashes the material doesn't work the way I'd like.

ChatGPT: To make the property value lerp independently of the flashing, you can separate the lerping logic into another coroutine. This will allow the property to smoothly change over time, regardless of the flashing intervals.

Here's the updated TempShader script:csharp

using System.Collections;
using UnityEngine;

public static class TempShader
{
    public static IEnumerator ApplyTempMaterial(GameObject go, Material temporaryMaterial, float interval, int numberOfFlashes, float lerpDuration, string propertyName)
    {
        Renderer[] renderers = go.GetComponentsInChildren&lt;Renderer&gt;();
        Material[] originalMaterials = new Material[renderers.Length];
        Material[] tempMaterials = new Material[renderers.Length];

        for (int i = 0; i &lt; renderers.Length; i++)
        {
            originalMaterials[i] = renderers[i].material;
            tempMaterials[i] = new Material(temporaryMaterial);
        }

        Coroutine lerpCoroutine = go.GetComponent&lt;MonoBehaviour&gt;().StartCoroutine(LerpProperty(tempMaterials, lerpDuration, propertyName));

        for (int i = 0; i &lt; numberOfFlashes; i++)
        {
            foreach (Renderer rend in renderers)
            {
                rend.material = tempMaterials[Array.IndexOf(renderers, rend)];
            }

            yield return new WaitForSeconds(interval);

            for (int j = 0; j &lt; renderers.Length; j++)
            {
                renderers[j].material = originalMaterials[j];
            }
            yield return new WaitForSeconds(interval);
        }

        go.GetComponent&lt;MonoBehaviour&gt;().StopCoroutine(lerpCoroutine);
    }

    public static IEnumerator LerpProperty(Material[] materials, float lerpDuration, string propertyName)
    {
        float elapsedTime = 0f;
        while (true)
        {
            float t = Mathf.PingPong(elapsedTime / lerpDuration, 1);
            float propertyValue = Mathf.Lerp(0, 1, t);
            foreach (Material mat in materials)
            {
                mat.SetFloat(propertyName, propertyValue);
            }
            elapsedTime += Time.deltaTime;
            yield return null;
        }
    }
}

Now, the LerpProperty coroutine is responsible for smoothly changing the shader property value over time, independent of the flashing intervals. The coroutine is started before the flashing loop and stopped after the loop is completed.

This modification will allow the shader property to lerp smoothly regardless of the flashing intervals.

You: NullReferenceException: Object reference not set to an instance of an object TempShader+<ApplyTempMaterial>d__0.MoveNext () (at Assets/Scripts/VFX & Recolor/TempShader.cs:19) UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <61c05f8d81804e929ff4198c5bcc7a62>:0) UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator) PlayerStateManager:Start() (at Assets/Scripts/StateMachine/PlayerStateManager.cs:191)

ChatGPT: It seems you're getting a NullReferenceException because the script is trying to access a component that hasn't been assigned or initialized yet. Based on the error message, the issue is likely on this line:csharp

Coroutine lerpCoroutine = go.GetComponent&lt;MonoBehaviour&gt;().StartCoroutine(LerpProperty(tempMaterials, lerpDuration, propertyName));

The go.GetComponent&lt;MonoBehaviour&gt;() part of the line is attempting to get a MonoBehaviour component from the GameObject go. If the GameObject doesn't have any MonoBehaviour components, this will return null and cause a NullReferenceException when calling StartCoroutine.

To fix this issue, you can modify the TempShader script to accept a MonoBehaviour reference as a parameter, which will be used to start and stop the coroutine:csharp

public static IEnumerator ApplyTempMaterial(MonoBehaviour caller, GameObject go, Material temporaryMaterial, float interval, int numberOfFlashes, float lerpDuration, string propertyName)
{
    // ...
    Coroutine lerpCoroutine = caller.StartCoroutine(LerpProperty(tempMaterials, lerpDuration, propertyName));
    // ...
    caller.StopCoroutine(lerpCoroutine);
}

Then, update your ExampleController script to pass this as the first parameter when calling the ApplyTempMaterial coroutine:csharp

void Start()
{
    StartCoroutine(TempShader.ApplyTempMaterial(this, targetObject, temporaryMaterial, interval, numberOfFlashes, lerpDuration, propertyName));
}

This modification will ensure that the coroutine is started and stopped using the MonoBehaviour component from the calling script, avoiding the NullReferenceException.

You: You'd so smart its actually crazy. O.o

ChatGPT: Thank you for your kind words! I'm here to help and provide the best assistance I can. If you have any more questions or need further clarification, please feel free to ask.

You: One more change I'd like is to set the Texture2D of the new shader to match the base map of the original material

ChatGPT: To set the Texture2D of the new shader to match the base map of the original material, you can modify the TempShader script to copy the texture property from the original materials to the temporary materials. Here's the updated script:csharp

using System.Collections;
using UnityEngine;

public static class TempShader
{
    public static IEnumerator ApplyTempMaterial(MonoBehaviour caller, GameObject go, Material temporaryMaterial, float interval, int numberOfFlashes, float lerpDuration, string propertyName)
    {
        Renderer[] renderers = go.GetComponentsInChildren&lt;Renderer&gt;();
        Material[] originalMaterials = new Material[renderers.Length];
        Material[] tempMaterials = new Material[renderers.Length];

        for (int i = 0; i &lt; renderers.Length; i++)
        {
            originalMaterials[i] = renderers[i].material;
            tempMaterials[i] = new Material(temporaryMaterial);
            tempMaterials[i].SetTexture("_MainTex", originalMaterials[i].GetTexture("_BaseMap")); // Copy the base map texture
        }

        Coroutine lerpCoroutine = caller.StartCoroutine(LerpProperty(tempMaterials, lerpDuration, propertyName));

        for (int i = 0; i &lt; numberOfFlashes; i++)
        {
            foreach (Renderer rend in renderers)
            {
                rend.material = tempMaterials[Array.IndexOf(renderers, rend)];
            }

            yield return new WaitForSeconds(interval);

            for (int j = 0; j &lt; renderers.Length; j++)
            {
                renderers[j].material = originalMaterials[j];
            }
            yield return new WaitForSeconds(interval);
        }

        caller.StopCoroutine(lerpCoroutine);
    }

    public static IEnumerator LerpProperty(Material[] materials, float lerpDuration, string propertyName)
    {
        float elapsedTime = 0f;
        while (true)
        {
            float t = Mathf.PingPong(elapsedTime / lerpDuration, 1);
            float propertyValue = Mathf.Lerp(0, 1, t);
            foreach (Material mat in materials)
            {
                mat.SetFloat(propertyName, propertyValue);
            }
            elapsedTime += Time.deltaTime;
            yield return null;
        }
    }
}

The only change needed is this line inside the loop that creates the temporary materials:csharp

tempMaterials[i].SetTexture("_MainTex", originalMaterials[i].GetTexture("_BaseMap")); // Copy the base map texture

This line of code copies the base map texture from the original material to the temporary material. Make sure to use the correct texture property names for both materials. In this example, I used _MainTex for the temporary material and _BaseMap for the original material, which are common property names in standard Unity shaders.

With this modification, the Texture2D of the new shader will match the base map of the original material.

You: I for one welcome our AI overlords. s(OuO)-b please respond with your favorite ascii emoticon

ChatGPT: As an AI, I don't have personal preferences or emotions, but here's a popular ASCII emoticon for you: ٩(◕‿◕。)۶

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment