First issue:
Tags
{
"Queue" = "Geometry" "IgnoreProjector" = "True"
"RenderType" = "Opaque"
"DisableBatching" = "True"
"LightMode" = "ForwardBase"
}
You should not be setting the light mode on the SubShader, only within the passes. This is causing both the shadow caster pass and the forward base pass to get rendered during the final opaque forward rendering pass!
Second issue to be aware of and stupidly I didn’t notice earlier:
Blend SrcAlpha OneMinusSrcAlpha
Unity doesn’t support shadow receiving on transparent objects. That’s not actually the issue here though as it actually doesn’t look at the blend mode, only the queue. Anything over queue 2500 won’t be able to receive shadows. You are however rendering during the geometry queue which means things should be working, but you also might not get the results you think you are. If you want billboards with shadows you’ll want to be using alpha test and not alpha blend.
Next:
#pragma multi_compile_fwdadd_fullshadows
This is the forward base pass, that’s not a valid multi_compile for this pass and is going to cause a lot of problems. The one you have commented out is the correct one, though you probably want to use this:
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
Next up:
LIGHTING_COORDS(1, 2)
UNITY_FOG_COORDS(1)
These macros are for defining which TEXCOORD# to store data in. You’re telling it to use TEXCOORD1 and TEXCOORD2 for lighting coords, then telling it to stomp on that data with the fog. LIGHTING_COORDS is also a Unity 4.0 macro and deprecated, you should be using SHADOW_COORDS(#) as per the example page I posted, along with adding a TRANSFER_SHADOW for each vertex output from the geometry shader and SHADOW_ATTENUATION not LIGHT_ATTENUATION.
That TRANSFER_SHADER is going to be a tricky one as some variants of the define expects an object space vertex position in v.vertex, but there is no “v”, and the vertex positions are already in world space. You could create a dummy v struct with the object space vertex position in it with v.vertex = mul(_World2Object, v[0]); or you could make your own override again. Screen space main directional light shadows just needs a valid .pos though, which you already have, so this is actually fine to use unmodified for the ForwardBase pass, it’s the ForwardAdd pass that’ll bite you, or systems that don’t support screen space shadows.
Now if you fix all of the above you should start seeing shadows on your billboards! They’ll be the shadows of the objects behind them though since we haven’t gotten to the shadow caster pass itself.
First off the pos in the v2f struct you have is set to TEXCOORD0, this means there is no SV_POSITION for the rasterizer to use to render, ie: no geometry will be rendered! The whole “NOPOS” thing is because they normally output the SV_POSITION as a out var on the vertex function so that the fragment shader can use VPOS, but that doesn’t work for geometry shaders so you need to have pos be SV_POSITION.
After that you got really, really close with your TRANSFER_SHADOW_CASTER_NOPOS2, but the vertex you’re passing in is a float3, and that needs to be a float4!
So, with all of that fixed plus a few other tweaks we get:
Shader "Test/TestShader"
{
Properties
{
_Sprite("Sprite", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
_Size("Size", Vector) = (1,1,0,0)
}
SubShader
{
Tags
{
"Queue" = "AlphaTest" "IgnoreProjector" = "True"
"DisableBatching" = "True"
}
LOD 100
Cull off
ZWrite On
ZTest LEqual
Pass
{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma target 2.0
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma multi_compile_fog
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
#include "AutoLight.cginc"
#include "UnityCG.cginc"
sampler2D _Sprite;
float4 _Color;
float2 _Size;
float3 _worldPos;
struct data
{
float3 pos;
};
//The buffer containing the points we want to draw.
StructuredBuffer<data> buf_Points;
struct input
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
SHADOW_COORDS(1)
UNITY_FOG_COORDS(2)
};
input vert(uint id : SV_VertexID)
{
input o;
UNITY_INITIALIZE_OUTPUT(input, o);
o.pos = float4(buf_Points[id].pos + _worldPos, 1.0f);
return o;
}
float4 RotPoint(float4 p, float3 offset, float3 sideVector, float3 upVector)
{
float3 finalPos = p.xyz;
finalPos += offset.x * sideVector;
finalPos += offset.y * upVector;
return float4(finalPos,1);
}
[maxvertexcount(4)]
void geom(point input p[1], inout TriangleStream<input> triStream)
{
float2 halfS = _Size;
float4 v[4];
v[0] = p[0].pos.xyzw + float4(-halfS.x, -halfS.y, 0, 0);
v[1] = p[0].pos.xyzw + float4(-halfS.x, halfS.y, 0, 0);
v[2] = p[0].pos.xyzw + float4(halfS.x, -halfS.y, 0, 0);
v[3] = p[0].pos.xyzw + float4(halfS.x, halfS.y, 0, 0);
float uid = normalize(p[0].pos);
input pIn;
UNITY_INITIALIZE_OUTPUT(input, pIn);
pIn.pos = mul(UNITY_MATRIX_VP, v[0]);
pIn.uv = float2(0.0f, 0.0f);
TRANSFER_SHADOW(pIn);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
pIn.pos = mul(UNITY_MATRIX_VP, v[1]);
pIn.uv = float2(0.0f, 1.0f);
TRANSFER_SHADOW(pIn);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
pIn.pos = mul(UNITY_MATRIX_VP, v[2]);
pIn.uv = float2(1.0f, 0.0f);
TRANSFER_SHADOW(pIn);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
pIn.pos = mul(UNITY_MATRIX_VP, v[3]);
pIn.uv = float2(1.0f, 1.0f);
TRANSFER_SHADOW(pIn);
UNITY_TRANSFER_FOG(pIn, pIn.pos);
triStream.Append(pIn);
}
float4 frag(input i) : COLOR
{
fixed4 col = tex2D(_Sprite, i.uv) * _Color;
clip(col.a - 0.5);
col *= SHADOW_ATTENUATION(i);
UNITY_APPLY_FOG(i.fogCoord, col); // apply fog
return col;
}
ENDCG
}
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
float2 _Size = float2(1,1);
float3 _worldPos;
struct data
{
float3 pos;
};
StructuredBuffer<data> buf_Points;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(uint id : SV_VertexID)
{
v2f o;
o.pos = float4(buf_Points[id].pos + _worldPos, 1.0f);
o.uv = 0; // dummy data just to skip warning.
return o;
}
#define TRANSFER_SHADOW_CASTER_NOPOS2(opos, vertex) \
opos = mul(UNITY_MATRIX_VP, vertex); \
opos = UnityApplyLinearShadowBias(opos);
[maxvertexcount(4)]
void geom(point v2f p[1], inout TriangleStream<v2f> triStream)
{
float2 halfS = _Size;
float3 v[4];
v[0] = p[0].pos + float3(-halfS.x, -halfS.y, 0);
v[1] = p[0].pos + float3(-halfS.x, halfS.y, 0);
v[2] = p[0].pos + float3(halfS.x, -halfS.y, 0);
v[3] = p[0].pos + float3(halfS.x, halfS.y, 0);
v2f pIn;
UNITY_INITIALIZE_OUTPUT(v2f, pIn);
pIn.uv = float2(0.0f, 0.0f);
TRANSFER_SHADOW_CASTER_NOPOS2(pIn.pos, float4(v[0], 1.0));
triStream.Append(pIn);
pIn.uv = float2(0.0f, 1.0f);
TRANSFER_SHADOW_CASTER_NOPOS2(pIn.pos, float4(v[1], 1.0));
triStream.Append(pIn);
pIn.uv = float2(1.0f, 0.0f);
TRANSFER_SHADOW_CASTER_NOPOS2(pIn.pos, float4(v[2], 1.0));
triStream.Append(pIn);
pIn.uv = float2(1.0f, 1.0f);
TRANSFER_SHADOW_CASTER_NOPOS2(pIn.pos, float4(v[3], 1.0));
triStream.Append(pIn);
}
sampler2D _Sprite;
float4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_Sprite, i.uv);
clip(col.a - 0.5);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
}
Things that still don’t work in this version:
- Any shadow from lights other than the main directional light. You would need forward add passes for that, and because we’re not applying the actual lighting from the lights that might look weird. That and getting the TRANSFER_SHADOW working on the ForwardAdd pass is more work than I want to do.
- Ambient lighting, or any lighting really. Since we’re not doing any lighting at all the stuff in shadow is just black. Might be good to apply the light color and the ambient light. You can do that by adding #include “UnityLightingCommon.cginc” and replacing the SHADOW_ATTENUATION line with:
col.rgb *= _LightColor0.rgb * SHADOW_ATTENUATION(i) + UNITY_LIGHTMODEL_AMBIENT.rgb;
- Alpha blend. As stated before Unity can’t do shadow receiving and proper alpha blend. You can make it cast shadows, either hard edged like in the above code, or stippled with some more work (this is what they use VPOS for). It makes sense for the way they do the main directional light shadow with the screen space pre-pass, but it’s frustrating they simply don’t pass anything about the shadow maps or the light matrices to transparent objects.