Help me write a pixel shader - Desaturate based on distance

I wish to create a shader to desaturate the frame based on distance such that pixels whose 3D position is close to the camera appear vibrant whilst the pixels whose 3D position is far from the camera appear slightly desaturated.

I have read that to desaturate a colour you can simply add white, grey, black or the complimentary colour to the current colour.

Edit:
All I need now is how to get the distance of the pixel.

If this is a 2d filter (which, by your use of the term ‘pixel shader’ I’m guessing it is), you should have access to the depth buffer via bgl_DepthTexture:


uniform sampler2D bgl_DepthTexture;

So, that special variable name makes it into a depth info carrier, as opposed to just another sampler2D? It doesn’t look/sound like standard GLSL -> more like a hack to provide information that could easily to be served from a vertex shader.

Where exactly is all this documented?

@ andrew-101

I made a demo for you (attached).

Use the up down keys to move the textured box farther/closer.

Attachments

distance_desaturation.blend (135 KB)

Hey thats great, exactly what I wanted, except, I don’t really understand it :confused:
Why do you need a vertex shader and a fragment shader, can it be done as just a fragment shader?

Where exactly is all this documented?
My thoughts exactly over the past days.

It’s possible to do it all within the fragment shader, but it’s a somewhat more demanding procedure: http://www.gamedev.net/community/forums/topic.asp?topic_id=498615

I suspect that this whole “bgl_DepthTexture” nonsense was implemented as a shortcut for something along those lines, but I have no idea how to actually get at the data (it just acts as a regular sampler in all my tests…there’s probably another “special variable” that you have to know about in order to make it all work).

In either case, using a varying to get world coordinates seems like the accepted “straight-forward” way of doing this, so I just followed that.

Also, I think there’s some benefit in avoiding these “bgl_ImSomeSpecialBlenderThing” shortcuts, and holding to just standard GLSL for which I can actually find some documentation/advice.

This should work:

uniform sampler2D bgl_RenderedTexture;
uniform sampler2D bgl_DepthTexture;

void main(void)
{
    vec4 texcol = texture2D(bgl_RenderedTexture, gl_TexCoord[0].xy);
    float depth = texture2D(bgl_DepthTexture, gl_TexCoord[0].xy);
    gl_FragColor.rgb = vec3(depth,depth, depth).xyz;
}

However, your solution seems to do the job alot better, I’ll have to look through that link.

here it is done in Material Node Editor:
distance_desaturate.blend (43.9 KB)

I simulated desaturation with this equation:

vec3 luminance = vec3(0.2125, 0.7154, 0.0721);
float desaturate =dot(color, luminance);

then mixed it with color using scene distance as mix factor.

What I need is to apply it to whole screen, so using material nodes makes it awkward (unless there is a quick way to apply it to all materials?)

Can I apply social’s method to the screen rather than just a material?

Here is where bgl_RenderedTexture, bgl_DepthTexture and bgl_LuminanceTexture come from:



void RAS_2DFilterManager::SetupTextures(bool depth, bool luminance)
{
	FreeTextures();
	
	glGenTextures(1, (GLuint*)&texname[0]);
	glBindTexture(GL_TEXTURE_2D, texname[0]);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texturewidth, textureheight, 0, GL_RGBA,
			GL_UNSIGNED_BYTE, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	if(depth){
		glGenTextures(1, (GLuint*)&texname[1]);
		glBindTexture(GL_TEXTURE_2D, texname[1]);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, texturewidth,textureheight, 
			0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE,NULL);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
		                GL_NONE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	}

	if(luminance){
		glGenTextures(1, (GLuint*)&texname[2]);
		glBindTexture(GL_TEXTURE_2D, texname[2]);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE16, texturewidth, textureheight,
			 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	}
}

Ah, that is an easy one:
(apply this to 2D Filter Actuator)


uniform sampler2D bgl_RenderedTexture;
uniform sampler2D bgl_DepthTexture;
 
void main() 
{
float f = 100.0; //camera far plane
float n = 0.1; //camera near plane
float z = (2.0 * n) / (f + n - texture2D(bgl_DepthTexture, gl_TexCoord[0]).x * (f - n));

vec4 color = texture2D(bgl_RenderedTexture, gl_TexCoord[0]);
float desaturate = dot(color, vec3(0.2125, 0.7154, 0.0721));

gl_FragColor = mix(desaturate, color, z);
}

and a blend
distance_desaturate.blend (44.8 KB)
(press spacebar to enable filter)

Just what I wanted, thank you very much!

Ahh, I see; you’re linearizing the depth texture values, which were, as I now know, originally exponential. Also, I suspected that there was something like the “mix” function around, but I was too lazy to look it up. Thanks for the example - it helped me clear things up.

That said, I would still prefer this:


varying vec4 world_coord;  // from vertex shader
uniform float totalmono_distance; 
uniform sampler2D regular_texture; 
 
void main(){ 
    vec4 texcol = texture2D(regular_texture, gl_TexCoord[0].xy); 
    float mono = dot(texcol.rgb, vec3(0.2125, 0.7154, 0.0721)); 
     
    // He wanted things to desaturate as the distance increased (hence the "1.0 -"):
    float mix_restraint = clamp(1.0 - (-world_coord.z/totalmono_distance, 0.0, 1.0)); 
    gl_FragColor = mix(vec4(mono), texcol, mix_restraint); 
}

Even though it requires the implementation of a minimal vertex shader, this fragment shader is far more flexible. Note the use of totalmono_distance to set the effect radius. Since I’m using python to “inject” my shader programs into the material, I can access (and therefore change) that shader variable directly from python with shader.setUniform1f(“totalmono_distance”, value), without having to actually recompile the shader itself. Afaik, 2D filters don’t provide that level of run-time control.

Then there’s the question of readability: the whole linearization procedure is far from intuitive, partly because people don’t immediately understand that depth texture values are exponential (and that therefore this procedure is necessary), but also because the result (z) doesn’t represent world-space depth. Also, the use of “near” and “far” planes leaves one with the impression that these are convenience variables that one could freely modify to shift the effect range, but in reality they have to be set to the camera clipping limits, because camera clipping limits effect the depth texture values. Setting near and far to something other than the camera clipping values could still produce the desired effect, but it’s not a very consistent thing, and the discrepancies become obvious soon after.

Also, shaders injected into material seem to pick-up on additional compilation warnings (things like implicit conversions, and so on), so that’s yet another benefit.

@ andrew

You should use whatever provides the desired effect for the least amount of effort.

But also, be aware that objects using the same material can all be drawn with a single shader. You just have to set it to the material in question, in the same exact way I did in my demo.

Note: ALT-D replicated objects won’t work (they have to be distinct copies, using the same material).

Yup, I would also prefer your code, Social. But with current 2D Filter abilities with some built-in uniform variables, I have to use hacky approaches to get the desired effect.

Now THAT is artistic. God only knows how you did it. This is perfect for killer7 texturing!! (see my thread : http://blenderartists.org/forum/showthread.php?t=183312)

BUT…

Does anybody know how THIS EFFECT could be applied to SEPARATE OBJECTS? As in, each object, no matter at what position in the screen, has this effect on? :confused:

If so, this is million dollar code!

Happy holidays,

1/1 credit(s)

Well, “this effect” (things saturating/desaturating based on distance) requires some kind of positional reference.

If you’re just looking for a shader to paint everything in “old tv” black and white, you can use this:


uniform sampler2D bgl_RenderedTexture; 
  
void main(){ 
    vec4 color = texture2D(bgl_RenderedTexture, gl_TexCoord[0]); 
    float desaturate = dot(color, vec3(0.2125, 0.7154, 0.0721)); 
 
    gl_FragColor = mix(desaturate, color, 0.0); 
}