GithubHelp home page GithubHelp logo

unity-gpu-based-occlusion-culling's People

Contributors

ejhuang-htc avatar przemyslawzaworski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unity-gpu-based-occlusion-culling's Issues

Async readback

_Writer.GetData looks pretty expensive. have you tried AsyncGPUReadback?

Improving optimisation + HDRP ?

Hello,

I know nothing about shaders, and wanted to know how I could make this work on HDRP. Currently no object is displayed if I run the Main scene in HDRP, but no error pops out either.

Also I would like to improve this using jobs for the renderers update. I might also improve some other things, need to look at the profiler for that. Will probably make a pull request shortly about that.

Thanks !

EDIT : after further investigation, Jobs cannot handle non native containers + unity objects state cannot be changed elsewhere then in the main thread... Welp

Fixed the issue with the shadows!

I have fixed the problem with the shadows , here is the updated code for the HardwareOcclusion.cs file :

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Rendering;
using Unity.Collections;
public class HardwareOcclusion : MonoBehaviour
{
public GameObject[] Targets;
public Shader HardwareOcclusionShader;
public ComputeShader IntersectionShader;
public bool Intersection = true;
public bool Dynamic = false;
public uint Delay = 1;
public bool Debug = false;

private Material _Material;
private ComputeBuffer _Reader;
private ComputeBuffer _Writer;
private Vector4[] _Elements;
private Vector4[] _Cache;
private List<List<MeshRenderer>> _MeshRenderers;
private List<Vector4> _Vertices;

private ComputeBuffer _AABB;
private ComputeBuffer _Intersection;
private Cuboid[] _Cuboids;
private int[] _Reset;
private int _CellIndex = -1;
private Coroutine _Coroutine;


struct Cuboid
{
	public Vector3 Center;
	public Vector3 Scale;
}

Vector3 GetCenterFromCubeVertices(Vector4[] verts)
{
	Vector3 total = Vector3.zero;
	int length = verts.Length;
	for (int i = 0; i < length; i++)
	{
		total += new Vector3(verts[i].x, verts[i].y, verts[i].z);
	}
	return total / length;
}

Vector3 GetScaleFromCubeVertices(Vector4[] verts)
{
	Vector3 min = Vector3.positiveInfinity;
	Vector3 max = Vector3.negativeInfinity;
	for (int i = 0; i < verts.Length; i++)
	{
		Vector3 point = new Vector3(verts[i].x, verts[i].y, verts[i].z);
		min = Vector3.Min(min, point);
		max = Vector3.Max(max, point);
	}
	return (max - min) * 0.5f;
}

Vector4[] GenerateCell(GameObject parent, int index)
{
	BoxCollider bc = parent.AddComponent<BoxCollider>();
	Bounds bounds = new Bounds(Vector3.zero, Vector3.zero);
	bool hasBounds = false;
	MeshRenderer[] renderers = parent.GetComponentsInChildren<MeshRenderer>();
	for (int i = 0; i < renderers.Length; i++)
	{
		if (hasBounds)
		{
			bounds.Encapsulate(renderers[i].bounds);
		}
		else
		{
			bounds = renderers[i].bounds;
			hasBounds = true;
		}
	}
	if (hasBounds)
	{
		bc.center = bounds.center - parent.transform.position;
		bc.size = bounds.size;
	}
	else
	{
		bc.size = bc.center = Vector3.zero;
		bc.size = Vector3.zero;
	}
	bc.size = Vector3.Scale(bc.size, new Vector3(1.01f, 1.01f, 1.01f));
	GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
	cube.transform.position = parent.transform.position + bc.center;
	cube.transform.localScale = bc.size;
	Mesh mesh = cube.GetComponent<MeshFilter>().sharedMesh;
	Vector4[] vertices = new Vector4[mesh.triangles.Length];
	for (int i = 0; i < vertices.Length; i++)
	{
		Vector3 p = cube.transform.TransformPoint(mesh.vertices[mesh.triangles[i]]);
		vertices[i] = new Vector4(p.x, p.y, p.z, index);
	}
	Destroy(bc);
	Destroy(cube);
	return vertices;
}

void GenerateMap()
{
	_Vertices.Clear();
	_Vertices.TrimExcess();
	for (int i = 0; i < Targets.Length; i++)
	{
		try
		{
			Vector4[] aabb = GenerateCell(Targets[i], i);
			_Cuboids[i].Center = GetCenterFromCubeVertices(aabb);
			_Cuboids[i].Scale = GetScaleFromCubeVertices(aabb);
			_Vertices.AddRange(aabb);
		}
		catch
		{
			XUpdateList();
		}

	}
	_Reader.SetData(_Vertices.ToArray());
}

bool ArrayState(Vector4[] a, Vector4[] b)
{
	for (int i = 0; i < a.Length; i++)
	{
		bool x = Vector4.Dot(a[i], a[i]) > 0.0f;
		bool y = Vector4.Dot(b[i], b[i]) > 0.0f;
		if (x != y) return false;
	}
	return true;
}

void ArrayCopy(Vector4[] source, Vector4[] destination)
{
	for (int i = 0; i < source.Length; i++) destination[i] = source[i];
}

void Init()
{
	if (_Material == null)
		_Material = new Material(HardwareOcclusionShader);

	_MeshRenderers = new List<List<MeshRenderer>>();

	int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(Cuboid));



	_Writer = new ComputeBuffer(Targets.Length, 16, ComputeBufferType.Default);
	_Elements = new Vector4[Targets.Length];
	_Cache = new Vector4[Targets.Length];
	_Cuboids = new Cuboid[Targets.Length];

	if (_Cache.Length > 0)
		_Cache[0] = Vector4.one;

	_Vertices = new List<Vector4>();

	Graphics.ClearRandomWriteTargets();
	Graphics.SetRandomWriteTarget(1, _Writer, false);

	for (int i = 0; i < Targets.Length; i++)
	{
		try
		{
			_MeshRenderers.Add(Targets[i].GetComponentsInChildren<MeshRenderer>().ToList());
			Vector4[] aabb = GenerateCell(Targets[i], i);
			_Cuboids[i].Center = GetCenterFromCubeVertices(aabb);
			_Cuboids[i].Scale = GetScaleFromCubeVertices(aabb);
			_Vertices.AddRange(aabb);
		}
		catch
		{
			XUpdateList();
		}
	}

	_Reader = new ComputeBuffer(_Vertices.Count, 16, ComputeBufferType.Default);
	_Reader.SetData(_Vertices.ToArray());

	_Material.SetBuffer("_Reader", _Reader);
	_Material.SetBuffer("_Writer", _Writer);
	_Material.SetInt("_Debug", System.Convert.ToInt32(Debug));

	// Adjusted the stride here as well
	_AABB = new ComputeBuffer(_Cuboids.Length, stride, ComputeBufferType.Default);

	_Intersection = new ComputeBuffer(1, sizeof(int), ComputeBufferType.Default);

	IntersectionShader.SetBuffer(0, "_AABB", _AABB);
	IntersectionShader.SetBuffer(0, "_Intersection", _Intersection);

	//// Adjusted the size of _Cuboids to match the stride

	_AABB.SetData(_Cuboids, 0, 0, _Cuboids.Length);

	//// Create an array of int to hold the reset value
	_Reset = new int[1] { -1 };

	// Check if the Intersection coroutine should be started
	//_Coroutine = Intersection ? StartCoroutine(UpdateAsync()) : null;
	StartCoroutine(UpdateAsync());

}

void OnEnable()
{
	if (Targets.Length == 0) return;
	Init();
}

void Update()
{

	if (Targets.Length == 0) return;
	if (Dynamic) GenerateMap();
	if (Time.frameCount % Delay != 0) return;
	_Writer.GetData(_Elements);
	bool state = ArrayState(_Elements, _Cache);
	if (!state)
	{
		for (int i = 0; i < _MeshRenderers.Count; i++)
		{
			for (int j = 0; j < _MeshRenderers[i].Count; j++)
			{
				try
				{
					if (i == _CellIndex)
					{
						_MeshRenderers[i][j].enabled = true;
						if (_MeshRenderers[i][j].shadowCastingMode == ShadowCastingMode.ShadowsOnly)
						{
							_MeshRenderers[i][j].shadowCastingMode = ShadowCastingMode.On;
						}
					}

					else
					{
						if (Vector4.Dot(_Elements[i], _Elements[i]) > 0.0f == false)
						{
							if (_MeshRenderers[i][j].shadowCastingMode == ShadowCastingMode.Off)
							{
								_MeshRenderers[i][j].enabled = false;
							}
							else if (_MeshRenderers[i][j].shadowCastingMode == ShadowCastingMode.On)
							{
								_MeshRenderers[i][j].shadowCastingMode = ShadowCastingMode.ShadowsOnly;
							}
						}
						else
						{
							_MeshRenderers[i][j].enabled = true;
							if (_MeshRenderers[i][j].shadowCastingMode == ShadowCastingMode.ShadowsOnly)
							{
								_MeshRenderers[i][j].shadowCastingMode = ShadowCastingMode.On;
							}

						}
					}

				}
				catch
				{
					XUpdateList();
				}
			}
		}
		ArrayCopy(_Elements, _Cache);
	}
	System.Array.Clear(_Elements, 0, _Elements.Length);
	_Writer.SetData(_Elements);
}

IEnumerator UpdateAsync()
{
	while (true)
	{
		Vector3 position = Camera.main.transform.position;
		IntersectionShader.SetVector("_Point", new Vector4(position.x, position.y, position.z, 0.0f));
		_Intersection.SetData(_Reset);
		int threadGroupsX = (int)Mathf.Ceil(_Cuboids.Length / 8.0f);
		IntersectionShader.Dispatch(0, threadGroupsX, 1, 1);
		AsyncGPUReadbackRequest request = AsyncGPUReadback.Request(_Intersection);
		yield return new WaitUntil(() => request.done);
		_CellIndex = request.GetData<int>()[0];
	}
}

void OnRenderObject()
{
	if (_Vertices == null) return;
	_Material.SetPass(0);
	Graphics.DrawProceduralNow(MeshTopology.Triangles, _Vertices.Count, 1);
}

void OnDisable()
{
	//if (Targets.Length == 0) return;
	//if (_Coroutine != null) StopCoroutine(_Coroutine);
	//_Reader.Release();
	//_Writer.Release();
	//_AABB.Release();
	//_Intersection.Release();
	//for (int i = 0; i < _MeshRenderers.Count; i++)
	//{
	//	for (int j = 0; j < _MeshRenderers[i].Count; j++)
	//	{
	//		_MeshRenderers[i][j].enabled = true;
	//	}
	//}
}

public void XUpdateList()
{
	Targets = GameObject.FindGameObjectsWithTag("XOccludeMe");
}

void HandleLog(string logString, string stackTrace, LogType type)
{

	XUpdateList();
}

}

Process breakdown on huge models?

I'm seeing unexpected results when import huge models (100 - 200k objects) like the update cycle stops working, or the compute shdader isn't responding anymore. It works fine on test scenes with ~30k objects and improves perf as expected. We're not Shader specialists, could we get some advice on how to debug what we're experiencing?

Has this been tested on mobile/VR stadalone?

Next chance I get I want to test this out, but I just wanted to pop a question in here in case anyone has had experience doing this.

Ps. I'm stoked to come across this. I was literally thinking of making a compute shader to do just this - now I don't have to do. As interesting as it may be, it saves a lot of time to not have to make it myself 😄.

If this really does yield Major performance benefits (and I suspect it would over other CPU based techniques), I'm surprised that Unity hasn't implemented this as a core feature.

How to install?

How would I install and set this up? Looking at the code it seems that you have to chose which meshes this applies to?

Managing occlusion on Additively loaded scenes

Hello i've been using this Occlusion culling utility and it's been working quite good.
However i've ran into an issue when wanting to use the script in multiple scenes, them being loaded additively.

The issue is the following, i'll try to explain.

  1. Upon starting the game, a scene called "GameScene" and "TunnelArea" are loaded. (GameScene contains the player with the Main Camera, then TunnelArea contains the occlusion culling script, cause thats where all the props are).
  2. Upon reaching a trigger, i additively load more scenes, as the map is quite big and i had to split it into areas.
  3. When the new area is loaded, the occlusion script from the "TunnelArea" which was loaded at first stops working, and the one from the new area works. But not both at the same time.

The result is upon loading an area all the props from the scene get culled, which is not the result i would need.

Here's a gif:
https://gyazo.com/583785cfa8ef1d56c740641b8e658d9a (player enters trigger, new scene is loaded additively, culling breaks)

While i try to find a solution by myself i wanted to post in case there's a workaround available :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.