summaryrefslogtreecommitdiffstats
path: root/impostor.cginc
blob: 5674c024850b515e400fd005c541949b3e19e305 (plain)
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#ifndef __IMPOSTOR_INC
#define __IMPOSTOR_INC

#include "UnityCG.cginc"
#include "globals.cginc"
#include "vertex_deformation.hlsl"

struct ImpostorResult {
    float4 albedo;
    float3 normal;
    float metallic;
    float smoothness;
    float3 objPos;
};

#if defined(_IMPOSTORS)
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 FrameBasis(float3 frameDir, out float3 planeX, out float3 planeY, out float3 planeN) {
    planeN = normalize(frameDir);
    float3 up = abs(planeN.y) > 0.999 ? float3(0,0,1) : float3(0,1,0);
    planeX = normalize(cross(planeN, up));
    planeY = cross(planeX, planeN);  // Already normalized since planeX and planeN are orthonormal
}

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), 0.0, gridRes - 1.0);
}

float3 DirFromCell(float2 cell, float gridRes) {
    float2 uv = cell * rcp(max(1.0, gridRes - 1.0)) * 2.0 - 1.0;
    return HemiOctDecode(uv);
}

float3 BarycentricWeights3(float2 gridFrac, bool isBottomRight) {
    float3 br = float3(1.0 - gridFrac.x, gridFrac.x - gridFrac.y, gridFrac.y);
    float3 tl = float3(1.0 - gridFrac.y, gridFrac.y - gridFrac.x, gridFrac.x);
    return isBottomRight ? br : tl;
}

float2 VirtualPlaneUV(float3 planeX, float3 planeY, float3 planeN, float3 pivotToCam, float3 vertexToCam) {
    float projPivot = dot(planeN, pivotToCam);
    float projVertex = dot(planeN, vertexToCam);
    projVertex = (abs(projVertex) < 1e-4) ? (projVertex < 0 ? -1e-4 : 1e-4) : projVertex;
    float ratio = projPivot / projVertex;

    float3 offset = vertexToCam * ratio - pivotToCam;

    return 0.5 - float2(dot(planeX, offset), dot(planeY, offset));
}

float2 ClampUvInCell(float2 uv, float2 halfTexelInCell) {
    return clamp(saturate(uv), halfTexelInCell, 1.0 - halfTexelInCell);
}

struct ImpostorSample {
    float4 albedo;
    float4 normal;
    float2 metallicGloss;
    float depth;
};

ImpostorSample SampleImpostorCell(float2 cell, float2 uvInCell, float invGridRes) {
    float2 atlasUv = (cell + uvInCell) * invGridRes;
    float2 gradX = ddx(uvInCell) * invGridRes;
    float2 gradY = ddy(uvInCell) * invGridRes;

    ImpostorSample s;
    s.albedo = _Impostors_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, gradX, gradY);
    s.normal = _Impostors_Normal_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, gradX, gradY);
    float4 mgd = _Impostors_Metallic_Gloss_Depth_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, gradX, gradY);
    s.metallicGloss = mgd.rg;
    s.depth = mgd.b;
    return s;
}

float2 ImpostorParallaxOffset(float3 planeX, float3 planeY, float3 planeN, float3 pivotToCamOS, float encodedDepth,
                              float impostorNear, float impostorFar, float impostorRadius) {
    float2 camXY = float2(dot(pivotToCamOS, planeX), dot(pivotToCamOS, planeY));
    float camZ = dot(pivotToCamOS, planeN);
    camZ = (abs(camZ) < 1e-4) ? (camZ < 0 ? -1e-4 : 1e-4) : camZ;

    float worldSpaceDepth = lerp(impostorNear, impostorFar, encodedDepth);
    float depth01 = (worldSpaceDepth - impostorNear) / (2.0 * impostorRadius);
    float height = 0.5 - depth01;

    return (camXY / camZ) * height;
}

float3 ReconstructObjectOffset(float3 planeX, float3 planeY, float3 planeN, float2 uv, float encodedDepth,
                               float impostorNear, float impostorFar, float impostorRadius) {
    float2 offsetXY = (0.5 - uv) * (2.0 * impostorRadius);
    float worldSpaceDepth = lerp(impostorNear, impostorFar, encodedDepth);
    float offsetZ = (impostorRadius + impostorNear) - worldSpaceDepth;

    return offsetXY.x * planeX + offsetXY.y * planeY + offsetZ * planeN;
}

ImpostorSample BlendImpostorSamples(ImpostorSample s0, ImpostorSample s1, ImpostorSample s2, float3 bw) {
    ImpostorSample result;
    float3 alpha = float3(s0.albedo.a, s1.albedo.a, s2.albedo.a);
    float alphaOut = dot(alpha, bw);
    float3 alphaBw = alpha * bw * rcp(max(alphaOut, 1e-4));

    result.albedo = float4(s0.albedo.rgb * alphaBw.x + s1.albedo.rgb * alphaBw.y + s2.albedo.rgb * alphaBw.z, alphaOut);
    result.normal = s0.normal * alphaBw.x + s1.normal * alphaBw.y + s2.normal * alphaBw.z;
    result.metallicGloss = s0.metallicGloss * alphaBw.x + s1.metallicGloss * alphaBw.y + s2.metallicGloss * alphaBw.z;
    return result;
}
#endif  // _IMPOSTORS

void impostor_vert(inout float3 vertexOS) {
#if defined(_IMPOSTORS)
    float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;

#if defined(_INSTANCE_TEXTURE_OFFSET) && defined(UNITY_INSTANCING_ENABLED)
    float3 instanceCenter;
    float4 instanceRotation;
    float3 instanceScale;
    if (get_instance_transform(instanceCenter, instanceRotation, instanceScale)) {
        center = mul(unity_ObjectToWorld, float4(instanceCenter, 1.0)).xyz;
    }
#endif  // _INSTANCE_TEXTURE_OFFSET && UNITY_INSTANCING_ENABLED

#ifdef SHADOW_CASTER_PASS
    float3 camPos = _Impostors_Main_Camera_Pos;
#else
    float3 camPos = _WorldSpaceCameraPos;
#endif  // SHADOW_CASTER_PASS

    float3 viewWS = normalize(camPos - center);
    float3 right, up;
    BillboardBasis(viewWS, right, up);
    float radiusScale = _Impostors_Sphere_Radius * 2.0;
    float3 worldPos = center + (vertexOS.x * right + vertexOS.y * up) * radiusScale;
    vertexOS = mul(unity_WorldToObject, float4(worldPos, 1.0)).xyz;
#endif  // _IMPOSTORS
}

ImpostorResult impostor_frag(float3 worldPos) {
    ImpostorResult result = (ImpostorResult)0;
#if defined(_IMPOSTORS)

    float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
    float4 instanceRotation = float4(0, 0, 0, 1);
    float instanceScale = 1.0;
    bool hasInstanceTransform = false;

#if defined(_INSTANCE_TEXTURE_OFFSET) && defined(UNITY_INSTANCING_ENABLED)
    float3 instanceCenter;
    float3 instanceScale3;
    if (get_instance_transform(instanceCenter, instanceRotation, instanceScale3)) {
        center = mul(unity_ObjectToWorld, float4(instanceCenter, 1.0)).xyz;
        instanceScale = max(instanceScale3.x, max(instanceScale3.y, instanceScale3.z));
        hasInstanceTransform = true;
    }
#endif  // _INSTANCE_TEXTURE_OFFSET && UNITY_INSTANCING_ENABLED

#ifdef SHADOW_CASTER_PASS
    float3 camPos = _Impostors_Main_Camera_Pos;
#else
    float3 camPos = _WorldSpaceCameraPos;
#endif
    float3 camToCenter = camPos - center;
    float3 viewDir = normalize(worldPos - camPos);

    float impostorRadius = _Impostors_Sphere_Radius * instanceScale;
    float impostorNear = _Impostors_Near_Clip * instanceScale;
    float impostorFar = _Impostors_Far_Clip * instanceScale;

    float b = dot(camToCenter, viewDir);
    float c = dot(camToCenter, camToCenter) - impostorRadius * impostorRadius;
    clip(b * b - c);

    float3x3 worldToObject = (float3x3)unity_WorldToObject;
    float3 viewOS = mul(worldToObject, normalize(camToCenter));

    float3 pivotToCamOS = mul(worldToObject, camToCenter);
    float3 vertexPosOS = mul(worldToObject, worldPos - center);

    if (hasInstanceTransform) {
        float4 instanceRotationInv = qconj(instanceRotation);
        viewOS = qrotate(instanceRotationInv, viewOS);
        pivotToCamOS = qrotate(instanceRotationInv, pivotToCamOS);
        vertexPosOS = qrotate(instanceRotationInv, vertexPosOS);
    }
    float3 vertexToCamOS = pivotToCamOS - vertexPosOS;

    float gridRes = (float)_Impostors_Grid_Resolution;
    float invGridRes = rcp(gridRes);
    float2 halfTexelInCell = 0.5 * _Impostors_Atlas_TexelSize.xy * gridRes;

    float2 grid = GridFromDir(viewOS, gridRes);
    float2 gridFloor = floor(grid);
    float2 gridFrac = frac(grid);

    bool isBottomRight = gridFrac.x > gridFrac.y;
    float3 bw = BarycentricWeights3(gridFrac, isBottomRight);

    float2 cell0 = clamp(gridFloor, 0, gridRes - 1);
    float2 cell1 = clamp(gridFloor + (isBottomRight ? float2(1,0) : float2(0,1)), 0, gridRes - 1);
    float2 cell2 = clamp(gridFloor + float2(1,1), 0, gridRes - 1);

    float3 frameDir0 = DirFromCell(cell0, gridRes);
    float3 frameDir1 = DirFromCell(cell1, gridRes);
    float3 frameDir2 = DirFromCell(cell2, gridRes);

    float3 planeX0, planeY0, planeN0;
    float3 planeX1, planeY1, planeN1;
    float3 planeX2, planeY2, planeN2;
    FrameBasis(frameDir0, planeX0, planeY0, planeN0);
    FrameBasis(frameDir1, planeX1, planeY1, planeN1);
    FrameBasis(frameDir2, planeX2, planeY2, planeN2);

    float2 uvBase0 = ClampUvInCell(VirtualPlaneUV(planeX0, planeY0, planeN0, pivotToCamOS, vertexToCamOS), halfTexelInCell);
    float2 uvBase1 = ClampUvInCell(VirtualPlaneUV(planeX1, planeY1, planeN1, pivotToCamOS, vertexToCamOS), halfTexelInCell);
    float2 uvBase2 = ClampUvInCell(VirtualPlaneUV(planeX2, planeY2, planeN2, pivotToCamOS, vertexToCamOS), halfTexelInCell);

    ImpostorSample s0 = SampleImpostorCell(cell0, uvBase0, invGridRes);
    ImpostorSample s1 = SampleImpostorCell(cell1, uvBase1, invGridRes);
    ImpostorSample s2 = SampleImpostorCell(cell2, uvBase2, invGridRes);

    float baseAlpha0 = s0.albedo.a, depth0 = s0.depth;
    float baseAlpha1 = s1.albedo.a, depth1 = s1.depth;
    float baseAlpha2 = s2.albedo.a, depth2 = s2.depth;

    float baseAlphaBlended = dot(float3(baseAlpha0, baseAlpha1, baseAlpha2), bw);
    float parallaxStrength = _Impostors_Parallax * smoothstep(_Impostors_Cutoff, 1.0, baseAlphaBlended);
    float depthBlended = dot(float3(depth0, depth1, depth2), bw);

    [branch]
    if (_Impostors_Debug_Depth > 0.5) {
        result.albedo = float4(depthBlended.xxx, 1);
        return result;
    }

    float2 parallaxOffset0 = 0, parallaxOffset1 = 0, parallaxOffset2 = 0;
    bool needsParallaxResample = false;

    [branch]
    if (parallaxStrength > 0.001) {
        parallaxOffset0 = ImpostorParallaxOffset(planeX0, planeY0, planeN0, pivotToCamOS, depthBlended,
                                                 impostorNear, impostorFar, impostorRadius) * parallaxStrength;
        parallaxOffset1 = ImpostorParallaxOffset(planeX1, planeY1, planeN1, pivotToCamOS, depthBlended,
                                                 impostorNear, impostorFar, impostorRadius) * parallaxStrength;
        parallaxOffset2 = ImpostorParallaxOffset(planeX2, planeY2, planeN2, pivotToCamOS, depthBlended,
                                                 impostorNear, impostorFar, impostorRadius) * parallaxStrength;

        float maxOffsetSq = max(max(dot(parallaxOffset0, parallaxOffset0), dot(parallaxOffset1, parallaxOffset1)), dot(parallaxOffset2, parallaxOffset2));
        needsParallaxResample = maxOffsetSq > 0.00005;
    }

    float2 finalUv0 = uvBase0, finalUv1 = uvBase1, finalUv2 = uvBase2;

    [branch]
    if (needsParallaxResample) {
        float2 uv0 = ClampUvInCell(uvBase0 + parallaxOffset0, halfTexelInCell);
        float2 uv1 = ClampUvInCell(uvBase1 + parallaxOffset1, halfTexelInCell);
        float2 uv2 = ClampUvInCell(uvBase2 + parallaxOffset2, halfTexelInCell);

        ImpostorSample ps0 = SampleImpostorCell(cell0, uv0, invGridRes);
        ImpostorSample ps1 = SampleImpostorCell(cell1, uv1, invGridRes);
        ImpostorSample ps2 = SampleImpostorCell(cell2, uv2, invGridRes);

        if (ps0.albedo.a >= _Impostors_Cutoff || baseAlpha0 <= _Impostors_Cutoff) {
            s0 = ps0;
            finalUv0 = uv0;
        }
        if (ps1.albedo.a >= _Impostors_Cutoff || baseAlpha1 <= _Impostors_Cutoff) {
            s1 = ps1;
            finalUv1 = uv1;
        }
        if (ps2.albedo.a >= _Impostors_Cutoff || baseAlpha2 <= _Impostors_Cutoff) {
            s2 = ps2;
            finalUv2 = uv2;
        }
    }

    ImpostorSample blended = BlendImpostorSamples(s0, s1, s2, bw);

    [branch]
    if (_Impostors_Debug_Mode > 0.5) {
        result.albedo = float4(bw, 1);
        return result;
    }

    clip(blended.albedo.a - _Impostors_Cutoff);

    result.albedo = blended.albedo;
    float3 normalOS = blended.normal.xyz * 2.0 - 1.0;
    if (hasInstanceTransform) {
        normalOS = qrotate(instanceRotation, normalOS);
    }
    result.normal = normalize(mul((float3x3)unity_ObjectToWorld, normalOS));
    result.metallic = blended.metallicGloss.r;
    result.smoothness = blended.metallicGloss.g;

#if defined(_IMPOSTORS_DEPTH)
    float3 offset0 = ReconstructObjectOffset(planeX0, planeY0, planeN0, finalUv0, depth0,
                                             impostorNear, impostorFar, impostorRadius);
    float3 offset1 = ReconstructObjectOffset(planeX1, planeY1, planeN1, finalUv1, depth1,
                                             impostorNear, impostorFar, impostorRadius);
    float3 offset2 = ReconstructObjectOffset(planeX2, planeY2, planeN2, finalUv2, depth2,
                                             impostorNear, impostorFar, impostorRadius);
    result.objPos = offset0 * bw.x + offset1 * bw.y + offset2 * bw.z;
#endif

    return result;
#endif  // _IMPOSTORS
}

#endif  // __IMPOSTOR_INC