Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Snow Shader/C# RenderTexture Casting Issues

Discussion in 'Scripting' started by Alex_ADEdge, Nov 6, 2020.

  1. Alex_ADEdge

    Alex_ADEdge

    Joined:
    Jul 28, 2013
    Posts:
    5
    Hi all,

    Following this great tutorial by peerplay. Im on the final part where you add a snowfall effect, see -


    Im a bit stumped with the use of RenderTextures and Graphics.Blit here, with the newer version of unity Im using (2019.4.13f1) I'm getting an error:



    It seems like casting a RenderTexture from a Texture2D doesnt work anymore?

    This section of code is getting the 'splatmap'/snow-track texture from the snow material, bringing it across to the snowfall shader, applying the result from the snowfall shader and then adding the resulting texture back to the snow material.
    Ive experimented a bit but havent been able to find a better way to 'GetTexture' into a RenderTexture, Ive even tried converting it to a Texture2D first and then using Graphics.Blit to apply it to a RenderTexture but still no luck. Ive looked at the resulting texture here and its always empty (when it should at least have the current snow tracks baked into it), before the 'snowfall' texture is applied (which doesnt seem to be happening either).

    So I guess my questions are:

    1) Should this cast of the 'GetTexture("_SplatTex")' to a RenderTexture work or is this no longer an option? (it works in the tutorial/older version of unity)

    2) Is there a better/alternate way of doing this I havent found yet?


    Section of the code which is causing issues:

    Code (CSharp):
    1. void Update()
    2.     {
    3.         //update values for the material shader
    4.         _snowFallMat.SetFloat("_FlakeAmount", _flakeAmount);
    5.         _snowFallMat.SetFloat("_FlakeOpacity", _flakeOpacity);
    6.  
    7.         //Cast to a Render Texture, the splat texture from the material
    8.         //Issue here??
    9.         RenderTexture _snow = (RenderTexture)_meshRenderer.material.GetTexture("_SplatTex");
    10.        
    11.         //Next:
    12.         //1) apply splat map to temp render texture + snowfall/shader material
    13.         //2) apply result back to splatmap
    14.  
    15.        //temporary render texture to apply result to
    16.        RenderTexture temp = RenderTexture.GetTemporary(_snow.width,_snow.height,0,RenderTextureFormat.ARGBFloat);
    17.  
    18.         //Blit Creates a draw call, takes source texture and writes
    19.         //to desitation texture with specific shader material
    20.         Graphics.Blit(_snow, temp, _snowFallMat);
    21.  
    22.         //Get resulting temp render texture back into snow rendertexture
    23.         Graphics.Blit(temp, _snow);
    24.  
    25.         //apply back to primary splat map texture
    26.         _meshRenderer.material.SetTexture("_SplatTex", _snow);
    27.  
    28.         RenderTexture.ReleaseTemporary(temp);
    29.        
    30.     }
     
  2. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    It's been a while for me, but the usual way to get a Texture into a RenderTexture is (was) using Graphics.Blit() - but you said that you tried that already. Since you said the result is always empty, an obvious reminder: before you Blit to the render texture, you must allocate and initialize the render texture (I'm sure you did, just to be sure). Then, if the result is still empty, also make sure that the texture2d you fetch isn't empty.
     
  3. Alex_ADEdge

    Alex_ADEdge

    Joined:
    Jul 28, 2013
    Posts:
    5
    Thanks csofranz. So I went over it in even more detail, checking texture at every step. I uncovered an issue that basically Graphics.Blit is only working if I pass it the following: Graphics.Blit(Texture, RenderTexture), passing in a source AND destination as a RenderTexture doesnt seem to work. So before passing a rendertexture in as the source value you just have to convert it to a texture.

    That got the snow tracks rendering again... however the snowfall shader now isnt applying via:

    Graphics.Blit(transferTexture, temp, _snowFallMat)


    transferTexture is the main track/splatmap texture, temp is a temporary rendertexture, _snowFallMat is the material/shader which slowly applies black areas to a texture (ie removing the splatmap tracks over time) - Ive tested this on its own material and its working, generating random black dots over time, so I know the shader itself works, just not the part where I apply it to the splat map (ie temp rendertexture)

    I'm a bit stumped again, if anyone knows why this might not be working it'd be great to know. Graphics.Blit is just behaving very oddly. Ill do some more testing tomorrow.
     
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,915
    I'm not sure you know what a cast is. A cast does not "convert" an object, it just changes the variable type. However such a cast is only possible when the actual object is compatible with the type you try to cast to. In other words a material can have references to Textures. Texture is the base class for all texture types. Usually when you import a texture asset from a file (jpg, png, ....) you get a Texture2D. A material can work with any kind of textures. Besides imported textures you can also create a RenderTexture asset in your project. That's what you are most likely missing. You have to manually assign a RenderTexture in the inspector of your material a RenderTexture asset to the splat map variable.

    In code when you use GetTexture you always get back a reference of type Texture. However the actual concrete type may be anything that is derived from Texture. Since you try to perform a downcast to a more specific type, the actual type has to be of that type or you get an invalid cast exception. See this SO question for an example.

    Since the code expects the "_SplatTex" to be a RenderTexture but the user (you) can assign any kind of texture this is of course a non handled exceptional case. So would be generally a good idea to insert some checking

    Code (CSharp):
    1. var splat = _meshRenderer.material.GetTexture("_SplatTex");
    2. if (splat == null)
    3.     Debug.LogError("_SplatTex of the material is null. You should assign a RenderTexture to it", _meshRenderer);
    4. else if (!(splat is RenderTexture))
    5.     Debug.LogError("_SplatTex need to be a RenderTexture. Currently you assigned a " + splat.GetType().Name, splat);
    6. RenderTexture _snow = (RenderTexture)splat;
    7.  
    Note that this of course doesn't change or remove the error when the texture is not a RenderTexture. However it gives you additional information that you assigned the wrong texture or that you forgot to assign it in the first place.
     
    Alex_ADEdge likes this.
  5. Alex_ADEdge

    Alex_ADEdge

    Joined:
    Jul 28, 2013
    Posts:
    5
    Ahh Bunny83, thanks a lot for going to the effort to explain that. I didnt even realize I was casting incorrectly in that manner. In fact what I did have as the 'SplatTex' was a placeholder texture for debugging, which should have either been replaced at runtime or just in general replaced with a RenderTexture created within the project (which is what I ended up doing)

    So yes, thats certainly why it wouldnt cast to a RenderTexture. And now everything works!


    So in summary, for anyone else having problems with part 5 of this tutorial, it does still work in newer versions of unity, just do the following:

    1) Create a render texture in your project (right click in assets/project Create > RenderTexture)
    2) In your 'SnowTracks' material (or whatever youve called it) in the inspector apply the RenderTexture to the 'Splat Map' texture

    Now your C# script will find a RenderTexture when it calls the following line:
    RenderTexture _snow = (RenderTexture)_meshRenderer.material.GetTexture("_SplatTex");


    Done!
     
  6. quniform

    quniform

    Joined:
    Nov 12, 2018
    Posts:
    4
    Hi All, (I also posted this here)
    Thanks for your posts about this. I've actually created the same script using Bolt, but having the same issues as others. The rendertexture only renders the current coordinates using the shader. The previous locations don't render – are they being overwritten or not being read? Placing the rendertexture asset into the mesh's material splatmap and the shader did not solve it. Also, I've tried the same setup with custom render texture, but have same issue.
    It seems like the issue is that it can't blit from the splatmap rendertexture to the temporary render texture. But I haven't found a workaround.

    Thanks for your help.
    Using Unity 2019.4.18f1
    Bolt 1.4.13
     

    Attached Files: