summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-18 10:48:07 -0800
committeryum <yum.food.vr@gmail.com>2026-01-18 10:52:10 -0800
commit50387110ee7ef1037fce6efabf4a1ea02be45259 (patch)
tree64e0e3083d9ff4de7d7586cedb2b460d987ee38a
parenta3c74c361ccba6f0ecee686d7e336cf905246c38 (diff)
Impostors: code cleanup
-rwxr-xr-ximpostor.cginc221
1 files changed, 80 insertions, 141 deletions
diff --git a/impostor.cginc b/impostor.cginc
index 1afbdea..fe08b67 100755
--- a/impostor.cginc
+++ b/impostor.cginc
@@ -5,7 +5,6 @@
#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);
@@ -20,7 +19,7 @@ void FrameBasis(float3 frameDir, out float3 planeX, out float3 planeY, out float
planeN = normalize(frameDir);
float3 up = abs(planeN.y) > 0.999 ? float3(0,0,1) : float3(0,1,0);
planeX = normalize(cross(planeN, up));
- planeY = normalize(cross(planeX, planeN));
+ planeY = cross(planeX, planeN); // Already normalized since planeX and planeN are orthonormal
}
void BillboardBasis(float3 fwd, out float3 right, out float3 up) {
@@ -38,22 +37,13 @@ float3 DirFromCell(float2 cell, float gridRes) {
return HemiOctDecode(uv);
}
-// Branchless barycentric weights for 3 points
float3 BarycentricWeights3(float2 gridFrac, bool isBottomRight) {
- if (isBottomRight) {
- // Bottom-Right Triangle: (0,0), (1,0), (1,1)
- return float3(1.0 - gridFrac.x, gridFrac.x - gridFrac.y, gridFrac.y);
- } else {
- // Top-Left Triangle: (0,0), (0,1), (1,1)
- return float3(1.0 - gridFrac.y, gridFrac.y - gridFrac.x, gridFrac.x);
- }
+ 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;
}
-// Compute UV on a virtual plane facing frameDir
-float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam) {
- float3 planeX, planeY, planeN;
- FrameBasis(frameDir, planeX, planeY, planeN);
-
+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;
@@ -66,11 +56,6 @@ float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam) {
#if defined(_IMPOSTORS)
-// Decode linear 0-1 depth (0=near clip, 1=far clip) to world space distance from camera.
-float DecodeImpostorDepth(float linearDepth) {
- return lerp(_Impostors_Near_Clip, _Impostors_Far_Clip, linearDepth);
-}
-
float2 ClampUvInCell(float2 uv, float2 halfTexelInCell) {
return clamp(saturate(uv), halfTexelInCell, 1.0 - halfTexelInCell);
}
@@ -78,7 +63,8 @@ float2 ClampUvInCell(float2 uv, float2 halfTexelInCell) {
struct ImpostorSample {
float4 albedo;
float4 normal;
- float4 metallicGloss;
+ float2 metallicGloss;
+ float depth;
};
struct ImpostorResult {
@@ -87,69 +73,35 @@ struct ImpostorResult {
float metallic;
float smoothness;
float3 objPos;
- // TODO rm
- float debug;
};
-// 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 SampleImpostorDepthCell(float2 cell, float2 uvInCell, float invGridRes) {
- AtlasUvGrad uv = PrepareAtlasUvGrad(cell, uvInCell, invGridRes);
- return _Impostors_Metallic_Gloss_Depth_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY).b;
-}
-
-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);
+ 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, uv.atlasUv, uv.gradX, uv.gradY);
- s.normal = _Impostors_Normal_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY);
- float4 mgd = _Impostors_Metallic_Gloss_Depth_Atlas.SampleGrad(bilinear_clamp_s, uv.atlasUv, uv.gradX, uv.gradY);
- s.metallicGloss = float4(mgd.rg, 0, 0);
+ 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;
}
-// Optimized version using pre-computed frame basis
-float2 ImpostorParallaxOffsetForFrameFast(float3 planeX, float3 planeY, float3 planeN, float3 pivotToCamOS, float encodedDepth, float objRadiusScaled, float objNearScaled) {
+float2 ImpostorParallaxOffset(float3 planeX, float3 planeY, float3 planeN, float3 pivotToCamOS, float encodedDepth, float rcpObjScale, 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;
float worldSpaceDepth = lerp(_Impostors_Near_Clip, _Impostors_Far_Clip, encodedDepth);
- float depth01 = (worldSpaceDepth - objNearScaled) * objRadiusScaled; // Pre-divided rcp(2*objRadius)
+ float depth01 = (worldSpaceDepth - objNearScaled) * objRadiusScaled;
float height = 0.5 - depth01;
return (camXY / camZ) * height;
}
-// Reconstruct object-space offset from impostor center.
-// frameDir: object-space direction from center to baking camera (same space as UV computation)
-// UV encodes X,Y on the virtual plane (0.5 = center), depth is linear 0-1 (near to far clip).
-float3 ReconstructObjectOffsetFromFrame(float3 frameDir, float2 uv, float encodedDepth) {
- float3 planeX, planeY, planeN;
- FrameBasis(frameDir, planeX, planeY, planeN);
-
- 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;
+float3 ReconstructObjectOffset(float3 planeX, float3 planeY, float3 planeN, float2 uv, float encodedDepth, float rcpObjScale, float objRadius, float objNear) {
float objFar = _Impostors_Far_Clip * rcpObjScale;
float2 offsetXY = (0.5 - uv) * (2.0 * objRadius);
@@ -163,23 +115,14 @@ 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 alphaBw = alpha * bw * rcp(max(alphaOut, 1e-4));
- // Premultiplied alpha blending
- float3 alphaBw = alpha * bw;
- 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.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;
}
-// Billboard vertex transformation for impostors
void impostor_vert(inout float3 vertexOS) {
float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
@@ -189,7 +132,6 @@ void impostor_vert(inout float3 vertexOS) {
float3 camPos = _WorldSpaceCameraPos;
#endif
- // Billboard facing the camera direction
float3 viewWS = normalize(camPos - center);
float3 right, up;
BillboardBasis(viewWS, right, up);
@@ -198,14 +140,11 @@ void impostor_vert(inout float3 vertexOS) {
vertexOS = mul(unity_WorldToObject, float4(worldPos, 1.0)).xyz;
}
-// Sample impostor atlas with view-dependent blending
ImpostorResult impostor_frag(float3 worldPos) {
ImpostorResult result = (ImpostorResult)0;
- // 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
@@ -218,30 +157,24 @@ ImpostorResult impostor_frag(float3 worldPos) {
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
float3x3 worldToObject = (float3x3)unity_WorldToObject;
- float3 viewOS = normalize(mul(worldToObject, normalize(camToCenter)));
+ float3 viewOS = mul(worldToObject, normalize(camToCenter));
- // 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);
- // Frame cells
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);
- // Compute shared vectors for virtual plane UV calculation
float3 pivotToCamOS = mul(worldToObject, camToCenter);
float3 vertexPosOS = mul(worldToObject, worldPos - center);
float3 vertexToCamOS = pivotToCamOS - vertexPosOS;
@@ -250,19 +183,27 @@ ImpostorResult impostor_frag(float3 worldPos) {
float3 frameDir1 = DirFromCell(cell1, gridRes);
float3 frameDir2 = DirFromCell(cell2, gridRes);
- float2 uvBase0 = ClampUvInCell(VirtualPlaneUV(frameDir0, pivotToCamOS, vertexToCamOS), halfTexelInCell);
- float2 uvBase1 = ClampUvInCell(VirtualPlaneUV(frameDir1, pivotToCamOS, vertexToCamOS), halfTexelInCell);
- float2 uvBase2 = ClampUvInCell(VirtualPlaneUV(frameDir2, pivotToCamOS, vertexToCamOS), halfTexelInCell);
+ 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 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, invGridRes);
- float depth1 = SampleImpostorDepthCell(cell1, uvBase1, invGridRes);
- float depth2 = SampleImpostorDepthCell(cell2, uvBase2, invGridRes);
float depthBlended = dot(float3(depth0, depth1, depth2), bw);
[branch]
@@ -271,52 +212,52 @@ ImpostorResult impostor_frag(float3 worldPos) {
return result;
}
- float2 uv0 = uvBase0;
- float2 uv1 = uvBase1;
- float2 uv2 = uvBase2;
+ // Pre-compute scale values once for both parallax and reconstruction
+ float objScale = max(2.0 * _Impostors_Sphere_Radius, 1e-4);
+ float rcpObjScale = rcp(objScale);
+ float objRadius = _Impostors_Sphere_Radius * rcpObjScale;
+ float objRadiusScaled = rcp(2.0 * objRadius);
+ float objNearScaled = _Impostors_Near_Clip * rcpObjScale;
+
+ float2 parallaxOffset0 = 0, parallaxOffset1 = 0, parallaxOffset2 = 0;
+ bool needsParallaxResample = false;
- // Pre-compute frame bases and scale factors for parallax
[branch]
if (parallaxStrength > 0.001) {
- 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;
- }
+ parallaxOffset0 = ImpostorParallaxOffset(planeX0, planeY0, planeN0, pivotToCamOS, depthBlended, rcpObjScale, objRadiusScaled, objNearScaled) * parallaxStrength;
+ parallaxOffset1 = ImpostorParallaxOffset(planeX1, planeY1, planeN1, pivotToCamOS, depthBlended, rcpObjScale, objRadiusScaled, objNearScaled) * parallaxStrength;
+ parallaxOffset2 = ImpostorParallaxOffset(planeX2, planeY2, planeN2, pivotToCamOS, depthBlended, rcpObjScale, objRadiusScaled, objNearScaled) * parallaxStrength;
- uv0 = ClampUvInCell(uv0, halfTexelInCell);
- uv1 = ClampUvInCell(uv1, halfTexelInCell);
- uv2 = ClampUvInCell(uv2, halfTexelInCell);
+ float maxOffsetSq = max(max(dot(parallaxOffset0, parallaxOffset0), dot(parallaxOffset1, parallaxOffset1)), dot(parallaxOffset2, parallaxOffset2));
+ needsParallaxResample = maxOffsetSq > 0.00005;
+ }
- ImpostorSample s0 = SampleImpostorCell(cell0, uv0, invGridRes);
- ImpostorSample s1 = SampleImpostorCell(cell1, uv1, invGridRes);
- ImpostorSample s2 = SampleImpostorCell(cell2, uv2, invGridRes);
+ float2 finalUv0 = uvBase0, finalUv1 = uvBase1, finalUv2 = uvBase2;
- // Parallax fallback: use base UVs if parallax pushed into transparent region
[branch]
- if (parallaxStrength > 0.001) {
- 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);
+ 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;
+ }
}
- // Blend 3 samples
ImpostorSample blended = BlendImpostorSamples(s0, s1, s2, bw);
[branch]
@@ -328,17 +269,15 @@ ImpostorResult impostor_frag(float3 worldPos) {
clip(blended.albedo.a - _Impostors_Cutoff);
result.albedo = blended.albedo;
- // 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.g;
#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);
+ float3 offset0 = ReconstructObjectOffset(planeX0, planeY0, planeN0, finalUv0, depth0, rcpObjScale, objRadius, objNearScaled);
+ float3 offset1 = ReconstructObjectOffset(planeX1, planeY1, planeN1, finalUv1, depth1, rcpObjScale, objRadius, objNearScaled);
+ float3 offset2 = ReconstructObjectOffset(planeX2, planeY2, planeN2, finalUv2, depth2, rcpObjScale, objRadius, objNearScaled);
result.objPos = offset0 * bw.x + offset1 * bw.y + offset2 * bw.z;
#endif