Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more..
    Dismiss Notice
  3. Dismiss Notice

Question Fading between Materials

Discussion in 'Scripting' started by jthiess, Jan 20, 2021.

  1. jthiess

    jthiess

    Joined:
    Nov 5, 2018
    Posts:
    31
    Hello,

    I am trying to make a controller that will fade between materials.

    This is my code so far:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. public class EnvironmentLibrary : MonoBehaviour
    6. {
    7.     public MaterialController controller;
    8.     public Material[] materials;
    9.     private int whichMaterial = 0; // can make public to debug
    10.     Renderer rend;
    11.  
    12.     void Start()
    13.     {
    14.         rend = GetComponent<Renderer>();
    15.         rend.enabled = true;
    16.         rend.sharedMaterial = materials[0];
    17.     }
    18.     private void Update()
    19.     {
    20.         if (Input.GetKeyDown(KeyCode.RightArrow))
    21.         {
    22.             whichMaterial++;
    23.             rend.sharedMaterial = materials[Mathf.Abs(whichMaterial % materials.Length)];
    24.         }
    25.         if (Input.GetKeyDown(KeyCode.LeftArrow))
    26.         {
    27.             whichMaterial--;
    28.             rend.sharedMaterial = materials[Mathf.Abs(whichMaterial % materials.Length)];
    29.         }
    30.     }
    31. }
    And I am trying to adapt the concept below (from here)
    Code (CSharp):
    1. // Fade Out
    2. float startValue = RenderSettings.skybox.GetFloat("_Exposure");
    3. yield return StartCoroutine(Interpolate(0.25f, startValue, 0.0f, UpdateExposureCallback));
    4.  
    5. // Set Texture
    6. RenderSettings.skybox.mainTexture = environment.m_Background;
    7.  
    8. // Fade In
    9. startValue = RenderSettings.skybox.GetFloat("_Exposure");
    10. yield return  StartCoroutine(Interpolate(0.25f, startValue, 1.0f, UpdateExposureCallback));
    My experience coding is very low. I am having trouble adapting the concept above to my code. How to change it, or where to put it!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,769
    I am not sure there's a general case for interpolating between materials. Materials each represent a particular shader with a set of inputs.

    The only general case I could see this working would be two Materials that support transparency, and then you would simply drive the alpha transparency down on one while you go up on the other.

    Also, there is only one skybox at a time, so I am thinking you'd need your own sphere, two of them with a slightly smaller one inside, and do the skybox all manually.

    Most materials are not transparent this way (for performance reasons), and you would never normally make a skybox transparent, but you might be able to transiently use another pair of transparent materials during the fade, then restore it.

    I've never actually tried this personally but those are the issues involved. The good news is that skybox materials are traditionally unlit, so you don't have to worry about lighting. I'm not sure what that "_Exposure" property does either. Is that supposed to be the alpha?
     
    lordofduct likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,769
  4. jthiess

    jthiess

    Joined:
    Nov 5, 2018
    Posts:
    31
    If I am understanding you correctly... Have 1 sphere with the 360 image on it, then have a slightly smaller sphere inside with black. Then adjust the alpha of the black sphere from 0% (see through) to 100% (black) change the larger photo sphere, then adjust the black sphere from 100% back down to 0%. (?)
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,769
    That would be the first thing I would try. I have no idea beyond that however. :)

    The two-fading texture shader I put in the second link sounds more promising however.
     
  6. jthiess

    jthiess

    Joined:
    Nov 5, 2018
    Posts:
    31
    This shader is well beyond my comprehension... It sounds like what I am looking for, but I have no idea how to adapt the script from the game skybox to my inside sphere shader.
     
  7. jthiess

    jthiess

    Joined:
    Nov 5, 2018
    Posts:
    31
    Okay, I am trying to build the 2nd black sphere within the larger one with coroutines. Here is my script so far, but it is not working as I get the error: "The modifier 'public' is not valid for this item" for lines 29, 43

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class AlphaFade : MonoBehaviour
    6. {
    7.     public bool fadeOut, fadeIn;
    8.     public float fadeSpeed;
    9.  
    10.     // Start is called before the first frame update
    11.     void Start()
    12.     {
    13.  
    14.     }
    15.  
    16.     // Update is called once per frame
    17.     void Update()
    18.     {
    19.         if(Input.GetKeyDown(KeyCode.UpArrow))
    20.         {
    21.             StartCoroutine(Invisible());
    22.         }
    23.  
    24.         if (Input.GetKeyDown(KeyCode.DownArrow))
    25.         {
    26.             StartCoroutine(Solid());
    27.         }
    28.  
    29.         public IEnumerator Invisible();
    30.         {
    31.             while (this.GetComponent<Renderer>.material.color.a > 0)
    32.             {
    33.                 Color objectColor = this.GetComponent<Renderer>().material.color;
    34.                 float fadeAmount = objectColor.a - (fadeSpeed * Time.deltaTime);
    35.  
    36.                 objectColor = new Color(objectColor.r, objectColor.g, objectColor.b, fadeAmount);
    37.                 this.GetComponent<Renderer>().material.color = objectColor;
    38.                 yield return null;
    39.             }
    40.            
    41.         }
    42.  
    43.         public IEnumerator Solid();
    44.         {
    45.             while (this.GetComponent<Renderer>().material.color.a < 1)
    46.             {
    47.                 Color objectColor = this.GetComponent<Renderer>().material.color;
    48.                 float fadeAmount = objectColor.a + (fadeSpeed * Time.deltaTime);
    49.  
    50.                 objectColor = new Color(objectColor.r, objectColor.g, objectColor.b, fadeAmount);
    51.                 this.GetComponent<Renderer>().material.color = objectColor;
    52.                 yield return null;
    53.             }
    54.         }
    55.     }
    56.  
    57.     public void Invisible()
    58.     {
    59.         invisible = true;
    60.     }
    61.  
    62.     public void Solid()
    63.     {
    64.         solid = true;
    65.     }
    66. }
    67.  
    And i would like to add a boolean to prevent the user from activating the function again until it is complete.

    Then, when the user initiates a scene change the black sphere fades in, the scene changer checks to see if it is solid - if it is it changes, then the black sphere fades back away.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,769
    Those functions are inside the Update() method. Either remove their access modifiers, or more likely, put them outside the Update() method.

    Set it at the beginning, clear it at the end of the coroutine.
     
  9. jthiess

    jthiess

    Joined:
    Nov 5, 2018
    Posts:
    31
    Okay, that worked well! The sphere starts invisible, then with an Up Arrow it turns solid. With another Up Arrow it goes to invisible. All the while changing the boolean state.

    The next step is, I would like the sphere to fade in with a click (this works) but then immediately fade back out. With the goal being, the boolean "Solid" state will trigger the scene change. I added a If Solid do Invisible.
    Only now the debugs for both Solid and Invisible are one every frame with my first activation.

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class AlphaTransition : MonoBehaviour
    7. {
    8.     public bool invisible, solid;
    9.     public float fadeSpeed;
    10.  
    11.     // Start is called before the first frame update
    12.     void Start()
    13.     {
    14.         this.GetComponent<MeshRenderer>().material.color = new Color(1.0f, 1.0f, 1.0f, 0.0f);
    15.         invisible = true;
    16.         Debug.Log("Sphere is Invisible");
    17.     }
    18.  
    19.     // Update is called once per frame
    20.     void Update()
    21.     {
    22.         if (Input.GetKeyDown(KeyCode.UpArrow) && invisible == true)
    23.         {
    24.             StartCoroutine(Solid());
    25.         }  
    26.  
    27.         if (solid == true)
    28.         {
    29.             StartCoroutine(Invisible());
    30.         }
    31.     }
    32.  
    33.     public IEnumerator Invisible()
    34.     {
    35.             while (this.GetComponent<Renderer>().material.color.a > 0)
    36.             {
    37.         Color objectColor = this.GetComponent<Renderer>().material.color;
    38.         float fadeAmount = objectColor.a - (fadeSpeed * Time.deltaTime);
    39.  
    40.         objectColor = new Color(objectColor.r, objectColor.g, objectColor.b, fadeAmount);
    41.         this.GetComponent<Renderer>().material.color = objectColor;
    42.         yield return null;
    43.             solid = false;
    44.             invisible = true;
    45.             Debug.Log("Sphere is Invisible");
    46.         }
    47.     }
    48.  
    49.     public IEnumerator Solid()
    50.         {
    51.             while (this.GetComponent<Renderer>().material.color.a < 1)
    52.             {
    53.         Color objectColor = this.GetComponent<Renderer>().material.color;
    54.         float fadeAmount = objectColor.a + (fadeSpeed * Time.deltaTime);
    55.  
    56.         objectColor = new Color(objectColor.r, objectColor.g, objectColor.b, fadeAmount);
    57.         this.GetComponent<Renderer>().material.color = objectColor;
    58.         yield return null;
    59.             solid = true;
    60.             invisible = false;
    61.             Debug.Log("Sphere is Solid");
    62.         }
    63.     }
    64. }
    65.  
    66.  
     
    Last edited: Jan 21, 2021
  10. jthiess

    jthiess

    Joined:
    Nov 5, 2018
    Posts:
    31
    I removed the invisible bool, leaving just solid.

    my goal is:
    1. object to start solid == false
    2. (Up Arrow && solid == false) triggers StartCoroutine(Solid)
    3. Once the coroutine is completed it solid == true
    4. solid == true then triggers StartCoroutine(Invisible)
    5. once that coroutine is complete solid == false
    6. wait for another input of (Up Arrow && solid == false)

    Is it possible for my bool to look at my object's alpha? example:
    if (alpha == 0) { solid == false }
    if (alpha == 1) { solid == true } then this true bool triggers the coroutine
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,769
    Indeed... I find it easier to just do away with the boolean... whenever I have a quantity that I want to gradually switch to, I use this pattern:

    Smoothing movement between discrete values:

    https://forum.unity.com/threads/beginner-need-help-with-smoothdamp.988959/#post-6430100

    The code: https://pastebin.com/ePnwWqnM

    That way your user input would just see whether you are fully one way or the other, then set the
    desired
    to the other value, and the above code would slowly move
    current
    to the other.
     
  12. jthiess

    jthiess

    Joined:
    Nov 5, 2018
    Posts:
    31
    Okay, I will take a look at that.
    For my own curiosity, is it possible to detect when alpha = 0 to change a my bool to solid using this script
    Code (CSharp):
    1.     public IEnumerator Solid()
    2.         {
    3.             while (this.GetComponent<Renderer>().material.color.a < 1)
    4.             {
    5.         Color objectColor = this.GetComponent<Renderer>().material.color;
    6.         float fadeAmount = objectColor.a + (fadeSpeed * Time.deltaTime);
    7.         objectColor = new Color(objectColor.r, objectColor.g, objectColor.b, fadeAmount);
    8.         this.GetComponent<Renderer>().material.color = objectColor;
    9.         yield return null;
    10.         }
    11. }
     
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,769
    Never test floating point numbers for equality. Instead test to see if they go past a value. This is best done by keeping the value from the previous frame and asking if the previous was below and the current is at or above (or vice versa).

    Also, don't use a color channel like that for counting. Keep your own float. The reason is there is no guarantee anywhere that if you put X into a field, then pull it back out, it might no longer be X. Underneath it all, colors are often mapped to 8-bit quantities per channel, then back to a float. This will almost never be the same float, so storing numbers "inside" a color like this is a bad idea.