# The Dot Product – For 3D Artists

The Dot Product is a useful little function which is helpful for game artists to understand. In this post I’m going to look at at what the Dot Product is, how it’s used, and more importantly, what it’s useful for.

## TL;DR

The Dot Product can be used when working out if two vectors are pointing in the same direction – *so lighting or edge glows,* or if a point in space is in front or behind something – *Backface culling*. These two fundamental vector operations are the basis of many shader effects, as well as many physics calculations.

The dot product (or scalar product as it’s also known) is shown mathematically with the dot `⋅`

symbol, represents quite a simple mathematical operation, where `a`

and `b`

are 3D direction vectors;

`(a`

= The _{x}b_{x} + a_{y}b_{y} + a_{z}b_{z})`cosine`

of the angle of the two vectors (some number)

But rather then writing the above each time, it’s written mathematically as `a⋅b`

, in HLSL as `dot(a, b)`

, or perhaps more relevantly to artists, you’ll have a simple `dot`

node.

Broadly in 3D graphics, the dot product is useful for two main things;

- Checking the direction of one unit vector relative to another – are they facing the same way?
*Projecting*a vector onto a unit vector – how far along the unit vector is the other? How far has something travelled in a specific direction?

## Unit Vectors and Vector Normalisation

When discussing the dot product, it’s important to understand the role of vector normalisation and *unit vectors*. Normalisation is the process of converting a vector to a unit vector – a vector with a length of one, which describes *only* the direction information of the original vector, not the length.

`(1, 0, 0) (0, 1, 0) (0, 0, 1)`

are unit vectors for each of the 3 cardinal axis, `X`

, `Y`

, and `Z`

.

`(1, 1, 1)`

would *not* be a unit vector, as it has a length of `1.73`

.

Normalisation ensures that the dot product only describes the (cosine of the) *angle* between the two vectors, not their length. This is particularly important in lighting calculations, direction checks, and other graphical computations where only the direction matters.

## 1. Direction Checking

The dot product of two unit vectors will result in a `-1.0`

to `1.0`

scalar value.

If both vectors are;

- Pointing in the same direction, the result will be
`1.0`

- Perpendicular (90 degrees) to each other, the result will be
`0.0`

- Pointing in opposite directions, the result will be
`-1.0`

### Diffuse Shading

This aspect of the dot product gives us one of the most basic shading methods in computer graphics, and one many 3D artists will be familiar with, Diffuse (Lambertian) shading.

Diffuse shading is the clamped (or `saturated`

) `0.0 - 1.0`

value of the dot product between the light direction vector, and the surface normal which is being shaded. This value is then multiplied over the material’s colour to give a final value. In HLSL it’ll look something similar to;

`color.rgb *= saturate(dot(lightDir, IN.normal));`

Negative values are discarded here with the `saturate()`

function as we don’t want our colour value being scaled by negative lighting terms, `0`

is as dark as it can be.

Worth noting that you’d very likely never implement the above in shader graph, or any node-based shader tool as they generally have a pre-written lighting model you’ll already use. Diffuse shading is a pretty old and primitive lighting model.

### Half Lambert

Not the same as Diffuse Lighting, but very similar, and very well known, the “Half Lambert” shading model was used by Valve heavily over the years, most notably with Team Fortress 2.

The benefit of this lighting model, is we don’t have shading approaching total darkness as fast, and in such an unrealistic manner. This lighting model can appear cartoony, but it in some ways begins to mimic the idea of Sub Surface Scattering, albeit in a very naïve way.

The half lambert is very similar to Diffuse lighting, except instead of clamping the dot product value, we remap it into a `0.0 - 1.0`

range instead. This means that the shading can still go to *pure black*, but only on faces pointing directly away from the light source.

`color.rgb *= dot(lightDir, IN.normal) * 0.5 + 0.5;`

### Edge Glow (Fresnel Approximation)

The same principle applies as diffuse shading, except we’re using the *view* direction rather then the *light* direction, and we’ll apply this value to our final pixel colour differently.

```
float vDotN = dot(viewDir, In.normal);
float edge = 1-pow(saturate(vDotN), _glowPower);
float3 finalGlow = edge * _glowColor;
// Assuming a surface shader with an emission channel
surface.emission = finalGlow;
```

In the above example, the result of the dot product when multiplying the colour, would give you the “centre” and not the “edge”, so we `One Minus 1-`

the result of the `Pow()`

function which gives us the edge we want.

And it looks like this, note the power lets us expand or contract the glow.

### Front or Behind Check

Another application of the dot product in this manner is to determine if something is in front, or behind a surface. To do this;

- Take an object’s position, subtract that from the vertex position and normalize it (to make it a unit vector).
- Dot this with the vertex normal
- If the resulting value is positive, the object is in front of the vertex’s face, if it’s negative it’s behind. If it’s 0 then it’s exactly on the surface.

One of the most common uses of this is to help with back-face culling, which might look something like;

```
float faceDot = dot(normalize(vertex.position - camera.position), vertex.normal);
bool isBackFacing = faceDot < 0;
```

The above example would appear in some form in a rendering pipeline, although bear in mind it might be heavily simplified. The Shadergraph version *won’t actually work* in most cases, as the Unity’s renderer has already culled back faces and thus the pixel shader won’t be invoked.

## 2. Distance Checking

This is marginally more complicated, but also pretty useful application. It’s main use is to check how far a point in space is, along a specified direction. The math of this is the same as above, except only one of your vectors would be unit vectors.

### Distance From Plane

This could be used for finding how far away a vertex of a mesh is from an arbitrary plane in space is. In more practical terms, if you have a “scanning” effect on an object, and you want it’s shader to glow where the scanning progress, you’d use this.

```
uniform float3 _planeNomal;
uniform float3 _planePos;
uniform float _glowDistance;
...
float3 delta = _planePos - IN.pos;
float distance = abs(dot(_planeNormal, delta));
float glow = 1-saruate(distance / _glowDistance);
```

This is a little more of a complex example, but hopefully the animated Shadergraph example below should help out. We use our dot product as above, but then introduce a new function, `abs()`

or `Absolute`

.

Out dot product can be positive (vertex position is in front of the plane), or negative (behind the plane). In this example, we don’t mind if it’s ahead or behind, only *how near* it is. This is what the `abs()`

function tells us. We then divide the result of the `abs()`

function by `_glowDistance`

which turns this distance into a *normalised range*, a `0.0 - 1.0`

value. The glow distance allows us to change how thick our glow line will be.

However, if our distance is *greater* then our `_glowDistance`

, then our value will be outside of a normalised range, so we then `saturate()`

it to keep it within our desired values. Once our distance is further then this, we don’t want to know anyway as this area of our mesh would not be glowing.

The final step is to `one minus`

the value, so the area where we want our glow is white, not black as it was, and thus ready to be masked as desired.

This dot product is also used in the point on plane calculation as seen in the fishbowl water, but I went into this in more specifics in the post, so I strongly suggest a read for a real world example.

## To Wrap Up

These are a few of the more common applications of the dot product, there are more ways to use it, but this should give a good practical overview with some examples of how any why it is useful. With this tool under our belt, we can begin to move to more complex concepts to build more interesting and dynamic effects.

Next time in my new “Math for Artists” series we’ll take a look at the `Cross Product`

, the `Dot Product's`

older, hairier sibling who’s old enough to by alcohol and stay out past midnight. We’ll see how that relates to this, and what more complex effects we can achieve.