summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-14 16:03:27 -0800
committeryum <yum.food.vr@gmail.com>2026-01-14 16:03:27 -0800
commita3bbff866fe4d0a4b5875dd5dd33dd1cb5411a48 (patch)
tree85fb047a7e158b3c7b59cd37733eae4732a1e760
parent5b2bf2250b64c5cbd2e0643301634dc80badc875 (diff)
Impostors: virtual frame projection first attempt
-rw-r--r--impostor.cginc114
1 files changed, 95 insertions, 19 deletions
diff --git a/impostor.cginc b/impostor.cginc
index fb41b19..d328223 100644
--- a/impostor.cginc
+++ b/impostor.cginc
@@ -31,13 +31,13 @@ void BillboardBasis(float3 fwd, out float3 right, out float3 up) {
up = cross(fwd, right);
}
-int2 GetCell(float3 viewDir) {
+float2 GridFromDir(float3 viewDir, float gridRes) {
float2 uv = HemiOctEncode(viewDir) * 0.5 + 0.5;
- return clamp(int2(round(uv * (_GridResolution - 1))), 0, _GridResolution - 1);
+ return clamp(uv * (gridRes - 1), 0, gridRes - 1);
}
-float3 CellDir(int2 cell) {
- float2 uv = float2(cell) / max(1.0, _GridResolution - 1) * 2.0 - 1.0;
+float3 DirFromCell(float2 cell, float gridRes) {
+ float2 uv = cell / max(1.0, gridRes - 1) * 2.0 - 1.0;
return HemiOctDecode(uv);
}
@@ -45,6 +45,38 @@ float4 SampleAtlas(float2 uv, float2 cell) {
return _ImpostorAtlas.Sample(bilinear_clamp_s, (cell + uv) / _GridResolution);
}
+// Compute blend weights for 3 frames in a grid cell triangle
+// .xyz = weights for frames 1,2,3
+// .w = which triangle (0 = lower-left, 1 = upper-right)
+float4 QuadBlendWeights(float2 frac) {
+ float4 res;
+ res.x = min(1.0 - frac.x, 1.0 - frac.y);
+ res.y = abs(frac.x - frac.y);
+ res.z = min(frac.x, frac.y);
+ res.w = ceil(frac.x - frac.y);
+ return res;
+}
+
+// Compute UV on a virtual plane facing frameDir
+float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam, float size) {
+ 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));
+
+ float projPivot = dot(planeN, pivotToCam);
+ float projVertex = dot(planeN, vertexToCam);
+ float ratio = projPivot / projVertex;
+
+ float3 offset = vertexToCam * ratio - pivotToCam;
+
+ float2 uv = float2(dot(planeX, offset), dot(planeY, offset));
+ uv /= (2.0f * size);
+ uv *= -1.0f;
+ uv += 0.5;
+ return uv;
+}
+
struct appdata {
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
@@ -58,10 +90,15 @@ struct v2f {
V2F_SHADOW_CASTER;
#else
float4 pos : SV_POSITION;
- UNITY_FOG_COORDS(3)
+ UNITY_FOG_COORDS(7)
#endif
- float2 uv : TEXCOORD1;
- float2 cell : TEXCOORD2;
+ float2 uv1 : TEXCOORD1;
+ float2 uv2 : TEXCOORD2;
+ float2 uv3 : TEXCOORD3;
+ nointerpolation float2 cell1 : TEXCOORD4;
+ nointerpolation float2 cell2 : TEXCOORD5;
+ nointerpolation float2 cell3 : TEXCOORD6;
+ float3 blendWeights : TEXCOORD8;
UNITY_VERTEX_OUTPUT_STEREO
};
@@ -82,44 +119,83 @@ v2f vert(appdata v) {
float3 camPos = _WorldSpaceCameraPos;
#endif
+ // View direction in object space
float3 viewOS = normalize(mul((float3x3)unity_WorldToObject, normalize(camPos - center)));
- int2 cell = GetCell(viewOS);
- float3 snapOS = CellDir(cell);
- float3 snapWS = normalize(mul((float3x3)unity_ObjectToWorld, snapOS));
+ // Get continuous grid position and find the 3 frames
+ float gridRes = (float)_GridResolution;
+ float2 grid = GridFromDir(viewOS, gridRes);
+ float2 gridFloor = floor(grid);
+ float2 gridFrac = frac(grid);
+
+ // Compute blend weights and determine which triangle
+ float4 weights = QuadBlendWeights(gridFrac);
+ o.blendWeights = weights.xyz;
+
+ // Frame 1: base corner
+ o.cell1 = gridFloor;
+
+ // Frame 2: depends on which triangle (lower-left vs upper-right)
+ // weights.w = 0: lower-left triangle -> (0,1) offset
+ // weights.w = 1: upper-right triangle -> (1,0) offset
+ o.cell2 = clamp(gridFloor + lerp(float2(0,1), float2(1,0), weights.w), 0, gridRes - 1);
+
+ // Frame 3: diagonal corner
+ o.cell3 = clamp(gridFloor + float2(1,1), 0, gridRes - 1);
+
+ // Get directions for each frame
+ float3 dir1 = DirFromCell(o.cell1, gridRes);
+ float3 dir2 = DirFromCell(o.cell2, gridRes);
+ float3 dir3 = DirFromCell(o.cell3, gridRes);
+
+ // Billboard facing the camera direction (not snapped)
+ float3 viewWS = normalize(camPos - center);
float3 right, up;
- BillboardBasis(snapWS, right, up);
+ BillboardBasis(viewWS, right, up);
float3 worldPos = center + v.vertex.x * right * scale.x + v.vertex.y * up * scale.y;
#ifdef IMPOSTOR_SHADOW_PASS
v.vertex = mul(unity_WorldToObject, float4(worldPos, 1));
- v.normal = -snapWS;
+ v.normal = -viewWS;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
#else
o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1));
UNITY_TRANSFER_FOG(o, o.pos);
#endif
- float3 localOff = mul((float3x3)unity_WorldToObject, worldPos - center);
- BillboardBasis(snapOS, right, up);
- o.uv = float2(1 - (dot(localOff, right) + 0.5), dot(localOff, up) + 0.5);
- o.cell = float2(cell);
+ // Compute virtual plane UVs for all 3 frames
+ // pivotToCam and vertexToCam in object space
+ float3 pivotToCamOS = mul((float3x3)unity_WorldToObject, camPos - center);
+ float3 vertexPosOS = mul((float3x3)unity_WorldToObject, worldPos - center);
+ float3 vertexToCamOS = pivotToCamOS - vertexPosOS;
+
+ // size = 0.5 because the mesh is a unit quad (-0.5 to 0.5)
+ o.uv1 = VirtualPlaneUV(dir1, pivotToCamOS, vertexToCamOS, 0.5);
+ o.uv2 = VirtualPlaneUV(dir2, pivotToCamOS, vertexToCamOS, 0.5);
+ o.uv3 = VirtualPlaneUV(dir3, pivotToCamOS, vertexToCamOS, 0.5);
return o;
}
+float4 BlendFrames(v2f i) {
+ float4 s1 = SampleAtlas(i.uv1, i.cell1);
+ float4 s2 = SampleAtlas(i.uv2, i.cell2);
+ float4 s3 = SampleAtlas(i.uv3, i.cell3);
+ return s1 * i.blendWeights.x + s2 * i.blendWeights.y + s3 * i.blendWeights.z;
+}
+
#ifdef IMPOSTOR_SHADOW_PASS
float4 frag(v2f i) : SV_Target {
- float4 col = SampleAtlas(i.uv, i.cell);
+ float4 col = BlendFrames(i);
clip(col.a - _Cutoff);
SHADOW_CASTER_FRAGMENT(i)
}
#else
float4 frag(v2f i) : SV_Target {
- float4 col = SampleAtlas(i.uv, i.cell);
+ float4 col = BlendFrames(i);
if (_DebugMode > 0.5)
- return float4(i.uv, 0, 1);
+ return float4(i.blendWeights.xy, 0, 1);
col *= _Color;
clip(col.a - _Cutoff);