diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-18 08:52:18 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-18 08:52:18 -0800 |
| commit | 50e451441f628aa3a28241af118700ae12147583 (patch) | |
| tree | 8a815e419b1f1d8667a5323e7fec289c8be60cfa /impostor.cginc | |
| parent | b925b4b1bf79e3d6f930a4d799a7194673b62bde (diff) | |
Impostors: begin optimization work
Diffstat (limited to 'impostor.cginc')
| -rwxr-xr-x[-rw-r--r--] | impostor.cginc | 247 |
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; } |
