- Home /
Getting a pixel's screen-space coordinate in a fragment shader
I'm trying to write a shader that works using screen-space pixels. Namely, something akin to a horizontal fade out across each object.
Is there a way to access the pixel's screen-space coordinate from within a fragment shader?
This documentation page's Detail Texture in Screen Space is pretty much what I want, but it uses Unity Surface Shaders, and I can't access clip() from there, and I need it for my shader.
Any ideas?
Answer by CHPedersen · May 02, 2013 at 09:57 AM
You're in luck! This is thankfully simple. :)
After a vertex has been multiplied by the modelviewprojection matrix (globally accessible in shaders as "UNITY_MATRIX_MVP"), its coordinates are in clip space. Clip space's coordinates range from (-1,-1,-1) to (1,1,1) in OpenGL, where the z coordinate is the depth value that gets written to the depth buffer. The range of z is 0 and 1 in Direct3D, but you don't have to care about this discrepancy, because for pixel positions, you're only interested in the x and y coordinates.
The center of clip space is the center of the screen, i.e. x = -1 is the left side of the screen and x = 1 is the right, with y = -1 the bottom and y = 1 the top. So all you have to do is save the clip space vertex coordinate using one of the texture channels in your vertex shader, then access the same texture channel in the fragment shader's input parameters. Then the rasterizer will have interpolated the pixel's clip space coordinate for you.
Using that, you can convert the pixel clip space coordinate to screen space using the screen resolution. This other question has the code samples and the conversion formula, saving me the trouble of writing it here:
http://answers.unity3d.com/questions/39803/how-to-get-an-objects-position-in-screen-space-in.html
Thanks for the answer!
All the tutorials I've seen so far gloss over what the $$anonymous$$VP multiplication was Actually doing in terms of numbers.
I read the linked formula, and I've tried to understand what it means by writing it out: http://pastie.org/7752194
However the left-right black-white gradient only stretches the centre of the screen, not all the way across. Could you tell me why? Shouldn't the X value range between 0 and 1 across the whole screen?
Okay... Here's some background on the $$anonymous$$VP matrix. I decided to write it out in the hope it would clear up what the other sites you've visited glossed over.
The $$anonymous$$VP matrix is a concatenated series of transforms between multiple coordinate systems. First, there's the model matrix, which sends a point from the object coordinate system ('local' coordinates in Unity scripts) to the world coordinate system. After that, there is the viewing matrix (made up by the camera's position and rotation) which sends points from the world coordinate system to the camera's coordinate system. Finally, the points are subjected to a projection transform that simulates the real-world phenomenon that objects appear smaller with distance. This last transform (which is not affine if the projection matrix is perspective) turns the camera's frustum into a cube, and it is this final coordinate system which is called "clip space". The projection matrix is the only difference between a perspective camera and an orthogonal camera. An orthogonal camera has another projection matrix whose projection is affine - it preserves parallel lines. After this, dividing all (4D) point coordinates with their fourth, homogeneous coordinate, w, is called 'perspective division' and is what makes all vector components lie within -1 to 1, hence the name 'normalized device coordinates'.
Rather than perfor$$anonymous$$g each step separately on each vertex in succession, it's more efficient to concatenate (multiply together to one) the three matrices and send the result as a single matrix to the graphics card. This concatenated matrix is thus the $$anonymous$$odel-View-Projection ($$anonymous$$VP) matrix. Our shaders then perform all 3 transforms in one go using that.
x ranges from -1 to 1 in normalized divice coordinates, but gets converted to the range 0 - screen_width by doing 0.5*(x+1)*screen_width (same for y, with height).
By the way, I learned about these transforms by reading NVidia's book "The Cg Tutorial". It's available online, and these transforms are explained in greater detail in chapter 4:
http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter04.html
It's a short chapter and well worth the read.