Think I’ve finally nailed it and so will post the source here for others in case its of any use. There are still issues with getting the correct lighting/shading on the pyramids, but that is an issue with the original code (see msdn link) not the shader code.
This is basically a proof-of-concept, without being able to use surface shader code directly the code has ended up being very long, a bit messy, non-optimal and only supports forward base, so no forward-add, no deferred and is Dx11 only. It should also be noted that this is just a replacement for the standard diffuse shader with a geometry shader designed to turn triangles into pyramids, so specifically it has limited uses.
I have no idea if recent Unity releases (e.g. 4.5) have addressed the overall issue of supporting Geometry shaders directly within surface shaders, I intend to check it out, but continued with this as a learning process. However as I said above the end result is very long and messy, so assuming Surface shaders do not support geometry shaders there will be considerable amount of effort to go through and clean up the code, more so to get something that is re-usable for different shader types.
In order to get it working I had to append code to both AutoLight.cginc and UnityCG.cginc, so these need to be place at the same directory level as the shaders that use them in your project in order for them to override the Unity defaults.
The main stumbling block in getting this working was replacing the various function defines that Unity/surface shaders use such as TRANSFER_VERTEX_TO_FRAGMENT, TRANSFER_SHADOW,TRANSFER_SHADOW_COLLECTOR and TRANSFER_SHADOW_CASTER.
The Triangle To Pyramid Geometry Shader
// Taken from DirectX SDK (June 2010) - Tutorial 13
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb205122%28v=vs.85%29.aspx
Shader "|Unity 4/Geometery/GeometryPyramidDiffuse"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Particle Texture", 2D) = "white" {}
_Explode ("Explode factor", Range(0.0, 4.0)) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
Name "FORWARD"
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_fwdbase
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_FORWARDBASE
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#define INTERNAL_DATA
#define WorldReflectionVector(data,normal) data.worldRefl
#define WorldNormalVector(data,normal) normal
#pragma only_renderers d3d11
// #pragma surface surf Lambert
// #pragma debug
sampler2D _MainTex;
fixed4 _Color;
float _Explode;
struct Input
{
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
struct v2f_surf
{
float4 pos : SV_POSITION;
float2 pack0 : TEXCOORD0;
fixed3 normal : TEXCOORD1;
fixed3 vlight : TEXCOORD2;
LIGHTING_COORDS(3,4)
};
float4 _MainTex_ST;
v2f_surf vert (appdata_full v)
{
v2f_surf o;
o.pos = v.vertex; // mul (UNITY_MATRIX_MVP, v.vertex);
o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
o.normal = worldN;
float3 shlight = ShadeSH9 (float4(worldN,1.0));
o.vlight = shlight;
#ifdef VERTEXLIGHT_ON
float3 worldPos = mul(_Object2World, v.vertex).xyz;
o.vlight += Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, worldPos, worldN );
#endif // VERTEXLIGHT_ON
// TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}
// Geometry Shader
[maxvertexcount(12)]
void geom( triangle v2f_surf input[3], inout TriangleStream<v2f_surf> outStream )
{
v2f_surf output;
// Calculate the face normal
float3 faceEdgeA = input[1].pos - input[0].pos;
float3 faceEdgeB = input[2].pos - input[0].pos;
float3 faceNormal = normalize( cross(faceEdgeA, faceEdgeB) );
float3 ExplodeAmt = faceNormal*_Explode;
// Calculate the face center
float3 centerPos = (input[0].pos.xyz + input[1].pos.xyz + input[2].pos.xyz)/3.0;
float2 centerTex = (input[0].pack0 + input[1].pack0 + input[2].pack0)/3.0;
centerPos += faceNormal*_Explode;
// Output the pyramid
for( int looper=0; looper<3; looper++ )
{
output.pos = input[looper].pos + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
output.normal = input[looper].normal;
output.pack0 = input[looper].pack0;
output.vlight = input[looper].vlight;
TRANSFER_GEOM_TO_FRAGMENT(output); // Probably need to do this by hand since it uses v.vertex!!
outStream.Append( output );
// uint iNext = (looper+1)%3; // Note: this causes errors in compilation
uint iNext = looper+1;
if (iNext>2) iNext = 0;
output.pos = input[iNext].pos + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
output.normal = input[iNext].normal;
output.pack0 = input[iNext].pack0;
output.vlight = input[iNext].vlight;
TRANSFER_GEOM_TO_FRAGMENT(output); // TRANSFER_VERTEX_TO_FRAGMENT
outStream.Append( output );
output.pos = float4(centerPos,1) + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
output.normal = faceNormal;
output.pack0 = centerTex;
output.vlight = input[looper].vlight;
TRANSFER_GEOM_TO_FRAGMENT(output);
outStream.Append( output );
outStream.RestartStrip();
}
for(int cpt=2; cpt>=0; cpt-- )
{
output.pos = input[cpt].pos + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
output.normal = -input[cpt].normal;
output.pack0 = input[cpt].pack0;
output.vlight = input[cpt].vlight;
TRANSFER_GEOM_TO_FRAGMENT(output);
outStream.Append( output );
}
outStream.RestartStrip();
}
fixed4 frag (v2f_surf IN) : COLOR
{
Input surfIN;
surfIN.uv_MainTex = IN.pack0.xy;
#ifdef UNITY_COMPILER_HLSL
SurfaceOutput o = (SurfaceOutput)0;
#else
SurfaceOutput o;
#endif
o.Albedo = 0.0;
o.Emission = 0.0;
o.Specular = 0.0;
o.Alpha = 0.0;
o.Gloss = 0.0;
o.Normal = IN.normal;
surf (surfIN, o);
fixed atten = LIGHT_ATTENUATION(IN);
fixed4 c = 0;
c = LightingLambert (o, _WorldSpaceLightPos0.xyz, atten);
c.rgb += o.Albedo * IN.vlight;
return c;
}
ENDCG
} // Pass ForwardBase
// Pass to render object as a shadow caster
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
Fog {Mode Off}
ZWrite On ZTest LEqual Cull Off
Offset 1, 1
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma multi_compile_shadowcaster
#pragma only_renderers d3d11
#include "UnityCG.cginc"
#include "HLSLSupport.cginc"
float _Explode;
struct v2f
{
V2F_SHADOW_CASTER;
};
v2f vert( appdata_base v )
{
v2f o;
//o.pos = v.vertex;
TRANSFER_SHADOW_CASTER(o)
o.pos = v.vertex;
return o;
}
// Geometry Shader
[maxvertexcount(12)]
void geom( triangle v2f input[3], inout TriangleStream<v2f> outStream )
{
v2f output;
// Calculate the face normal
float3 faceEdgeA = input[1].pos - input[0].pos;
float3 faceEdgeB = input[2].pos - input[0].pos;
float3 faceNormal = normalize( cross(faceEdgeA, faceEdgeB) );
float3 ExplodeAmt = faceNormal*_Explode;
// Calculate the face center
float3 centerPos = (input[0].pos.xyz + input[1].pos.xyz + input[2].pos.xyz)/3.0;
centerPos += faceNormal*_Explode;
// Define for TRANSFER_GEOM_SHADOW_CASTER
float clamped;
// Output the pyramid
for( int looper=0; looper<3; looper++ )
{
output.pos = input[looper].pos + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_CASTER(output)
output.pos = input[looper].pos + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
outStream.Append( output );
uint iNext = looper+1;
if (iNext>2) iNext = 0;
output.pos = input[iNext].pos + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_CASTER(output)
output.pos = input[iNext].pos + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
outStream.Append( output );
output.pos = float4(centerPos,1) + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_CASTER(output)
output.pos = float4(centerPos,1) + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
outStream.Append( output );
outStream.RestartStrip();
}
for(int cpt=2; cpt>=0; cpt-- )
{
output.pos = input[cpt].pos + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_CASTER(output)
output.pos = input[cpt].pos + float4(ExplodeAmt,0);
output.pos = mul(UNITY_MATRIX_MVP, output.pos);
outStream.Append( output );
}
outStream.RestartStrip();
}
float4 frag( v2f i ) : COLOR
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
// Pass to render object as a shadow collector
Pass
{
Name "ShadowCollector"
Tags { "LightMode" = "ShadowCollector" }
Fog {Mode Off}
ZWrite On ZTest LEqual
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma multi_compile_shadowcollector
#pragma only_renderers d3d11
#define SHADOW_COLLECTOR_PASS
#include "UnityCG.cginc"
#include "HLSLSupport.cginc"
float _Explode;
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
V2F_SHADOW_COLLECTOR;
};
v2f vert (appdata v)
{
v2f o;
TRANSFER_SHADOW_COLLECTOR(o)
o.pos = v.vertex;
return o;
}
// Geometry Shader
[maxvertexcount(12)]
void geom( triangle v2f input[3], inout TriangleStream<v2f> outStream )
{
v2f output;
// Calculate the face normal
float3 faceEdgeA = input[1].pos - input[0].pos;
float3 faceEdgeB = input[2].pos - input[0].pos;
float3 faceNormal = normalize( cross(faceEdgeA, faceEdgeB) );
float3 ExplodeAmt = faceNormal*_Explode;
// Calculate the face center
float3 centerPos = (input[0].pos.xyz + input[1].pos.xyz + input[2].pos.xyz)/3.0;
centerPos += faceNormal*_Explode;
// Define for TRANSFER_GEOM_SHADOW_CASTER
float clamped;
float4 vert;
float4 wpos;
// Output the pyramid
for( int looper=0; looper<3; looper++ )
{
vert = input[looper].pos + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_COLLECTOR(output, vert)
outStream.Append( output );
uint iNext = looper+1;
if (iNext>2) iNext = 0;
vert = input[iNext].pos + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_COLLECTOR(output, vert)
outStream.Append( output );
vert = float4(centerPos,1) + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_COLLECTOR(output, vert)
outStream.Append( output );
outStream.RestartStrip();
}
for(int cpt=2; cpt>=0; cpt-- )
{
vert = input[cpt].pos + float4(ExplodeAmt,0);
TRANSFER_GEOM_SHADOW_COLLECTOR(output, vert)
outStream.Append( output );
}
outStream.RestartStrip();
}
fixed4 frag (v2f i) : COLOR
{
SHADOW_COLLECTOR_FRAGMENT(i)
}
ENDCG
}
} // subshader
// FallBack "Diffuse" // Use for testing shadows via Normal-VertexLit.shader fallback.
FallBack Off
}
The AutoLight.cginc replacement
#ifndef AUTOLIGHT_INCLUDED
#define AUTOLIGHT_INCLUDED
#include "HLSLSupport.cginc"
// Unity 4.3.4
// 2014.06.09 - Amended to support Geometry Shader
// ------------ Shadow helpers --------
// ---- Screen space shadows
#if defined (SHADOWS_SCREEN)
uniform float4 _ShadowOffsets[4];
#if defined(SHADOWS_NATIVE)
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#else
uniform sampler2D _ShadowMapTexture;
#endif
#define SHADOW_COORDS(idx1) float4 _ShadowCoord : TEXCOORD##idx1;
#if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_World2Shadow[0], mul( _Object2World, v.vertex ) );
// GEOMETRY SHADER
#define TRANSFER_GEOM_SHADOW(a) a._ShadowCoord = mul( unity_World2Shadow[0], mul( _Object2World, a.pos ) );
inline fixed unitySampleShadow (float4 shadowCoord)
{
#if defined(SHADOWS_NATIVE)
fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
return shadow;
#else
float dist = tex2Dproj( _ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord) ).x;
// tegra is confused if we use _LightShadowData.x directly
// with "ambiguous overloaded function reference max(mediump float, float)"
half lightShadowDataX = _LightShadowData.x;
return max(dist > (shadowCoord.z/shadowCoord.w), lightShadowDataX);
#endif
}
#else // !((defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE))
#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
// GEOMETRY SHADER: WHAT TO DO HERE????
#define TRANSFER_GEOM_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
inline fixed unitySampleShadow (float4 shadowCoord)
{
fixed shadow = tex2Dproj( _ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord) ).r;
return shadow;
}
#endif
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif
// ---- Depth map shadows
#if defined (SHADOWS_DEPTH) && defined (SPOT)
#if !defined(SHADOWMAPSAMPLER_DEFINED)
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#endif
#if defined (SHADOWS_SOFT)
uniform float4 _ShadowOffsets[4];
#endif
inline fixed unitySampleShadow (float4 shadowCoord)
{
#if defined (SHADOWS_SOFT)
// 4-tap shadows
float3 coord = shadowCoord.xyz / shadowCoord.w;
#if defined (SHADOWS_NATIVE)
half4 shadows;
shadows.x = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[0]);
shadows.y = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[1]);
shadows.z = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[2]);
shadows.w = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[3]);
shadows = _LightShadowData.rrrr + shadows * (1-_LightShadowData.rrrr);
#else
float4 shadowVals;
shadowVals.x = UNITY_SAMPLE_DEPTH (tex2D( _ShadowMapTexture, coord + _ShadowOffsets[0].xy ));
shadowVals.y = UNITY_SAMPLE_DEPTH (tex2D( _ShadowMapTexture, coord + _ShadowOffsets[1].xy ));
shadowVals.z = UNITY_SAMPLE_DEPTH (tex2D( _ShadowMapTexture, coord + _ShadowOffsets[2].xy ));
shadowVals.w = UNITY_SAMPLE_DEPTH (tex2D( _ShadowMapTexture, coord + _ShadowOffsets[3].xy ));
half4 shadows = (shadowVals < coord.zzzz) ? _LightShadowData.rrrr : 1.0f;
#endif
// average-4 PCF
half shadow = dot (shadows, 0.25f);
#else
// 1-tap shadows
#if defined (SHADOWS_NATIVE)
half shadow = UNITY_SAMPLE_SHADOW_PROJ(_ShadowMapTexture, shadowCoord);
shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
#else
half shadow = UNITY_SAMPLE_DEPTH(tex2Dproj (_ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord))) < (shadowCoord.z / shadowCoord.w) ? _LightShadowData.r : 1.0;
#endif
#endif
return shadow;
}
#define SHADOW_COORDS(idx1) float4 _ShadowCoord : TEXCOORD##idx1;
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_World2Shadow[0], mul(_Object2World,v.vertex));
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
// GEOMETRY SHADER:
#define TRANSFER_GEOM_SHADOW(a) a._ShadowCoord = mul (unity_World2Shadow[0], mul(_Object2World, a.pos));
#endif
// ---- Point light shadows
#if defined (SHADOWS_CUBE)
uniform samplerCUBE _ShadowMapTexture;
inline float SampleCubeDistance (float3 vec)
{
float4 packDist = texCUBE (_ShadowMapTexture, vec);
return DecodeFloatRGBA( packDist );
}
inline float unityCubeShadow (float3 vec)
{
float mydist = length(vec) * _LightPositionRange.w;
mydist *= 0.97; // bias
#if defined (SHADOWS_SOFT)
float z = 1.0/128.0;
float4 shadowVals;
shadowVals.x = SampleCubeDistance (vec+float3( z, z, z));
shadowVals.y = SampleCubeDistance (vec+float3(-z,-z, z));
shadowVals.z = SampleCubeDistance (vec+float3(-z, z,-z));
shadowVals.w = SampleCubeDistance (vec+float3( z,-z,-z));
half4 shadows = (shadowVals < mydist.xxxx) ? _LightShadowData.rrrr : 1.0f;
return dot(shadows,0.25);
#else
float dist = SampleCubeDistance (vec);
return dist < mydist ? _LightShadowData.r : 1.0;
#endif
}
#define SHADOW_COORDS(idx1) float3 _ShadowCoord : TEXCOORD##idx1;
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul(_Object2World, v.vertex).xyz - _LightPositionRange.xyz;
#define SHADOW_ATTENUATION(a) unityCubeShadow(a._ShadowCoord)
// GEOMETRY SHADER:
#define TRANSFER_GEOM_SHADOW(a) a._ShadowCoord = mul(_Object2World, a.pos).xyz - _LightPositionRange.xyz;
#endif
// ---- Shadows off
#if !defined (SHADOWS_SCREEN) && !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE)
#define SHADOW_COORDS(idx1)
#define TRANSFER_SHADOW(a)
#define SHADOW_ATTENUATION(a) 1.0
// GEOMETRY SHADER:
#define TRANSFER_GEOM_SHADOW(a)
#endif
// ------------ Light helpers --------
#ifdef POINT
#define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
uniform sampler2D _LightTexture0;
uniform float4x4 _LightMatrix0;
#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz; TRANSFER_SHADOW(a)
#define LIGHT_ATTENUATION(a) (tex2D(_LightTexture0, dot(a._LightCoord,a._LightCoord).rr).UNITY_ATTEN_CHANNEL * SHADOW_ATTENUATION(a))
// GEOMETRY SHADER:
#define TRANSFER_GEOM_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, a.pos)).xyz; TRANSFER_GEOM_SHADOW(a)
#endif
#ifdef SPOT
#define LIGHTING_COORDS(idx1,idx2) float4 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
uniform sampler2D _LightTexture0;
uniform float4x4 _LightMatrix0;
uniform sampler2D _LightTextureB0;
#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)); TRANSFER_SHADOW(a)
// GEOMETRY SHADER:
#define TRANSFER_GEOM_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, a.pos)); TRANSFER_GEOM_SHADOW(a)
inline fixed UnitySpotCookie(float4 LightCoord)
{
return tex2D(_LightTexture0, LightCoord.xy / LightCoord.w + 0.5).w;
}
inline fixed UnitySpotAttenuate(float3 LightCoord)
{
return tex2D(_LightTextureB0, dot(LightCoord, LightCoord).xx).UNITY_ATTEN_CHANNEL;
}
#define LIGHT_ATTENUATION(a) ( (a._LightCoord.z > 0) * UnitySpotCookie(a._LightCoord) * UnitySpotAttenuate(a._LightCoord.xyz) * SHADOW_ATTENUATION(a) )
#endif
#ifdef DIRECTIONAL
#define LIGHTING_COORDS(idx1,idx2) SHADOW_COORDS(idx1)
#define TRANSFER_VERTEX_TO_FRAGMENT(a) TRANSFER_SHADOW(a)
#define LIGHT_ATTENUATION(a) SHADOW_ATTENUATION(a)
// GEOMETRY SHADER:
#define TRANSFER_GEOM_TO_FRAGMENT(a) TRANSFER_GEOM_SHADOW(a)
#endif
#ifdef POINT_COOKIE
#define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
uniform samplerCUBE _LightTexture0;
uniform float4x4 _LightMatrix0;
uniform sampler2D _LightTextureB0;
#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz; TRANSFER_SHADOW(a)
// GEOMETRY SHADER:
#define TRANSFER_GEOM_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, a.pos)).xyz; TRANSFER_GEOM_SHADOW(a)
#define LIGHT_ATTENUATION(a) (tex2D(_LightTextureB0, dot(a._LightCoord,a._LightCoord).rr).UNITY_ATTEN_CHANNEL * texCUBE(_LightTexture0, a._LightCoord).w * SHADOW_ATTENUATION(a))
#endif
#ifdef DIRECTIONAL_COOKIE
#define LIGHTING_COORDS(idx1,idx2) float2 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
uniform sampler2D _LightTexture0;
uniform float4x4 _LightMatrix0;
#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xy; TRANSFER_SHADOW(a)
// GEOMETRY SHADER:
#define TRANSFER_GEOM_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, a.pos)).xy; TRANSFER_GEOM_SHADOW(a)
#define LIGHT_ATTENUATION(a) (tex2D(_LightTexture0, a._LightCoord).w * SHADOW_ATTENUATION(a))
#endif
#endif
The UnityCG.cginc replacement
// Unity 4.3.4
// 2014.06.09 - Amended to support Geometry Shader
#ifndef UNITY_CG_INCLUDED
#define UNITY_CG_INCLUDED
#include "UnityShaderVariables.cginc"
#if SHADER_API_FLASH
uniform float4 unity_NPOTScale;
#endif
#if defined(SHADER_API_PS3)
# define UNITY_SAMPLE_DEPTH(value) (dot((value).wxy, float3(0.996093809371817670572857294849, 0.0038909914428586627756752238080039, 1.5199185323666651467481343000015e-5)))
#elif defined(SHADER_API_FLASH)
# define UNITY_SAMPLE_DEPTH(value) (DecodeFloatRGBA(value))
#else
# define UNITY_SAMPLE_DEPTH(value) (value).r
#endif
uniform fixed4 unity_ColorSpaceGrey;
// -------------------------------------------------------------------
// helper functions and macros used in many standard shaders
#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) || defined (POINT) || defined (SPOT) || defined (POINT_NOATT) || defined (POINT_COOKIE)
#define USING_LIGHT_MULTI_COMPILE
#endif
#define SCALED_NORMAL (v.normal * unity_Scale.w)
struct appdata_base {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct appdata_tan {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
fixed4 color : COLOR;
#if defined(SHADER_API_XBOX360)
half4 texcoord2 : TEXCOORD2;
half4 texcoord3 : TEXCOORD3;
half4 texcoord4 : TEXCOORD4;
half4 texcoord5 : TEXCOORD5;
#endif
};
// Computes world space light direction
inline float3 WorldSpaceLightDir( in float4 v )
{
float3 worldPos = mul(_Object2World, v).xyz;
#ifndef USING_LIGHT_MULTI_COMPILE
return _WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w;
#else
#ifndef USING_DIRECTIONAL_LIGHT
return _WorldSpaceLightPos0.xyz - worldPos;
#else
return _WorldSpaceLightPos0.xyz;
#endif
#endif
}
// Computes object space light direction
inline float3 ObjSpaceLightDir( in float4 v )
{
float3 objSpaceLightPos = mul(_World2Object, _WorldSpaceLightPos0).xyz;
#ifndef USING_LIGHT_MULTI_COMPILE
return objSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w;
#else
#ifndef USING_DIRECTIONAL_LIGHT
return objSpaceLightPos.xyz * unity_Scale.w - v.xyz;
#else
return objSpaceLightPos.xyz;
#endif
#endif
}
// Computes world space view direction
inline float3 WorldSpaceViewDir( in float4 v )
{
return _WorldSpaceCameraPos.xyz - mul(_Object2World, v).xyz;
}
// Computes object space view direction
inline float3 ObjSpaceViewDir( in float4 v )
{
float3 objSpaceCameraPos = mul(_World2Object, float4(_WorldSpaceCameraPos.xyz, 1)).xyz * unity_Scale.w;
return objSpaceCameraPos - v.xyz;
}
// Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \
float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w; \
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
float3 Shade4PointLights (
float4 lightPosX, float4 lightPosY, float4 lightPosZ,
float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
float4 lightAttenSq,
float3 pos, float3 normal)
{
// to light vectors
float4 toLightX = lightPosX - pos.x;
float4 toLightY = lightPosY - pos.y;
float4 toLightZ = lightPosZ - pos.z;
// squared lengths
float4 lengthSq = 0;
lengthSq += toLightX * toLightX;
lengthSq += toLightY * toLightY;
lengthSq += toLightZ * toLightZ;
// NdotL
float4 ndotl = 0;
ndotl += toLightX * normal.x;
ndotl += toLightY * normal.y;
ndotl += toLightZ * normal.z;
// correct NdotL
float4 corr = rsqrt(lengthSq);
ndotl = max (float4(0,0,0,0), ndotl * corr);
// attenuation
float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
float4 diff = ndotl * atten;
// final color
float3 col = 0;
col += lightColor0 * diff.x;
col += lightColor1 * diff.y;
col += lightColor2 * diff.z;
col += lightColor3 * diff.w;
return col;
}
float3 ShadeVertexLights (float4 vertex, float3 normal)
{
float3 viewpos = mul (UNITY_MATRIX_MV, vertex).xyz;
float3 viewN = mul ((float3x3)UNITY_MATRIX_IT_MV, normal);
float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
for (int i = 0; i < 4; i++) {
float3 toLight = unity_LightPosition[i].xyz - viewpos.xyz * unity_LightPosition[i].w;
float lengthSq = dot(toLight, toLight);
float atten = 1.0 / (1.0 + lengthSq * unity_LightAtten[i].z);
float diff = max (0, dot (viewN, normalize(toLight)));
lightColor += unity_LightColor[i].rgb * (diff * atten);
}
return lightColor;
}
// normal should be normalized, w=1.0
half3 ShadeSH9 (half4 normal)
{
half3 x1, x2, x3;
// Linear + constant polynomial terms
x1.r = dot(unity_SHAr,normal);
x1.g = dot(unity_SHAg,normal);
x1.b = dot(unity_SHAb,normal);
// 4 of the quadratic polynomials
half4 vB = normal.xyzz * normal.yzzx;
x2.r = dot(unity_SHBr,vB);
x2.g = dot(unity_SHBg,vB);
x2.b = dot(unity_SHBb,vB);
// Final quadratic polynomial
float vC = normal.x*normal.x - normal.y*normal.y;
x3 = unity_SHC.rgb * vC;
return x1 + x2 + x3;
}
// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
// Transforms 4D UV by a texture matrix (use only if you know exactly which matrix you need)
#define TRANSFORM_UV(idx) mul (UNITY_MATRIX_TEXTURE##idx, v.texcoord).xy
struct v2f_vertex_lit {
float2 uv : TEXCOORD0;
fixed4 diff : COLOR0;
fixed4 spec : COLOR1;
};
inline fixed4 VertexLight( v2f_vertex_lit i, sampler2D mainTex )
{
fixed4 texcol = tex2D( mainTex, i.uv );
fixed4 c;
c.xyz = ( texcol.xyz * i.diff.xyz + i.spec.xyz * texcol.a ) * 2;
c.w = texcol.w * i.diff.w;
return c;
}
// Calculates UV offset for parallax bump mapping
inline float2 ParallaxOffset( half h, half height, half3 viewDir )
{
h = h * height - height/2.0;
float3 v = normalize(viewDir);
v.z += 0.42;
return h * (v.xy / v.z);
}
// Converts color to luminance (grayscale)
inline fixed Luminance( fixed3 c )
{
return dot( c, fixed3(0.22, 0.707, 0.071) );
}
// Decodes lightmaps:
// - doubleLDR encoded on GLES
// - RGBM encoded with range [0;8] on other platforms using surface shaders
inline fixed3 DecodeLightmap( fixed4 color )
{
#if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
return 2.0 * color.rgb;
#else
// potentially faster to do the scalar multiplication
// in parenthesis for scalar GPUs
return (8.0 * color.a) * color.rgb;
#endif
}
// Helpers used in image effects. Most image effects use the same
// minimal vertex shader (vert_img).
struct appdata_img {
float4 vertex : POSITION;
half2 texcoord : TEXCOORD0;
};
struct v2f_img {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
float2 MultiplyUV (float4x4 mat, float2 inUV) {
float4 temp = float4 (inUV.x, inUV.y, 0, 0);
temp = mul (mat, temp);
return temp.xy;
}
v2f_img vert_img( appdata_img v )
{
v2f_img o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.uv = MultiplyUV( UNITY_MATRIX_TEXTURE0, v.texcoord );
return o;
}
// Encoding/decoding [0..1) floats into 8 bit/channel RGBA. Note that 1.0 will not be encoded properly.
inline float4 EncodeFloatRGBA( float v )
{
float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 160581375.0);
float kEncodeBit = 1.0/255.0;
float4 enc = kEncodeMul * v;
enc = frac (enc);
enc -= enc.yzww * kEncodeBit;
return enc;
}
inline float DecodeFloatRGBA( float4 enc )
{
float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/160581375.0);
return dot( enc, kDecodeDot );
}
// Encoding/decoding [0..1) floats into 8 bit/channel RG. Note that 1.0 will not be encoded properly.
inline float2 EncodeFloatRG( float v )
{
float2 kEncodeMul = float2(1.0, 255.0);
float kEncodeBit = 1.0/255.0;
float2 enc = kEncodeMul * v;
enc = frac (enc);
enc.x -= enc.y * kEncodeBit;
return enc;
}
inline float DecodeFloatRG( float2 enc )
{
float2 kDecodeDot = float2(1.0, 1/255.0);
return dot( enc, kDecodeDot );
}
// Encoding/decoding view space normals into 2D 0..1 vector
inline float2 EncodeViewNormalStereo( float3 n )
{
float kScale = 1.7777;
float2 enc;
enc = n.xy / (n.z+1);
enc /= kScale;
enc = enc*0.5+0.5;
return enc;
}
inline float3 DecodeViewNormalStereo( float4 enc4 )
{
float kScale = 1.7777;
float3 nn = enc4.xyz*float3(2*kScale,2*kScale,0) + float3(-kScale,-kScale,1);
float g = 2.0 / dot(nn.xyz,nn.xyz);
float3 n;
n.xy = g*nn.xy;
n.z = g-1;
return n;
}
inline float4 EncodeDepthNormal( float depth, float3 normal )
{
float4 enc;
enc.xy = EncodeViewNormalStereo (normal);
enc.zw = EncodeFloatRG (depth);
return enc;
}
inline void DecodeDepthNormal( float4 enc, out float depth, out float3 normal )
{
depth = DecodeFloatRG (enc.zw);
normal = DecodeViewNormalStereo (enc);
}
inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
fixed3 normal;
normal.xy = packednormal.wy * 2 - 1;
#if defined(SHADER_API_FLASH)
// Flash does not have efficient saturate(), and dot() seems to require an extra register.
normal.z = sqrt(1 - normal.x*normal.x - normal.y*normal.y);
#else
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
#endif
return normal;
}
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
return packednormal.xyz * 2 - 1;
#else
return UnpackNormalDXT5nm(packednormal);
#endif
}
// Z buffer to linear 0..1 depth (0 at eye, 1 at far plane)
inline float Linear01Depth( float z )
{
return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}
// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{
return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}
// Depth render texture helpers
#if defined(UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE)
#define UNITY_TRANSFER_DEPTH(oo) oo = o.pos.zw
#if SHADER_API_FLASH
#define UNITY_OUTPUT_DEPTH(i) return EncodeFloatRGBA(i.x/i.y)
#else
#define UNITY_OUTPUT_DEPTH(i) return i.x/i.y
#endif
#else
#define UNITY_TRANSFER_DEPTH(oo)
#define UNITY_OUTPUT_DEPTH(i) return 0
#endif
#define DECODE_EYEDEPTH(i) LinearEyeDepth(i)
#define COMPUTE_EYEDEPTH(o) o = -mul( UNITY_MATRIX_MV, v.vertex ).z
#define COMPUTE_DEPTH_01 -(mul( UNITY_MATRIX_MV, v.vertex ).z * _ProjectionParams.w)
#define COMPUTE_VIEW_NORMAL mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)
// Projected screen position helpers
#define V2F_SCREEN_TYPE float4
inline float4 ComputeScreenPos (float4 pos) {
float4 o = pos * 0.5f;
#if defined(UNITY_HALF_TEXEL_OFFSET)
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w * _ScreenParams.zw;
#else
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
#endif
#if defined(SHADER_API_FLASH)
o.xy *= unity_NPOTScale.xy;
#endif
o.zw = pos.zw;
return o;
}
inline float4 ComputeGrabScreenPos (float4 pos) {
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*scale) + o.w;
o.zw = pos.zw;
return o;
}
// snaps post-transformed position to screen pixels
inline float4 UnityPixelSnap (float4 pos)
{
float2 hpc = _ScreenParams.xy * 0.5;
#ifdef UNITY_HALF_TEXEL_OFFSET
float2 hpcO = float2(-0.5,0.5);
#else
float2 hpcO = float2(0,0);
#endif
float2 pixelPos = floor ((pos.xy / pos.w) * hpc + 0.5);
pos.xy = (pixelPos + hpcO) / hpc * pos.w;
return pos;
}
inline float2 TransformViewToProjection (float2 v) {
return float2(v.x*UNITY_MATRIX_P[0][0], v.y*UNITY_MATRIX_P[1][1]);
}
inline float3 TransformViewToProjection (float3 v) {
return float3(v.x*UNITY_MATRIX_P[0][0], v.y*UNITY_MATRIX_P[1][1], v.z*UNITY_MATRIX_P[2][2]);
}
// Shadow caster pass helpers
#ifdef SHADOWS_CUBE
#define V2F_SHADOW_CASTER float4 pos : SV_POSITION; float3 vec : TEXCOORD0
#define TRANSFER_SHADOW_CASTER(o) o.vec = mul( _Object2World, v.vertex ).xyz - _LightPositionRange.xyz; o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
#define SHADOW_CASTER_FRAGMENT(i) return EncodeFloatRGBA( min(length(i.vec) * _LightPositionRange.w, 0.999) );
#define TRANSFER_GEOM_SHADOW_CASTER(o) o.vec = mul( _Object2World, o.pos ).xyz - _LightPositionRange.xyz; o.pos = mul(UNITY_MATRIX_MVP, o.pos);
#else
#if defined(UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE)
#define V2F_SHADOW_CASTER float4 pos : SV_POSITION; float4 hpos : TEXCOORD0
#define TRANSFER_SHADOW_CASTER(o) o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.pos.z += unity_LightShadowBias.x; \
float clamped = max(o.pos.z, o.pos.w*UNITY_NEAR_CLIP_VALUE); o.pos.z = lerp(o.pos.z, clamped, unity_LightShadowBias.y); o.hpos = o.pos;
#define TRANSFER_GEOM_SHADOW_CASTER(o) o.pos = mul(UNITY_MATRIX_MVP, o.pos); o.pos.z += unity_LightShadowBias.x; \
clamped = max(o.pos.z, o.pos.w*UNITY_NEAR_CLIP_VALUE); o.pos.z = lerp(o.pos.z, clamped, unity_LightShadowBias.y); o.hpos = o.pos;
#else
#define V2F_SHADOW_CASTER float4 pos : SV_POSITION
#define TRANSFER_SHADOW_CASTER(o) o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.pos.z += unity_LightShadowBias.x; \
float clamped = max(o.pos.z, o.pos.w*UNITY_NEAR_CLIP_VALUE); o.pos.z = lerp(o.pos.z, clamped, unity_LightShadowBias.y);
#define TRANSFER_GEOM_SHADOW_CASTER(o) o.pos = mul(UNITY_MATRIX_MVP, o.pos); o.pos.z += unity_LightShadowBias.x; \
clamped = max(o.pos.z, o.pos.w*UNITY_NEAR_CLIP_VALUE); o.pos.z = lerp(o.pos.z, clamped, unity_LightShadowBias.y);
#endif
#define SHADOW_CASTER_FRAGMENT(i) UNITY_OUTPUT_DEPTH(i.hpos.zw);
#endif
// Shadow collector pass helpers
#ifdef SHADOW_COLLECTOR_PASS
#if !defined(SHADOWMAPSAMPLER_DEFINED)
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#endif
#define V2F_SHADOW_COLLECTOR float4 pos : SV_POSITION; float3 _ShadowCoord0 : TEXCOORD0; float3 _ShadowCoord1 : TEXCOORD1; float3 _ShadowCoord2 : TEXCOORD2; float3 _ShadowCoord3 : TEXCOORD3; float4 _WorldPosViewZ : TEXCOORD4
#define TRANSFER_SHADOW_COLLECTOR(o) \
o.pos = mul(UNITY_MATRIX_MVP, v.vertex); \
float4 wpos = mul(_Object2World, v.vertex); \
o._WorldPosViewZ.xyz = wpos; \
o._WorldPosViewZ.w = -mul( UNITY_MATRIX_MV, v.vertex ).z; \
o._ShadowCoord0 = mul(unity_World2Shadow[0], wpos).xyz; \
o._ShadowCoord1 = mul(unity_World2Shadow[1], wpos).xyz; \
o._ShadowCoord2 = mul(unity_World2Shadow[2], wpos).xyz; \
o._ShadowCoord3 = mul(unity_World2Shadow[3], wpos).xyz;
#define TRANSFER_GEOM_SHADOW_COLLECTOR(o, v) \
o.pos = mul(UNITY_MATRIX_MVP, v); \
wpos = mul(_Object2World, v); \
o._WorldPosViewZ.xyz = wpos; \
o._WorldPosViewZ.w = -mul( UNITY_MATRIX_MV, v ).z; \
o._ShadowCoord0 = mul(unity_World2Shadow[0], wpos).xyz; \
o._ShadowCoord1 = mul(unity_World2Shadow[1], wpos).xyz; \
o._ShadowCoord2 = mul(unity_World2Shadow[2], wpos).xyz; \
o._ShadowCoord3 = mul(unity_World2Shadow[3], wpos).xyz;
#if defined (SHADOWS_NATIVE)
#define SAMPLE_SHADOW_COLLECTOR_SHADOW(coord) \
half shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture,coord); \
shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
#else
#define SAMPLE_SHADOW_COLLECTOR_SHADOW(coord) \
float shadow = UNITY_SAMPLE_DEPTH(tex2D( _ShadowMapTexture, coord.xy )) < coord.z ? _LightShadowData.r : 1.0;
#endif
#define COMPUTE_SHADOW_COLLECTOR_SHADOW(i, weights, shadowFade) \
float4 coord = float4(i._ShadowCoord0 * weights[0] + i._ShadowCoord1 * weights[1] + i._ShadowCoord2 * weights[2] + i._ShadowCoord3 * weights[3], 1); \
SAMPLE_SHADOW_COLLECTOR_SHADOW(coord) \
float4 res; \
res.x = saturate(shadow + shadowFade); \
res.y = 1.0; \
res.zw = EncodeFloatRG (1 - i._WorldPosViewZ.w * _ProjectionParams.w); \
return res;
#if defined (SHADOWS_SPLIT_SPHERES)
#define SHADOW_COLLECTOR_FRAGMENT(i) \
float3 fromCenter0 = i._WorldPosViewZ.xyz - unity_ShadowSplitSpheres[0].xyz; \
float3 fromCenter1 = i._WorldPosViewZ.xyz - unity_ShadowSplitSpheres[1].xyz; \
float3 fromCenter2 = i._WorldPosViewZ.xyz - unity_ShadowSplitSpheres[2].xyz; \
float3 fromCenter3 = i._WorldPosViewZ.xyz - unity_ShadowSplitSpheres[3].xyz; \
float4 distances2 = float4(dot(fromCenter0,fromCenter0), dot(fromCenter1,fromCenter1), dot(fromCenter2,fromCenter2), dot(fromCenter3,fromCenter3)); \
float4 cascadeWeights = float4(distances2 < unity_ShadowSplitSqRadii); \
cascadeWeights.yzw = saturate(cascadeWeights.yzw - cascadeWeights.xyz); \
float sphereDist = distance(i._WorldPosViewZ.xyz, unity_ShadowFadeCenterAndType.xyz); \
float shadowFade = saturate(sphereDist * _LightShadowData.z + _LightShadowData.w); \
COMPUTE_SHADOW_COLLECTOR_SHADOW(i, cascadeWeights, shadowFade)
#else
#define SHADOW_COLLECTOR_FRAGMENT(i) \
float4 viewZ = i._WorldPosViewZ.w; \
float4 zNear = float4( viewZ >= _LightSplitsNear ); \
float4 zFar = float4( viewZ < _LightSplitsFar ); \
float4 cascadeWeights = zNear * zFar; \
float shadowFade = saturate(i._WorldPosViewZ.w * _LightShadowData.z + _LightShadowData.w); \
COMPUTE_SHADOW_COLLECTOR_SHADOW(i, cascadeWeights, shadowFade)
#endif
#endif
#endif