Is there a way to disable frustum culling on specific objects? Or perhaps disable it entirely?
I'm displacing the vertices in my mesh in a vertex shader. They are displaced to the extent that the original mesh (on the CPU side) is out of the view frustum, whereas the displaced vertices (on the GPU side) ARE in the view frustum. This causes the mesh to suddenly stop rendering and disappear, which is obviously not the desired behavior.
I tried solving this by overriding the OnBecameInvisible() method and forcing the renderer to be enabled, but that did nothing. (I had thought that the frustum culling disabled the renderer, but that is not the case.)
I wish the isVisible field on the renderer was able to be set. I feel that would solve this issue. Any other ideas?
Update:
I thought about forcing the bounds on the renderer to be the same size as the maximum displacement, but the bounds property is read-only too.
I then tried having a second camera in the scene that is static and moved back enough to always have the displaced mesh in view, but that didn't work either. Apparently each camera does it's own frustum culling, which makes sense.
I’m pretty sure the best solution here is to use the cullingMatrix (the matrix used for culling only) which should normally be equal to projection * worldToCameraMatrix.
I’m exactly in your case changing vert position in a shader and I wrote this script :
What is does is create a cullingMatrix equivalent to the culling matrix that would be built by Unity with a camera moved back from your actual point of view and a gigantic orthogonal frustrum (you could use a perspective projection, it’s equivalent as soon as all objects are in the frustrum).
When you want to enable frustrum culling again, you disable the script.
After fighting with a similar issue (displacement in the vertex shader), I found a possible solution to change the bounds: Replace your model’s MeshRenderer with SkinnedMeshRenderer, which is actually ment to be used with animated meshes that have larger bounds than the mesh itself.
One way to get a SkinnedMeshRenderer component automatically generated for your mesh, is to create one (useless) bone/armature parented to your mesh in your 3d modelling software. Then in Unity, import the rig for the model as well (you don’t need to import animations, at least not in Unity 4). When you have the model in the project hierarchy, you can even disable the animator component and the armature/bone gameobject (everything animation-related excess except for SkinnedMeshRenderer itself. Deleting those seems to cause problems, though). There may be an other way to create a proper SkinnedMeshRenderer, but I don’t know yet if it’s possible just by scripting.
Now, the trick is that SkinnedMeshRenderer has a localBounds member that can be changed. This is because an animated model may move/change inside its boundaries. Supposing that you know the maximum bounds (or maximum displacement as you mentioned) for your model, you can initialize the renderer with proper bounds once (in OnEnable(), for example):
// Get the SkinnedMeshRenderer component
skinnedMeshRenderer = gameObject.GetComponent<SkinnedMeshRenderer>();
// Create and set your new bounds
Bounds newBounds = new Bounds(myMaxBoundsCenter, myMaxBoundsSize);
skinnedMeshRenderer.localBounds = newBounds;
As a result, the rendering will use these bounds for frustum culling, and you don’t have to worry about the bounds after initializing them once. A nice thing about SkinnedMeshRenderer is also that it shows the actual AABB bounding box in the editor, so it’s easy to see if the runtime-set boundaries are correct.
This solution took a while to come up with, but at least so far it has worked like a charm.
// boundsTarget is the center of the camera's frustum, in world coordinates:
Vector3 camPosition = camera.transform.position;
Vector3 normCamForward = Vector3.Normalize(camera.transform.forward);
float boundsDistance = (camera.farClipPlane - camera.nearClipPlane) / 2 + camera.nearClipPlane;
Vector3 boundsTarget = camPosition + (normCamForward * boundsDistance);
// The game object's transform will be applied to the mesh's bounds for frustum culling checking.
// We need to "undo" this transform by making the boundsTarget relative to the game object's transform:
Vector3 realtiveBoundsTarget = this.transform.InverseTransformPoint(boundsTarget);
// Set the bounds of the mesh to be a 1x1x1 cube (actually doesn't matter what the size is)
Mesh mesh = GetComponent<MeshFilter>().mesh;
mesh.bounds = new Bounds(realtiveBoundsTarget, Vector3.one);
I have also had issues with the frustum culling taking place too early / incorrectly. When using the exact same code, the frustum would cull early if the Initialization of the GameObject took place in my Update() call rather than Start(). Very strange. I changed the bounds size to be extremely large and less culling occurred, but it was still bad. When I moved the bounds center to the center of the frustum, it worked every time. To move the frustum I placed the following code at the end of my Update() - after the mesh modification:
As you can see, it sets the field of view to the standard 60 degrees, sets the shader View and Projection matrices, and then forces the field of view to 179 degrees (the maximum). So, from the shader's point of view, the camera is normal, but in Unity's point of view, the frustum is widened out to the extreme. This means that when Unity does the culling and determines which objects to render, a lot more objects pass the test.
Of course, this means that you have to change your shaders to use your own matrix variables instead of the Unity built-in variables, but that's not too bad:
As I said, it's not perfect and some of the displaced meshes still are clipped, but it's only about 1% of the time now as opposed to about 90% of the time before.
A perhaps better approach for new versions of Unity (5.x and later) is to use the new preculling callback. It works with SpriteRenderers, whereas most of the above solutions do not.
private Camera thisCamera;
public void Start(){
thisCamera = GetComponent<Camera> ();
}
public void PreRenderAdjustFOV(Camera cam){
if (cam == thisCamera) {
Debug.Log ("MyPreRender: " + cam.gameObject.name);
cam.fieldOfView = 60;
}
}
// callback to be called before any culling
public void PreCullAdjustFOV(Camera cam)
{
if (cam == thisCamera) {
Debug.Log ("PreCull: " + cam.gameObject.name);
cam.fieldOfView = 70;
//These are needed for the FOV change to take effect.
cam.ResetWorldToCameraMatrix();
cam.ResetProjectionMatrix();
}
}
public void OnEnable()
{
// register the callback when enabling object
Camera.onPreCull += PreCullAdjustFOV;
Camera.onPreRender += PreRenderAdjustFOV;
}
public void OnDisable()
{
// remove the callback when disabling object
Camera.onPreCull -= PreCullAdjustFOV;
Camera.onPreRender -= PreRenderAdjustFOV;
}
This allows a small increase in the FOV for culling, and turns it back to the proper field of view when it’s time to render. You get the benefits of the frustum culling with an adjustable “extra space” for the geometry of your vertex shader.
myTerrain.basemapDistance = 5000; //Allows terrain textures to be shown at a greater distance. myTerrain.detailObjectDistance = 5000; //Allows grass and similiar to be shown at greater distance.
I did it without code by using a second camera with a far far clip plane distance, and placed the object on a separate layer and told the camera to only render that layer, then adjusted the depth of the camera and the clear flags to depth only…
Just to add to EeroH’s SkinnedMeshRenderer answer, I guess at some point Unity added the Bounds as a visible property of SkinnedMeshRenderer as you can edit the bounds in the inspector window / scene view without having to do it in a script.
You can create a dummy child camera and increase the FOV, then copy its culling matrix to the main camera.
(Camera.onPreCull and Camera.onPreRender only work with the built-in render pipeline, not URP.)
Example:
public class MyCameraController : MonoBehaviour
{
private Camera mainCamera;
private Camera cullingCamera;
[SerializeField] private float extraFrustumCullRange = 10;
private void Awake()
{
mainCamera = GetComponent<Camera>();
SetupCullingCamera();
}
private void SetupCullingCamera()
{
var cullingCamGo = new GameObject("CullingCamera");
cullingCamGo.SetActive(false);
cullingCamGo.transform.SetParent(transform, false);
cullingCamera = cullingCamGo.AddComponent<Camera>();
}
private void LateUpdate()
{
// Adjust field of view for frustum culling matrix to give some extra space so billboard sprites dont get culled while on screen
cullingCamera.fieldOfView = mainCamera.fieldOfView + extraFrustumCullRange;
mainCamera.cullingMatrix = cullingCamera.cullingMatrix;
}
}