1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
#ifndef __IMPOSTOR_INC
#define __IMPOSTOR_INC
#include "UnityCG.cginc"
#include "vertex_deformation.hlsl"
SamplerState bilinear_clamp_s;
Texture2D _ImpostorAtlas;
Texture2D _ImpostorDepthAtlas;
int _GridResolution;
float _SphereRadius;
float _Cutoff;
float3 _ImpostorMainCameraPos;
#ifndef IMPOSTOR_SHADOW_PASS
float4 _Color;
float _DebugMode;
float _DebugDepth;
#endif
float2 HemiOctEncode(float3 N) {
N.y = max(N.y, 1e-4);
N = normalize(N);
float3 p = hemi_octahedron_to_plane(N, 0, float3(1,0,0), float3(0,1,0), 1);
return float2(p.x, p.z);
}
float3 HemiOctDecode(float2 uv) {
return normalize(plane_to_hemi_octahedron(float3(uv.x, 0, uv.y), 0, float3(1,0,0), float3(0,1,0), 1));
}
void BillboardBasis(float3 fwd, out float3 right, out float3 up) {
right = abs(fwd.y) > 0.999 ? float3(-1,0,0) : normalize(cross(float3(0,1,0), fwd));
up = cross(fwd, right);
}
int2 GetCell(float3 viewDir) {
float2 uv = HemiOctEncode(viewDir) * 0.5 + 0.5;
return clamp(int2(round(uv * (_GridResolution - 1))), 0, _GridResolution - 1);
}
float3 CellDir(int2 cell) {
float2 uv = float2(cell) / max(1.0, _GridResolution - 1) * 2.0 - 1.0;
return HemiOctDecode(uv);
}
float4 SampleAtlas(float2 uv, float2 cell) {
return _ImpostorAtlas.Sample(bilinear_clamp_s, (cell + uv) / _GridResolution);
}
float SampleDepthAtlas(float2 uv, float2 cell) {
return _ImpostorDepthAtlas.Sample(bilinear_clamp_s, (cell + uv) / _GridResolution).r;
}
// ============================================================================
// Vertex/Fragment
// ============================================================================
struct appdata {
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
#ifdef IMPOSTOR_SHADOW_PASS
float3 normal : NORMAL;
#endif
};
struct v2f {
#ifdef IMPOSTOR_SHADOW_PASS
V2F_SHADOW_CASTER;
#else
float4 pos : SV_POSITION;
UNITY_FOG_COORDS(7)
#endif
float2 uv : TEXCOORD1;
float2 cell : TEXCOORD2;
// For depth reconstruction
float3 worldPos : TEXCOORD3;
float3 viewWS : TEXCOORD4;
float3 center : TEXCOORD5;
float radius : TEXCOORD6;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert(appdata v) {
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
float3 scale = float3(
length(unity_ObjectToWorld._m00_m10_m20),
length(unity_ObjectToWorld._m01_m11_m21),
length(unity_ObjectToWorld._m02_m12_m22));
#ifdef IMPOSTOR_SHADOW_PASS
float3 camPos = _ImpostorMainCameraPos;
#else
float3 camPos = _WorldSpaceCameraPos;
#endif
float3 viewOS = normalize(mul((float3x3)unity_WorldToObject, normalize(camPos - center)));
int2 cell = GetCell(viewOS);
float3 snapOS = CellDir(cell);
float3 snapWS = normalize(mul((float3x3)unity_ObjectToWorld, snapOS));
float3 right, up;
BillboardBasis(snapWS, right, up);
float3 worldPos = center + v.vertex.x * right * scale.x + v.vertex.y * up * scale.y;
#ifdef IMPOSTOR_SHADOW_PASS
v.vertex = mul(unity_WorldToObject, float4(worldPos, 1));
v.normal = -snapWS;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
#else
o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1));
UNITY_TRANSFER_FOG(o, o.pos);
#endif
float3 localOff = mul((float3x3)unity_WorldToObject, worldPos - center);
BillboardBasis(snapOS, right, up);
o.uv = float2(1 - (dot(localOff, right) + 0.5), dot(localOff, up) + 0.5);
o.cell = float2(cell);
// Pass data for depth reconstruction
float3 viewWS = normalize(camPos - center);
o.worldPos = worldPos;
o.viewWS = viewWS;
o.center = center;
o.radius = _SphereRadius * scale.x;
return o;
}
#ifdef IMPOSTOR_SHADOW_PASS
// Shadow pass - just alpha test, no depth output
float4 frag(v2f i) : SV_Target {
float4 col = SampleAtlas(i.uv, i.cell);
clip(col.a - _Cutoff);
SHADOW_CASTER_FRAGMENT(i)
}
#else
// Forward pass - with depth output
struct FragOutput {
float4 color : SV_Target;
float depth : SV_Depth;
};
FragOutput frag(v2f i) {
FragOutput o;
float4 col = SampleAtlas(i.uv, i.cell);
float depth = SampleDepthAtlas(i.uv, i.cell);
// Compute depth-corrected world position
// depth=0 means front of sphere (toward camera), depth=1 means back (away from camera)
float3 depthWorldPos = i.worldPos - i.viewWS * (2.0 * depth - 1.0) * i.radius;
// Convert to clip space depth
float4 clipPos = mul(UNITY_MATRIX_VP, float4(depthWorldPos, 1.0));
o.depth = clipPos.z / clipPos.w;
if (_DebugMode > 0.5) {
o.color = float4(i.uv, 0, 1);
return o;
}
if (_DebugDepth > 0.5) {
o.color = float4(depth.xxx, 1);
return o;
}
col *= _Color;
clip(col.a - _Cutoff);
UNITY_APPLY_FOG(i.fogCoord, col);
o.color = col;
return o;
}
#endif
#endif
|