This post is a continuation of a series of posts about WebGL. The first started with fundamentals. This article covers perspective correct texture mapping. To understand it you probably need to read up on perspective projection and maybe texturing as well. You also need to know about varyings and what they do but I'll cover them briefly here.
So in the "how it works" article we covered how varyings work. A vertex shader can declare a varying and set it to some value. Once the vertex shader has been called 3 times WebGL will draw a triangle. While it's drawing that triangle for every pixel it will call our fragment shader and ask it what color to make that pixel. Between the 3 vertices of the triangle it will pass us our varyings interpolated between the 3 values.
Going back to our first article we drew a triangle in clip space, no math. We just passed in some clip space coordinates to a simple vertex shader that looked like this
We had a simple fragment shader that draws a constant color
So let's make that draw 2 rectangles in clip space. We'll pass it this
data with X
, Y
, Z
, and W
for each vertex.
Here's that
Let's add a single varying float. We'll pass that directly from the vertex shader to the fragment shader.
In the fragment shader we'll use that varying to set the color
We need to supply data for that varying so we'll make a buffer and put in some data. One value per vertex. We'll set all the brightness values for vertices on the left to 0 and those on the right to 1.
We also need to look up the location of the a_brightness
attribute
at init time
and setup that attribute at render time
And now when we render we get two rectangles that are black on the left
when brightness
is 0 and red on the right when brightness
is 1 and
for the area in between brightness
is interpolated or (varied) as it
goes across the triangles.
So then, from the perspective article we know that WebGL takes whatever value we put in gl_Position
and it divides it by
gl_Position.w
.
In the vertices above we supplied 1
for W
but since we know WebGL
will divide by W
then we should be able do something like this
and get the same result.
Above you can see that for every point on the right in the second
rectangle we are multiplying X
and Y
by mult
but, we are also
setting W
to mult
. Since WebGL will divide by W
we should get
the exact same result right?
Well here's that
Note the 2 rectangles are drawn in the same place they were before. This
proves X * MULT / MULT(W)
is still just X
and same for Y
. But, the
colors are different. What's going on?
It turns out WebGL uses W
to implement perspective correct
texture mapping or rather to do perspective correct interpolation
of varyings.
In fact to make it easier to see let's hack the fragment shader to this
multiplying v_brightness
by 10 will make the value go from 0 to 10. fract
will
just keep the fractional part so it will go 0 to 1, 0 to 1, 0 to 1, 10 times
Now it should be easy to see the perspective.
A linear interpolation from one value to another would be this formula
Where t
is a value from 0 to 1 representing some position between a
and b
. 0 at a
and 1 at b
.
For varyings though WebGL uses this formula
Where aW
is the W
that was set on gl_Position.w
when the varying was
as set to a
and bW
is the W
that was set on gl_Position.w
when the
varying was set to b
.
Why is that important? Well here's a simple textured cube like we ended up with in the article on textures. I've adjusted the UV coordinates to go from 0 to 1 on each side and it's using a 4x4 pixel texture.
Now let's take that example and change the vertex shader so that
we divide by W
ourselves. We just need to add 1 line.
Dividing by W
means gl_Position.w
will end up being 1.
X
, Y
, and Z
will come out just like they would if we let
WebGL do the division for us. Well here are the results.
We still get a 3D cube but the textures are getting warped. This
is because by not passing W
as it was before WebGL is not able to do
perspective correct texture mapping. Or more correctly, WebGL is not
able to do perspective correct interpolation of varyings.
If you recall W
was our Z
value from our perspective matrix.
With W
just being 1
WebGL just ends up doing a linear interpolation.
In fact if you take the equation above
And change all the W
s to 1s we get
Dividing by 1 does nothing so we can simplify to this
(1 - t) + t
is the same as 1
. For example
if t
was .7
we'd get (1 - .7) + .7
which is .3 + .7
which is 1
. In other words we can remove the bottom so we're left with
Which the same as the linear interpolation equation above.
Hopefully it's now clear why WebGL uses a 4x4 matrix and
4 value vectors with X
, Y
, Z
, and W
. X
and Y
divided by W
get a clipspace coordinate. Z
divided by W
also get a clipspace coordinate in Z and W
is still used during interopation of varyings and
provides the ability to do perspective correct texture mapping.