Consider the following problem:
Given a non-zero vector v in 3D space, find a non-zero vector w orthogonal to v (i.e. such that v.w = 0).
At first sight, this seems easy. For every possible v there are an infinite number of orthogonal vectors lying in a plane:
We just need to pick one. But it’s not as trivial as it may seem.
First attempts
The following vector satisfies v.w1 = 0:

Unfortunately that w1 vector can be zero, for instance when v = [0 0 1]. The original problem requires a non-zero vector.
Let’s try another one:

Again, it works most of the time, except in some cases, for instance v = [1 0 0].
How about adding them together? Certainly w1 and w2 cannot be both zero at the same time!

Alas, this still doesn’t work all the time: it fails with v = ![1 0 1].
Should we try harder? Isn’t there a linear combination of these that will work all the time?
Well, no. There isn’t.
You can’t comb a coconut
Sadly, the hairy ball theorem, a consequence of Brouwer’s fixed-point theorem, states that any continuous function f that maps the set of unit vectors to orthogonal vectors takes the value zero somewhere.
Whichever way you look at the sphere of unit vectors, a continuous tangent field will always have a zero point:
Most of the maths operations we usually do (adding, multiplying, taking square roots, etc.) are continuous. Fortunately, we are not limited to them: we can use a simple if() construct to create a discontinuity that will save us.
A very good implementation
Here is my personal implementation of the orthogonal vector problem. It is very good. It has excellent numerical stability. It only does one test. It is short. It is beautiful. I really hope you consider using it.
/* Always works if the input is non-zero. * Doesn’t require the input to be normalised. * Doesn’t normalise the output. */ vec3 orthogonal(vec3 v) { return abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) : vec3(0.0, -v.z, v.y); }
Many implementations, such as Ogre3D’s, have additional tests or operations to perform this task, and use epsilons and normalised vectors to avoid singularities.
But our only test is actually enough: if |x|>|z|, it means the largest component in absolute value in v is either x or y. So we build a vector using x and y. Otherwise, the largest component in absolute value is either y or z. So we build a vector using y and z. That way, we ensure that the length of our returned vector never, ever comes near zero.
Whether some given code will cause inefficient branching is often unclear. In our case, it is very likely that the ternary operator will actually be branchless, with some help of the compiler and the hardware.
That said, how about we try to get rid of the ternary operator, just for fun?
Going branchless for fun
Let’s see. We had these two candidate vectors, w1 and w2, which worked almost always except when specific values of v caused them to be zero. And whatever the constant k we may pick, a vector of the form w1 + k w2 will eventually take the value zero for some value of v, too, because of the hairy ball theorem.
Now here comes the trick. Instead of using a constant k, we use a function f(x,y,z). This is what our w vector looks like:

From now I shall assume that v is a unit vector.
What can cause w to be zero, then?
One necessary condition is y = 0. When y ≠ 0 we can chose anything we want for f(x,y,z), it’ll always give us a good orthogonal vector. This restricts the problem to the y = 0 circle, giving us the useful equality x² + z² = 1.
The other condition is x = z*f(x,y,z). So if we manage to build a function f such that f(x,y,z) never equals x/z, we win.
Using x² + z² = 1 we can plot all the possible values for x/z as a function of x. It will show us, for a given x, the values that f cannot take:
The almost vertical slopes you see go to infinity upwards and downwards. As expected, this prevents us from using a continuous function: it would obviously cross one of these walls at some point.
Well, let’s try a non-continuous function, then. What are our options?
- fmod
- floor, ceil, round…
- fract
Here is one that I came up with and which works reasonably well:

Look how it nicely avoids x/z:
And finally, our resulting branchless code:
/* Requires the input to be normalised. * Doesn’t normalise the output. */ vec3 orthogonal(vec3 v) { float k = fract(abs(v.x) + 0.5); return vec3(-v.y, v.x - k * v.z, k * v.y); }
I find it highly unlikely that this second version will perform better than the branching one. However, I haven’t put much thought into it and maybe someone will come up with a much better solution using the ideas presented here. Have fun!