Improvised occlusion culling in any version of Unity via dynamic frustum far plane shifting

What is it?

Occlusion culling in 3D graphics is the technique of not rendering objects that are completely hidden by other opaque objects. A car behind the far side of a building is not visible, and therefore will not be rendered into the scene. This can improve performance greatly in complex, multi-layered scenes.

The free version of Unity3D lacks occlusion culling entirely, while the Pro version supports only precomputed occlusion culling. This requires completely static scenery, and cannot be used with procedurally generated content.

This script (C# for Unity3D, attach it to the Main Camera) presents a method for improvised occlusion culling by repurposing Unity’s built-in view frustum culling. It uses collision data from multiple rays fired into the viewport to find the maximum depth at certain key areas of the screen, and shifts the view frustum’s far plane close to that depth. The shift of the far plane stops objects beyond it from rendering.

By default, rays are fired into the viewport in this shape, from top to bottom (where X marks indicate the destination of a ray into the normalised viewport). Rays are fired at the very extremities of the viewport to make this script as conservative as possible to reduce pop-in, but you can tweak it for your own game’s requirements.

What is its performance cost?

The test scene consisted of twelve identical buildings in a single row, whose walls and floors were combined into single meshes but whose furniture pieces were not. The scene contained 108 point lights, all of which were set to Important and rendered as pixel lights (this was done in Unity Free, so there was no shadowing). Testing was conducted using a fly cam with mouse look and movement deactivated. The camera was positioned at the start of the row so that all houses would be within the view frustum. The tests were performed in maximised Play view at a size of 1920 × 920.


Table 1. Camera settings for test cases (IOC = Improvised Occlusion Culling).

No occlusion culling Improvised occlusion culling
IOC enabled No Yes
Far plane (wu) 1000 Dynamic
Positive midfield rays - Yes
Negative midfield rays - Yes
Debug rays - Yes
Default far plane - 100
Min distance - 20
Max distance - 200
Far plane buffer - 10


Table 2. Test scene statistics with and without improvised occlusion culling.

No occlusion culling Improvised occlusion culling
Draw calls 4835 640
Tris 3.3 million 556 k
Verts 6.4 million 1.1 million
VRAM usage 20.2–23.8 MB 20.2–23.8 MB
Main thread 13.3 ms 15.3 ms
Renderer 4.9 ms 0.7 ms
FPS 75 65.2 66


Table 3. Percent difference in test scene statistics with improvised occlusion culling, compared to no occlusion culling.

With improvised occlusion culling
Draw calls 86.8% lower
Tris 83.2% lower
Verts 82.8% lower
VRAM usage No difference
Main thread 15% slower (by 2ms)
Renderer 85.7% faster (by 4.2ms)
FPS 13% 12% slower (by 10.2 9 FPS)


When would it be ideal to use this?

Improvised occlusion culling delivers greatly reduced draw calls and rendered mesh features, and a faster renderer loop. It does this at the cost of a longer main loop and reduced FPS (Table 3). It would be most sensible to use this when the performance of a game is being held back by a large number of draw calls and/or unbatched meshes. Where these problems don’t exist, the extra overheard of IOC only slows the game down.

This is all very nice, but does it work well?

Yes, actually. You will need to tweak it to find a rate of change that doesn’t result in pop-in, while still updating quickly enough for your game. It can be optimised for specific game requirements by adding or removing ray groups (for example, a Doom-like with no Y axis mouselook can use a series of rays along the x axis).

Code (C#)

8 September 2012: AdjustFarPlane() was changed to a loop so that it could be shunted to Start() instead of being recreated per frame in Update(). This gives a small boost in FPS (1% improvement).

12 September 2012: I removed the system of multiple arrays of ray destinations being used, replacing it with a method using only two arrays. Fewer arrays accesses gets you a bit more speed.

using UnityEngine;
using System.Collections;

public class ImprovisedOcclusionCulling : MonoBehaviour {

	public bool makeRaysVisible = false;

	public int defaultFarPlane = 100;
	public int minDistance = 20;
	public int maxDistance = 200;
	public int farPlaneBuffer = 10;
	public int rateOfReceding = 16;

	private float[] rayGridY = new float[] {1.00f, 0.60f, 0.59f, 0.58f, 0.57f, 0.56f, 0.55f, 0.54f, 0.53f, 0.52f, 0.51f, 0.50f, 0.49f, 0.48f, 0.47f, 0.46f, 0.45f, 0.44f, 0.43f, 0.42f, 0.41f, 0.40f, 0.00f};
	private float[] rayGridX = new float[] {0.00f, 0.01f, 0.06f, 0.11f, 0.16f, 0.21f, 0.26f, 0.31f, 0.36f, 0.41f, 0.43f, 0.45f, 0.47f, 0.48f, 0.49f, 0.50f, 0.51f, 0.52f, 0.53f, 0.55f, 0.57f, 0.59f, 0.64f, 0.69f, 0.74f, 0.79f, 0.84f, 0.89f, 0.94f, 0.99f, 1.00f};
	void Start ()
	{
		camera.farClipPlane = defaultFarPlane;

		StartCoroutine (AdjustFarPlane());
	}

	IEnumerator AdjustFarPlane ()
	{
		while (true)
		{
			int farPlane = (int) camera.farClipPlane + farPlaneBuffer;
			int distance = minDistance;
			bool ExtendFarPlane = false;

			foreach (float y in rayGridY)
			{
				foreach (float x in rayGridX)
				{
					int tempDist = CastOcclusionRay (x, y);
					if (tempDist >= farPlane)
					{
						distance = tempDist;
						ExtendFarPlane = true;
						goto SHIFT_FAR_PLANE;
					}
				}

				yield return 0;
			}

			SHIFT_FAR_PLANE:
				// Far plane should extend instantly, but recede slowly.
				if (ExtendFarPlane == false)
				{
					camera.farClipPlane -= rateOfReceding;
					if (camera.farClipPlane < minDistance) camera.farClipPlane = minDistance;
				}
				else
				{
					camera.farClipPlane = distance;
				}
		}
	}

	int CastOcclusionRay (float graphX, float graphY)
	{
		Ray ray = camera.ViewportPointToRay (new Vector3 (graphX, graphY, 0));

		if (makeRaysVisible == true) Debug.DrawRay (ray.origin, ray.direction*20, Color.red);

        RaycastHit hit;
		if (Physics.Raycast (ray, out hit) && hit.distance < maxDistance)
		{
			return (int) hit.distance + farPlaneBuffer;
		}
		else
		{
			return (int) maxDistance;
		}
	}
}
Cowface

Tags:  

    

Nike Air Max classic Nike Free Run sale Nike Air Max kopen Nike Air Max shop Nike Air Max classic goedkoop Nike Air Max 90 goedkoop Nike Air Max 1 kopen Nike Air Max sale Nike Air Max 90 sale Nike Air Max kopen online