summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--3ner.shader1
-rw-r--r--Scripts/Impostors.cs65
-rw-r--r--globals.cginc1
-rw-r--r--impostor.cginc121
4 files changed, 167 insertions, 21 deletions
diff --git a/3ner.shader b/3ner.shader
index b210532..94a41e1 100644
--- a/3ner.shader
+++ b/3ner.shader
@@ -387,6 +387,7 @@ Shader "yum_food/3ner"
_Impostors_Grid_Resolution("Grid Resolution", Int) = 5
_Impostors_Sphere_Radius("Sphere Radius", Float) = 1.0
_Impostors_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5
+ _Impostors_Parallax("Parallax Strength", Range(0, 1)) = 1
[Toggle] _Impostors_Debug_Mode("Debug Mode", Float) = 0
[Toggle] _Impostors_Debug_Depth("Debug Depth", Float) = 0
diff --git a/Scripts/Impostors.cs b/Scripts/Impostors.cs
index ffd32b4..46e6cd1 100644
--- a/Scripts/Impostors.cs
+++ b/Scripts/Impostors.cs
@@ -26,7 +26,7 @@ public class Impostors : MonoBehaviour
private Material impostorMaterial;
public bool HasImpostor => impostorObject != null;
- private float Radius => sphere_radius_ * transform.lossyScale.x;
+ private float Radius => sphere_radius_ * Mathf.Max(transform.lossyScale.x, transform.lossyScale.y, transform.lossyScale.z);
private const string OutputFolder = "Assets/yum_food/3ner/Impostor_Generated";
void OnEnable() => Camera.onPreRender += UpdateMainCameraPos;
@@ -35,7 +35,7 @@ public class Impostors : MonoBehaviour
static void UpdateMainCameraPos(Camera cam)
{
if (cam.cameraType == CameraType.Game || cam.cameraType == CameraType.SceneView)
- Shader.SetGlobalVector("_ImpostorMainCameraPos", cam.transform.position);
+ Shader.SetGlobalVector("_Impostors_Main_Camera_Pos", cam.transform.position);
}
void OnDrawGizmos()
@@ -105,9 +105,9 @@ public class Impostors : MonoBehaviour
Camera cam = camObj.AddComponent<Camera>();
cam.orthographic = true;
- cam.orthographicSize = sphere_radius_;
+ cam.orthographicSize = Radius;
cam.nearClipPlane = nearClippingDistance;
- cam.farClipPlane = sphere_radius_ * 2f + nearClippingDistance;
+ cam.farClipPlane = Radius * 2f + nearClippingDistance;
cam.cullingMask = cullingMask;
cam.clearFlags = renderSkybox ? CameraClearFlags.Skybox : CameraClearFlags.SolidColor;
cam.backgroundColor = Color.clear;
@@ -337,6 +337,61 @@ public class Impostors : MonoBehaviour
tex.Apply();
}
+ void DilateDepthTexture(Texture2D depthTex, Texture2D alphaSource, int iterations = 8)
+ {
+ int w = depthTex.width, h = depthTex.height;
+ Color[] depth = depthTex.GetPixels();
+ Color[] mask = alphaSource.GetPixels();
+
+ bool[] filled = new bool[depth.Length];
+ for (int i = 0; i < depth.Length; i++)
+ filled[i] = mask[i].a > 0.01f;
+
+ Color[] buffer = (Color[])depth.Clone();
+ bool[] filledNext = new bool[filled.Length];
+
+ int[] dx = { -1, 1, 0, 0 };
+ int[] dy = { 0, 0, -1, 1 };
+
+ for (int iter = 0; iter < iterations; iter++)
+ {
+ System.Array.Copy(filled, filledNext, filled.Length);
+
+ for (int y = 0; y < h; y++)
+ {
+ for (int x = 0; x < w; x++)
+ {
+ int i = y * w + x;
+ if (filled[i]) continue;
+
+ float sum = 0f;
+ int count = 0;
+ for (int d = 0; d < 4; d++)
+ {
+ int nx = x + dx[d], ny = y + dy[d];
+ if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue;
+ int ni = ny * w + nx;
+ if (!filled[ni]) continue;
+ sum += depth[ni].r;
+ count++;
+ }
+
+ if (count > 0)
+ {
+ buffer[i].r = sum / count;
+ filledNext[i] = true;
+ }
+ }
+ }
+
+ var tmpFilled = filled; filled = filledNext; filledNext = tmpFilled;
+ var tmpDepth = depth; depth = buffer; buffer = tmpDepth;
+ }
+
+ depthTex.SetPixels(depth);
+ depthTex.Apply();
+ }
+
void SaveAndConfigureTexture(Texture2D atlas, TextureExportSettings settings, string baseName, out string path)
{
path = Path.Combine(OutputFolder, $"{baseName}_{settings.suffix}.{(settings.isEXR ? "exr" : "png")}");
@@ -386,6 +441,7 @@ public class Impostors : MonoBehaviour
DilateTexture(albedoAtlas, albedoAtlas, preserveAlpha: true);
DilateTexture(normalAtlas, albedoAtlas);
DilateTexture(metallicGlossAtlas, albedoAtlas);
+ DilateDepthTexture(depthAtlas, albedoAtlas);
Texture2D[] atlases = { albedoAtlas, normalAtlas, metallicGlossAtlas, depthAtlas };
string[] paths = new string[exportSettings.Length];
@@ -426,6 +482,7 @@ public class Impostors : MonoBehaviour
impostorMaterial.SetInt("_Impostors_Grid_Resolution", gridResolution);
impostorMaterial.SetFloat("_Impostors_Sphere_Radius", sphere_radius_);
impostorMaterial.SetFloat("_Impostors_Enabled", 1);
+ impostorMaterial.SetFloat("_Impostors_Parallax", 1);
impostorMaterial.SetFloat("_Cull", (float)UnityEngine.Rendering.CullMode.Front);
AssetDatabase.CreateAsset(impostorMaterial, Path.Combine(OutputFolder, $"{baseName}_mat.mat"));
diff --git a/globals.cginc b/globals.cginc
index b1de3d8..6387c7e 100644
--- a/globals.cginc
+++ b/globals.cginc
@@ -190,6 +190,7 @@ Texture2D _Impostors_Depth_Atlas;
int _Impostors_Grid_Resolution;
float _Impostors_Sphere_Radius;
float _Impostors_Cutoff;
+float _Impostors_Parallax;
float _Impostors_Debug_Mode;
float _Impostors_Debug_Depth;
float3 _Impostors_Main_Camera_Pos;
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);