You can add arbitrary passes while using a surface shader.
You just stick everything else in Pass {} tags and ensure that your surface shader isn’t inside any of them (as Unity will generate them as it parses the surface shader).
For my river shader, I’ve successfully done a depth-based pass then a grab pass then a distortion pass which then has a surface shader rendering on top.
Example code;
Shader "Exploration/River" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_DepthColor ("Depth Color", Color) = (1,1,1,1)
_WaterDepth ("Water Depth", Range (0, 10)) = 1
_BumpMap ("Normal Shading (Normal)", 2D) = "bump" {}
_WaterSpeed ("Water Speed", Range (0, 10)) = 1
_WaterSpeed2 ("Water Speed", Range (0, 10)) = 0.37
_Fresnel ("Fresnel Value", Float) = 0.028
}
SubShader{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
Blend One One
Pass
{
Name "RiverDepth"
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct v2f {
float4 pos : POSITION;
float4 screenPos : TEXCOORD0;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.screenPos = ComputeScreenPos(o.pos);
return o;
}
sampler2D _CameraDepthTexture;
float4 _DepthColor;
float _WaterDepth;
half4 frag( v2f i ) : COLOR
{
float depth = 1 - saturate(_WaterDepth - (LinearEyeDepth(tex2D(_CameraDepthTexture, i.screenPos.xy / i.screenPos.w).r) - i.screenPos.z));
return half4(_DepthColor.rgb, depth * _DepthColor.a);
}
ENDCG
}
GrabPass {
Name "RiverGrab"
}
Pass
{
Name "RiverDistortion"
Blend Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct v2f {
float4 pos : POSITION;
float4 uvgrab : TEXCOORD0;
float2 uv : TEXCOORD1;
float4 screenPos : TEXCOORD2;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
o.uvgrab.xy = (float2(o.pos.x, o.pos.y * scale) + o.pos.w) * 0.5;
o.uvgrab.zw = o.pos.zw;
o.uv = v.texcoord.xy;
return o;
}
sampler2D _BumpMap;
float _WaterSpeed, _WaterSpeed2;
sampler2D _GrabTexture;
float4 _GrabTexture_TexelSize;
half4 frag( v2f i ) : COLOR
{
float2 riverUVs = i.uv;
riverUVs.y += _Time * _WaterSpeed;
float3 normal1 = UnpackNormal(tex2D(_BumpMap, riverUVs));
riverUVs = i.uv;
riverUVs.x *= -1;
riverUVs.y += 0.3 + _Time * _WaterSpeed2;
float3 normal2 = UnpackNormal(tex2D(_BumpMap, riverUVs));
normal2 *= float3(1, 1, 0.5);
float3 combinedNormal = normalize(normal1 * normal2);
float2 offset = combinedNormal.xy * 5 * _GrabTexture_TexelSize.xy;
i.uvgrab.xy = (offset * i.uvgrab.z) + i.uvgrab.xy;
return half4(tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)).rgb, 1);
}
ENDCG
}
CGPROGRAM
#include "ExplorationLighting.cginc"
#pragma surface surf ExplorationRiver noambient novertexlights nolightmap
#pragma target 3.0
struct Input
{
float2 uv_BumpMap;
};
sampler2D _BumpMap, _CameraDepthTexture;
float _Specular, _Gloss, _WaterSpeed, _WaterSpeed2;
void surf (Input IN, inout SurfaceOutput o)
{
float2 riverUVs = IN.uv_BumpMap;
riverUVs.y += _Time * _WaterSpeed;
float3 normal1 = UnpackNormal(tex2D(_BumpMap, riverUVs));
riverUVs = IN.uv_BumpMap;
riverUVs.x *= -1;
riverUVs.y += 0.3 + _Time * _WaterSpeed2;
float3 normal2 = UnpackNormal(tex2D(_BumpMap, riverUVs));
normal2 *= float3(1, 1, 0.5);
float3 combinedNormal = normalize(normal1 * normal2);
o.Albedo = fixed3(1);
o.Normal = combinedNormal;
o.Alpha = 0;
}
ENDCG
}
Fallback "Transparent/VertexLit"
}
You can even use multiple surface shaders on top of one another and change the blending/zwrite/blah values of them;
/*
Alpha tested pass.
Alpha blended pass w/ ZWrite off and alphatest greater than _Cutoff.
Anisotropic highlight.
*/
Shader "Exploration/Hair Soft Edge Surface" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {}
_SpecularTex ("Specular (R) Gloss (G) Null (B)", 2D) = "gray" {}
_BumpMap ("Normal (Normal)", 2D) = "bump" {}
_AnisoTex ("Anisotropic Direction (RGB)", 2D) = "bump" {}
_AnisoOffset ("Anisotropic Highlight Offset", Range(-0.5,0.5)) = -0.2
_Cutoff ("Alpha Cut-Off Threshold", Range(0,1)) = 0.5
_Fresnel ("Fresnel Value", Float) = 0.028
}
SubShader {
Tags { "Queue"="AlphaTest" "RenderType"="TransparentCutout" }
CGPROGRAM
#include "ExplorationLighting.cginc"
#pragma surface surf ExplorationSoftHairFirst fullforwardshadows exclude_path:prepass
#pragma target 3.0
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex, _SpecularTex, _BumpMap, _AnisoDirection;
void surf (Input IN, inout SurfaceOutputCharacter o)
{
fixed4 albedo = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = albedo.rgb;
o.Alpha = albedo.a;
o.AnisoDir = tex2D(_AnisoDirection, IN.uv_MainTex);
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
o.Specular = tex2D(_SpecularTex, IN.uv_MainTex).rgb;
}
ENDCG
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#include "ExplorationLighting.cginc"
#pragma surface surf ExplorationSoftHairSecond fullforwardshadows exclude_path:prepass noforwardadd
#pragma target 3.0
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex, _SpecularTex, _BumpMap, _AnisoDirection;
void surf (Input IN, inout SurfaceOutputCharacter o)
{
fixed4 albedo = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = albedo.rgb;
o.Alpha = albedo.a;
o.AnisoDir = tex2D(_AnisoDirection, IN.uv_MainTex);
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
o.Specular = tex2D(_SpecularTex, IN.uv_MainTex).rgb;
}
ENDCG
}
FallBack "Transparent/Cutout/VertexLit"
}