Skip to content
Tags

77. Shaders – deleting pixels for effect

June 5, 2013

There are are some nice effects you can get by deleting pixels with a fragment shader.

Removing transparent pixels

When OpenGL draws pixels, it keeps a note of how far away (z) they are. If it gets asked to draw a pixel on top of another pixel, it will only do so if the new pixel is closer (there is no point drawing stuff that can’t be seen).

There is one problem with this – transparent pixels with value (0,0,0,0). Every image you use in Codea has transparent pixels surrounding the picture in the centre. We want OpenGL to treat these pixels as though they aren’t there, so we can see things behind them. However, OpenGL treats them like any other pixel, so if I draw one image, then I draw another one behind it, OpenGL will not draw any pixels behind the first image – not even behind the transparent part.

You can see this below, where the left hand image obscures the red background, even though all those white pixels are supposed to be transparent. What we actually want is on the right.
http://instagram.com/p/aCnUaMBHV1/

Deleting transparent pixels

It is quite easy to write a fragment shader (fragment because we are dealing with pixels, not vertices) to deal with this. We can simply test if the alpha part of the pixel colour is nil, and then discard the pixel.

You haven’t seen the discard command before, but this is what you use to not draw anything at all.

So the main function in our fragment shader becomes

    lowp vec4 col=texture2D(texture, vTexCoord);
    if (col.a==0.) discard; //note decimal behind zero!
    else gl_FragColor = col * vColor;

so we get the colour from the texture first, then check if the alpha is zero, and if it is, we discard the pixel, else we draw it.

The only problem is the results aren’t great. There’s quite a white border on the right hand image, which uses the shader.
http://instagram.com/p/aCnIa8hHVe/

The reason for this is that Codea blends the image into its background, so when the pixels are sent to the fragment shader, the image includes an outer edge that is partly transparent, ie alpha is not nil, so the pixels are not discarded.

What we need to do is to discard pixels with low alpha as well as those with nil alpha, so if we try this:

    lowp vec4 col=texture2D(texture, vTexCoord);
    if (col.a<0.2) discard;
    else gl_FragColor = col * vColor;

we get a better result.
http://instagram.com/p/aCnUaMBHV1/

Full code is here.

We could be more accurate and test the r,g,b values too, but I don’t want to spend too much processor time on this, so the simpler the test, the better.

Final note – the results still aren’t perfect, and for best results, I’ve found I have to sort all the meshes by distance and draw them from furthest to nearest. Seriously.

Making a hole in a mesh

I have partly covered this in one of my 3D posts, but now I can explain how it works. Suppose we want to make a window or door in a wall, and see through it. The immediate problem is that you can’t make a wall mesh, and then make a hole in it. You have to leave the hole in the first place. This can make your mesh quite complex, especially if you have more than one hole, because you need to define a set of triangles that cover the wall but leave holes in the right place. So there could be quite a lot of manual fiddling about to get it right.

The alternative is just to draw the wall, but get the fragment shader to discard any pixels in the hole. To do this, we first need to pass through the location of the hole to the fragment shader, which I do with code like this, which defines a hole by bottom left x,y and top right x,y, all as fractions of the texture width and height, to make things easy for the shader.

    myMesh.shader.hole=vec4(0.5,0,0.7,0.7)

Then my shader needs the hole defined, before the main function. It is a vec4, and it is a uniform variable because it’s coming from Codea.

    uniform vec4 hole;

And now the main function, which tests if the pixel is within the hole, and if it isn’t, it draws it, otherwise it discards it.

    if (vTexCoord.x=hole.z ||
        vTexCoord.y<=hole.y || vTexCoord.y<=hole.w)
        gl_FragColor = texture2D(texture, vTexCoord);
    else
        discard;

.
Note how we use x,y,z,w to get the four values we passed through. The only puzzling thing in the code may be ||, which means “or” in C. If you wanted to say “and” instead, you would use .

And so you get this kind of result, where I can see through the doorway, which is what I want.
http://instagram.com/p/aCsPfsBHc_/

If you want more holes, you have to include more tests, and you should only use this shader for meshes that really need it, to avoid slowing yourself down too much. There is no harm in having several shaders for different purposes.

The full code is here.

Advertisement

From → Shaders

One Comment

Trackbacks & Pingbacks

  1. 240. WoT – Adding scenery | coolcodea

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: