Unity / How do I change the color of an object based on its depth?

The objective here is to change the color of the nearest object. (the nearer the object, the more "colorful" the object gets rendered; and the farther away, the more "grey" the object gets rendered), so far away objects are completely grey and close objects are in full color.

You know, every frame, there is this certain float value, which determines if a pixel should be drawn or not. (aka z-buffer)

Can anyone tell me the variable - name of it? Or how can I access it? Can I even access it?

Edit:

The GPU is comparing two values: the depth of the new fragment you're drawing (a) and the depth value of whatever's already been drawn in that spot of the depth buffer (b).

Typically if b <= a it skips drawing the fragment because it's behind whatever's already been rendered previously.

I think I need both (a) and (b) variables - since I always need the nearer one.

Im not sure though... I think its also fine if I could only access the (b) and check every frame if the previous b was smaller.

• 1) Are you working in Unity & Shaderlab, or raw DX11 & hlsl? The answer will be different in the two cases. 2) Are you asking about the depth of the fragment you're drawing (a), or the existing content of the depth buffer at that pixel (b) (ie. depth of something drawn previously)? You could use (a) to do things like add fog to distant surfaces. (b) you'd often use for screenspace effects like outlining through edge detection, or things like deferred decals. – DMGregory Jul 4 '16 at 20:58
• Thanks for your answer! 1. Im using Unity! 2. ummmm ..... i dont know which one honestly. :-( Im asking about that one float variable the gpu compares with the float variable of the "nearest" pixel. (and then decides backface culling) And I need that float variable of that nearest pixel. I could use that to add fog to distant surfaces...... so i think a? – OC_Raiz Jul 4 '16 at 21:14
• The GPU is comparing two values: the depth of the new fragment you're drawing (a) and the depth value of whatever's already been drawn in that spot of the depth buffer (b). Typically if b <= a it skips drawing the fragment because it's behind whatever's already been rendered previously. With that in mind, can you edit your question to clarify which one you want? When in doubt, describing the end goal you're trying to achieve often helps remove ambiguity in cases like this. – DMGregory Jul 4 '16 at 22:01
• If you only want to tint the colour of each object based on its depth, then you don't need to know the underlying depth stored in the depth buffer from previously rendered content (b). You can tint based on the depth of the current fragment you're drawing now (a). If that sounds like it describes what you want to do, I can write up an answer that describes that. Getting the second depth value (b) is more work (and can require an extra pass in forward rendering) so it's simplest to avoid that complexity if you don't really need it. – DMGregory Jul 4 '16 at 23:51
• oh man.. "b" would be so cool i really want that one float value... i could do so much cool stuff with that. i mean... isnt the number just stored somewhere.... ? :-( But okay- give me the "a" so: Whats the name of the variable? ^^ Bonus Question: How can I change the color of the one pixel which is exactly in the middle of the screen? (if i would had an (imaginary) 1000x500 display, that would be the one which is 500 right and 250 down. – OC_Raiz Jul 4 '16 at 23:59

The lowest-effort way to get what you describe:

far away objects are completely grey and close objects are in full color

...is to use fog, either directly through the Lighting settings for your scene (see the Fog entries at this link, no custom shaders required) or by using the legacy shader paths.

Here's an example of what it looks like with linear fog on a series of four identical red cubes at increasing distance:

This is also straightforward to incorporate into a Surface shader:

To do this, we add a fog colour parameter in the shader Properties block:

_FogColor ("Fog Color", Color) = (0.5, 0.5, 0.5, 1)


and define a corresponding variable in our CGProgram: fixed4 _FogColor;

Then modify our Input struct to include the screenPos parameter, which signals to Unity that it should pass the screenspace position of the fragment to us:

struct Input {
float2 uv_MainTex;
float4 screenPos; // This we add.
};


Now we use the z value of this screenspace coordinate to blend between our texture colour and the fog colour, z being the depth or distance of the fragment from the camera:

void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
c = lerp(c, _FogColor, saturate(IN.screenPos.z * 0.05f));
...


Here the * 0.05f is just a magic number to get the fog to fall off over the example depth range I was using. In practice you might want to expose extra variables to control the fog density or falloff distance.

Note that applying it this way only affects the object's albedo. Lighting and shadow will be applied on top of this colour. To apply the fog at the end, we can do this in a finalcolor method instead:

#pragma surface surf Standard fullforwardshadows finalcolor:mycolor
...
void mycolor(Input IN, SurfaceOutputStandard o, inout fixed4 color)
{
color = lerp(color, _FogColor, saturate(IN.screenPos.z * 0.05f - 0.5f));
}


...also note that the snippets above were written for a DirectX system, where screenspace z increases with distance. To make it work on DirectX and OpenGL you might need to add a bit like this:

#if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
// multiply z by -1 before using it to compute fog amount.
#endif


Lastly we can do this in a vertex/fragment shader pair like so (using an unlit shader in this example for simplicity):

Shader "Unlit/UnlitManualFog"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_FogColor ("Fog Color", Color) = (0.5, 0.5, 0.5, 1.0)
}
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
// Add a variable to carry depth information
float depth : TEXCOORD1;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _FogColor;

v2f vert (appdata v)
{
v2f o;
// Transform vertex into view space
o.vertex = mul(UNITY_MATRIX_MV, v.vertex);
// Copy the viewspace z coordinate and call it depth
o.depth = o.vertex.z;
// Finish transforming the vetex by applying the projection matrix
o.vertex = mul(UNITY_MATRIX_P, o.vertex);

o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// Blend colour using depth parameter we calculated earlier
return lerp(col, _FogColor, saturate(-0.05f * i.depth));
}
ENDCG
}
}
}


To answer your "bonus question" about reading from the depth buffer to change behaviour based on what's already rendered, I'd recommend checking out the Unity presentation "Special Effects with Depth." It's a five years old now, but the core techniques hold up, and it gives a solid primer on how this works in Unity, what you can use it to achieve, and what costs it comes with.