[Edit: Since publishing this I found out the illimitable, magnificent Keijiro Takahashi had already written a suite of utilities for the PP stack which – don’t you know it – includes a FocusPuller which does exactly what I implemented here, and is probably better written. If you just want to get ‘er done, that’s your best bet. But for an in depth, beginner-friendly, under-the-hood look at Unity DoF – read on!]
Post-processing effects are a critical part of any digital visual experience. They are often utilized to enhance visual experiences to that delicious level of tear-inducing beauty. Just watch Adam: Bloom, Depth of Field, antialiasing, Motion Blur, Color Grading – there’s a reason that demo looks like a AAA Hollywood production, and that reason is Post-processing.
Unity’s own PP solutions have been a bit of a mixed bag over the years, but a recent development – the Post Processing Stack – shows a lot of promise and seems to be the shape of things to come. Originally released in October ’16, the stack has since garnered a lot of praise from devs, and for good reason: it’s modular but not overwhelming, easy to plug and play, automatically determines the correct order with which to arrange the effects – in short, it gives you just enough rope to hang yourself with horrifying, overdone after effects – or actually use them make something beautiful, if you’re so inclined.
But that solution is not without its flaws, and today I’ll address one of them: the absence of Auto-Focus in the Depth of Field effect.
That’s deep, man
What is Depth of Field, you ask? It’s that effect where you focus on one thing and then everything within other focal depths is blurry and – ugh, okay. Here. This.
The new PP stack has a mostly sensible, effective DoF feature. Mostly, I say, because it doesn’t have auto-focus. This unfortunate omission makes DoF the equivalent of walking through your game with reading glasses; anything within a certain distance will look sharp, and everything in the background will be blurry, and that certain distance can’t change, regardless of what you look at. It’s the opposite of how our eyes work. For cutscenes and cinematic content that’s pretty okay – you can set a generally accurate focal depth for the exact shot you need. For realtime 3D content it’s stupid and useless and not fun. It means that once you add it to your camera and take a look around, almost everything is blurry.
Somehow AutoFocus, perhaps the most obvious feature for in-game DoF, hadn’t ever occurred to anyone @Unity as a necessary functionality that needs be included in the new stack for DoF to be useful.
Luckily, a while back, a kind fellow named Frank Otto contributed an auto-focus script to Unity’s Community Wiki. It worked great, except it was written for the previous, stack-less post-processing system that was more cumbersome and had different naming conventions. However, the fundamental autofocus logic remains the same – perhaps we can salvage that script fairly easily and modify it to our own insidious purposes?
Let’s copy-paste the code to a new script of the same name, drop it on our camera-carrying GameObject, and expect everything to work flawlessly.
(It doesn’t work flawlessly.)
One of the issues is that the entire postprocessing way of doing business has changed. The new postprocessing stack relies on an individual profile which you can change or substitute at will for easy A/Bing or different use cases – that’s one of the nice things about it. So instead of fetching the actual effect by reference:
We fetch our profile by value (more on that difference later), which will contain our DoF settings, among others.
It’s a fairly trivial change, but a good time-saving example of realtime PP profile modification already exists here. We substitute the DepthOfField declaration with:
and fetch it similarly to that referenced script. Also note that we have to add a “using” statement to refer to the PostProcessing stack:
The previous way the parameter ObjectFocus was referred to is now broken, too, because of the naming scheme change. Poking around other classes (namely DepthOfFieldComponent.cs), and looking at the new naming conventions, brings us to the following revision of the script’s Start function:
Our efforts are rewarded with a brand new error:
Which is Unity complaining that we’re trying to convert a thing to a float: in this case, the float is a measurement of distance from the thing. A-ha! This is because our previous variable wasn’t focusDistance but ObjectFocus, a useful parameter in the old DoF that allowed you to simply set a target object, the distance from which was presumably calculated internally.
Our script, however, will simply create an empty object, raycast every frame (or FixedFrame, depending on your quality settings), and sets that object position to the raycast’s hitpoint, before equating the DoF’s parameter “objectFocus” to the distance between that object and ourselves. Pretty simple.
Most of the logic remains intact, but in the new API we have a focusDistance parameter, so let’s feed it what it wants to eat – the distance between our target object to ourselves:
(disregarding other trivial errors, related to Unity API changes, that are easily fixed and not worth getting into,) that’s most of the work. Based on the example mentioned above, the realtime settings changing logic is a little more cumbersome (due to the fact we’re fetching the settings by value, not reference, so we then have to reassign the actual profile’s settings to the internal copy we made and modified). Our last conditional now looks like:
And voila – AutoFocus.
Slow Down, Brah
The interpolation feature basically lets you lerp into your current focus distance to create a more fluid focus transition, and is already mostly in place – you just have to replace the old logic with our new one (and remember to keep updating the settings object every time – again, value vs. reference shenanigans.)
That’s pretty much it!
There are other optimizations or features that could be added later, but I was primarily interested in adapting the original script to work with the new stack and learning how to change the PP in realtime (some realtime changes – bloom, vignette – could be really useful).
You’re welcome to grab the final gist, modify it at will and use it in whatever way you want! And always remember – [insert inspirational quote about staying autofocused]