I’ve been writing a view model shader which draws the model on top of everything while receiving lighting information. I tried doing this as a surface shader today but for some reason it only works in forward rendering. I know its possible to have it work for deferred as I’ve modified the base unity standard shader to support it by adding Ztest Always and ZWrite On to the deferred pass. I am not sure where I’ve messed up here, but I suspect this may be a bug , can anyone confirm?:
Shader "Custom/ViewmodelProjected"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
[NoScaleOffset]
_Metallic ("Metallic (RGB)", 2D) = "black" {}
[NoScaleOffset]
_Normal ("Normal (RGB)", 2D) = "bump" {}
}
SubShader
{
Pass
{
// Write Z buffer information after all other geometry, so that the Z buffer contains informations that will allow the Z ignoring objects to be written on top of the world
Tags{ "Queue" = "Geometry+1" }
ColorMask 0
ZTest Always
ZWrite On
}
Tags { "RenderType"="Opaque" "Queue" = "Geometry+2" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows vertex:vert
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
sampler2D _Metallic;
sampler2D _Normal;
float4x4 _Projection;
struct Input
{
float2 uv_MainTex;
};
void vert (inout appdata_full v) {
//unity_MatrixVP = _Projection;
}
fixed4 _Color;
// 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)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
fixed4 metal = tex2D (_Metallic, IN.uv_MainTex);
fixed3 normal = UnpackScaleNormal(tex2D(_Normal, IN.uv_MainTex), 1);
o.Albedo = c.rgb;
o.Normal = normal;
o.Metallic = metal.rgb;
o.Smoothness = metal.a;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Not a “bug” per se, but there are a few hidden limitations here to be aware of.
Limitation 1: You cannot have more than 1 queue in a shader. In fact any queue inside of a Pass { Tags {} } will be entirely ignored, and instead the first queue set in the SubShader { Tags {} } will be the one used by the material. Though this isn’t really a problem here as Unity will (usually) render the passes in the shader in the order they appear so there’s no need to assign unique queues for each pass.
Limitation 2: The deferred renderer will only use one deferred compatible pass in a shader and ignore the rest! If you have a multi-pass shader, like this one, those additional passes will not get used when rendering the object using the deferred rendering path. There are multiple reasons for this, but the basic reason is passes default to being for the forward rendering path, and you have to explicitly call out a pass as being for the deferred rendering path as there are additional requirements for a deferred compatible shader pass. Surface Shaders automatically generate this pass, so that pass is the one that’s being used. So that “write z buffer” pass is entirely ignored when rendering deferred. You could add "LightMode"="Deferred" to that pass’s Tags, but then the deferred pass generated by the Surface Shader won’t be used!
Limitation 3: Okay, so you can’t have multiple queues, or multiple deferred passes in a single shader. So the solution might seem like it would be to setup two separate materials. But that’s where the third limitation comes in to bite you. The deferred rendering path ignores the material queue for sorting! Unity just checks to see if the queue is less than 3000, and if the material’s shader has a deferred pass. Then it sorts everything front to back and ignores the queue! This means you can’t force this material to render after everything else. You can’t even guarantee the two separate materials will render in the correct order!
So what’s the solution? The easiest “solution” is don’t use the deferred rendering path. This can mean either don’t use it at all for rendering, or you can force your Surface Shader to not generate a deferred pass and thus force it to fall back to forward rendering even when deferred rendering is enabled. The later can be done by adding exclude_path:deferred to the #pragma surface line.
However there is one caveat to be aware of with a setup like this. Any directional shadows will still appear to be casting onto any geometry that was closer to the camera. This will be true regardless of if you’re rendering using deferred or forward rendering due to how Unity handles directional shadows on desktop and console. The only solution there is to not let Unity render this object as a renderer component and handle the rendering of it manually using command buffers. This would also let you make it work with deferred rendering as you would have absolute control over the order it renders in, and can explicitly have it render after everything else in two passes.
I really appreciate such a detailed response! This is disheartening but your solution of moving just this shader to be forward rendered would probably work best. My question now would be for a single mesh, if the rest of the game is optimized for deferred rendering, will the performance essentially be bottlenecked by the forward rendered shader? The original idea behind this was to write a viewmodel shader that didn’t rely on the awkwardness of the two camera set up and still received lighting info but I will admit, at this point, I am a bit lost.
Not remotely. That one object will be limited by the number of lights that can affect it, but that’s about it. All deferred rendered games are hybrid deferred / forward renderers because transparent objects or any object that doesn’t perfectly conform to the deferred shading model will be rendered using forward rendering. In engines that have support for skin shading for example, those are often still done via forward rendering.