# Question Depth Texture Perspective Distortion

Discussion in 'Shaders' started by HamLard, Dec 28, 2022.

1. ### HamLard

Joined:
Nov 27, 2016
Posts:
8
Hi,

I'm writing a shader that makes use of the _CameraDepthTexture, but am noticing a strange artifact where the depth is less towards the center of the camera's view. The attached images are of a scene with a larger cube outputting the processed difference between the depth texture sample and its own distance from the camera.

What's causing the depth distortion?

2. ### halley

Joined:
Aug 26, 2013
Posts:
1,399
Technically speaking, things do get closer as they're closer to the center of the screen. Stand five paces from a flat brick wall and stare directly at the wall. The closest brick to your eye is the one directly in front of it.

It seems like you may be exaggerating that difference somehow, maybe by normalizing or "saturating" it?

3. ### bgolus

Joined:
Dec 7, 2012
Posts:
12,005
Distance is not depth.

Distance is a measure of how far apart two points are, in this case the camera position as measured in 3D space (either world or view space).

Depth is a measure of how far apart a point is from a constant plane, in this case the view plane (or how far the camera is from a plane parallel with the view plane that intersects with the other point).

If you’re trying to compare camera depth and distance, unless you’re comparing at a pixel at the exact center of the screen, these are two different types of measurements, and you’re going to get confusing results.

That said, as @halley tried to describe, turning the camera can have unexpected results for both depth and distance depending on how you’re thinking about them. Though I think the description they tried to use only confuses the situation more as in your case you’re comparing the depth of a cube with the distance of another cube that overlaps it. The depth values in the camera depth texture will absolutely change as you rotate, getting “closer” as the camera rotates, though the distance should not. Though it would help to see the shader to get a better understanding of what you’re trying to do with it, and to help point you in the right way to use the values from the depth texture.

4. ### HamLard

Joined:
Nov 27, 2016
Posts:
8
Thanks for the responses @halley and @bgolus!

I see now how I'm confusing the depth sample with the distance from the camera. The shader I'm writing is using ray marching to sample a height map texture in a cube volume, as shown in the below images. What I want to do is limit the distance the ray marches using the depth texture, in case it collides with solid objects before reaching the other side of the cube.

What I'm thinking at the moment, is that I need to somehow derive the world space position of a fragment given it's depth, although I'm not sure how to do that (my maths is a bit shaky).

Let me know if I can provide any further information, any help is much appreciated!

Code (CSharp):
2. {
3.     Properties
4.     {
5.         _MainTex ("Texture", 2D) = "white" {}
6.         _Lacunarity ("Lacunarity", Float) = 2.0
7.         _Persistence ("Persistence", Range(0.0, 1.0)) = 0.5
8.         _NoiseScale ("Noise Scale", Float) = 1.0
9.         _DisplacementScale ("Displacement Scale", Float) = 1.0
10.         _Samples ("Samples", Int) = 10
11.     }
13.     {
14.         Blend SrcAlpha OneMinusSrcAlpha
15.         ZWrite Off ZTest Always Cull Off
16.         Tags { "RenderType"="Transparent" "Queue"="Transparent" }
17.
18.         Pass
19.         {
20.             CGPROGRAM
21.             #pragma vertex vert
22.             #pragma fragment frag
23.
24.             #include "UnityCG.cginc"
25.
26.             struct appdata
27.             {
28.                 float4 vertex : POSITION;
29.                 float2 uv : TEXCOORD0;
30.             };
31.
32.             struct v2f
33.             {
34.                 float4 vertex : SV_POSITION;
35.                 float2 uv : TEXCOORD0;
36.                 float3 object_position : TEXCOORD1;
37.                 float4 screen_pos : TEXCOORD2;
38.             };
39.
40.             sampler2D _MainTex;
41.             float4 _MainTex_ST;
43.             uint _Samples;
44.             float _Lacunarity;
45.             float _Persistence;
46.             uint _Octave;
47.             float _NoiseScale;
48.             float _DisplacementScale;
49.
50.             // Calculates points of intersection between a ray and a box, returning the distance from the ray origin to the box, and the distance the ray travels in the box.
51.             float2 ray_box_distance(const float3 min_bounds, const float3 max_bounds, const float3 ray_origin, const float3 ray_direction)
52.             {
53.                 const float3 t0 = (min_bounds - ray_origin) / ray_direction;
54.                 const float3 t1 = (max_bounds - ray_origin) / ray_direction;
55.                 const float3 t_min = min(t0, t1);
56.                 const float3 t_max = max(t0, t1);
57.
58.                 const float a_dst = max(max(t_min.x, t_min.y), t_min.z);
59.                 const float b_dst = min(t_max.x, min(t_max.y, t_max.z));
60.
61.                 const float dst_to_box = max(0, a_dst);
62.                 const float dst_in_box = max(0, b_dst - dst_to_box);
63.
64.                 return float2(dst_to_box, dst_in_box);
65.             }
66.
67.             v2f vert (appdata v)
68.             {
69.                 v2f o;
70.                 o.vertex = UnityObjectToClipPos(v.vertex);
71.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
72.                 o.object_position = v.vertex.xyz;
73.                 o.screen_pos = ComputeScreenPos(o.vertex);
74.                 return o;
75.             }
76.
77.             fixed4 frag (v2f i) : SV_Target
78.             {
79.                 // Cube bounds (object space)
80.                 const float3 max_bounds = float3(0.5, 0.5, 0.5);
81.                 const float3 min_bounds = -max_bounds;
82.
83.                 // Cube intersection (object space)
84.                 const float3 ray_origin = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
85.                 const float3 ray_direction = normalize(i.object_position - mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1)));
86.
87.                 const float2 box_info = ray_box_distance(min_bounds, max_bounds, ray_origin, ray_direction);
88.                 const float dst_to = box_info.x;
89.                 const float dst_in = box_info.y;
90.
91.                 // Sample depth texture
92.                 const float2 screen_uv = i.screen_pos.xy / i.screen_pos.w;
93.                 const float depth_01 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UNITY_PROJ_COORD(screen_uv)).r;
94.                 const float depth_linear = LinearEyeDepth(depth_01);
95.
96.                 // Convert depth to a world space distance
97.                 const float3 ray_direction_world = normalize(UnityObjectToWorldDir(ray_direction)) * depth_linear;
98.                 const float object_depth = length(mul(unity_WorldToObject, ray_direction_world));
99.                 const float dst_limit = min(object_depth - dst_to, dst_in);
100.
101.                 // Calculate step size and sample limit of ray march
102.                 const float step_size = dst_in / _Samples;
103.                 const float sample_limit = min(_Samples, 100);
104.
105.                 // Parameters for texture sampling
106.                 const float frequency = pow(abs(_Lacunarity), _Octave);
107.                 const float amplitude = pow(abs(_Persistence), _Octave);
108.
109.                 const float height_min = mul(unity_ObjectToWorld, float4(min_bounds, 1)).y;
110.                 const float height_max = height_min + _DisplacementScale * amplitude;
111.
112.                 // Ray marching loop
113.                 float dst_travelled = 0;
114.                 for (int it = 0; it < sample_limit && dst_travelled < dst_limit; it++)
115.                 {
116.                     const float3 ray_position = ray_origin + ray_direction * (dst_to + dst_travelled);
117.                     const float3 world_position = mul(unity_ObjectToWorld, float4(ray_position, 1.0));
118.
119.                     const float2 uv = world_position.xz * frequency / _NoiseScale;
120.                     const float height_sample = tex2Dlod(_MainTex, float4(uv, 0, 0));
121.
122.                     if (world_position.y < lerp(height_min, height_max, height_sample))
123.                         break;
124.
125.                     dst_travelled += step_size;
126.                 }
127.
128.                 // calculate output colour based on where the ray terminated
129.                 const float height = mul(unity_ObjectToWorld, float4(ray_origin + ray_direction * (dst_to + dst_travelled), 1.0)).y;
130.                 float col = (height - height_min) / (height_max - height_min);
131.
132.                 float4 result = float4(col,col, col, dst_limit - dst_travelled > 0.001 ? 1 : 0);
133.                 return result;
134.             }
135.             ENDCG
136.         }
137.     }
138. }
139.

5. ### bgolus

Joined:
Dec 7, 2012
Posts:
12,005
Code (csharp):
1. const float3 ray_direction_world = normalize(UnityObjectToWorldDir(ray_direction)) * depth_linear;
2. const float object_depth = length(mul(unity_WorldToObject, ray_direction_world));
3. const float dst_limit = min(object_depth - dst_to, dst_in);
Here's what I would do to replace the above block of code.

Code (csharp):
1. // same as what you did before, just w/o multiplying by depth
2. // really just so the variable name is correct
3. float3 ray_direction_world = normalize(UnityObjectToWorldDir(ray_direction));
4. // get the world space camera forward vector
5. // view space is -Z forward, and the view matrix is always a uniform scale matrix
6. // swapping the vector and matrix order in the mul() applies the transposed matrix to the vector
7. // and the transpose of a uniform matrix is identical to the inverse matrix
8. // so this is a view to world
9. float3 cam_forward_world = mul(float3(0,0,-1), (float3x3)UNITY_MATRIX_V);
10. // the dot product of the normalized forward and ray direction effectively
11. // returns the "depth" of the ray direction vector
12. float ray_depth_world = dot(cam_forward_world, ray_direction_world);
13. // with all that we can reconstruct a world position
14. float3 world_position_from_depth = ray_direction_world / ray_depth_world * depth_linear + _WorldSpaceCameraPos;
And with all that you can then transform that world position into object space and get a useful length from the object space ray origin to limit your traversal to.

6. ### HamLard

Joined:
Nov 27, 2016
Posts:
8
It works perfectly, thanks so much for providing comments along with the code, it goes a long way in helping me learn