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:
void Update()
{
//update values for the material shader
_snowFallMat.SetFloat("_FlakeAmount", _flakeAmount);
_snowFallMat.SetFloat("_FlakeOpacity", _flakeOpacity);
//Cast to a Render Texture, the splat texture from the material
//Issue here??
RenderTexture _snow = (RenderTexture)_meshRenderer.material.GetTexture("_SplatTex");
//Next:
//1) apply splat map to temp render texture + snowfall/shader material
//2) apply result back to splatmap
//temporary render texture to apply result to
RenderTexture temp = RenderTexture.GetTemporary(_snow.width,_snow.height,0,RenderTextureFormat.ARGBFloat);
//Blit Creates a draw call, takes source texture and writes
//to desitation texture with specific shader material
Graphics.Blit(_snow, temp, _snowFallMat);
//Get resulting temp render texture back into snow rendertexture
Graphics.Blit(temp, _snow);
//apply back to primary splat map texture
_meshRenderer.material.SetTexture("_SplatTex", _snow);
RenderTexture.ReleaseTemporary(temp);
}
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.
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.
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
var splat = _meshRenderer.material.GetTexture("_SplatTex");
if (splat == null)
Debug.LogError("_SplatTex of the material is null. You should assign a RenderTexture to it", _meshRenderer);
else if (!(splat is RenderTexture))
Debug.LogError("_SplatTex need to be a RenderTexture. Currently you assigned a " + splat.GetType().Name, splat);
RenderTexture _snow = (RenderTexture)splat;
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.
1 Like
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:
- Create a render texture in your project (right click in assets/project Create > RenderTexture)
- 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!
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