Monday, 6 January 2014

Creating a mesh or 3d model skybox in Unity

Happy new year, I recently returned from holidays with the family so no game progress to speak of.  Instead I'd thought I do a little walk through on creating the sky sphere drawing I used for the previous build.  This is not the same as the normal unity skybox which uses images to fill the screen.  What I wanted was some mesh line drawing in the sky much like the skysphere I used in 'Last Chapter'.  Useful if you want to render a mountain range or a city skyline, something like that.

You could draw what you need positioned really far away but you'd have to set the camera far clip plane to an equally large distance and that's possibly going to cause depth flickering (z fighting) from the reduced depth buffer accuracy.

A much better way is to use a little camera trick.  You draw the background scene in any scale but you move its position to wherever the game camera is. Then you clear the depth buffer which means anything drawn after will be on top, and draw the rest of your game as normal.  I actually talk through the method in the Last Chapter documentary at 8m50s where you can see the effect in action.

In XNA I had good setup for the drawing order and I could clear depth buffer as needed, in Unity I needed to setup a second camera and layer for just the background scene and set a number of little things to make it work.

Walkthrough

First, create a new layer to hold the backgound objects.  "Edit > Project Settings > Tags & Layers".  Add a new one and I called mine 'skysphere'.  Anything you want to draw in the background should be placed in this layer.  If you're using code to do that you use the number (gameobject.layer = 8;)

add a layer for our background scene objects

Now let's set up the second camera.  ... "GameObject > Create Other > Camera." and I called mine skySphereCamera.  We want the following settings from top to bottom.

Fig 1 - skybox camera settings


a1) Tag - should be untagged.  Layer - we can leave this as default but let's move it to skysphere for neatness.

b1) Clear flags - solid color.  We want to clear the scene with a solid color before we start our drawing for a frame, I've chosen a dark gray.

c1) Culling mask - open the list and select ONLY the 'skysphere' layer.   Here we're telling the camera to only draw objects placed in this layer.

d1) Projection & field of view - these settings should be the same as the main game camera.

e1) Clipping planes - near 0.1 & far 20.  My background objects are quite small and should happily fit within this range nicely.

f1) Depth - 0.  We want to draw this camera first, so set depth to zero.

g1) Audio Listener - toggle off.  If you compiled with this on it would throw an error about having two audio listeners, one on each camera, so we toggle this one off.


Now back to our main game camera.

fig 2 - main game camera settings


a2) Tag - MainCamera. Layer - default.  Make sure that your main game camera is tagged as MainCamera as we can use this later in code.

b2) Clear flags - depth only.  We've drawn the background, so we only want to clear the depth buffer before drawing the rest of the game.

c2) Culling mask - open the list and we want to have every layer ticked except the skysphere.

d2) Projection & field of view - match these settings to the background camera.

e2) Clipping planes - near 0.1 & far 20.  My game is mostly drawing a small unit sphere so these settings are fine for me.  You may need a further far clip plane, and you don't need to have the same values as the other camera.

f2) Depth - 1.  We want this camera to draw after the background so set this to 1.

g2) Audio listener - on.  This camera is going to handle audio.


Almost there, we have our two cameras set up so we're clearing the right buffers in the right order.  Now we need to do a couple of other things.  Whenever we move our game camera we simply need to also move the other camera with it.  Any little script will do, I created a simple CameraManager that takes the two cameras as gameobjects.  In the update loop I just copy the values across.

SkyCamera.transform.position = ArcBallCamera.transform.position;
SkyCamera.transform.rotation = ArcBallCamera.transform.rotation;

drag & drop camera objects
to the script in Unity editor.


The cameras are now in sync.  We lastly need to move the background objects along with the camera for the illusion to work.  That's simply done in the Update loop again, but we only match position not rotation.  This is where the MainCamera tag comes into play.

backgroundGameObject.transform.localPosition = Camera.main.transform.position;

Fin.

Hopefully that was reasonably clear any questions give me an email.  I guess I have to get back to making this game then, hmmmm, 2014 ... here...we...go.