diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-18 10:48:07 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-18 10:52:10 -0800 |
| commit | 50387110ee7ef1037fce6efabf4a1ea02be45259 (patch) | |
| tree | 64e0e3083d9ff4de7d7586cedb2b460d987ee38a | |
| parent | a3c74c361ccba6f0ecee686d7e336cf905246c38 (diff) | |
Impostors: code cleanup
| -rwxr-xr-x | impostor.cginc | 221 |
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 |
