From ad551018904f13b8af0d1c4c2aa8380ac57de89a Mon Sep 17 00:00:00 2001 From: yum Date: Tue, 13 Jan 2026 20:42:27 -0800 Subject: Impostors: fix depth capture --- DepthBlit.shader | 52 ++++++++++++++++++++++++ Impostor.shader | 3 ++ Scripts/Impostors.cs | 109 +++++++++++++++++++++++++++++++++++++++------------ impostor.cginc | 67 +++++++++++++++++++++++++++---- 4 files changed, 198 insertions(+), 33 deletions(-) create mode 100644 DepthBlit.shader diff --git a/DepthBlit.shader b/DepthBlit.shader new file mode 100644 index 0000000..04187c2 --- /dev/null +++ b/DepthBlit.shader @@ -0,0 +1,52 @@ +Shader "Hidden/yum_food/DepthBlit" +{ + // Reads from depth texture and outputs linearized depth to color + Properties + { + _MainTex ("", 2D) = "white" {} + } + + SubShader + { + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + sampler2D _DepthTex; + + struct v2f + { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + v2f vert(float4 vertex : POSITION, float2 uv : TEXCOORD0) + { + v2f o; + o.pos = UnityObjectToClipPos(vertex); + o.uv = uv; + return o; + } + + float4 frag(v2f i) : SV_Target + { + float rawDepth = tex2D(_DepthTex, i.uv).r; + + // Orthographic depth is already linear. + // We just need to handle the platform-specific Z-buffer direction. + #if defined(UNITY_REVERSED_Z) + return 1.0 - rawDepth; + #else + return rawDepth; + #endif + } + ENDCG + } + } +} diff --git a/Impostor.shader b/Impostor.shader index 1dff6c9..f2eb602 100644 --- a/Impostor.shader +++ b/Impostor.shader @@ -3,11 +3,14 @@ Shader "yum_food/Gimmicks/Impostors" Properties { _ImpostorAtlas("Impostor Atlas", 2D) = "white" {} + _ImpostorDepthAtlas("Impostor Depth Atlas", 2D) = "white" {} _GridResolution("Grid Resolution", Int) = 5 + _SphereRadius("Sphere Radius", Float) = 1.0 _Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5 _Color("Tint", Color) = (1, 1, 1, 1) [Toggle] _DebugMode("Debug Mode", Float) = 0 + [Toggle] _DebugDepth("Debug Depth", Float) = 0 [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 0 [Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Int) = 1 diff --git a/Scripts/Impostors.cs b/Scripts/Impostors.cs index 6d8d67b..ded7e6d 100644 --- a/Scripts/Impostors.cs +++ b/Scripts/Impostors.cs @@ -130,29 +130,67 @@ public class Impostors : MonoBehaviour SetRenderersEnabled(true); if (cameras == null || cameras.Length != gridResolution * gridResolution || cameras[0] == null) CreateCameras(); + // Load depth blit shader + Shader depthBlitShader = Shader.Find("Hidden/yum_food/DepthBlit"); + if (depthBlitShader == null) { Debug.LogError("DepthBlit shader not found"); return; } + Material depthBlitMat = new Material(depthBlitShader); + // Render atlas int size = cameraResolution * gridResolution; - Texture2D atlas = new Texture2D(size, size, TextureFormat.RGBA32, false); - RenderTexture rt = RenderTexture.GetTemporary(cameraResolution, cameraResolution, 24); + Texture2D colorAtlas = new Texture2D(size, size, TextureFormat.RGBA32, false); + Texture2D depthAtlas = new Texture2D(size, size, TextureFormat.RFloat, false); + + // Create RT with depth buffer that can be sampled as texture + RenderTextureDescriptor desc = new RenderTextureDescriptor(cameraResolution, cameraResolution, RenderTextureFormat.ARGB32, 24); + desc.sRGB = true; + RenderTexture colorRT = RenderTexture.GetTemporary(desc); + + // Separate depth texture + RenderTextureDescriptor depthDesc = new RenderTextureDescriptor(cameraResolution, cameraResolution, RenderTextureFormat.Depth, 24); + RenderTexture depthOnlyRT = RenderTexture.GetTemporary(depthDesc); + + // Output for linearized depth + RenderTexture linearDepthRT = RenderTexture.GetTemporary(cameraResolution, cameraResolution, 0, RenderTextureFormat.RFloat); int idx = 0; for (int y = 0; y < gridResolution; y++) { for (int x = 0; x < gridResolution; x++) { - cameras[idx++].targetTexture = rt; - cameras[idx - 1].Render(); - RenderTexture.active = rt; - Texture2D temp = new Texture2D(cameraResolution, cameraResolution); - temp.ReadPixels(new Rect(0, 0, cameraResolution, cameraResolution), 0, 0); - temp.Apply(); - atlas.SetPixels(x * cameraResolution, y * cameraResolution, cameraResolution, cameraResolution, temp.GetPixels()); - DestroyImmediate(temp); + Camera cam = cameras[idx++]; + + // Render to color + depth buffers simultaneously + cam.SetTargetBuffers(colorRT.colorBuffer, depthOnlyRT.depthBuffer); + cam.Render(); + + // Read color + RenderTexture.active = colorRT; + Texture2D colorTemp = new Texture2D(cameraResolution, cameraResolution); + colorTemp.ReadPixels(new Rect(0, 0, cameraResolution, cameraResolution), 0, 0); + colorTemp.Apply(); + colorAtlas.SetPixels(x * cameraResolution, y * cameraResolution, cameraResolution, cameraResolution, colorTemp.GetPixels()); + DestroyImmediate(colorTemp); + + // Blit depth buffer through linearization shader + depthBlitMat.SetTexture("_DepthTex", depthOnlyRT); + Graphics.Blit(null, linearDepthRT, depthBlitMat); + + // Read linearized depth + RenderTexture.active = linearDepthRT; + Texture2D depthTemp = new Texture2D(cameraResolution, cameraResolution, TextureFormat.RFloat, false); + depthTemp.ReadPixels(new Rect(0, 0, cameraResolution, cameraResolution), 0, 0); + depthTemp.Apply(); + depthAtlas.SetPixels(x * cameraResolution, y * cameraResolution, cameraResolution, cameraResolution, depthTemp.GetPixels()); + DestroyImmediate(depthTemp); } } - atlas.Apply(); + colorAtlas.Apply(); + depthAtlas.Apply(); RenderTexture.active = null; - RenderTexture.ReleaseTemporary(rt); + RenderTexture.ReleaseTemporary(colorRT); + RenderTexture.ReleaseTemporary(depthOnlyRT); + RenderTexture.ReleaseTemporary(linearDepthRT); + DestroyImmediate(depthBlitMat); // Save if (!AssetDatabase.IsValidFolder(OutputFolder)) @@ -163,24 +201,43 @@ public class Impostors : MonoBehaviour } string name = gameObject.name.Replace(" ", "_"); - string path = Path.Combine(OutputFolder, $"{name}_atlas.png"); - File.WriteAllBytes(Path.Combine(Application.dataPath, "..", path), atlas.EncodeToPNG()); - DestroyImmediate(atlas); + string colorPath = Path.Combine(OutputFolder, $"{name}_atlas.png"); + string depthPath = Path.Combine(OutputFolder, $"{name}_depth.exr"); + + File.WriteAllBytes(Path.Combine(Application.dataPath, "..", colorPath), colorAtlas.EncodeToPNG()); + File.WriteAllBytes(Path.Combine(Application.dataPath, "..", depthPath), depthAtlas.EncodeToEXR(Texture2D.EXRFlags.OutputAsFloat)); + DestroyImmediate(colorAtlas); + DestroyImmediate(depthAtlas); AssetDatabase.Refresh(); - TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; - if (importer != null) + + // Configure color atlas importer + TextureImporter colorImporter = AssetImporter.GetAtPath(colorPath) as TextureImporter; + if (colorImporter != null) + { + colorImporter.mipmapEnabled = true; + colorImporter.alphaIsTransparency = true; + colorImporter.wrapMode = TextureWrapMode.Clamp; + colorImporter.filterMode = FilterMode.Trilinear; + colorImporter.SaveAndReimport(); + } + + // Configure depth atlas importer + TextureImporter depthImporter = AssetImporter.GetAtPath(depthPath) as TextureImporter; + if (depthImporter != null) { - importer.mipmapEnabled = true; - importer.alphaIsTransparency = true; - importer.wrapMode = TextureWrapMode.Clamp; - importer.filterMode = FilterMode.Trilinear; - importer.SaveAndReimport(); + depthImporter.mipmapEnabled = false; + depthImporter.sRGBTexture = false; // Linear data + depthImporter.wrapMode = TextureWrapMode.Clamp; + depthImporter.filterMode = FilterMode.Bilinear; + depthImporter.textureCompression = TextureImporterCompression.Uncompressed; + depthImporter.SaveAndReimport(); } // Create impostor - Texture2D tex = AssetDatabase.LoadAssetAtPath(path); - if (tex != null) + Texture2D colorTex = AssetDatabase.LoadAssetAtPath(colorPath); + Texture2D depthTex = AssetDatabase.LoadAssetAtPath(depthPath); + if (colorTex != null) { DestroyExistingImpostor(); @@ -188,8 +245,10 @@ public class Impostors : MonoBehaviour if (shader == null) { Debug.LogError("Shader not found"); return; } impostorMaterial = new Material(shader); - impostorMaterial.SetTexture("_ImpostorAtlas", tex); + impostorMaterial.SetTexture("_ImpostorAtlas", colorTex); + impostorMaterial.SetTexture("_ImpostorDepthAtlas", depthTex); impostorMaterial.SetInt("_GridResolution", gridResolution); + impostorMaterial.SetFloat("_SphereRadius", sphere_radius_); AssetDatabase.CreateAsset(impostorMaterial, Path.Combine(OutputFolder, $"{name}_mat.mat")); impostorObject = GameObject.CreatePrimitive(PrimitiveType.Quad); diff --git a/impostor.cginc b/impostor.cginc index e265438..f6c0d72 100644 --- a/impostor.cginc +++ b/impostor.cginc @@ -6,13 +6,16 @@ SamplerState bilinear_clamp_s; Texture2D _ImpostorAtlas; +Texture2D _ImpostorDepthAtlas; int _GridResolution; +float _SphereRadius; float _Cutoff; float3 _ImpostorMainCameraPos; #ifndef IMPOSTOR_SHADOW_PASS float4 _Color; float _DebugMode; +float _DebugDepth; #endif float2 HemiOctEncode(float3 N) { @@ -45,6 +48,10 @@ float4 SampleAtlas(float2 uv, float2 cell) { return _ImpostorAtlas.Sample(bilinear_clamp_s, (cell + uv) / _GridResolution); } +float SampleDepthAtlas(float2 uv, float2 cell) { + return _ImpostorDepthAtlas.Sample(bilinear_clamp_s, (cell + uv) / _GridResolution).r; +} + // ============================================================================ // Vertex/Fragment // ============================================================================ @@ -62,10 +69,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; + // For depth reconstruction + float3 worldPos : TEXCOORD3; + float3 viewWS : TEXCOORD4; + float3 center : TEXCOORD5; + float radius : TEXCOORD6; UNITY_VERTEX_OUTPUT_STEREO }; @@ -108,22 +120,61 @@ v2f vert(appdata v) { BillboardBasis(snapOS, right, up); o.uv = float2(1 - (dot(localOff, right) + 0.5), dot(localOff, up) + 0.5); o.cell = float2(cell); + + // Pass data for depth reconstruction + float3 viewWS = normalize(camPos - center); + o.worldPos = worldPos; + o.viewWS = viewWS; + o.center = center; + o.radius = _SphereRadius * scale.x; + return o; } +#ifdef IMPOSTOR_SHADOW_PASS +// Shadow pass - just alpha test, no depth output float4 frag(v2f i) : SV_Target { float4 col = SampleAtlas(i.uv, i.cell); -#ifndef IMPOSTOR_SHADOW_PASS - if (_DebugMode > 0.5) return float4(1, 0, 0, 1); - col *= _Color; -#endif clip(col.a - _Cutoff); -#ifdef IMPOSTOR_SHADOW_PASS SHADOW_CASTER_FRAGMENT(i) +} #else +// Forward pass - with depth output +struct FragOutput { + float4 color : SV_Target; + float depth : SV_Depth; +}; + +FragOutput frag(v2f i) { + FragOutput o; + + float4 col = SampleAtlas(i.uv, i.cell); + float depth = SampleDepthAtlas(i.uv, i.cell); + + // Compute depth-corrected world position + // depth=0 means front of sphere (toward camera), depth=1 means back (away from camera) + float3 depthWorldPos = i.worldPos - i.viewWS * (2.0 * depth - 1.0) * i.radius; + + // Convert to clip space depth + float4 clipPos = mul(UNITY_MATRIX_VP, float4(depthWorldPos, 1.0)); + o.depth = clipPos.z / clipPos.w; + + if (_DebugMode > 0.5) { + o.color = float4(i.uv, 0, 1); + return o; + } + if (_DebugDepth > 0.5) { + o.color = float4(depth.xxx, 1); + return o; + } + + col *= _Color; + clip(col.a - _Cutoff); + UNITY_APPLY_FOG(i.fogCoord, col); - return col; -#endif + o.color = col; + return o; } +#endif #endif -- cgit v1.2.3