# GLSL Programming/Unity/Projection of Bumpy Surfaces

This tutorial covers (single-step) parallax mapping.
It extends and is based on Section “Lighting of Bumpy Surfaces”.

### Improving Normal Mapping

The normal mapping technique presented in Section “Lighting of Bumpy Surfaces” only changes the lighting of a flat surface to create the illusion of bumps and dents. If one looks straight onto a surface (i.e. in the direction of the surface normal vector), this works very well. However, if one looks onto a surface from some other angle (as in the image to the left), the bumps should also stick out of the surface while the dents should recede into the surface. Of course, this could be achieved by geometrically modeling bumps and dents; however, this would require to process many more vertices. On the other hand, single-step parallax mapping is a very efficient techniques similar to normal mapping, which doesn't require additional triangles but can still move virtual bumps by several pixels to make them stick out of a flat surface. However, the technique is limited to bumps and dents of small heights and requires some fine-tuning for best results.

### Parallax Mapping Explained

Parallax mapping was proposed in 2001 by Tomomichi Kaneko et al. in their paper “Detailed shape representation with parallax mapping” (ICAT 2001). The basic idea is to offset the texture coordinates that are used for the texturing of the surface (in particular normal mapping). If this offset of texture coordinates is computed appropriately, it is possible to move parts of the texture (e.g. bumps) as if they were sticking out of the surface.
The illustration to the left shows the view vector V in the direction to the viewer and the surface normal vector N in the point of a surface that is rasterized in a fragment shader. Parallax mapping proceeds in 3 steps:
* Lookup of the height $h$ at the rasterized point in a height map, which is depicted by the wavy line on top of the straight line at the bottom in the illustration.
* Computation of the intersection of the viewing ray in direction of V with a surface at height $h$ parallel to the rendered surface. The distance $o$ is the distance between the rasterized surface point moved by $h$ in the direction of N and this intersection point. If these two points are projected onto the rendered surface, $o$ is also the distance between the rasterized point and a new point on the surface (marked by a cross in the illustration). This new surface point is a better approximation to the point that is actually visible for the view ray in direction V if the surface was displaced by the height map.
* Transformation of the offset $o$ into texture coordinate space in order to compute an offset of texture coordinates for all following texture lookups.
For the computation of $o$ we require the height $h$ of a height map at the rasterized point, which is implemented in the example by a texture lookup in the A component of the texture property _ParallaxMap, which should be a gray-scale image representing heights as discussed in Section “Lighting of Bumpy Surfaces”. We also require the view direction V in the local surface coordinate system formed by the normal vector ($z$ axis), the tangent vector ($x$ axis), and the binormal vector ($y$ axis), which was also introduced Section “Lighting of Bumpy Surfaces”. To this end we compute a transformation from local surface coordinates to object space with:
$\mathrm {M} _{{\text{surface}}\to {\text{object}}}=\left[{\begin{matrix}T_{x}&B_{x}&N_{x}\\T_{y}&B_{y}&N_{y}\\T_{z}&B_{z}&N_{z}\end{matrix}}\right]$
where T, B and N are given in object coordinates. (In Section “Lighting of Bumpy Surfaces” we had a similar matrix but with vectors in world coordinates.)
We compute the view direction V in object space (as the difference between the rasterized position and the camera position transformed from world space to object space) and then we transform it to the local surface space with the matrix $\mathrm {M} _{{\text{object}}\to {\text{surface}}}$ which can be computed as:
$\mathrm {M} _{{\text{object}}\to {\text{surface}}}=\mathrm {M} _{{\text{surface}}\to {\text{object}}}^{-1}=\mathrm {M} _{{\text{surface}}\to {\text{object}}}^{T}\,$
This is possible because T, B and N are orthogonal and normalized. (Actually, the situation is a bit more complicated because we won't normalize these vectors but use their length for another transformation; see below.) Thus, in order to transform V from object space to the local surface space, we have to multiply it with the transposed matrix $(\mathrm {M} _{{\text{surface}}\to {\text{object}}})^{T}$. In GLSL, this is achieved by multiplying the vector from the left to the matrix $\mathrm {M} _{{\text{surface}}\to {\text{object}}}$.
Once we have the V in the local surface coordinate system with the $z$ axis in the direction of the normal vector N, we can compute the offsets $o_{x}$ (in $x$ direction) and $o_{y}$ (in $y$ direction) by using similar triangles (compare with the illustration):
${\frac {o_{x}}{h}}={\frac {V_{x}}{V_{z}}}$  and ${\frac {o_{y}}{h}}={\frac {V_{y}}{V_{z}}}$.
Thus:
$o_{x}=h{\frac {V_{x}}{V_{z}}}$  and $o_{y}=h{\frac {V_{y}}{V_{z}}}$.
Note that it is not necessary to normalize V because we use only ratios of its components, which are not affected by the normalization.
Finally, we have to transform $o_{x}$ and $o_{y}$ into texture space. This would be quite difficult if Unity wouldn't help us: the tangent attribute Tangent is actually appropriately scaled and has a fourth component Tangent.w for scaling the binormal vector such that the transformation of the view direction V scales $V_{x}$ and $V_{y}$ appropriately to have $o_{x}$ and $o_{y}$ in texture coordinate space without further computations.

### Implementation

The implementation shares most of the code with Section “Lighting of Bumpy Surfaces”. In particular, the same scaling of the binormal vector with the fourth component of the Tangent attribute is used in order to take the mapping of the offsets from local surface space to texture space into account:
In the vertex shader, we have to add a varying for the view vector V in the local surface coordinate system (with the scaling of axes to take the mapping to texture space into account). This varying is called viewDirInScaledSurfaceCoords. It is computed by multiplying the view vector in object coordinates (viewDirInObjectCoords) from the left to the matrix $\mathrm {M} _{{\text{surface}}\to {\text{object}}}$ (localSurface2ScaledObject) as explained above:
The rest of the vertex shader is the same as for normal mapping, see Section “Lighting of Bumpy Surfaces”.
In the fragment shader, we first query the height map for the height of the rasterized point. This height is specified by the A component of the texture _ParallaxMap. The values between 0 and 1 are transformed to the range -_Parallax/2 to +_Parallax with a shader property _Parallax in order to offer some user control over the strength of the effect (and to be compatible with the fallback shader):
The offsets $o_{x}$ and $o_{y}$ are then computed as described above. However, we also clamp each offset between a user-specified interval -_MaxTexCoordOffset and _MaxTexCoordOffset in order to make sure that the offset stays in reasonable bounds. (If the height map consists of more or less flat plateaus of constant height with smooth transitions between these plateaus, _MaxTexCoordOffset should be smaller than the thickness of these transition regions; otherwise the sample point might be in a different plateau with a different height, which would mean that the approximation of the intersection point is arbitrarily bad.) The code is:
In the following code, we have to apply the offsets to the texture coordinates in all texture lookups; i.e., we have to replace vec2(textureCoordinates) (or equivalently textureCoordinates.xy) by (textureCoordinates.xy + texCoordOffsets), e.g.:
The rest of the fragment shader code is just as it was for Section “Lighting of Bumpy Surfaces”.

### Complete Shader Code

As discussed in the previous section, most of this code is taken from Section “Lighting of Bumpy Surfaces”. Note that if you want to use the code on a mobile device with OpenGL ES, make sure to change the decoding of the normal map as described in that tutorial.
The part about parallax mapping is actually only a few lines. Most of the names of the shader properties were chosen according to the fallback shader; the user interface labels are much more descriptive.

### Summary

Congratulations! If you actually understand the whole shader, you have come a long way. In fact, the shader includes lots of concepts (transformations between coordinate systems, application of the inverse of an orthogonal matrix by multiplying a vector from the left to it, the Phong reflection model, normal mapping, parallax mapping, ...). More specifically, we have seen:
* How parallax mapping improves upon normal mapping.
* How parallax mapping is described mathematically.
* How parallax mapping is implemented.

### Further Reading

If you still want to know more
* about details of the shader code, you should read Section “Lighting of Bumpy Surfaces”.
* about parallax mapping, you could read the original publication by Tomomichi Kaneko et al.: “Detailed shape representation with parallax mapping”, ICAT 2001, pages 205–208, which is available online.
< GLSL Programming/Unity