Cameras: Is what you see, what you get?
People sometimes say that “Perception is Reality”. What we perceive, we know to exist and how we see things will often influence how we perceive those things.
In theatre, you may see a room on stage with painted walls and furniture but what you don’t see are all the pieces of wood holding the stage together or the wires strewn about the back to light things up in a very specific way. Everything is placed so that no one can see just how little substance this room has or that the stage crew is literally standing three feet from the entrance to the scene, ready to introduce the next set of props.
In a 3D Scene, cameras work in a similar fashion. They are your eyes as you look at how things are placed, lit, and textured. You may have the freedom with some cameras to move around and see different angles but not always. The purpose of this blog post is to show you a few of the different cameras that Babylon.js offers and even show you a trick or two with how to show people only what you want them to see.
All cameras available in Babylon.js have the same purpose, to capture what a scene looks like from a specific viewpoint. Where they all differ, in some way, is based on how they react and are controlled in a scene. All cameras inherit from what is known as a TargetCamera. The basic idea of this camera is that the camera is given a point in a scene that it focuses on as a “target”.
From here, there are five different cameras that inherit from TargetCamera: FreeCamera, ArcRotateCamera, FlyCamera, FollowCamera, and ArcFollowCamera. While we could trace through the lineage of all of the cameras, I’d like to just touch on a few of the more commonly used cameras. Those cameras are the FreeCamera, ArcRotateCamera, and FollowCamera.
The FreeCamera is the camera that almost everyone starts with when developing with Babylon.js. It’s the default camera in our starting Playgrounds. It was originally created as a way to freely move around a scene and lends itself well to FPS style gaming. The FreeCamera is free to move around a scene as far as the scene and coding will allow. For gaming and immersion, this camera is one of the best options.
The constructor for the FreeCamera looks like this:
new FreeCamera(name: string, position: Vector3, scene: Scene, setActiveOnSceneIfNoneActive?: boolean): FreeCamera
First, you have the
name. This is just an id that can be used to identify your camera in a scene. You’ll want to make this unique and meaningful to you. As you swap between active cameras and add more complex configurations, you’ll need to properly differentiate between which camera is which.
scene pretty self-explanatory. They describe what scene your camera is associated with and where it is within that scene.
setActiveOnSceneIfNoneActive. If you haven’t added a camera to a specific scene, this boolean will make sure to set this camera as the active camera. This parameter is optional and is true by default.
The default movement controls for the FreeCamera are that the arrow keys Move Forward (Up Arrow), Backward (Down Arrow), Strafe Left (Left Arrow), and Right (Right Arrow). Mouse movements will rotate the view of the camera. All arrow movements are relative to the direction that the camera is facing.
As a bit of an addendum, I would also recommend looking into a descendant of the FreeCamera called the UniversalCamera. The UniversalCamera has the same functionality but also adds built-in support for Touch and Gamepads. Its constructor is almost the same as FreeCamera but without
new UniversalCamera(name: string, position: Vector3, scene: Scene): UniversalCamera
The FollowCamera is camera type that will take a given object and follow its movement. Think of the FollowCamera as a cameraman to an action movie. It will move and pan while trying to keep the object it’s tracking in the center. This camera serves as a more passive camera type for showing off the movement of something in a specific scene. Cinematic mode in Red Dead Redemption 2 is an ideal kind of scenario for a FollowCamera. It follows along, keeping the character in frame without input from the user.
new FollowCamera(name: string, position: Vector3, scene: Scene, lockedTarget?: Nullable<AbstractMesh>): FollowCamera
Its first three parameters serve the exact same purpose as FreeCamera. It differs on that last parameter,
lockedTarget is the actual object that the FollowCamera will track. It should be noted that while this parameter IS optional, you will need to define this at some point to have it track something. It just doesn’t need to be define at initialization because the camera may be getting created before the object it’s tracking.
The ArcRotateCamera is probably the second most commonly seen camera on Playgrounds and demos. The purpose of this camera is to take a specific point in a scene and have your camera focus on that point. Any movement will rotate around that singular point. This camera is best for showcasing a specific mesh or object. An example of this is in a game like Resident Evil where you’re given the option to view a item and rotate it to possibly find a clue.
Of the three camera types mentioned, it has the most complex constructor but it’s not bad when you look at each parameter, one by one:
new ArcRotateCamera(name: string, alpha: number, beta: number, radius: number, target: Vector3, scene: Scene, setActiveOnSceneIfNoneActive?: boolean): ArcRotateCamera
Much like FreeCamera and FollowCamera, name and scene work in the same way. Instead of position, we have three parameters that set its initial location:
alpha defines what its initial horizontal angle is, with respect to its target (the position it’s rotating around).
beta does the same thing but for its vertical angle. Both of these values need to be in radians. If you need to convert a value to radians, check out the static function
radius is simply the distance from the target to the camera.
Finally, we have the
target is where the ArcRotateCamera is looking and what point it uses as its center point. While this must be set when the camera is created, it can be changed with the
These cameras (or cameras that inherit from them like UniversalCamera) should allow you to show off your Babylon.js scene from a desired perspective but you may be asking yourself, “What else can I do with cameras?” The answer is, a lot.
A few months ago, I put out a basic demo showing a disembodied head block moving around a scene. The main camera works exactly as you would expect a FreeCamera to work but then there’s also a small window with the head looking up and around. That small window in the upper right hand corner is the view from a different camera with a bit of clever code to keep the head visible, even when there’s a floor in the way. To do this, I made use of two camera concepts that I’ll be going over, Viewports and Layer Masks.
A viewport is can be defined as the position and size of a display from some source (like a camera). All cameras have a viewport object. By default, they start at the 0,0 screen coordinate and extend the full width and height of the display area. Its constructor looks like this:
new Viewport(x: number, y: number, width: number, height: number): Viewport
The big thing to note is that all of these values are normalized. What that means is that they go from 0 to 1, regardless of what the resolution of your display area is. Think of it as a percentage. For your standard display, it’ll look like:
This translates to start at 0,0 (the lower left-hand corner) and take up the full width and height of the display area. Let’s say that you wanted to do a split screen multiplayer game. It might look something like:
// This creates and positions both free cameras
var p1 = new FreeCamera("player1", new Vector3(0, 5, -10), scene);
var p2 = new FreeCamera("player2", new Vector3(10, 5, -10), scene);// Player 1 starts on the left and goes halfway across the screen
p1.viewport = new Viewport(0,0,0.5,1);// Player 2 starts halfway through and goes to the end of the screen
p2.viewport = new Viewport(0.5,0,0.5,1);
This is just a simple example of a split-screen setup with the players on the left and right of each other. Check out this playground. You may notice one particular issue:
The left side is showing up but not the right. So what’s happening is that our left camera (p1) was created and, by default, was set as the active camera for the scene (remember
setActiveOnSceneIfNoneActive?) because there were no cameras created. When p2 was created, there was an active camera so there was no need to make p2 an active camera. So how do we get both cameras active?
That’s easy! Each scene contains a list that you can add active cameras to (
scene.activeCameras). All that we need to do is to push our camera onto this list:
In that playground, I provided a couple of paragraphs ago, if you uncomment lines 18 and 19 and run the Playground, both sides should show up. One important thing to note. The order of how these camera is pushed onto this list matters. For my picture-in-picture example, if you swap lines 78 and 79, the camera display in the upper right-hand corner disappears. The newest camera added to
scene.activeCameras is always on top.
You might be thinking, multiple cameras is great but what if I want one of my cameras not to see something. How do I do that? That takes me to the other subject that I’d like to talk about, Layer Masks.
When using cameras, there are times when you have a mesh in a scene but don’t want to display it for every single camera. Engines like Unity make use of a layer system where each object is on a layer and you can control what layers a camera can see. Babylon.js can do the same thing using a bit-wise style of assignment. If you take a look at this demo, you can see that there are three different cameras showing off three different views.
In this picture, you see a sphere in the left and middle and a plane in the right and middle. The sphere has been assigned to the layer mask of 0x10000000 (bit-wise 1) and the plane has been assigned to the layer mask of 0x20000000 (bit-wise 10). The left camera was also given the layer mask of 0x10000000. That means that it can display any objects with that layer mask. The right one was given 0x20000000. Since 1 and 2 don’t share bits, the right camera won’t show the sphere and the left won’t show the plane. The middle has been assigned the layer mask of 0x30000000 (bit-wise 11). Since it has both the bit for 1 and the bit for 2, it will be able to display both objects (eg. 11 & 01 = 01). Using layer masks can be useful in preventing things from appearing on cameras that they’re not supposed to.
Using both of these concepts, I’ve created a simple Resident Evil-style item viewer demo (Playground Demo).
In this demo, we have a scene that you can navigate (Mouse to look, Arrows to move). You can also pull up the item viewer with the
As you navigate the scene, you notice that you’re moving around the scene with a FreeCamera. These movement controls are just what comes, by default, with that camera type. When you hit
I, you’ll notice a dagger appear, like the picture above (Credit to Patrick Ryan for creating the dagger). Interestingly enough, you can’t move around the scene but you CAN move the dagger around with both the mouse and arrow keys. The only change is that your active camera is now an ArcRotateCamera and you’re not rotating the dagger but actually rotating around it.
One of the tricks used with things like maps and items in menus is that the object you’re viewing is actually somewhere else in the scene. This is done so that you can pre-load the item into memory and just view it when you need it. In the example, the actual dagger was loaded and placed at (250, 250, 0). For viewing the dagger, we’ve placed our ArcRotateCamera nearby and given both a layer mask of 0x20000000. Our main camera and the main scene meshes all have a layer mask of 0x10000000 so neither camera should see what the other sees.
I is pressed, we detach the controls for one camera and re-attach the controls for the other (See lines 163–178). For our item viewing camera, we push it on top of the
scene.activeCameras list when we want it displayed and pop the top when we don’t. This allows us to not only add the item viewing camera when we need it but make sure that it’s always overlaying the display and not being covered by the main camera. Another thing that we’ll want to do is to resize the item viewing screen so that it isn’t covering the whole display, just in case we wanted to add any additional HUD elements. That’s where our viewport object comes in handy:
itemCam.viewport = new BABYLON.Viewport(0.2, 0.3, 0.6, 0.6);
This should give us a general layout of this:
Finally, we have the GUI. There are two fullscreen UIs in this scene, the
itemViewerTexture and the
mainUITexture. Because we don’t want the elements of both to appear on each camera, we have to use layer masks to separate them. Since this is a 2D UI, we’ll need to access its Layer object to change its layer mask:
// Example of setting the layer mask
mainUITexture.layer.layerMask = 0x10000000;
With everything put together, we can now toggle between moving around and having a Resident Evil-style item viewer.
Everything covered here is only a fraction of what you can do with cameras so feel free to experiment and see what you can create. As always, you can ask any questions or even show off a demo of your own on our forums (https://forum.babylonjs.com). Happy coding!
Dave Solares — https://twitter.com/PolygonalSun