summaryrefslogtreecommitdiffstats
path: root/impostor.cginc
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-18 08:52:18 -0800
committeryum <yum.food.vr@gmail.com>2026-01-18 08:52:18 -0800
commit50e451441f628aa3a28241af118700ae12147583 (patch)
tree8a815e419b1f1d8667a5323e7fec289c8be60cfa /impostor.cginc
parentb925b4b1bf79e3d6f930a4d799a7194673b62bde (diff)
Impostors: begin optimization work
Diffstat (limited to 'impostor.cginc')
-rwxr-xr-x[-rw-r--r--]impostor.cginc247
1 files changed, 124 insertions, 123 deletions
diff --git a/impostor.cginc b/impostor.cginc
index e84f9db..048eaf6 100644..100755
--- a/impostor.cginc
+++ b/impostor.cginc
@@ -30,11 +30,11 @@ void BillboardBasis(float3 fwd, out float3 right, out float3 up) {
float2 GridFromDir(float3 viewDir, float gridRes) {
float2 uv = HemiOctEncode(viewDir) * 0.5 + 0.5;
- return clamp(uv * (gridRes - 1), 0, gridRes - 1);
+ return clamp(uv * (gridRes - 1.0), 0.0, gridRes - 1.0);
}
float3 DirFromCell(float2 cell, float gridRes) {
- float2 uv = cell / max(1.0, gridRes - 1) * 2.0 - 1.0;
+ float2 uv = cell * rcp(max(1.0, gridRes - 1.0)) * 2.0 - 1.0;
return HemiOctDecode(uv);
}
@@ -61,8 +61,7 @@ float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam) {
float3 offset = vertexToCam * ratio - pivotToCam;
- float2 uv = float2(dot(planeX, offset), dot(planeY, offset));
- return uv * -1.0 + 0.5;
+ return 0.5 - float2(dot(planeX, offset), dot(planeY, offset));
}
#if defined(_IMPOSTORS)
@@ -72,10 +71,8 @@ float DecodeImpostorDepth(float linearDepth) {
return lerp(_Impostors_Near_Clip, _Impostors_Far_Clip, linearDepth);
}
-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);
+float2 ClampUvInCell(float2 uv, float2 halfTexelInCell) {
+ return clamp(saturate(uv), halfTexelInCell, 1.0 - halfTexelInCell);
}
struct ImpostorSample {
@@ -94,59 +91,51 @@ struct ImpostorResult {
float debug;
};
-float SampleImpostorDepthCell(float2 cell, float2 uvInCell, float gridRes) {
- uvInCell = ClampUvInCell(uvInCell);
- float invGridRes = rcp(gridRes);
- float2 atlasUv = (cell + uvInCell) * invGridRes;
- float2 gradX = ddx(uvInCell) * invGridRes;
- float2 gradY = ddy(uvInCell) * invGridRes;
- // 0 = near clip plane, 1 = far clip plane
- return _Impostors_Depth_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, gradX, gradY).r;
+// Unified sampling with cached gradient calculations
+struct AtlasUvGrad {
+ float2 atlasUv;
+ float2 gradX;
+ float2 gradY;
+};
+
+AtlasUvGrad PrepareAtlasUvGrad(float2 cell, float2 uvInCell, float invGridRes) {
+ AtlasUvGrad result;
+ result.atlasUv = (cell + uvInCell) * invGridRes;
+ result.gradX = ddx(uvInCell) * invGridRes;
+ result.gradY = ddy(uvInCell) * invGridRes;
+ return result;
}
-float SampleImpostorAlphaCell(float2 cell, float2 uvInCell, float gridRes) {
- uvInCell = ClampUvInCell(uvInCell);
- float invGridRes = rcp(gridRes);
- float2 atlasUv = (cell + uvInCell) * invGridRes;
- float2 gradX = ddx(uvInCell) * invGridRes;
- float2 gradY = ddy(uvInCell) * invGridRes;
- return _Impostors_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, gradX, gradY).a;
+float SampleImpostorDepthCell(float2 cell, float2 uvInCell, float invGridRes) {
+ AtlasUvGrad uv = PrepareAtlasUvGrad(cell, uvInCell, invGridRes);
+ return _Impostors_Depth_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY).r;
}
-ImpostorSample SampleImpostorCell(float2 cell, float2 uvInCell, float gridRes) {
- uvInCell = ClampUvInCell(uvInCell);
- float invGridRes = rcp(gridRes);
- float2 atlasUv = (cell + uvInCell) * invGridRes;
- float2 gradX = ddx(uvInCell) * invGridRes;
- float2 gradY = ddy(uvInCell) * invGridRes;
+float SampleImpostorAlphaCell(float2 cell, float2 uvInCell, float invGridRes) {
+ AtlasUvGrad uv = PrepareAtlasUvGrad(cell, uvInCell, invGridRes);
+ return _Impostors_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY).a;
+}
+ImpostorSample SampleImpostorCell(float2 cell, float2 uvInCell, float invGridRes) {
+ AtlasUvGrad uv = PrepareAtlasUvGrad(cell, 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);
- s.metallicGloss = _Impostors_Metallic_Gloss_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, gradX, gradY).xzzy;
+ s.albedo = _Impostors_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY);
+ s.normal = _Impostors_Normal_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY);
+ s.metallicGloss = _Impostors_Metallic_Gloss_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY);
return s;
}
-float2 ImpostorParallaxOffsetForFrame(float3 frameDir, float3 pivotToCamOS, float2 uvBase, float encodedDepth) {
- float3 planeX, planeY, planeN;
- FrameBasis(frameDir, planeX, planeY, planeN);
-
+// Optimized version using pre-computed frame basis
+float2 ImpostorParallaxOffsetForFrameFast(float3 planeX, float3 planeY, float3 planeN, float3 pivotToCamOS, float encodedDepth, float objRadiusScaled, float objNearScaled) {
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;
- // Convert world-space depth to object space to match scaled impostor.
- float objScale = max(2.0 * _Impostors_Sphere_Radius, 1e-4);
- float objRadius = _Impostors_Sphere_Radius / objScale;
- float objNear = _Impostors_Near_Clip / objScale;
- float worldSpaceDepth = DecodeImpostorDepth(encodedDepth);
- float objectSpaceDepth = worldSpaceDepth / objScale;
- float depth01 = (objectSpaceDepth - objNear) / (2.0 * objRadius);
- // Convert to signed "height" where nearer pixels shift more (matches typical bump-offset convention).
- float zSurface = (depth01 - 0.5);
- float2 planeCoord = 0.5 - uvBase;
-
- return (planeCoord - camXY) * (zSurface / camZ);
+ float worldSpaceDepth = lerp(_Impostors_Near_Clip, _Impostors_Far_Clip, encodedDepth);
+ float depth01 = (worldSpaceDepth - objNearScaled) * objRadiusScaled; // Pre-divided rcp(2*objRadius)
+ float height = 0.5 - depth01;
+
+ return (camXY / camZ) * height;
}
// Reconstruct object-space offset from impostor center.
@@ -156,12 +145,13 @@ float3 ReconstructObjectOffsetFromFrame(float3 frameDir, float2 uv, float encode
float3 planeX, planeY, planeN;
FrameBasis(frameDir, planeX, planeY, planeN);
- float objScale = max(2.0f * _Impostors_Sphere_Radius, 1e-4);
- float objRadius = _Impostors_Sphere_Radius / objScale;
- float objNear = _Impostors_Near_Clip / objScale;
- float objFar = _Impostors_Far_Clip / objScale;
+ float objScale = max(2.0 * _Impostors_Sphere_Radius, 1e-4);
+ float rcpObjScale = rcp(objScale);
+ float objRadius = _Impostors_Sphere_Radius * rcpObjScale;
+ float objNear = _Impostors_Near_Clip * rcpObjScale;
+ float objFar = _Impostors_Far_Clip * rcpObjScale;
- float2 offsetXY = (0.5f - uv) * 2.0f * objRadius;
+ float2 offsetXY = (0.5 - uv) * (2.0 * objRadius);
float objectSpaceDepth = lerp(objNear, objFar, encodedDepth);
float offsetZ = (objRadius + objNear) - objectSpaceDepth;
@@ -172,16 +162,16 @@ ImpostorSample BlendImpostorSamples(ImpostorSample s0, ImpostorSample s1, Impost
ImpostorSample result;
float3 alpha = float3(s0.albedo.a, s1.albedo.a, s2.albedo.a);
float alphaOut = dot(alpha, bw);
- float3 premul =
- s0.albedo.rgb * (alpha.x * bw.x) +
- s1.albedo.rgb * (alpha.y * bw.y) +
- s2.albedo.rgb * (alpha.z * bw.z);
- float3 rgbOut = premul / max(alphaOut, 1e-4);
- result.albedo = float4(rgbOut, alphaOut);
-
- // Weight normal/metallicGloss by alpha to avoid blending with transparent (zero) pixels
+
+ // Premultiplied alpha blending
float3 alphaBw = alpha * bw;
- alphaBw /= max(alphaBw.x + alphaBw.y + alphaBw.z, 0.001);
+ float3 premul = s0.albedo.rgb * alphaBw.x + s1.albedo.rgb * alphaBw.y + s2.albedo.rgb * alphaBw.z;
+ result.albedo = float4(premul * rcp(max(alphaOut, 1e-4)), alphaOut);
+
+ // Weight normal/metallicGloss by alpha to avoid blending with transparent pixels
+ float alphaBwSum = dot(alphaBw, 1.0);
+ float rcpAlphaBwSum = rcp(max(alphaBwSum, 1e-3));
+ alphaBw *= rcpAlphaBwSum;
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;
@@ -198,13 +188,13 @@ void impostor_vert(inout float3 vertexOS) {
float3 camPos = _WorldSpaceCameraPos;
#endif
- // Billboard facing the camera direction (world space, then convert back to object space).
+ // Billboard facing the camera direction
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 * radiusScale + vertexOS.y * up * radiusScale;
- vertexOS = mul(unity_WorldToObject, float4(worldPos, 1)).xyz;
+ float3 worldPos = center + (vertexOS.x * right + vertexOS.y * up) * radiusScale;
+ vertexOS = mul(unity_WorldToObject, float4(worldPos, 1.0)).xyz;
}
// Sample impostor atlas with view-dependent blending
@@ -220,25 +210,27 @@ ImpostorResult impostor_frag(float3 worldPos) {
#else
float3 camPos = _WorldSpaceCameraPos;
#endif
+ float3 camToCenter = camPos - center;
float3 viewDir = normalize(worldPos - camPos);
- float radiusWS = _Impostors_Sphere_Radius;
-
- float3 originToRo = camPos - center;
- float b = dot(originToRo, viewDir);
- float c = dot(originToRo, originToRo) - radiusWS * radiusWS;
+ float b = dot(camToCenter, viewDir);
+ float c = dot(camToCenter, camToCenter) - _Impostors_Sphere_Radius * _Impostors_Sphere_Radius;
clip(b * b - c);
- // For lattice lookup, use the camera-to-impostor-center direction (matches billboard orientation).
+ // For lattice lookup, use the camera-to-impostor-center direction
float3x3 worldToObject = (float3x3)unity_WorldToObject;
- float3 viewOS = normalize(mul(worldToObject, normalize(camPos - center)));
+ float3 viewOS = normalize(mul(worldToObject, normalize(camToCenter)));
- // Get continuous grid position and find the 3 nearest frames
+ // Cache frequently used values
float gridRes = (float)_Impostors_Grid_Resolution;
+ float invGridRes = rcp(gridRes);
+ float2 halfTexelInCell = 0.5 * _Impostors_Atlas_TexelSize.xy * gridRes;
+
+ // Get continuous grid position and find the 3 nearest frames
float2 grid = GridFromDir(viewOS, gridRes);
float2 gridFloor = floor(grid);
float2 gridFrac = frac(grid);
-
+
// Determine triangle and weights
bool isBottomRight = gridFrac.x > gridFrac.y;
float3 bw = BarycentricWeights3(gridFrac, isBottomRight);
@@ -249,7 +241,7 @@ ImpostorResult impostor_frag(float3 worldPos) {
float2 cell2 = clamp(gridFloor + float2(1,1), 0, gridRes - 1);
// Compute shared vectors for virtual plane UV calculation
- float3 pivotToCamOS = mul(worldToObject, camPos - center);
+ float3 pivotToCamOS = mul(worldToObject, camToCenter);
float3 vertexPosOS = mul(worldToObject, worldPos - center);
float3 vertexToCamOS = pivotToCamOS - vertexPosOS;
@@ -257,21 +249,22 @@ ImpostorResult impostor_frag(float3 worldPos) {
float3 frameDir1 = DirFromCell(cell1, gridRes);
float3 frameDir2 = DirFromCell(cell2, gridRes);
- float2 uvBase0 = ClampUvInCell(VirtualPlaneUV(frameDir0, pivotToCamOS, vertexToCamOS));
- float2 uvBase1 = ClampUvInCell(VirtualPlaneUV(frameDir1, pivotToCamOS, vertexToCamOS));
- float2 uvBase2 = ClampUvInCell(VirtualPlaneUV(frameDir2, pivotToCamOS, vertexToCamOS));
+ float2 uvBase0 = ClampUvInCell(VirtualPlaneUV(frameDir0, pivotToCamOS, vertexToCamOS), halfTexelInCell);
+ float2 uvBase1 = ClampUvInCell(VirtualPlaneUV(frameDir1, pivotToCamOS, vertexToCamOS), halfTexelInCell);
+ float2 uvBase2 = ClampUvInCell(VirtualPlaneUV(frameDir2, pivotToCamOS, vertexToCamOS), halfTexelInCell);
- float baseAlpha0 = SampleImpostorAlphaCell(cell0, uvBase0, gridRes);
- float baseAlpha1 = SampleImpostorAlphaCell(cell1, uvBase1, gridRes);
- float baseAlpha2 = SampleImpostorAlphaCell(cell2, uvBase2, gridRes);
- float baseAlphaBlended = baseAlpha0 * bw.x + baseAlpha1 * bw.y + baseAlpha2 * bw.z;
+ float baseAlpha0 = SampleImpostorAlphaCell(cell0, uvBase0, invGridRes);
+ float baseAlpha1 = SampleImpostorAlphaCell(cell1, uvBase1, invGridRes);
+ float baseAlpha2 = SampleImpostorAlphaCell(cell2, uvBase2, invGridRes);
+ float baseAlphaBlended = dot(float3(baseAlpha0, baseAlpha1, baseAlpha2), bw);
float parallaxStrength = _Impostors_Parallax * smoothstep(_Impostors_Cutoff, 1.0, baseAlphaBlended);
- float depth0 = SampleImpostorDepthCell(cell0, uvBase0, gridRes);
- float depth1 = SampleImpostorDepthCell(cell1, uvBase1, gridRes);
- float depth2 = SampleImpostorDepthCell(cell2, uvBase2, gridRes);
- float depthBlended = depth0 * bw.x + depth1 * bw.y + depth2 * bw.z;
+ float depth0 = SampleImpostorDepthCell(cell0, uvBase0, invGridRes);
+ float depth1 = SampleImpostorDepthCell(cell1, uvBase1, invGridRes);
+ float depth2 = SampleImpostorDepthCell(cell2, uvBase2, invGridRes);
+ float depthBlended = dot(float3(depth0, depth1, depth2), bw);
+ [branch]
if (_Impostors_Debug_Depth > 0.5) {
result.albedo = float4(depthBlended.xxx, 1);
return result;
@@ -280,41 +273,53 @@ ImpostorResult impostor_frag(float3 worldPos) {
float2 uv0 = uvBase0;
float2 uv1 = uvBase1;
float2 uv2 = uvBase2;
+
+ // Pre-compute frame bases and scale factors for parallax
+ [branch]
if (parallaxStrength > 0.001) {
- uv0 = uvBase0 + ImpostorParallaxOffsetForFrame(frameDir0, pivotToCamOS, uvBase0, depthBlended) * parallaxStrength;
- uv1 = uvBase1 + ImpostorParallaxOffsetForFrame(frameDir1, pivotToCamOS, uvBase1, depthBlended) * parallaxStrength;
- uv2 = uvBase2 + ImpostorParallaxOffsetForFrame(frameDir2, pivotToCamOS, uvBase2, depthBlended) * parallaxStrength;
+ float objScale = max(2.0 * _Impostors_Sphere_Radius, 1e-4);
+ float objRadiusScaled = rcp(2.0 * (_Impostors_Sphere_Radius / objScale));
+ float objNearScaled = _Impostors_Near_Clip / objScale;
+
+ 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);
+
+ uv0 = uvBase0 + ImpostorParallaxOffsetForFrameFast(planeX0, planeY0, planeN0, pivotToCamOS, depthBlended, objRadiusScaled, objNearScaled) * parallaxStrength;
+ uv1 = uvBase1 + ImpostorParallaxOffsetForFrameFast(planeX1, planeY1, planeN1, pivotToCamOS, depthBlended, objRadiusScaled, objNearScaled) * parallaxStrength;
+ uv2 = uvBase2 + ImpostorParallaxOffsetForFrameFast(planeX2, planeY2, planeN2, pivotToCamOS, depthBlended, objRadiusScaled, objNearScaled) * parallaxStrength;
}
- float2 uv0Final = uv0;
- float2 uv1Final = uv1;
- float2 uv2Final = uv2;
+ uv0 = ClampUvInCell(uv0, halfTexelInCell);
+ uv1 = ClampUvInCell(uv1, halfTexelInCell);
+ uv2 = ClampUvInCell(uv2, halfTexelInCell);
- ImpostorSample s0 = SampleImpostorCell(cell0, uv0Final, gridRes);
- ImpostorSample s1 = SampleImpostorCell(cell1, uv1Final, gridRes);
- ImpostorSample s2 = SampleImpostorCell(cell2, uv2Final, gridRes);
+ ImpostorSample s0 = SampleImpostorCell(cell0, uv0, invGridRes);
+ ImpostorSample s1 = SampleImpostorCell(cell1, uv1, invGridRes);
+ ImpostorSample s2 = SampleImpostorCell(cell2, uv2, invGridRes);
- // Parallax can push UVs into transparent pixels at silhouettes; fall back to unshifted UVs when that happens.
+ // Parallax fallback: use base UVs if parallax pushed into transparent region
+ [branch]
if (parallaxStrength > 0.001) {
- if (s0.albedo.a < _Impostors_Cutoff && baseAlpha0 > _Impostors_Cutoff) {
- uv0Final = uvBase0;
- s0 = SampleImpostorCell(cell0, uv0Final, gridRes);
- }
- if (s1.albedo.a < _Impostors_Cutoff && baseAlpha1 > _Impostors_Cutoff) {
- uv1Final = uvBase1;
- s1 = SampleImpostorCell(cell1, uv1Final, gridRes);
- }
- if (s2.albedo.a < _Impostors_Cutoff && baseAlpha2 > _Impostors_Cutoff) {
- uv2Final = uvBase2;
- s2 = SampleImpostorCell(cell2, uv2Final, gridRes);
- }
+ float3 needFallback = float3(
+ (s0.albedo.a < _Impostors_Cutoff && baseAlpha0 > _Impostors_Cutoff) ? 1.0 : 0.0,
+ (s1.albedo.a < _Impostors_Cutoff && baseAlpha1 > _Impostors_Cutoff) ? 1.0 : 0.0,
+ (s2.albedo.a < _Impostors_Cutoff && baseAlpha2 > _Impostors_Cutoff) ? 1.0 : 0.0
+ );
+
+ if (needFallback.x > 0.5) s0 = SampleImpostorCell(cell0, uvBase0, invGridRes);
+ if (needFallback.y > 0.5) s1 = SampleImpostorCell(cell1, uvBase1, invGridRes);
+ if (needFallback.z > 0.5) s2 = SampleImpostorCell(cell2, uvBase2, invGridRes);
}
// Blend 3 samples
ImpostorSample blended = BlendImpostorSamples(s0, s1, s2, bw);
+ [branch]
if (_Impostors_Debug_Mode > 0.5) {
- // Debug view of weights
result.albedo = float4(bw, 1);
return result;
}
@@ -322,23 +327,19 @@ ImpostorResult impostor_frag(float3 worldPos) {
clip(blended.albedo.a - _Impostors_Cutoff);
result.albedo = blended.albedo;
- result.normal = normalize(blended.normal.xyz * 2.0 - 1.0);
+ // Decode and transform normal
+ float3 normalOS = blended.normal.xyz * 2.0 - 1.0;
+ result.normal = normalize(mul((float3x3)unity_ObjectToWorld, normalOS));
result.metallic = blended.metallicGloss.r;
result.smoothness = blended.metallicGloss.a;
- // Reconstruct object-space position from each frame's UV and depth, then blend.
- float depth0Pos = SampleImpostorDepthCell(cell0, uv0Final, gridRes);
- float depth1Pos = SampleImpostorDepthCell(cell1, uv1Final, gridRes);
- float depth2Pos = SampleImpostorDepthCell(cell2, uv2Final, gridRes);
-
- // Use object-space frame directions (same space as UV computation)
- float3 offset0 = ReconstructObjectOffsetFromFrame(frameDir0, uv0Final, depth0Pos);
- float3 offset1 = ReconstructObjectOffsetFromFrame(frameDir1, uv1Final, depth1Pos);
- float3 offset2 = ReconstructObjectOffsetFromFrame(frameDir2, uv2Final, depth2Pos);
- float3 objOffset = offset0 * bw.x + offset1 * bw.y + offset2 * bw.z;
-
- // Return object-space position (offset from origin)
- result.objPos = objOffset;
+#if defined(_IMPOSTORS_DEPTH)
+ // Reuse depth samples for position reconstruction
+ float3 offset0 = ReconstructObjectOffsetFromFrame(frameDir0, uv0, depth0);
+ float3 offset1 = ReconstructObjectOffsetFromFrame(frameDir1, uv1, depth1);
+ float3 offset2 = ReconstructObjectOffsetFromFrame(frameDir2, uv2, depth2);
+ result.objPos = offset0 * bw.x + offset1 * bw.y + offset2 * bw.z;
+#endif
return result;
}