CG Shader problem: doesn't work on particle renderers or Line Renderers

I have a custom shader which basically just renders transparent textures with a tint, plus a special transparency effect specific to the needs of our game. It works great on everything, except it appears totally invisible and nothing is rendered when I want to use it on a particle renderer or on a Line Renderer. Problem is I’m not well-versed in CG at all and I’m not certain what the problem could be.

Edit: see thread below… seems to likely be Unity affecting the MVP data passed to the shader. More info needed!

Is there any obvious common problem anyone can think of that might be causing this?

Shader is below. Note: _EntityPos is sent to the shader by an external script. The shader just fades the alpha per-pixel based on the distance of the player.

Shader "Custom/ProximityTransparency" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Color ("Tint Color (RGB)", Color) = (1,0,0,1)
		
		_EntityPos ("Entity Position", Vector) = (0,0,0)
		_VisibleRange ("Fully Visible Range", Float) = 1
    	_TransDistance ("Transparent Distance", Float) = 37
	}
	
	SubShader {
		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="GlowMap" }
		Blend SrcAlpha OneMinusSrcAlpha
		ZWrite Off
		
		Pass {
			CGPROGRAM
// Upgrade NOTE: excluded shader from Xbox360; has structs without semantics (struct v2f members fpos)
#pragma exclude_renderers xbox360
				#pragma vertex vert
				#pragma fragment frag
				#pragma fragmentoption ARB_precision_hint_fastest

				#include "UnityCG.cginc"

				struct v2f {
					float4 pos : POSITION;
					float3 fpos;
					half2 uv : TEXCOORD0;
				};
		
				v2f vert (appdata_img v)
				{
					v2f o;
					o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
					float4 temp = mul (_Object2World, v.vertex);
					o.fpos = temp.xyz / temp.w;
					o.uv = MultiplyUV (UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
					return o;
				}
		
				sampler2D _MainTex;
				float4 _Color;
				float _VisibleRange;
				float _TransDistance;
				float3 _EntityPos;
		
				float4 frag( v2f i ) : COLOR
				{
					// grab the color from the texture and tint it by _Color
					float4 c = tex2D (_MainTex, i.uv) * _Color;
					// get the distance between the surface fragment and the entity position
					float dist = distance (i.fpos, _EntityPos);
					// lerp the alpha (negative and values over 1f will just be clamped to 0 and 1)
					dist -= _VisibleRange;
					dist /= (_TransDistance - _VisibleRange);
					dist /= 2; // This line and the next reduce the range to 0 to 0.5 alpha rather than fully visible
					c.a *= 0.5 - dist;
					
					// return the color
					return c;
				}
			ENDCG
		}
	} 
	FallBack "Fallback Invisible"
}

Hi,

Set up your shader in a quick test scene and everything appears to work fine, including particles and line renderers. However in the case of particles and line renderers the camera position is somehow coming into play and affecting the distance calculations!

So for example when i set up a sphere, particle system and line renderer all around the world origin, use vector(0,0,0) for entity pos, with visibleRange=2 and transparentDist = 10. I can see all elements with the camera at (0,0,-5). However pull the camera back to (0,0,-10) and the particle system and line renderers both fade out to nothing, but the sphere remains in its translucent state.

This is very odd as the camera should not be affecting the results at all, all three models should remain in their translucent state like the sphere does.

I’m sure i’m missing something obvious here or Unity must be doing something strange with particles and line renderers that are dependant upon the camera position. I tried multiplying the vertex position by the unity_scale, but that had no effect and I removed the matrix mul to _ObjectToWorld( since everything is more or less at the world origin), same results.

Perhaps someone else can explain why this is happening.

Wow!

Thanks so much for pointing that out, and hopefully someone here can figure out why this seems to be happening.

After some more investigation: it isn’t just effected by the game camera position, but even in Scene View by the editor camera. Move the scene view around and you can see it fading in and out!
Weirdly, there’s a small range where it is visible in any way here - get too close and it fades out again.

Yep, definitely something weird going on, just can’t quite put my finger on it, but I have a feeling I will be kicking myself when someone eventually points it out :wink:

I’m wondering if for particles and line renderer if Unity is affecting the vertex data in some way and then changing the MVP to match. In which case removing/inverse the VP part might get the data into world space? Overkill for standard objects, but might be needed for particles/line renderer. Though i guess easy to split into two different shaders based on requirements. Still feels strange though.

Ok think i’ve got a workaround for you.

The shader multiplies the input vertex by the modelView matrix (MV) and then multiplies the result of that by the inverse View matrix, which is provided as input to the shader every frame from a c# script on Update (might be a better function like OnPreRender() or something).

Pretty sure you could use the same material/shader for all objects, though the particle version does do more work and so might be overkill for normal models/meshes.

So it would appear that Unity does something strange to both particle and line renderer vertex data and addresses it by adjusting the MVP matrix supplied to the shader. Would love to know what and why, perhaps Aras might explain if he reads this?

The C# monobehaviour script to be applied to camera.

Make sure you assign the correct material to it (I forgot and wondered why it wasn’t working :wink: ) and currently the whole thing only works when Unity is playing. You could fix that by adding [PlayInEditorMode] or whatever that directive is called. Obviously it is also only correct for whatever camera you use to pass the inverse view matrix from. Maybe there is a better way of doing this?

using UnityEngine;

public class ProximitryTransparencyParticles : MonoBehaviour 
{
	public Material		ProxTranMat;
	
	void Update () 
	{
		ProxTranMat.SetMatrix("_InverseViewMatrix", Camera.mainCamera.worldToCameraMatrix.inverse );
	}		
}

The Shader

Shader "Custom/ProximitryTransparency4Particles"
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Color ("Tint Color (RGB)", Color) = (1,0,0,1)
           
		_EntityPos ("Entity Position", Vector) = (0,0,0)
		_VisibleRange ("Fully Visible Range", Float) = 1
		_TransDistance ("Transparent Distance", Float) = 37
	}
       
	SubShader 
	{
		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="GlowMap" }
		Blend SrcAlpha OneMinusSrcAlpha
		ZWrite Off
           
		Pass 
		{
			CGPROGRAM  
				#pragma vertex vert
				#pragma fragment frag
				#pragma fragmentoption ARB_precision_hint_fastest
     
				#include "UnityCG.cginc"
                    
				float4x4  _InverseViewMatrix;
                    
     
				struct v2f 
				{
					float4 pos  : POSITION;                       
					half2 uv    : TEXCOORD0;
					float3 fpos : TEXCOORD1;
				};
           
				v2f vert (appdata_img v)
				{
					v2f o;
					o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
	                
	                // Particles and lineRenderers would appear to have weird MVP.
	                // So here we multiply the vertex position by MV, then by the inverse view (supplied as input to the material)
	                // To obtain the correct world space position of the vertex
					float4 temp = mul (UNITY_MATRIX_MV, v.vertex );
					temp = mul (temp, _InverseViewMatrix);
	                o.fpos = temp.xyz / temp.w;
	                
	                o.uv = MultiplyUV (UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
					return o;
				}
           
				sampler2D 	_MainTex;
				float4 		_Color;
				float 		_VisibleRange;
				float 		_TransDistance;
				float3 		_EntityPos;
           
				float4 frag( v2f i ) : COLOR
				{
					// grab the color from the texture and tint it by _Color
					float4 c = tex2D (_MainTex, i.uv) * _Color;
	                                               
					// get the distance between the surface fragment and the entity position
					float dist = distance (i.fpos, _EntityPos);
					// lerp the alpha (negative and values over 1f will just be clamped to 0 and 1)
					dist -= _VisibleRange;
					dist /= (_TransDistance - _VisibleRange);
					dist /= 2; // This line and the next reduce the range to 0 to 0.5 alpha rather than fully visible
					c.a *= 0.5 - dist;
	                       
					// return the color
					return c;
				}
			ENDCG
		}
	}
	
	FallBack "Fallback Invisible"
}

Hey, thanks so much for doing this. Really appreciate it. I’ll give it a shot.

If Unity is really doing this, I guess it probably has to do with the fact that both the particle quads and line renderer are normally billboarded towards the viewport at all times. Whatever way they are doing that billboard rotation must be affecting the vertex data like you say.

It would be very nice if there were a more elegant solution around this, though.

Did you get the workaround to work? I gave it a shot and still ended up with the same problem whenever the object is in a position other than 0,0,0.

As far as I can tell, the new shader is correctly receiving the matrix from the script, but only the last number of the matrix ever changes and all the rest are being sent as 0,0,0. I’m using an orthographic camera.

The problem still seems to be that the material doesn’t know where it is in the world. It seems to always believe it is at 0,0,0 in worldspace - which means it can’t calculate relative distance to _EntityPos properly.

Yep, seems to work for me regardless of objects position and entityPos value in the shader or camera projection type.

I’ve just changed my test scene, so that the particles and line render are no longer at the world origin and the EntityPos is set to a position roughly in the middle of all the objects. I can run the scene and move the camera around and all objects maintain their translucency, regardless of where the camera is or its projection type.

You did remember to assign the correct material to the c# script monobehaviour?
Also if you have multiple materials, then you’ll have to send the inverse view to all of them, also be careful if you create clone instances of any of the materials.

Have a look at the package (from Unity 3.5.7) and see if that works and gives you any clues as to why the shader doesn’t appear to be working for you.

Note: I’m using two materials, one for the spheres (your original shader) and one for the particles and line renderer, so you have to remember to update entityPos and distance values in both, but the InverseView is only set on the particle/line material. You could also just use the proxTran4Particles shader for all materials if you wanted, it should work, just a bit inefficient.

1128549–42694–$ProxTransparencyPackage_3.5.7.unitypackage (187 KB)
1128549–42697–$ProxTransparencyPackage_3.5.7.zip (187 KB)

Attachment link seems to be broken for the moment… I’m blaming that one on the forums.

I’ve tried it a couple ways, first just in a simple test with your script on the main camera referencing a single material with your modified shader.

Ultimately I need to reference multiple cloned instance materials, so I’ve also tried putting this in the Update of the object itself:

Added a zipped version see if that works.

Hmm, that sounds like a bad idea ( fetching and calculating the inverse view for the camera on each object), but ok for testing purposes.
One thought is you game camera the main camera? If not then you’d want to add a reference to your actual main camera and use that instead of Camera.mainCamera. PLus as I mentioned, it wont work unless the game is running and i’m not entirely sure of the results in the scene view regardless.

Edit:
Opps, ok you are right. I added a gameobject and some code to represent the entityPos and it appears that particles and line have the inverse affect when using it. I.e. When I place the entity centered on the sphere I have placed at the particle spawn position, the sphere is solid, but the particles disappear, as I move the entityPos away from this point, the particles become solid and the sphere goes transparent. Weird.

Might just be a question of tweaking the shader some more, not sure.

Edit 2:
Could it be this easy?

temp = mul (_InverseViewMatrix, temp );

New package with above fix and an ‘EntityPos’ gameObject that can be moved around the scene when its running for testing/debugging.

1128570–42699–$ProxTransparencyPackage_3.5.7_mk2.zip (203 KB)

I still can’t download attachments for some reason… though I’m at work right now and there’s a chance the network is just blocking it somehow.
Anyway, let me try to upload my “simplest possible” test scene (sounds about the same as yours), and maybe if you can get a chance to tell me what I’m doing wrong :slight_smile:

The particles being faded are over at x=60, but they only fade in when the Entity is close to x=0.
Thanks again for all this.

1128583–42698–$testProx.unitypackage (945 KB)

Nope, unitypackage extension attachments aren’t working for me either, but the zip does.
Argghh - nope now the second zip doesn’t work for me, but the first one does. Guess the forum server is having a mild breakdown :wink:

Check out my edits to my last post and if that doesn’t fix the issue, upload your attachment again as a zip pls.

Yes, I think it could be that easy :slight_smile: Seems to work!