diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-15 16:52:25 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-15 17:28:02 -0800 |
| commit | 568f813fe37cd7beb1709fb1a92268fef2581d4e (patch) | |
| tree | 461042b70cac0a2b5b444a8bf57a9202b99dbe61 /impostor.cginc | |
| parent | 2e99a2275ce3d0d9eef7892cf7485a5f278cacb4 (diff) | |
Impostors: add parallax correction
Diffstat (limited to 'impostor.cginc')
| -rw-r--r-- | impostor.cginc | 121 |
1 files changed, 104 insertions, 17 deletions
diff --git a/impostor.cginc b/impostor.cginc index 1623681..5a923c5 100644 --- a/impostor.cginc +++ b/impostor.cginc @@ -16,6 +16,13 @@ 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 = normalize(cross(planeX, planeN)); +} + 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); @@ -42,10 +49,8 @@ float4 GridCellBarycentric4(float2 p) { // Compute UV on a virtual plane facing frameDir float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam) { - float3 planeN = normalize(frameDir); - float3 up = abs(planeN.y) > 0.999 ? float3(0,0,1) : float3(0,1,0); - float3 planeX = normalize(cross(planeN, up)); - float3 planeY = normalize(cross(planeX, planeN)); + float3 planeX, planeY, planeN; + FrameBasis(frameDir, planeX, planeY, planeN); float projPivot = dot(planeN, pivotToCam); float projVertex = dot(planeN, vertexToCam); @@ -86,14 +91,21 @@ struct ImpostorResult { float smoothness; }; -ImpostorSample SampleImpostorCell(float2 cell, float3 frameDir, float3 pivotToCamOS, float3 vertexToCamOS, float gridRes) { - float2 uv = VirtualPlaneUV(frameDir, pivotToCamOS, vertexToCamOS); - uv = ClampUvInCell(uv); +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; + return _Impostors_Depth_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, gradX, gradY).r; +} +ImpostorSample SampleImpostorCell(float2 cell, float2 uvInCell, float gridRes) { + uvInCell = ClampUvInCell(uvInCell); float invGridRes = rcp(gridRes); - float2 atlasUv = (cell + uv) * invGridRes; - float2 gradX = ddx(uv) * invGridRes; - float2 gradY = ddy(uv) * 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); @@ -102,9 +114,59 @@ ImpostorSample SampleImpostorCell(float2 cell, float3 frameDir, float3 pivotToCa return s; } +float2 ImpostorParallaxOffsetForFrame(float3 frameDir, float3 pivotToCamOS, float2 uvBase, float depth01) { + float3 planeX, planeY, planeN; + FrameBasis(frameDir, planeX, planeY, planeN); + + 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; + + // Bake depth is linear in ortho: 0=near surface, 1=far surface. + // 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); +} + +ImpostorSample SampleImpostorCellParallaxSafe( + float2 cell, + float3 frameDir, + float3 pivotToCamOS, + float3 vertexToCamOS, + float gridRes) +{ + float2 uvBase = ClampUvInCell(VirtualPlaneUV(frameDir, pivotToCamOS, vertexToCamOS)); + ImpostorSample baseS = SampleImpostorCell(cell, uvBase, gridRes); + + float baseAlpha = baseS.albedo.a; + float parallaxStrength = _Impostors_Parallax * smoothstep(_Impostors_Cutoff, 1.0, baseAlpha); + if (parallaxStrength <= 0.001) return baseS; + + float depth01 = SampleImpostorDepthCell(cell, uvBase, gridRes); + float2 uvParallax = uvBase + ImpostorParallaxOffsetForFrame(frameDir, pivotToCamOS, uvBase, depth01) * parallaxStrength; + ImpostorSample parS = SampleImpostorCell(cell, uvParallax, gridRes); + + float denom = max(baseAlpha - _Impostors_Cutoff, 1e-4); + float t = saturate((parS.albedo.a - _Impostors_Cutoff) / denom); + baseS.albedo = lerp(baseS.albedo, parS.albedo, t); + baseS.normal = lerp(baseS.normal, parS.normal, t); + baseS.metallicGloss = lerp(baseS.metallicGloss, parS.metallicGloss, t); + return baseS; +} + ImpostorSample BlendImpostorSamples(ImpostorSample s00, ImpostorSample s01, ImpostorSample s10, ImpostorSample s11, float4 bw) { ImpostorSample result; - result.albedo = s00.albedo * bw.x + s01.albedo * bw.y + s10.albedo * bw.z + s11.albedo * bw.w; + float4 alpha = float4(s00.albedo.a, s01.albedo.a, s10.albedo.a, s11.albedo.a); + float alphaOut = dot(alpha, bw); + float3 premul = + s00.albedo.rgb * (alpha.x * bw.x) + + s01.albedo.rgb * (alpha.y * bw.y) + + s10.albedo.rgb * (alpha.z * bw.z) + + s11.albedo.rgb * (alpha.w * bw.w); + float3 rgbOut = premul / max(alphaOut, 1e-4); + result.albedo = float4(rgbOut, alphaOut); // Weight normal/metallicGloss by alpha to avoid blending with transparent (zero) pixels float4 alphaBw = float4(s00.albedo.a, s01.albedo.a, s10.albedo.a, s11.albedo.a) * bw; @@ -151,7 +213,14 @@ ImpostorResult impostor_frag(float3 worldPos) { float3 camPos = _WorldSpaceCameraPos; #endif float3 viewDir = normalize(worldPos - camPos); - bool didIntersect = RaySphereIntersect(camPos, viewDir, center, _Impostors_Sphere_Radius); + + float3 scale = float3( + length(unity_ObjectToWorld._m00_m10_m20), + length(unity_ObjectToWorld._m01_m11_m21), + length(unity_ObjectToWorld._m02_m12_m22)); + float radiusWS = _Impostors_Sphere_Radius * max(scale.x, max(scale.y, scale.z)); + + bool didIntersect = RaySphereIntersect(camPos, viewDir, center, radiusWS); clip(didIntersect - 0.5); // For lattice lookup, use the camera-to-impostor-center direction (matches billboard orientation). @@ -178,11 +247,29 @@ ImpostorResult impostor_frag(float3 worldPos) { float3 vertexPosOS = mul(worldToObject, worldPos - center); float3 vertexToCamOS = pivotToCamOS - vertexPosOS; - // Sample all atlases for each frame cell - ImpostorSample s00 = SampleImpostorCell(cell00, DirFromCell(cell00, gridRes), pivotToCamOS, vertexToCamOS, gridRes); - ImpostorSample s01 = SampleImpostorCell(cell01, DirFromCell(cell01, gridRes), pivotToCamOS, vertexToCamOS, gridRes); - ImpostorSample s10 = SampleImpostorCell(cell10, DirFromCell(cell10, gridRes), pivotToCamOS, vertexToCamOS, gridRes); - ImpostorSample s11 = SampleImpostorCell(cell11, DirFromCell(cell11, gridRes), pivotToCamOS, vertexToCamOS, gridRes); + float3 frameDir00 = DirFromCell(cell00, gridRes); + float3 frameDir01 = DirFromCell(cell01, gridRes); + float3 frameDir10 = DirFromCell(cell10, gridRes); + float3 frameDir11 = DirFromCell(cell11, gridRes); + + if (_Impostors_Debug_Depth > 0.5) { + float2 uvBase00 = ClampUvInCell(VirtualPlaneUV(frameDir00, pivotToCamOS, vertexToCamOS)); + float2 uvBase01 = ClampUvInCell(VirtualPlaneUV(frameDir01, pivotToCamOS, vertexToCamOS)); + float2 uvBase10 = ClampUvInCell(VirtualPlaneUV(frameDir10, pivotToCamOS, vertexToCamOS)); + float2 uvBase11 = ClampUvInCell(VirtualPlaneUV(frameDir11, pivotToCamOS, vertexToCamOS)); + float depth00 = SampleImpostorDepthCell(cell00, uvBase00, gridRes); + float depth01 = SampleImpostorDepthCell(cell01, uvBase01, gridRes); + float depth10 = SampleImpostorDepthCell(cell10, uvBase10, gridRes); + float depth11 = SampleImpostorDepthCell(cell11, uvBase11, gridRes); + float depthBlended = depth00 * bw.x + depth01 * bw.y + depth10 * bw.z + depth11 * bw.w; + result.albedo = float4(depthBlended.xxx, 1); + return result; + } + + ImpostorSample s00 = SampleImpostorCellParallaxSafe(cell00, frameDir00, pivotToCamOS, vertexToCamOS, gridRes); + ImpostorSample s01 = SampleImpostorCellParallaxSafe(cell01, frameDir01, pivotToCamOS, vertexToCamOS, gridRes); + ImpostorSample s10 = SampleImpostorCellParallaxSafe(cell10, frameDir10, pivotToCamOS, vertexToCamOS, gridRes); + ImpostorSample s11 = SampleImpostorCellParallaxSafe(cell11, frameDir11, pivotToCamOS, vertexToCamOS, gridRes); // Blend all samples ImpostorSample blended = BlendImpostorSamples(s00, s01, s10, s11, bw); |
