Trying to make shader which would smootly fade occluding geometry around my object (player, for example).
Here is the shader:
Shader "Custom/NewSurfaceShader"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Metallic ("Metallic", 2D) = "black"
_Normal ("Normal (RGB)", 2D) = "bump" {}
_Position("Position", Vector) = (.0, .0, .0)
}
SubShader
{
Tags { "Queue" = "Transparent" "RenderType"="Transparent" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows alpha
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
sampler2D _Metallic;
sampler2D _Normal;
uniform float3 _Position;
struct Input
{
float2 uv_MainTex;
float3 worldPos;
};
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Metallic = tex2D(_Metallic, IN.uv_MainTex);
o.Normal = UnpackScaleNormal(tex2D(_Normal, IN.uv_MainTex), 1);
float4 pixPos = mul(UNITY_MATRIX_V, float4(IN.worldPos, 1.0));
float4 objPos = mul(UNITY_MATRIX_V, float4(_Position, 1.0));
if (pixPos.z > objPos.z) o.Alpha *= saturate(distance(objPos.xy, pixPos.xy) * .5);
}
ENDCG
}
FallBack "Diffuse"
}
And it works as needed:
Well… almost. When object is close to camera z axis it looks ok, but if I move it somwhere (bottom righ, for example) it looks like this:
Looks like I just forget to adjust for perspective somewhere. But I can’t spot where. I tried UNITY_MATRIX_VP instead UNITY_MATRIX_V without success. It gives the same result but makes the transparent spot shape oval.
The UNITY_MATRIX_V matrix transforms a position from world to view space. View space is not viewport space, it is just an object’s position relative to the camera’s position and orientation, but still in world space scaling and with no knowledge of the camera’s perspective projection. In the above setup it looks like your camera is basically world space axis aligned, so applying UNITY_MATRIX_V isn’t going to produce a significantly different result than just using the x and y in world space.
What you want is the screen space (aka viewport) position with aspect ratio correction. With Surface Shaders you can get the screen space position of the current object by adding float4 screenPos; to the Input struct, then in the surf function use:
// apply perspective divide to get the 0.0 to 1.0 screen position
float2 pixScreenPos = IN.screenPos.xy / IN.screenPos.w;
// correct for aspect ratio
pixScreenPos.x *= _ScreenParams.y / _ScreenParams.x;
To get the screen space position of a world position, you need to do a little extra work, but not much.
// transform world position into clip space
float4 objClipPos = mul(UNITY_MATRIX_VP, float4(_Position.xyz, 1));
// convert clip space position into screen space
float4 objScreenPos = ComputeScreenPos(objClipPos);
// apply perspective divide
objScreenPos.xy /= objScreenPos.w;
// correct for aspect ratio
objScreenPos.x *= _ScreenParams.y / _ScreenParams.x;
After that, you’re free to use the same distance test you were doing before. One minor tweak though, you’ll want to use the pixScreenPos.w and objScreenPos.w to replace the pixPos.z and objPos.z you’re using the if statement. They’re actually the same values! (The clip space w is the view space z.)
I edited code as you said and now it looks like this:
Shader "Custom/NewSurfaceShader"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Metallic ("Metallic", 2D) = "black"
_Normal ("Normal (RGB)", 2D) = "bump" {}
_Position("Position", Vector) = (.0, .0, .0)
}
SubShader
{
Tags { "Queue" = "Transparent" "RenderType"="Transparent" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows alpha
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
sampler2D _Metallic;
sampler2D _Normal;
uniform float3 _Position;
struct Input
{
float2 uv_MainTex;
float4 screenPos;
};
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Metallic = tex2D(_Metallic, IN.uv_MainTex);
o.Normal = UnpackScaleNormal(tex2D(_Normal, IN.uv_MainTex), 1);
float2 pixScreenPos = IN.screenPos.xy / IN.screenPos.w; // apply perspective divide to get the 0.0 to 1.0 screen position
pixScreenPos.x *= _ScreenParams.y / _ScreenParams.x; // correct for aspect ratio
float4 objClipPos = mul(UNITY_MATRIX_VP, float4(_Position.xyz, 1)); // transform world position into clip space
float4 objScreenPos = ComputeScreenPos(objClipPos); // convert clip space position into screen space
objScreenPos.xy /= objScreenPos.w; // apply perspective divide
objScreenPos.x *= _ScreenParams.y / _ScreenParams.x; // correct for aspect ratio
if (IN.screenPos.w < objScreenPos.w) o.Alpha *= saturate(distance(objScreenPos.xy, pixScreenPos.xy) * 10);
}
ENDCG
}
FallBack "Diffuse"
}
It starts to fade when the object was in front and became opaque when the object behind, so I guessed I should check IN.screenPos.w < objScreenPos.w instead of “>” but don’t quite understand why so. Could you please explain. What is the w exactly?
Also console sometimes warnings me about “floating point division by zero at line 51 (on d3d11)” and here how it looks:
The hole is even more oval than when I used VP matrix. Shouldn’t it be round?
The w component of clip space (and the screen space position) is that position’s view space depth. The actual view space in UNITY_MATRIX_V is -z forward, so in your original code further away objects’s z value was lower than objects close. But in clip space, the view space z is inverted so it’s a positive value, so further objects are have a higher w value than close ones.
So, it’d be more accurate to say that clipSpace.w == -viewSpace.z.
But this is a bigger question. And technically the clip space w being the view space -z is only true for perspective cameras. If you want to understand why, it’s used to correct for screen space distortion when interpolating perspective values in screen space. If you divide by w in the vertex shader and interpolate the xy values alone you’ll get strange warping. Think of old PS1 games and the strange way those textures looked. That was caused by the PS1 not handling perspective interpolation correctly. You can try reading up on perspective correction and “perspective divide” elsewhere.
@bgolus , did I understand correct from my research and your answers to other guys on this forum that now it is impossible to make such “see through” effect with this plane receiving shadows without huge performance impact?
If it is possible at all (even with high performance cost) is there a way to add shadows to my surface shader? Or I should switch to LWRP/HDRP and do something there?
No. Unity does not support shadows on transparent objects when using the built in rendering paths.
There are work around a for supporting shadows from the main directional light that you can use, but all other lights are essentially impossible at any cost. See this project for an example of reading from the main directional light’s shadow maps directly: https://github.com/Gaxil/Unity-InteriorMapping
Otherwise, yes, you’ll need to use the URP or HDRP to get shadows on transparent objects, and for the HDRP you’ll have to disable shadow cascades entirely since there’s a now year old bug that the directional light shadows are broken on transparent objects when using cascaded shadow maps. You’ll have to rewrite any custom shaders using Shader Graph though, since the SRPs don’t support Surface Shaders.
Aaaarrgh… tried to switch to URP but can’t even just to reproduce the same shader in shader graph! I think the problem with world to screen space conversion but I’m not sure. How to use Unity helper functions from shader graph? There is no such node or anything. There is a function ComputeScreenPos in Packages/Shader Graph/ShaderGraphLibrary/Functions.hlsl but how to use it from graph? I made custom node but it doesn’t work with type:File because it has no “_float” suffix in the script, so i just copied its content in string mode of custom node:
Is it correct approach? Feels very wrong. Why I can’t just use built in Unity functions as I did from code before?
Here is my graph without Z distance check and aspect ratio adjustments (for simplicity) but it doesn’t work and I completely stuck to figure out why:
It renders just opaque plane but with shadow of occluded object for some reason:
Could anyone say what’s wrong with this freacking graph?
Yes(ish), and yes, and no idea. Some built in functions work fine, some don’t, and I’m not entirely sure why since those same functions work totally fine if you manually edit the generated hlsl code. I’m personally pretty unhappy with the Custom Function node in general and I can’t get it to work properly half the times I use it; it’s poorly documented and confusingly implemented, and doesn’t seem to get a lot of use internally so it remains in a semi-broken state when used with the in-line text version of the node rather than the separate .hlsl file option that they seem to want to make others use (and almost all of their internally made shader examples use).
One tweak is the Camera node’s Z Buffer Sign is not the property you want there. In fact the one you need is not exposed by any node! Luckily, that doesn’t matter and the real one can be accessed by a Custom Function node without any problem.
Here’s how I wrote my version of that node:
Also note the property names in the blackboard (what they call the area you define your properties in) are just the display names. Equivalent to the text in the quotes when you write shaders directly. The actual property name that you would set from script is set by the Reference text field you see when you expand a property.
Here’s a working version of the shader with some notes. The Custom Function node is the one above.
Yay, dude, thanks a ton again! Finally it works in shader graph as it were in surface shader. But I realized that our custom node could looks just like that:
And because it finally works as expected in old way similar to surface shader variant:
Thanks again for your help and nice suggestion for improvement with smoothstep. I’ll deffenetly add it later when finally understand math behind it and how it works.
Indeed you’re right! That’s funny because that’s not technically what that parameter is supposed to mean, but they do usually line up. It’s entirely possible they always line up.
Hello Serepa, I am currently trying to achieve that exact effect but my shader graph (that I just copied from your screenshot ) does not seem to be working. Could you maybe post your asset?
Thank you for the graph, I had some minor (stupid) erros in my graph (for example: I forgot to set the workflow mode to transparent). I also added a “feature” to your solution. With your graph the “wall” (or similar) would get this see-through effect every time the camera and target object (the player for example) line up, even if the wall is not in between camera and player.
I added some logic in the shader graph that uses the direction vector from the camera to the object as a normal for a plane and checks whether the camera and the player object are on the same side of the plane, if so, the alpha value is set to 1, if not (if the wall is between camera and player) your effect is used in the alpha channel.
For anyone interested, the Logic part can be seen here (I left the effect part out because it would be hard to see everything):
Edit 1: In the Vector3 which is constructed using the direction vector from the camera to the “wall” I set the y-Value to 0 to get a plane which is only rotated around the y-axis.