summaryrefslogtreecommitdiffstats
path: root/impostor.cginc
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-14 23:00:42 -0800
committeryum <yum.food.vr@gmail.com>2026-01-14 23:00:42 -0800
commitd1eb208ecbeba1ab7bf894d9f14d2652739bc63e (patch)
treef80778fef335ea69fb1cb8892e908a56be0fef1b /impostor.cginc
parentf0d753b1cfa07079886abe0c3c5fc7ee75b426fd (diff)
Impostors: integrate into 3ner
Diffstat (limited to 'impostor.cginc')
-rw-r--r--impostor.cginc151
1 files changed, 49 insertions, 102 deletions
diff --git a/impostor.cginc b/impostor.cginc
index 4236778..1d5c9b5 100644
--- a/impostor.cginc
+++ b/impostor.cginc
@@ -2,19 +2,10 @@
#define __IMPOSTOR_INC
#include "UnityCG.cginc"
+#include "globals.cginc"
#include "vertex_deformation.hlsl"
-SamplerState bilinear_clamp_s;
-Texture2D _ImpostorAtlas;
-float4 _ImpostorAtlas_TexelSize;
-int _GridResolution;
-float _Cutoff;
-float _SphereRadius;
-float3 _ImpostorMainCameraPos;
-
-float4 _Color;
-float _DebugMode;
-
+// Utility functions for hemispherical octahedral mapping
float2 HemiOctEncode(float3 N) {
N.y = max(N.y, 1e-4);
float3 p = hemi_octahedron_to_plane(normalize(N), 0, float3(1,0,0), float3(0,1,0), 1);
@@ -40,27 +31,7 @@ float3 DirFromCell(float2 cell, float gridRes) {
return HemiOctDecode(uv);
}
-float2 ClampUvInCell(float2 uv) {
- uv = saturate(uv);
- float2 halfTexelInCell = 0.5 * _ImpostorAtlas_TexelSize.xy * (float)_GridResolution;
- return clamp(uv, halfTexelInCell, 1.0 - halfTexelInCell);
-}
-
-float4 SampleAtlasGrad(float2 uv, float2 cell, float2 uvGradX, float2 uvGradY) {
- uv = ClampUvInCell(uv);
-
- float invGridRes = rcp((float)_GridResolution);
- float2 atlasUv = (cell + uv) * invGridRes;
-
- // Important: gradients must be computed in-cell; do not let integer cell offsets affect mip selection.
- return _ImpostorAtlas.SampleGrad(bilinear_clamp_s, atlasUv, uvGradX * invGridRes, uvGradY * invGridRes);
-}
-
-// 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.
+// Branchless barycentric weights in a unit square split into two triangles
float4 GridCellBarycentric4(float2 p) {
float w00 = 1.0 - max(p.x, p.y);
float w11 = min(p.x, p.y);
@@ -86,40 +57,43 @@ float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam) {
return uv * -1.0 + 0.5;
}
-struct appdata {
- float4 vertex : POSITION;
- UNITY_VERTEX_INPUT_INSTANCE_ID
-#ifdef IMPOSTOR_SHADOW_PASS
- float3 normal : NORMAL;
-#endif
-};
+// General purpose ray-sphere intersection
+bool RaySphereIntersect(float3 ro, float3 rayDir, float3 origin, float radius) {
+ float3 originToRo = ro - origin;
+ float b = dot(originToRo, rayDir);
+ float c = dot(originToRo, originToRo) - radius * radius;
+ return (b * b - c) >= 0.0;
+}
-struct v2f {
-#ifdef IMPOSTOR_SHADOW_PASS
- V2F_SHADOW_CASTER;
-#else
- float4 pos : SV_POSITION;
- UNITY_FOG_COORDS(7)
-#endif
- float3 worldPos : TEXCOORD1;
- float3 centerPos : TEXCOORD2;
- UNITY_VERTEX_OUTPUT_STEREO
-};
+#if defined(_IMPOSTORS)
+
+float2 ClampUvInCell(float2 uv) {
+ uv = saturate(uv);
+ float2 halfTexelInCell = 0.5 * _Impostors_Atlas_TexelSize.xy * (float)_Impostors_Grid_Resolution;
+ return clamp(uv, halfTexelInCell, 1.0 - halfTexelInCell);
+}
+
+float4 SampleAtlasGrad(float2 uv, float2 cell, float2 uvGradX, float2 uvGradY) {
+ uv = ClampUvInCell(uv);
-v2f vert(appdata v) {
- v2f o;
- UNITY_SETUP_INSTANCE_ID(v);
- UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
+ float invGridRes = rcp((float)_Impostors_Grid_Resolution);
+ float2 atlasUv = (cell + uv) * invGridRes;
+
+ // Important: gradients must be computed in-cell; do not let integer cell offsets affect mip selection.
+ return _Impostors_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, uvGradX * invGridRes, uvGradY * invGridRes);
+}
+// Billboard vertex transformation for impostors
+void impostor_vert(float4 vertexOS, inout float3 worldPos) {
float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
- o.centerPos = center;
+
float3 scale = float3(
length(unity_ObjectToWorld._m00_m10_m20),
length(unity_ObjectToWorld._m01_m11_m21),
length(unity_ObjectToWorld._m02_m12_m22));
-#ifdef IMPOSTOR_SHADOW_PASS
- float3 camPos = _ImpostorMainCameraPos;
+#ifdef SHADOW_CASTER_PASS
+ float3 camPos = _Impostors_Main_Camera_Pos;
#else
float3 camPos = _WorldSpaceCameraPos;
#endif
@@ -128,56 +102,35 @@ v2f vert(appdata v) {
float3 viewWS = normalize(camPos - center);
float3 right, up;
BillboardBasis(viewWS, right, up);
- float3 worldPos = center + v.vertex.x * right * scale.x + v.vertex.y * up * scale.y;
- o.worldPos = worldPos;
-
-#ifdef IMPOSTOR_SHADOW_PASS
- v.vertex = mul(unity_WorldToObject, float4(worldPos, 1));
- 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
-
- return o;
+ worldPos = center + vertexOS.x * right * scale.x + vertexOS.y * up * scale.y;
}
-// General purpose ray-sphere intersection.
-bool RaySphereIntersect(float3 ro, float3 rayDir, float3 origin, float radius) {
- float3 originToRo = ro - origin;
- float b = dot(originToRo, rayDir);
- float c = dot(originToRo, originToRo) - radius * radius;
- return (b * b - c) >= 0.0;
-}
+// Sample impostor atlas with view-dependent blending
+float4 impostor_frag(float3 worldPos) {
+ // Calculate center in fragment shader to avoid extra interpolator
+ float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz;
-float4 frag(v2f i) : SV_Target {
// Sphere culling first
- float3 viewDir = normalize(i.worldPos - _WorldSpaceCameraPos);
- bool didIntersect = RaySphereIntersect(_WorldSpaceCameraPos, viewDir, i.centerPos, _SphereRadius);
- clip(didIntersect - 0.5);
-
- // Camera position for grid computation (matches billboard orientation)
-#ifdef IMPOSTOR_SHADOW_PASS
- float3 camPos = _ImpostorMainCameraPos;
+#ifdef SHADOW_CASTER_PASS
+ float3 camPos = _Impostors_Main_Camera_Pos;
#else
float3 camPos = _WorldSpaceCameraPos;
#endif
- float3 center = i.centerPos;
+ float3 viewDir = normalize(worldPos - camPos);
+ bool didIntersect = RaySphereIntersect(camPos, viewDir, center, _Impostors_Sphere_Radius);
+ clip(didIntersect - 0.5);
// For lattice lookup, use the camera-to-impostor-center direction (matches billboard orientation).
- // Using the per-fragment view ray (camera→quad point) causes the selected lattice points/weights
- // to vary across the billboard, which reads as "not smooth" while moving around the impostor.
float3x3 worldToObject = (float3x3)unity_WorldToObject;
float3 viewOS = normalize(mul(worldToObject, normalize(camPos - center)));
// Get continuous grid position and find the 4 frames
- float gridRes = (float)_GridResolution;
+ float gridRes = (float)_Impostors_Grid_Resolution;
float2 grid = GridFromDir(viewOS, gridRes);
float2 gridFloor = floor(grid);
float2 gridFrac = frac(grid);
- // Branchless barycentric blend weights (avoids discontinuities along triangle/cell boundaries).
+ // Branchless barycentric blend weights
float4 bw = GridCellBarycentric4(gridFrac);
// Frame cells (square corners)
@@ -194,7 +147,7 @@ float4 frag(v2f i) : SV_Target {
// Compute virtual plane UVs for all 4 frames
float3 pivotToCamOS = mul(worldToObject, camPos - center);
- float3 vertexPosOS = mul(worldToObject, i.worldPos - center);
+ float3 vertexPosOS = mul(worldToObject, worldPos - center);
float3 vertexToCamOS = pivotToCamOS - vertexPosOS;
float2 uv00 = VirtualPlaneUV(dir00, pivotToCamOS, vertexToCamOS);
@@ -209,19 +162,13 @@ float4 frag(v2f i) : SV_Target {
float4 s11 = SampleAtlasGrad(uv11, cell11, ddx(uv11), ddy(uv11));
float4 col = s00 * bw.x + s01 * bw.y + s10 * bw.z + s11 * bw.w;
-#ifndef IMPOSTOR_SHADOW_PASS
- if (_DebugMode > 0.5)
+ if (_Impostors_Debug_Mode > 0.5)
return float4(bw.yz, 0, 1);
-#endif
- col *= _Color;
- clip(col.a - _Cutoff);
-#ifdef IMPOSTOR_SHADOW_PASS
- SHADOW_CASTER_FRAGMENT(i)
-#else
- UNITY_APPLY_FOG(i.fogCoord, col);
+ clip(col.a - _Impostors_Cutoff);
return col;
-#endif
}
-#endif
+#endif // _IMPOSTORS
+
+#endif // __IMPOSTOR_INC