summaryrefslogtreecommitdiffstats
path: root/impostor.cginc
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-15 16:52:25 -0800
committeryum <yum.food.vr@gmail.com>2026-01-15 17:28:02 -0800
commit568f813fe37cd7beb1709fb1a92268fef2581d4e (patch)
tree461042b70cac0a2b5b444a8bf57a9202b99dbe61 /impostor.cginc
parent2e99a2275ce3d0d9eef7892cf7485a5f278cacb4 (diff)
Impostors: add parallax correction
Diffstat (limited to 'impostor.cginc')
-rw-r--r--impostor.cginc121
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);