Hello,
In our game, we put lights on our enemy and friendly shots so that they highlight the environments and the other players. A very nice effect that obviously has the drawback of adding dozens of small lights to the scene. Using the deferred shader, that shouldn’t be a problem right? It’s the whole point of the thing after all.
As it turns out, Unity doesn’t use GPU instancing to render point lights in the deferred renderer. This is problematic on the Switch as those dozens of draw calls have a very high performance impact on the render thread. To solve the issue, I wrote a custom light renderer that uses instancing so that there is just one draw call for all the lights.
This works great, except for one thing: it lights all the pixels instead of just the necessary pixels as the original light shader of unity does. This has obviously a huge performance impact. My problem is that I have no idea which values I should use for the Stencil test. I tried the ones I found in the regular deferred shader:
Stencil
{
Ref [_StencilNonBackground]
ReadMask [_StencilNonBackground]
CompBack Equal
CompFront Equal
}
They seem to work in the editor but not on the Switch. Any idea how to get the right stencil values ?
Editor: (stencil ref is 128, looks correct)
Switch: (stencil ref is 0, probably wrong, takes to 2ms to render so very probably wrong)
Shader:
Shader "CustomLights/InstancedLight"
{
SubShader
{
Tags { "Queue" = "Transparent-1" }
Pass
{
Fog { Mode Off }
ZWrite Off
ZTest LEqual
Blend One One
Cull Back
Stencil
{
Ref [_StencilNonBackground]
ReadMask [_StencilNonBackground]
CompBack Equal
CompFront Equal
}
CGPROGRAM
#pragma target 3.0
#pragma vertex vert_deferred_instanced
#pragma fragment frag
#pragma multi_compile_lightpass
#pragma multi_compile_instancing UNITY_HDR_ON
#pragma instancing_options nolodfade nolightprobe nolightmap
#pragma exclude_renderers nomrt
#include "UnityCG.cginc"
#include "UnityDeferredLibrary.cginc"
#include "UnityPBSLighting.cginc"
#include "UnityStandardUtils.cginc"
#include "UnityGBuffer.cginc"
#include "UnityStandardBRDF.cginc"
sampler2D _CameraGBufferTexture0;
sampler2D _CameraGBufferTexture1;
sampler2D _CameraGBufferTexture2;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
struct appdata
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct unity_v2f_deferred_instanced {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 ray : TEXCOORD1;
half4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
unity_v2f_deferred_instanced vert_deferred_instanced(appdata v)
{
unity_v2f_deferred_instanced o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = ComputeScreenPos(o.pos);
o.ray = UnityObjectToViewPos(v.vertex) * float3(-1, -1, 1);
o.color = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
return o;
}
// Common lighting data calculation (direction, attenuation, ...)
void DeferredCalculateLightParams(
unity_v2f_deferred_instanced i,
out float3 outWorldPos,
out float2 outUV,
out half3 outLightDir,
out float outAtten,
out float outFadeDist)
{
i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
float2 uv = i.uv.xy / i.uv.w;
// read depth and reconstruct world position
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv);
depth = Linear01Depth(depth);
float4 vpos = float4(i.ray * depth, 1);
float3 wpos = mul(unity_CameraToWorld, vpos).xyz;
float fadeDist = UnityComputeShadowFadeDistance(wpos, vpos.z);
float3 lightPos = float3(unity_ObjectToWorld[0][3], unity_ObjectToWorld[1][3], unity_ObjectToWorld[2][3]);
float3 tolight = wpos - lightPos;
half3 lightDir = -normalize(tolight);
float att = dot(tolight, tolight) * i.color.w; // color.w is inversed squared range. ie 1/(r*r)
float atten = tex2D(_LightTextureB0, att.rr).r;
atten *= UnityDeferredComputeShadow(tolight, fadeDist, uv);
outWorldPos = wpos;
outUV = uv;
outLightDir = lightDir;
outAtten = atten;
outFadeDist = fadeDist;
}
half4 frag(unity_v2f_deferred_instanced i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
float3 wpos;
float2 uv;
float atten, fadeDist;
UnityLight light;
UNITY_INITIALIZE_OUTPUT(UnityLight, light);
DeferredCalculateLightParams(i, wpos, uv, light.dir, atten, fadeDist);
light.color = i.color.rgb * atten;
// unpack Gbuffer
half4 gbuffer0 = tex2D(_CameraGBufferTexture0, uv);
half4 gbuffer1 = tex2D(_CameraGBufferTexture1, uv);
half4 gbuffer2 = tex2D(_CameraGBufferTexture2, uv);
UnityStandardData data = UnityStandardDataFromGbuffer(gbuffer0, gbuffer1, gbuffer2);
float3 eyeVec = normalize(wpos - _WorldSpaceCameraPos);
half oneMinusReflectivity = 1 - SpecularStrength(data.specularColor.rgb);
UnityIndirect ind;
UNITY_INITIALIZE_OUTPUT(UnityIndirect, ind);
ind.diffuse = 0;
ind.specular = 0;
return UNITY_BRDF_PBS(data.diffuseColor, data.specularColor, oneMinusReflectivity,
data.smoothness, data.normalWorld, -eyeVec, light, ind);
}
ENDCG
}
}
}


