The last time I wrote, I described a relatively easy way to get a nice depth of field effect. Let's see how we can add a colour shift effect as well. I've tried to repeat most of the relevant information here, but you might want to read the previous post before you continue.
We can modify the depth of field algorithm so that you offset the rendered point in two dimensions, not in 3D. That is, we offset it inside a circle, not a sphere. This has the added benefit of requiring slightly fewer calculations. And it will look quite similar.
We assume that we have the same definitions as before:
- A line in 3D,
- a camera at position,
- a focus distance from the camera,
- a distance function,
dst(a, b)that yields the distance between
- a function,
lerp(a, b, s) = a + s*(b-a), which interpolates between
- a function,
rnd(), that yields random numbers between
In addition, we need the following new functions:
rndNorm(), returns a normally distributed random number with a zero mean and unit variance. ;
rndNorm2D(r) =, returns
r * [rndNorm(), rndNorm()]
clamp(v, l, h), returns
min(h, max(l, v));
pow(a, b), returns
ato the power of
cross(a, b), returns
a.x*b.y - a.y*b.x;
hsvToRgb(h, s, v)converts a HSV value to an RGB value;
vto unit length; and
vfrom 3D into 2D.
The modified algorithm is as follows:
; a point on l v = lerp(a, b, rnd()) ; projection of v on the canvas q = proj(v) ; sample radius based on distance to camera ; larger m yields more blur ; start with e=1 and adjust r = m * pow(abs(f - dst(v, c)), e) ; random vector o = rndNorm2D(r) ; position to draw on canvas w = q + o ; mid is the middle of your canvas x = cross(nrm(q-mid), o)
Finally we are ready to obtain the colour value. We assume HSV where where hue,
saturation and value range between
also assumes a dark background. To obtain different results, you will have to
; start with he=1, and adjust ; lower hs yields more colour variation rr = pow(abs(r) / hs, he) ; select your colours if r < 0: ; cyan-ish hueStart = 0.5 else: ; magenta-ish hueStart = 0.833 hue = clamp(hueStart + rr, 0, 1) ; start with se=1, and adjust ; higher ss yields lower saturation sat = clamp(pow(abs(r) / ss, se), 0, 1) ; value = 1 rgb = hsvToRgb(hue, sat, 1)
Now that we have an RGB colour we can
draw a point at
rgb. As in the previous post, this method
relies on sampling a large number of points from
l, and drawing those points with a low alpha
There are a number of factors in here that will depend on the 3D shapes
you are drawing, the desired output resolution as well as your programming
environment. The trickiest part might be to scale the values of
in mind that you want
sat to fall largely
are there for safety.
Hopefully this is enough to give you some ideas.
- Note that this is a description of how my code works, it is not an "out of the box" solution.
- This is a little simplified. My projection function,
proj(v), uses orthographic projection. Because of this, the distance function does not calculate the distance from
c. Instead it calculates the distance from
pto the point where
phits the camera plane. Using the euclidean distance will also work. Depending on how you project, you might notice that the focal area is curved.
- One way to get this is to use the Box-Muller transform. Many random number libraries will also provide this readily.
- This depends a lot on what tools you use. My images use an orthographic projection. Mostly because it is not too complicated to implement.