Inside The Box
Inside the Box serves as a forum for individuals involved in the production of Gearbox Software content to share personal motives, methods, process and results. Gearbox Software projects are created by a diverse range of individuals spanning a spectrum of different backgrounds, interests, objectives and world views. The views and opinions expressed in this article are those of the author and do not necessarily reflect the official policy or position of Gearbox Software or any of its individual members outside of the author.
Let's talk about field of view (FOV).
As a UI Programmer on Borderlands 2, I championed the effort to make FOV a player-adjustable setting in the PC version of Borderlands 2. I went into this with no idea of how far-reaching the ramifications might be, only knowing that this was one of the most frequent complaints we heard about Borderlands 1 and that my own experience with the PC version of BL1 had been soured by the narrow FOV.
So, where to begin? Most of the time, when I see discussions about FOV on internet forums, I see a lot of comments about preferred FOV values, usually in the 90-100 range. What I haven't seen as much is a clear understanding of the relationship between FOV and aspect ratio, especially as it relates to widescreen behavior. So that's mostly what I want to talk about. That, and math.
Why is FOV a big deal? If you’ve ever heard someone talk about how certain games make them feel motion sick – or maybe you’ve experienced this yourself – this should give you some insight into why finding a natural FOV is so important. But what makes an FOV feel natural? This is a product of two factors: the size of the display, and how far the viewer is from it. Imagine you were to draw lines from your eyes to each of the four corners of your screen. Now imagine what would happen to those lines as you walked nearer or farther from the screen. When you are very far away, the lines will become almost parallel as the angle between them grows very small. Conversely, the closer you get to the screen, the wider this angle become. Also worth noting since I’ll be using the term again is that in graphics coder speak, the pyramidal cone formed by these lines is called a “view frustum.”
Now if we were able to match the in-game camera exactly to the viewing angle of the player, we would have completely natural results, as though the screen were a window into the game world. In practice, this angle is actually far too narrow to be of any real use, but it’s important to understand that this relationship exists between the two viewing angles inside and outside the screen. An FOV that feels appropriate when sitting several feet away from a TV probably isn’t going to feel right when seated directly in front of a monitor. This discrepancy is what can cause motion sickness or a feeling of tunnel vision.
I mentioned aspect ratios previously. How different aspect ratios are handled in games is something that has become another hot topic in recent years with the advent of widescreen displays, and it’s intrinsically linked to FOV. Borderlands 1 shipped with what is referred to by the widescreen community as "Vert-" behavior. What this means is that the horizontal field of view is fixed, and when you go from standard def to widescreen, content is cropped off the top and bottom of the frame. This is counterintuitive to what most people expect (due in large part, I'd imagine, to the precedent the film industry has set), where content is framed to the vertical axis and widescreen adds content to the left and right of the frame. This behavior is called "Hor+" and is generally considered to be “correct.”
Now let's go back to those preferred FOV values. When someone says they prefer a 90-degree FOV, what does that really mean? Conventionally (dating back to the Quake era or even earlier), FOV values in games have referred to the horizontal field of view. The Unreal Engine in particular has historically treated FOVs as horizontal by default. But a view frustum has two angles, one horizontal and one vertical. We can therefore talk about having both a horizontal FOV and a vertical FOV, and we can see that the ratio between these is related in some way to the aspect ratio of the screen. (Unfortunately, it's not exactly equal to this ratio. That would be too easy. But I'll get to that.)
So we actually have two FOVs, but the horizontal FOV is more intuitive, so let's talk about that one. Let's say we want a horizontal FOV of 90 degrees, so we can see 45 degrees to the left and 45 degrees to the right, right? Okay, that's all well and good, but now what happens when we change the aspect ratio of the screen? We have two choices: we can either continue to keep our horizontal viewing angle locked at 90 degrees and allow our vertical bounds to shift (which means we’ll be adding or removing content from the top and bottom of the screen), or we can keep our vertical bounds the same as they are and adjust the horizontal...but then it won't be 90 degrees anymore.
So the first thing to understand is that, if we want to respect Hor+ behavior, we can't think in terms of a fixed horizontal FOV. We have to start thinking about vertical FOV, because that's our constant. The horizontal FOV will be a variable factor of both the vertical FOV and the aspect ratio of the screen.
A fixed horizontal FOV crops the top and bottom of the viewing area when playing in widescreen.
Now the problem we have is that, as mentioned, horizontal FOVs are conventional and intuitive. I'm going to go out on a limb and guess that when most people talk about FOV values in the 90-100 range, they're talking horizontal FOVs. (This limb feels sturdy; vertical FOVs in that range would be a little absurd.) So Borderlands 1 shipped with a 70-degree horizontal FOV. At our target 16:9 aspect ratio, a 70 degree horizontal FOV corresponds to about a 43 degree vertical FOV (actually 42.995659 if you want to get really specific).
Even at a 90 degree horizontal FOV, which is generally a pretty safe default on PC (and is in fact the default I chose for the PC version of Borderlands 2), the corresponding vertical FOV at an aspect ratio of 16:9 is only about 58.7 degrees. That still sounds really low when you're expecting numbers in the 90-100 range. If we were to display the vertical FOV on the options menu, that would just be confusing. We'd probably wind up inadvertently encouraging some players to push their FOV up beyond what's actually comfortable into crazy skewy distorted land just to make the numbers feel right. That would be unfortunate.
So we definitely want to show a horizontal FOV in the options menu, but since this is a factor of the screen's aspect ratio, we're now faced with the decision to either base this value on the actual aspect ratio as it currently is, which would create an odd problem in which it would appear to change if the screen or window size changed, or else...we can just fudge it and display a value that's relative to a fixed aspect ratio.
I chose the latter option. Regardless of your actual monitor resolution or window size, when you look at the FOV slider in Borderlands 2, that's a horizontal FOV relative to our target 16:9 aspect ratio. If you're playing in 16:9, then it will accurately reflect the horizontal FOV you'll see. In any other case, what you'll actually get on the horizontal axis will be some other value that's scaled to fit whatever vertical FOV corresponds to the chosen horizontal value at a 16:9 aspect ratio. Confusing enough?
Okay, so that was the easy part. Now we've established our vocabulary, so let's move on to actual implementation.
As mentioned previously, the Unreal Engine exhibits Vert- by default, but it does now also support Hor+ out of the box, so gone are the days when a coder would have to go futzing around with the projection matrix to make this change locally. (I was actually a little disappointed to discover this. I love futzing around with matrices!) Anyway, we flip this switch and then step through the math of converting our chosen horizontal FOV into a vertical FOV assuming a 16:9 aspect ratio, and we set that as our actual in-game vertical FOV. Sweet, we're seeing what we expect. Time to check it in and call it a day? If only it were that easy. First we gotta find all those happy fun edge cases that no one ever thinks about until they pop up and are suddenly the most glaringly obvious thing in the world.
We do some FOV scaling for actions like sprinting, boosting in a vehicle, and so on. Normally, this scalar is represented as a percent of the default FOV angle, so for instance, if our base FOV is 70 degrees and we want to increase it by 10%, it would now be 77 degrees. But, uh-oh! Now that our default FOV isn't always fixed at 70 degrees, this is totally broken! As an example, let's say we did something dumb and set our base FOV to 170 degrees. Now we increase it by ten percent, and we get...187 degrees? Well, crap. That doesn't make sense. By their very nature, view frustums can't meet or exceed 180 degrees, so that's not gonna work. Even in moderate examples -- say for instance the difference between 70 degrees and 90 degree base FOVs -- what we find is that scaling the angle by a fixed percent creates drastically different results depending on the base.
So we ask ourselves, what are we really trying to do here? When we say "increase the FOV by 10%," we're really saying we want to see 10% more of the environment than we are right now. As it turns out, we can accomplish this not by scaling the FOV angle directly by this percent, but by instead scaling it such that its tangent grows by this percent. In doing so, the actual angle by which we increase our FOV will be less and less the higher the base value is, but it will create the effect we want, and best of all, it won't ever reach 180 degrees, so we've restored some sanity there.
Borderlands 2 uses a fixed vertical FOV, which adds more viewing area to the sides in widescreen.
Okay, so now we understand that particular problem; next comes the fun task of auditing the entire codebase to see where all FOV is scaled and replacing that code with the new code that works correctly for any base FOV. That means lots of searching, sanity-checking the actual intent (hey, who knows when this code was written or by whom), replacing, and testing.
All right, now let's launch the game and see if anything's broken. Load up a test map, change the slider a few times, seems to be working as we expect...exit back to the main menu and oh-what-the-crap, everything's broken again! The FOV is way too wide and the awesome title screen that used to be so nicely framed is all wrong now. So we spend a while looking through code for where this FOV value comes from and eventually discover it comes from a camera in a Matinee sequence. Okay, so Matinee cameras specify an FOV, and our level design and cinematics guys have historically treated this as a horizontal FOV. That doesn't jive with the switch to Hor+ behavior, but we don't want to make those guys redo their work, so let's patch this up such that this value is treated as horizontal in code, too. Now all our existing content works and those guys can keep framing Matinee sequences as they're used to, the same way they always have. Crisis averted.
Run around the game some more...oh hey, vehicles are a thing; let's see what happens when we get in one of those. Uh-oh, broken! Vehicles use a third-person camera, and the camera follows behind the vehicle at some variable distance that depends on its speed. Well cool, that looked fine when we had a fixed 70 degree view, but now it's wider and the vehicle looks tiny on-screen. So even though the code is adjusting the camera’s distance from the vehicle, it's not really the distance we care about; it's how big the vehicle appear on the screen versus how much of the surrounding environment we can see. So we refactor that code to take the vehicle’s dimensions into account and allow that distance to scale such that the vehicle always appears at the correct size regardless of the FOV. Cool. One more bug down.
More testing, more testing… Hey, Scooter has a mission for me! I go talk to him, the mission UI pops up, and… Oh man, really? This is broken too? Right, we're scaling the FOV while this UI is open to focus on the NPC or object giving the mission, but we're scaling it to fit the vertical axis, and we used to assume the FOV were horizontal, and now it's vertical, so the code that was there before to make this conversion is no longer needed. Tear it out, verify that things look correct now.... And oh right, we're doing the same thing for crosshair scaling, so let's go make that change too before we forget. That one's subtle and wouldn't appear obviously broken to someone unfamiliar with the system, so it could easily go overlooked. Those are the worst kind of bugs. Let's be glad we caught it now!
Okay, so now after all that, we're ready to check it in.
These are all real-world examples of problems I encountered and had to address before I could make an initial check-in to add an FOV slider to the options menu. (And oh yeah, at some point during all that, I actually added the slider to the options menu. D’oh!) This took about four days, mostly working late into the evenings after hours, as this wasn't part of my normally scheduled workload; it was just something I took on as a pet project because I felt passionate about it. In the weeks and months that followed, several more edge cases cropped up. Some of these were obviously related to the FOV change and went straight to me to be fixed; others had to wind their way across several departments before they landed in my queue and I could address them.
So that's the longwinded story of how the FOV slider came to be in BL2.
An early implementation of the FOV slider in Borderlands 2 PC.
I said I was going to talk about math, and I haven't really brought math into this yet. Let's remedy that. So...okay, here's a fun one. I said that we now treat the horizontal FOV as a variable factor of both the aspect ratio and the vertical FOV. That's pretty easy to understand if we draw a picture (I'll leave that as an exercise to the reader), but how does it actually translate to code? Since we define our FOV in terms of degrees but the math libraries expect radians, we have to convert those. Then we’re actually only interested in half the FOV, as this forms a nice right angle, so we can take its tangent, which represents how much we can see along half the horizontal axis. Then we scale this by the inverse of the screen's aspect ratio, and that gives us our tangent along the vertical axis. Take the inverse tangent of this to get back to an angle, multiply it by two, convert back to degrees, and you’ve got your vertical FOV. So the whole thing looks like this:
VerticalFOV = (180 ÷ π) × arctan(tan((π ÷ 180) × HorizontalFOV ÷ 2) ÷ (ScreenWidth ÷ ScreenHeight)) × 2
To go from a vertical FOV to a horizontal FOV, we just repeat the same steps in reverse. You'll notice the only differences are that we provide the vertical FOV as input instead of the horizontal FOV, and we multiply by the aspect ratio rather than dividing.
HorizontalFOV = (180 ÷ π) × arctan(tan((π ÷ 180) × VerticalFOV ÷ 2) × (ScreenWidth ÷ ScreenHeight)) × 2
I guess that's about it.
For the curious, I made 70 degrees the lowest setting because that's our canonical default on consoles and we pretty much have to go at least that low, but I can't imagine anyone would want to take it any lower. For a while, our high end was limited by shadow map artifacts, and I was worried we might have to clamp this to 90 degrees or less, and even then we might have to put up a warning that adjusting the FOV would cause artifacts. But one of our rendering gurus fixed those issues, and I was able to raise the cap. I picked 110 degrees because I had seen it suggested a few times in a few separate forum threads as a good upper bound. For my money, 95-100 feels comfortable when I'm playing in fullscreen on a 24" monitor, but of course, this is highly dependent on how big your screen is and how far back you sit. That's probably enough rambling for now.
Find more articles and behind-the-scenes insights at the main Inside the Box hub!