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
|
#ifndef __IMPOSTOR_INC
#define __IMPOSTOR_INC
#include "UnityCG.cginc"
#include "globals.cginc"
#include "vertex_deformation.hlsl"
// Utility functions for hemispherical octahedral mapping
float2 HemiOctEncode(float3 N) {
N.y = max(N.y, 1e-4);
float3 p = hemi_octahedron_to_plane(normalize(N), 0, float3(1,0,0), float3(0,1,0), 1);
return p.xz;
}
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);
}
float2 GridFromDir(float3 viewDir, float gridRes) {
float2 uv = HemiOctEncode(viewDir) * 0.5 + 0.5;
return clamp(uv * (gridRes - 1), 0, gridRes - 1);
}
float3 DirFromCell(float2 cell, float gridRes) {
float2 uv = cell / max(1.0, gridRes - 1) * 2.0 - 1.0;
return HemiOctDecode(uv);
}
// Branchless barycentric weights in a unit square split into two triangles
float4 GridCellBarycentric4(float2 p) {
float w00 = 1.0 - max(p.x, p.y);
float w11 = min(p.x, p.y);
float w01 = max(p.y - p.x, 0.0);
float w10 = max(p.x - p.y, 0.0);
return float4(w00, w01, w10, w11);
}
// Compute UV on a virtual plane facing frameDir
float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam) {
float3 planeN = normalize(frameDir);
float3 up = abs(planeN.y) > 0.999 ? float3(0,0,1) : float3(0,1,0);
float3 planeX = normalize(cross(planeN, up));
float3 planeY = normalize(cross(planeX, planeN));
float projPivot = dot(planeN, pivotToCam);
float projVertex = dot(planeN, vertexToCam);
float ratio = projPivot / projVertex;
float3 offset = vertexToCam * ratio - pivotToCam;
float2 uv = float2(dot(planeX, offset), dot(planeY, offset));
return uv * -1.0 + 0.5;
}
// General purpose ray-sphere intersection
bool RaySphereIntersect(float3 ro, float3 rayDir, float3 origin, float radius) {
float3 originToRo = ro - origin;
float b = dot(originToRo, rayDir);
float c = dot(originToRo, originToRo) - radius * radius;
return (b * b - c) >= 0.0;
}
#if defined(_IMPOSTORS)
float2 ClampUvInCell(float2 uv) {
uv = saturate(uv);
float2 halfTexelInCell = 0.5 * _Impostors_Atlas_TexelSize.xy * (float)_Impostors_Grid_Resolution;
return clamp(uv, halfTexelInCell, 1.0 - halfTexelInCell);
}
float4 SampleAtlasGrad(float2 uv, float2 cell, float2 uvGradX, float2 uvGradY) {
uv = ClampUvInCell(uv);
float invGridRes = rcp((float)_Impostors_Grid_Resolution);
float2 atlasUv = (cell + uv) * invGridRes;
// Important: gradients must be computed in-cell; do not let integer cell offsets affect mip selection.
return _Impostors_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, uvGradX * invGridRes, uvGradY * invGridRes);
}
// Billboard vertex transformation for impostors
void impostor_vert(float4 vertexOS, inout float3 worldPos) {
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 SHADOW_CASTER_PASS
float3 camPos = _Impostors_Main_Camera_Pos;
#else
float3 camPos = _WorldSpaceCameraPos;
#endif
// Billboard facing the camera direction
float3 viewWS = normalize(camPos - center);
float3 right, up;
BillboardBasis(viewWS, right, up);
worldPos = center + vertexOS.x * right * scale.x + vertexOS.y * up * scale.y;
}
// Sample impostor atlas with view-dependent blending
float4 impostor_frag(float3 worldPos) {
// Calculate center in fragment shader to avoid extra interpolator
float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
// Sphere culling first
#ifdef SHADOW_CASTER_PASS
float3 camPos = _Impostors_Main_Camera_Pos;
#else
float3 camPos = _WorldSpaceCameraPos;
#endif
float3 viewDir = normalize(worldPos - camPos);
bool didIntersect = RaySphereIntersect(camPos, viewDir, center, _Impostors_Sphere_Radius);
clip(didIntersect - 0.5);
// For lattice lookup, use the camera-to-impostor-center direction (matches billboard orientation).
float3x3 worldToObject = (float3x3)unity_WorldToObject;
float3 viewOS = normalize(mul(worldToObject, normalize(camPos - center)));
// Get continuous grid position and find the 4 frames
float gridRes = (float)_Impostors_Grid_Resolution;
float2 grid = GridFromDir(viewOS, gridRes);
float2 gridFloor = floor(grid);
float2 gridFrac = frac(grid);
// Branchless barycentric blend weights
float4 bw = GridCellBarycentric4(gridFrac);
// Frame cells (square corners)
float2 cell00 = clamp(gridFloor, 0, gridRes - 1);
float2 cell01 = clamp(gridFloor + float2(0,1), 0, gridRes - 1);
float2 cell10 = clamp(gridFloor + float2(1,0), 0, gridRes - 1);
float2 cell11 = clamp(gridFloor + float2(1,1), 0, gridRes - 1);
// Get directions for each frame
float3 dir00 = DirFromCell(cell00, gridRes);
float3 dir01 = DirFromCell(cell01, gridRes);
float3 dir10 = DirFromCell(cell10, gridRes);
float3 dir11 = DirFromCell(cell11, gridRes);
// Compute virtual plane UVs for all 4 frames
float3 pivotToCamOS = mul(worldToObject, camPos - center);
float3 vertexPosOS = mul(worldToObject, worldPos - center);
float3 vertexToCamOS = pivotToCamOS - vertexPosOS;
float2 uv00 = VirtualPlaneUV(dir00, pivotToCamOS, vertexToCamOS);
float2 uv01 = VirtualPlaneUV(dir01, pivotToCamOS, vertexToCamOS);
float2 uv10 = VirtualPlaneUV(dir10, pivotToCamOS, vertexToCamOS);
float2 uv11 = VirtualPlaneUV(dir11, pivotToCamOS, vertexToCamOS);
// Sample and blend frames
float4 s00 = SampleAtlasGrad(uv00, cell00, ddx(uv00), ddy(uv00));
float4 s01 = SampleAtlasGrad(uv01, cell01, ddx(uv01), ddy(uv01));
float4 s10 = SampleAtlasGrad(uv10, cell10, ddx(uv10), ddy(uv10));
float4 s11 = SampleAtlasGrad(uv11, cell11, ddx(uv11), ddy(uv11));
float4 col = s00 * bw.x + s01 * bw.y + s10 * bw.z + s11 * bw.w;
if (_Impostors_Debug_Mode > 0.5)
return float4(bw.yz, 0, 1);
clip(col.a - _Impostors_Cutoff);
return col;
}
#endif // _IMPOSTORS
#endif // __IMPOSTOR_INC
|