summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-14 18:56:26 -0800
committeryum <yum.food.vr@gmail.com>2026-01-14 18:56:26 -0800
commitba0d61e1e38c06758947936f2524b172d33471e2 (patch)
tree9dc1ef66c7c84aa86a8df95b0d954f631d90c88f
parente2bdfd38fa2e2371202d115dd60e33427a3b4597 (diff)
Impostors: continue work on view dependent sampling
-rw-r--r--impostor.cginc103
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;