diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-14 18:56:26 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-14 18:56:26 -0800 |
| commit | ba0d61e1e38c06758947936f2524b172d33471e2 (patch) | |
| tree | 9dc1ef66c7c84aa86a8df95b0d954f631d90c88f | |
| parent | e2bdfd38fa2e2371202d115dd60e33427a3b4597 (diff) | |
Impostors: continue work on view dependent sampling
| -rw-r--r-- | impostor.cginc | 103 |
1 files changed, 66 insertions, 37 deletions
diff --git a/impostor.cginc b/impostor.cginc index bb4c38f..1245a1c 100644 --- a/impostor.cginc +++ b/impostor.cginc @@ -6,6 +6,7 @@ SamplerState bilinear_clamp_s; Texture2D _ImpostorAtlas; +float4 _ImpostorAtlas_TexelSize; int _GridResolution; float _Cutoff; float _SphereRadius; @@ -43,24 +44,37 @@ float3 DirFromCell(float2 cell, float gridRes) { } float4 SampleAtlas(float2 uv, float2 cell) { - return _ImpostorAtlas.Sample(bilinear_clamp_s, (cell + uv) / _GridResolution); + uv = clamp(uv, 0.0, 1.0); + float2 halfTexelInCell = 0.5 * _ImpostorAtlas_TexelSize.xy * (float)_GridResolution; + uv = clamp(uv, halfTexelInCell, 1.0 - halfTexelInCell); + return _ImpostorAtlas.Sample(bilinear_clamp_s, (cell + uv) / (float)_GridResolution); } -// Compute barycentric weights for a point within a grid cell. -// The cell is a unit square split into two triangles along the (0,0)-(1,1) diagonal: -// Triangle 0 (lower-left): vertices at (0,0), (0,1), (1,1) -// Triangle 1 (upper-right): vertices at (0,0), (1,0), (1,1) -// Returns: -// .xyz = barycentric weights for the 3 triangle vertices -// .w = which triangle (0 or 1) -float4 GridCellBarycentric(float2 p) { - float4 res; - // Branchless barycentric weights (works for both triangles due to symmetry) - res.x = min(1.0 - p.x, 1.0 - p.y); // weight for (0,0) vertex - res.y = abs(p.x - p.y); // weight for (0,1) or (1,0) vertex - res.z = min(p.x, p.y); // weight for (1,1) vertex - res.w = ceil(p.x - p.y); // triangle select: 0 if p.y >= p.x, 1 otherwise - return res; +float4 SampleAtlasGrad(float2 uv, float2 cell, float2 uvGradX, float2 uvGradY) { + uv = clamp(uv, 0.0, 1.0); + float2 halfTexelInCell = 0.5 * _ImpostorAtlas_TexelSize.xy * (float)_GridResolution; + uv = clamp(uv, halfTexelInCell, 1.0 - halfTexelInCell); + + float invGridRes = 1.0 / (float)_GridResolution; + float2 atlasUv = (cell + uv) * invGridRes; + + // Important: gradients must be computed in-cell; do not let integer cell offsets affect mip selection. + float2 atlasGradX = uvGradX * invGridRes; + float2 atlasGradY = uvGradY * invGridRes; + return _ImpostorAtlas.SampleGrad(bilinear_clamp_s, atlasUv, atlasGradX, atlasGradY); +} + +// Branchless barycentric weights in a unit square split into two triangles along the (0,0)-(1,1) diagonal. +// Returns weights for the 4 square corners: +// .x = (0,0), .y = (0,1), .z = (1,0), .w = (1,1) +// Only three weights are non-zero for any point (true barycentric within one triangle), +// but sampling both possible "middle" vertices avoids triangle-selection discontinuities. +float4 GridCellBarycentric4(float2 p) { + float w00 = 1.0 - max(p.x, p.y); + float w11 = min(p.x, p.y); + float w01 = max(p.y - p.x, 0.0); + float w10 = max(p.x - p.y, 0.0); + return float4(w00, w01, w10, w11); } // Compute UV on a virtual plane facing frameDir @@ -183,8 +197,11 @@ float4 frag(v2f i) : SV_Target { #endif float3 center = i.centerPos; - // View direction in object space - float3 viewOS = normalize(mul((float3x3)unity_WorldToObject, normalize(camPos - center))); + // For lattice lookup, we need the reverse view direction. + // Lattice points are indexed by outward direction (origin → lattice). + // We want lattice points whose inward direction (lattice → origin) matches the view ray. + // So we lookup using -viewDir to find lattice points pointing opposite to the ray. + float3 viewOS = normalize(mul((float3x3)unity_WorldToObject, -viewDir)); // Get continuous grid position and find the 3 frames float gridRes = (float)_GridResolution; @@ -192,38 +209,50 @@ float4 frag(v2f i) : SV_Target { float2 gridFloor = floor(grid); float2 gridFrac = frac(grid); - // Compute barycentric weights and determine which triangle - float4 bary = GridCellBarycentric(gridFrac); - float3 blendWeights = bary.xyz; + // Branchless barycentric blend weights (avoids discontinuities along triangle/cell boundaries). + float4 bw = GridCellBarycentric4(gridFrac); - // Frame cells - float2 cell1 = gridFloor; - float2 cell2 = clamp(gridFloor + lerp(float2(0,1), float2(1,0), bary.w), 0, gridRes - 1); - float2 cell3 = clamp(gridFloor + float2(1,1), 0, gridRes - 1); + // Frame cells (square corners) + float2 cell00 = clamp(gridFloor, 0, gridRes - 1); + float2 cell01 = clamp(gridFloor + float2(0,1), 0, gridRes - 1); + float2 cell10 = clamp(gridFloor + float2(1,0), 0, gridRes - 1); + float2 cell11 = clamp(gridFloor + float2(1,1), 0, gridRes - 1); // Get directions for each frame - float3 dir1 = DirFromCell(cell1, gridRes); - float3 dir2 = DirFromCell(cell2, gridRes); - float3 dir3 = DirFromCell(cell3, gridRes); + float3 dir00 = DirFromCell(cell00, gridRes); + float3 dir01 = DirFromCell(cell01, gridRes); + float3 dir10 = DirFromCell(cell10, gridRes); + float3 dir11 = DirFromCell(cell11, gridRes); // Compute virtual plane UVs for all 3 frames - float3 pivotToCamOS = mul((float3x3)unity_WorldToObject, camPos - center) * 100.0; + float3 pivotToCamOS = mul((float3x3)unity_WorldToObject, camPos - center); float3 vertexPosOS = mul((float3x3)unity_WorldToObject, i.worldPos - center); float3 vertexToCamOS = pivotToCamOS - vertexPosOS; - float2 uv1 = VirtualPlaneUV(dir1, pivotToCamOS, vertexToCamOS, 0.5); - float2 uv2 = VirtualPlaneUV(dir2, pivotToCamOS, vertexToCamOS, 0.5); - float2 uv3 = VirtualPlaneUV(dir3, pivotToCamOS, vertexToCamOS, 0.5); + float2 uv00 = VirtualPlaneUV(dir00, pivotToCamOS, vertexToCamOS, 0.5); + float2 uv01 = VirtualPlaneUV(dir01, pivotToCamOS, vertexToCamOS, 0.5); + float2 uv10 = VirtualPlaneUV(dir10, pivotToCamOS, vertexToCamOS, 0.5); + float2 uv11 = VirtualPlaneUV(dir11, pivotToCamOS, vertexToCamOS, 0.5); + + float2 uv00dx = ddx(uv00); + float2 uv00dy = ddy(uv00); + float2 uv01dx = ddx(uv01); + float2 uv01dy = ddy(uv01); + float2 uv10dx = ddx(uv10); + float2 uv10dy = ddy(uv10); + float2 uv11dx = ddx(uv11); + float2 uv11dy = ddy(uv11); // Sample and blend frames - float4 s1 = SampleAtlas(uv1, cell1); - float4 s2 = SampleAtlas(uv2, cell2); - float4 s3 = SampleAtlas(uv3, cell3); - float4 col = s1 * blendWeights.x + s2 * blendWeights.y + s3 * blendWeights.z; + float4 s00 = SampleAtlasGrad(uv00, cell00, uv00dx, uv00dy); + float4 s01 = SampleAtlasGrad(uv01, cell01, uv01dx, uv01dy); + float4 s10 = SampleAtlasGrad(uv10, cell10, uv10dx, uv10dy); + float4 s11 = SampleAtlasGrad(uv11, cell11, uv11dx, uv11dy); + float4 col = s00 * bw.x + s01 * bw.y + s10 * bw.z + s11 * bw.w; #ifndef IMPOSTOR_SHADOW_PASS if (_DebugMode > 0.5) - return float4(blendWeights.xy, 0, 1); + return float4(bw.yz, 0, 1); #endif col *= _Color; |
